Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / base / IDTracker.cpp
blob4db592534d8b382c2339e1fbba13d013462b4655
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/. */
7 #include "IDTracker.h"
9 #include "mozilla/Encoding.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/DocumentOrShadowRoot.h"
12 #include "mozilla/dom/ShadowRoot.h"
13 #include "nsAtom.h"
14 #include "nsContentUtils.h"
15 #include "nsIURI.h"
16 #include "nsIReferrerInfo.h"
17 #include "nsEscape.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,
32 const nsAString& aID,
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
40 // trees.
41 if (LookupElement(*shadow, aID, aReferenceImage)) {
42 return shadow;
44 shadow = shadow->Host()->GetContainingShadow();
47 if (shadow) {
48 return shadow;
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");
64 Unlink();
66 if (!aURI) return;
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();
78 nsAutoString ref;
79 nsresult rv = encoding->DecodeWithoutBOMHandling(refPart, ref);
80 if (NS_FAILED(rv) || ref.IsEmpty()) {
81 return;
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
88 // etc.)
89 Element* anonRoot =
90 doc->GetAnonRootIfInAnonymousContentContainer(aFromContent);
91 if (anonRoot) {
92 mElement = nsContentUtils::MatchElementId(anonRoot, ref);
93 // We don't have watching working yet for anonymous content, so bail out
94 // here.
95 return;
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));
106 docOrShadow = doc;
107 if (!doc) {
108 if (!load || !aWatch) {
109 // Nothing will ever happen here
110 return;
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
119 } else {
120 docOrShadow = FindTreeToWatch(*aFromContent, ref, aReferenceImage);
123 if (aWatch) {
124 mWatchID = NS_Atomize(ref);
127 mReferencingImage = aReferenceImage;
128 HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, ref);
131 void IDTracker::ResetWithLocalRef(Element& aFrom, const nsAString& aLocalRef,
132 bool aWatch) {
133 MOZ_ASSERT(nsContentUtils::IsLocalRefURL(aLocalRef));
135 nsAutoCString ref;
136 if (!AppendUTF16toUTF8(Substring(aLocalRef, 1), ref, mozilla::fallible)) {
137 Unlink();
138 return;
140 NS_UnescapeURL(ref);
141 RefPtr<nsAtom> idAtom = NS_Atomize(ref);
142 ResetWithID(aFrom, idAtom, aWatch);
145 void IDTracker::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch) {
146 MOZ_ASSERT(aID);
148 Unlink();
150 if (aID->IsEmpty()) {
151 return;
154 if (aWatch) {
155 mWatchID = aID;
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) {
168 if (aWatch) {
169 mWatchDocumentOrShadowRoot = nullptr;
170 if (aDocOrShadow) {
171 mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
172 mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
173 mReferencingImage);
175 return;
178 if (!aDocOrShadow) {
179 return;
182 if (Element* e = LookupElement(*aDocOrShadow, aRef, mReferencingImage)) {
183 mElement = e;
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() {
195 if (mWatchID) {
196 if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
197 docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
198 mReferencingImage);
201 if (mPendingNotification) {
202 mPendingNotification->Clear();
203 mPendingNotification = nullptr;
205 mWatchDocumentOrShadowRoot = nullptr;
206 mWatchID = nullptr;
207 mElement = nullptr;
208 mReferencingImage = false;
211 void IDTracker::ElementChanged(Element* aFrom, Element* aTo) { mElement = aTo; }
213 bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
214 void* aData) {
215 IDTracker* p = static_cast<IDTracker*>(aData);
216 if (p->mPendingNotification) {
217 p->mPendingNotification->SetTo(aNewElement);
218 } else {
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();
226 if (!keepTracking) {
227 p->mWatchDocumentOrShadowRoot = nullptr;
228 p->mWatchID = nullptr;
230 return keepTracking;
233 IDTracker::ChangeNotification::ChangeNotification(IDTracker* aTarget,
234 Element* aFrom, Element* aTo)
235 : mozilla::Runnable("IDTracker::ChangeNotification"),
236 Notification(aTarget),
237 mFrom(aFrom),
238 mTo(aTo) {}
240 IDTracker::ChangeNotification::~ChangeNotification() = default;
242 void IDTracker::ChangeNotification::SetTo(Element* aTo) { mTo = aTo; }
244 void IDTracker::ChangeNotification::Clear() {
245 Notification::Clear();
246 mFrom = nullptr;
247 mTo = nullptr;
250 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
251 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
253 NS_IMETHODIMP
254 IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
255 const char* aTopic,
256 const char16_t* aData) {
257 NS_ASSERTION(!strcmp(aTopic, "external-resource-document-created"),
258 "Unexpected topic");
259 if (mTarget) {
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);
268 return NS_OK;
271 DocumentOrShadowRoot* IDTracker::GetWatchDocOrShadowRoot() const {
272 if (!mWatchDocumentOrShadowRoot) {
273 return nullptr;
275 MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
276 mWatchDocumentOrShadowRoot->IsShadowRoot());
277 if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
278 return shadow;
280 return mWatchDocumentOrShadowRoot->AsDocument();
283 } // namespace mozilla::dom