1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "mozilla/Encoding.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/DocumentOrShadowRoot.h"
12 #include "mozilla/dom/ShadowRoot.h"
14 #include "nsContentUtils.h"
16 #include "nsIReferrerInfo.h"
18 #include "nsCycleCollectionParticipant.h"
19 #include "nsStringFwd.h"
21 namespace mozilla::dom
{
23 static Element
* LookupElement(DocumentOrShadowRoot
& aDocOrShadow
,
24 const nsAString
& aRef
, bool aReferenceImage
) {
25 if (aReferenceImage
) {
26 return aDocOrShadow
.LookupImageElement(aRef
);
28 return aDocOrShadow
.GetElementById(aRef
);
31 static DocumentOrShadowRoot
* FindTreeToWatch(nsIContent
& aContent
,
33 bool aReferenceImage
) {
34 ShadowRoot
* shadow
= aContent
.GetContainingShadow();
36 // We allow looking outside an <svg:use> shadow tree for backwards compat.
37 while (shadow
&& shadow
->Host()->IsSVGElement(nsGkAtoms::use
)) {
38 // <svg:use> shadow trees are immutable, so we can just early-out if we find
39 // our relevant element instead of having to support watching multiple
41 if (LookupElement(*shadow
, aID
, aReferenceImage
)) {
44 shadow
= shadow
->Host()->GetContainingShadow();
51 return aContent
.OwnerDoc();
54 IDTracker::IDTracker() = default;
56 IDTracker::~IDTracker() { Unlink(); }
58 void IDTracker::ResetToURIFragmentID(nsIContent
* aFromContent
, nsIURI
* aURI
,
59 nsIReferrerInfo
* aReferrerInfo
,
60 bool aWatch
, bool aReferenceImage
) {
61 MOZ_ASSERT(aFromContent
,
62 "ResetToURIFragmentID() expects non-null content pointer");
68 nsAutoCString refPart
;
69 aURI
->GetRef(refPart
);
70 // Unescape %-escapes in the reference. The result will be in the
71 // document charset, hopefully...
72 NS_UnescapeURL(refPart
);
74 // Get the thing to observe changes to.
75 Document
* doc
= aFromContent
->OwnerDoc();
76 auto encoding
= doc
->GetDocumentCharacterSet();
79 nsresult rv
= encoding
->DecodeWithoutBOMHandling(refPart
, ref
);
80 if (NS_FAILED(rv
) || ref
.IsEmpty()) {
84 if (aFromContent
->IsInNativeAnonymousSubtree()) {
85 // This happens, for example, if aFromContent is part of the content
86 // inserted by a call to Document::InsertAnonymousContent, which we
87 // also want to handle. (It also happens for other native anonymous content
90 doc
->GetAnonRootIfInAnonymousContentContainer(aFromContent
);
92 mElement
= nsContentUtils::MatchElementId(anonRoot
, ref
);
93 // We don't have watching working yet for anonymous content, so bail out
99 bool isEqualExceptRef
;
100 rv
= aURI
->EqualsExceptRef(doc
->GetDocumentURI(), &isEqualExceptRef
);
101 DocumentOrShadowRoot
* docOrShadow
;
102 if (NS_FAILED(rv
) || !isEqualExceptRef
) {
103 RefPtr
<Document::ExternalResourceLoad
> load
;
104 doc
= doc
->RequestExternalResource(aURI
, aReferrerInfo
, aFromContent
,
105 getter_AddRefs(load
));
108 if (!load
|| !aWatch
) {
109 // Nothing will ever happen here
113 DocumentLoadNotification
* observer
=
114 new DocumentLoadNotification(this, ref
);
115 mPendingNotification
= observer
;
116 load
->AddObserver(observer
);
117 // Keep going so we set up our watching stuff a bit
120 docOrShadow
= FindTreeToWatch(*aFromContent
, ref
, aReferenceImage
);
124 mWatchID
= NS_Atomize(ref
);
127 mReferencingImage
= aReferenceImage
;
128 HaveNewDocumentOrShadowRoot(docOrShadow
, aWatch
, ref
);
131 void IDTracker::ResetWithLocalRef(Element
& aFrom
, const nsAString
& aLocalRef
,
133 MOZ_ASSERT(nsContentUtils::IsLocalRefURL(aLocalRef
));
136 if (!AppendUTF16toUTF8(Substring(aLocalRef
, 1), ref
, mozilla::fallible
)) {
141 RefPtr
<nsAtom
> idAtom
= NS_Atomize(ref
);
142 ResetWithID(aFrom
, idAtom
, aWatch
);
145 void IDTracker::ResetWithID(Element
& aFrom
, nsAtom
* aID
, bool aWatch
) {
150 if (aID
->IsEmpty()) {
158 mReferencingImage
= false;
160 nsDependentAtomString
str(aID
);
161 DocumentOrShadowRoot
* docOrShadow
=
162 FindTreeToWatch(aFrom
, str
, /* aReferenceImage = */ false);
163 HaveNewDocumentOrShadowRoot(docOrShadow
, aWatch
, str
);
166 void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot
* aDocOrShadow
,
167 bool aWatch
, const nsString
& aRef
) {
169 mWatchDocumentOrShadowRoot
= nullptr;
171 mWatchDocumentOrShadowRoot
= &aDocOrShadow
->AsNode();
172 mElement
= aDocOrShadow
->AddIDTargetObserver(mWatchID
, Observe
, this,
182 if (Element
* e
= LookupElement(*aDocOrShadow
, aRef
, mReferencingImage
)) {
187 void IDTracker::Traverse(nsCycleCollectionTraversalCallback
* aCB
) {
188 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB
, "mWatchDocumentOrShadowRoot");
189 aCB
->NoteXPCOMChild(mWatchDocumentOrShadowRoot
);
190 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB
, "mElement");
191 aCB
->NoteXPCOMChild(mElement
);
194 void IDTracker::Unlink() {
196 if (DocumentOrShadowRoot
* docOrShadow
= GetWatchDocOrShadowRoot()) {
197 docOrShadow
->RemoveIDTargetObserver(mWatchID
, Observe
, this,
201 if (mPendingNotification
) {
202 mPendingNotification
->Clear();
203 mPendingNotification
= nullptr;
205 mWatchDocumentOrShadowRoot
= nullptr;
208 mReferencingImage
= false;
211 void IDTracker::ElementChanged(Element
* aFrom
, Element
* aTo
) { mElement
= aTo
; }
213 bool IDTracker::Observe(Element
* aOldElement
, Element
* aNewElement
,
215 IDTracker
* p
= static_cast<IDTracker
*>(aData
);
216 if (p
->mPendingNotification
) {
217 p
->mPendingNotification
->SetTo(aNewElement
);
219 NS_ASSERTION(aOldElement
== p
->mElement
, "Failed to track content!");
220 ChangeNotification
* watcher
=
221 new ChangeNotification(p
, aOldElement
, aNewElement
);
222 p
->mPendingNotification
= watcher
;
223 nsContentUtils::AddScriptRunner(watcher
);
225 bool keepTracking
= p
->IsPersistent();
227 p
->mWatchDocumentOrShadowRoot
= nullptr;
228 p
->mWatchID
= nullptr;
233 IDTracker::ChangeNotification::ChangeNotification(IDTracker
* aTarget
,
234 Element
* aFrom
, Element
* aTo
)
235 : mozilla::Runnable("IDTracker::ChangeNotification"),
236 Notification(aTarget
),
240 IDTracker::ChangeNotification::~ChangeNotification() = default;
242 void IDTracker::ChangeNotification::SetTo(Element
* aTo
) { mTo
= aTo
; }
244 void IDTracker::ChangeNotification::Clear() {
245 Notification::Clear();
250 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification
, mozilla::Runnable
)
251 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification
, nsIObserver
)
254 IDTracker::DocumentLoadNotification::Observe(nsISupports
* aSubject
,
256 const char16_t
* aData
) {
257 NS_ASSERTION(!strcmp(aTopic
, "external-resource-document-created"),
260 nsCOMPtr
<Document
> doc
= do_QueryInterface(aSubject
);
261 mTarget
->mPendingNotification
= nullptr;
262 NS_ASSERTION(!mTarget
->mElement
, "Why do we have content here?");
263 // If we got here, that means we had Reset*() called with
264 // aWatch == true. So keep watching if IsPersistent().
265 mTarget
->HaveNewDocumentOrShadowRoot(doc
, mTarget
->IsPersistent(), mRef
);
266 mTarget
->ElementChanged(nullptr, mTarget
->mElement
);
271 DocumentOrShadowRoot
* IDTracker::GetWatchDocOrShadowRoot() const {
272 if (!mWatchDocumentOrShadowRoot
) {
275 MOZ_ASSERT(mWatchDocumentOrShadowRoot
->IsDocument() ||
276 mWatchDocumentOrShadowRoot
->IsShadowRoot());
277 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot
)) {
280 return mWatchDocumentOrShadowRoot
->AsDocument();
283 } // namespace mozilla::dom