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
));
135 auto ref
= Substring(aLocalRef
, 1);
141 nsAutoCString utf8Ref
;
142 if (!AppendUTF16toUTF8(ref
, utf8Ref
, mozilla::fallible
)) {
147 // Only unescape ASCII characters; if we were to unescape arbitrary bytes,
148 // we'd potentially end up with invalid UTF-8.
149 nsAutoCString unescaped
;
151 if (NS_FAILED(NS_UnescapeURL(utf8Ref
.BeginReading(), utf8Ref
.Length(),
152 esc_OnlyASCII
| esc_AlwaysCopy
, unescaped
,
153 appended
, mozilla::fallible
))) {
158 RefPtr
<nsAtom
> idAtom
= NS_Atomize(unescaped
);
159 ResetWithID(aFrom
, idAtom
, aWatch
);
162 void IDTracker::ResetWithID(Element
& aFrom
, nsAtom
* aID
, bool aWatch
) {
167 if (aID
->IsEmpty()) {
175 mReferencingImage
= false;
177 nsDependentAtomString
str(aID
);
178 DocumentOrShadowRoot
* docOrShadow
=
179 FindTreeToWatch(aFrom
, str
, /* aReferenceImage = */ false);
180 HaveNewDocumentOrShadowRoot(docOrShadow
, aWatch
, str
);
183 void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot
* aDocOrShadow
,
184 bool aWatch
, const nsString
& aRef
) {
186 mWatchDocumentOrShadowRoot
= nullptr;
188 mWatchDocumentOrShadowRoot
= &aDocOrShadow
->AsNode();
189 mElement
= aDocOrShadow
->AddIDTargetObserver(mWatchID
, Observe
, this,
199 if (Element
* e
= LookupElement(*aDocOrShadow
, aRef
, mReferencingImage
)) {
204 void IDTracker::Traverse(nsCycleCollectionTraversalCallback
* aCB
) {
205 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB
, "mWatchDocumentOrShadowRoot");
206 aCB
->NoteXPCOMChild(mWatchDocumentOrShadowRoot
);
207 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB
, "mElement");
208 aCB
->NoteXPCOMChild(mElement
);
211 void IDTracker::Unlink() {
213 if (DocumentOrShadowRoot
* docOrShadow
= GetWatchDocOrShadowRoot()) {
214 docOrShadow
->RemoveIDTargetObserver(mWatchID
, Observe
, this,
218 if (mPendingNotification
) {
219 mPendingNotification
->Clear();
220 mPendingNotification
= nullptr;
222 mWatchDocumentOrShadowRoot
= nullptr;
225 mReferencingImage
= false;
228 void IDTracker::ElementChanged(Element
* aFrom
, Element
* aTo
) { mElement
= aTo
; }
230 bool IDTracker::Observe(Element
* aOldElement
, Element
* aNewElement
,
232 IDTracker
* p
= static_cast<IDTracker
*>(aData
);
233 if (p
->mPendingNotification
) {
234 p
->mPendingNotification
->SetTo(aNewElement
);
236 NS_ASSERTION(aOldElement
== p
->mElement
, "Failed to track content!");
237 ChangeNotification
* watcher
=
238 new ChangeNotification(p
, aOldElement
, aNewElement
);
239 p
->mPendingNotification
= watcher
;
240 nsContentUtils::AddScriptRunner(watcher
);
242 bool keepTracking
= p
->IsPersistent();
244 p
->mWatchDocumentOrShadowRoot
= nullptr;
245 p
->mWatchID
= nullptr;
250 IDTracker::ChangeNotification::ChangeNotification(IDTracker
* aTarget
,
251 Element
* aFrom
, Element
* aTo
)
252 : mozilla::Runnable("IDTracker::ChangeNotification"),
253 Notification(aTarget
),
257 IDTracker::ChangeNotification::~ChangeNotification() = default;
259 void IDTracker::ChangeNotification::SetTo(Element
* aTo
) { mTo
= aTo
; }
261 void IDTracker::ChangeNotification::Clear() {
262 Notification::Clear();
267 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification
, mozilla::Runnable
)
268 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification
, nsIObserver
)
271 IDTracker::DocumentLoadNotification::Observe(nsISupports
* aSubject
,
273 const char16_t
* aData
) {
274 NS_ASSERTION(!strcmp(aTopic
, "external-resource-document-created"),
277 nsCOMPtr
<Document
> doc
= do_QueryInterface(aSubject
);
278 mTarget
->mPendingNotification
= nullptr;
279 NS_ASSERTION(!mTarget
->mElement
, "Why do we have content here?");
280 // If we got here, that means we had Reset*() called with
281 // aWatch == true. So keep watching if IsPersistent().
282 mTarget
->HaveNewDocumentOrShadowRoot(doc
, mTarget
->IsPersistent(), mRef
);
283 mTarget
->ElementChanged(nullptr, mTarget
->mElement
);
288 DocumentOrShadowRoot
* IDTracker::GetWatchDocOrShadowRoot() const {
289 if (!mWatchDocumentOrShadowRoot
) {
292 MOZ_ASSERT(mWatchDocumentOrShadowRoot
->IsDocument() ||
293 mWatchDocumentOrShadowRoot
->IsShadowRoot());
294 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot
)) {
297 return mWatchDocumentOrShadowRoot
->AsDocument();
300 } // namespace mozilla::dom