Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / base / IDTracker.cpp
blob4b36c0387e970c896b09a025d28398c4e136268d
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::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch) {
132 MOZ_ASSERT(aID);
134 if (aWatch) {
135 mWatchID = aID;
138 mReferencingImage = false;
140 nsDependentAtomString str(aID);
141 DocumentOrShadowRoot* docOrShadow =
142 FindTreeToWatch(aFrom, str, /* aReferenceImage = */ false);
143 HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, str);
146 void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot* aDocOrShadow,
147 bool aWatch, const nsString& aRef) {
148 if (aWatch) {
149 mWatchDocumentOrShadowRoot = nullptr;
150 if (aDocOrShadow) {
151 mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
152 mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
153 mReferencingImage);
155 return;
158 if (!aDocOrShadow) {
159 return;
162 if (Element* e = LookupElement(*aDocOrShadow, aRef, mReferencingImage)) {
163 mElement = e;
167 void IDTracker::Traverse(nsCycleCollectionTraversalCallback* aCB) {
168 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocumentOrShadowRoot");
169 aCB->NoteXPCOMChild(mWatchDocumentOrShadowRoot);
170 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mElement");
171 aCB->NoteXPCOMChild(mElement);
174 void IDTracker::Unlink() {
175 if (mWatchID) {
176 if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
177 docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
178 mReferencingImage);
181 if (mPendingNotification) {
182 mPendingNotification->Clear();
183 mPendingNotification = nullptr;
185 mWatchDocumentOrShadowRoot = nullptr;
186 mWatchID = nullptr;
187 mElement = nullptr;
188 mReferencingImage = false;
191 void IDTracker::ElementChanged(Element* aFrom, Element* aTo) { mElement = aTo; }
193 bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
194 void* aData) {
195 IDTracker* p = static_cast<IDTracker*>(aData);
196 if (p->mPendingNotification) {
197 p->mPendingNotification->SetTo(aNewElement);
198 } else {
199 NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!");
200 ChangeNotification* watcher =
201 new ChangeNotification(p, aOldElement, aNewElement);
202 p->mPendingNotification = watcher;
203 nsContentUtils::AddScriptRunner(watcher);
205 bool keepTracking = p->IsPersistent();
206 if (!keepTracking) {
207 p->mWatchDocumentOrShadowRoot = nullptr;
208 p->mWatchID = nullptr;
210 return keepTracking;
213 IDTracker::ChangeNotification::ChangeNotification(IDTracker* aTarget,
214 Element* aFrom, Element* aTo)
215 : mozilla::Runnable("IDTracker::ChangeNotification"),
216 Notification(aTarget),
217 mFrom(aFrom),
218 mTo(aTo) {}
220 IDTracker::ChangeNotification::~ChangeNotification() = default;
222 void IDTracker::ChangeNotification::SetTo(Element* aTo) { mTo = aTo; }
224 void IDTracker::ChangeNotification::Clear() {
225 Notification::Clear();
226 mFrom = nullptr;
227 mTo = nullptr;
230 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
231 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
233 NS_IMETHODIMP
234 IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
235 const char* aTopic,
236 const char16_t* aData) {
237 NS_ASSERTION(!strcmp(aTopic, "external-resource-document-created"),
238 "Unexpected topic");
239 if (mTarget) {
240 nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
241 mTarget->mPendingNotification = nullptr;
242 NS_ASSERTION(!mTarget->mElement, "Why do we have content here?");
243 // If we got here, that means we had Reset*() called with
244 // aWatch == true. So keep watching if IsPersistent().
245 mTarget->HaveNewDocumentOrShadowRoot(doc, mTarget->IsPersistent(), mRef);
246 mTarget->ElementChanged(nullptr, mTarget->mElement);
248 return NS_OK;
251 DocumentOrShadowRoot* IDTracker::GetWatchDocOrShadowRoot() const {
252 if (!mWatchDocumentOrShadowRoot) {
253 return nullptr;
255 MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
256 mWatchDocumentOrShadowRoot->IsShadowRoot());
257 if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
258 return shadow;
260 return mWatchDocumentOrShadowRoot->AsDocument();
263 } // namespace mozilla::dom