Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / IDTracker.cpp
blob813aa44e118b36af84a0b3c5f0204632c0c7a189
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 auto ref = Substring(aLocalRef, 1);
136 if (ref.IsEmpty()) {
137 Unlink();
138 return;
141 nsAutoCString utf8Ref;
142 if (!AppendUTF16toUTF8(ref, utf8Ref, mozilla::fallible)) {
143 Unlink();
144 return;
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;
150 bool appended;
151 if (NS_FAILED(NS_UnescapeURL(utf8Ref.BeginReading(), utf8Ref.Length(),
152 esc_OnlyASCII | esc_AlwaysCopy, unescaped,
153 appended, mozilla::fallible))) {
154 Unlink();
155 return;
158 RefPtr<nsAtom> idAtom = NS_Atomize(unescaped);
159 ResetWithID(aFrom, idAtom, aWatch);
162 void IDTracker::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch) {
163 MOZ_ASSERT(aID);
165 Unlink();
167 if (aID->IsEmpty()) {
168 return;
171 if (aWatch) {
172 mWatchID = aID;
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) {
185 if (aWatch) {
186 mWatchDocumentOrShadowRoot = nullptr;
187 if (aDocOrShadow) {
188 mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
189 mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
190 mReferencingImage);
192 return;
195 if (!aDocOrShadow) {
196 return;
199 if (Element* e = LookupElement(*aDocOrShadow, aRef, mReferencingImage)) {
200 mElement = e;
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() {
212 if (mWatchID) {
213 if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
214 docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
215 mReferencingImage);
218 if (mPendingNotification) {
219 mPendingNotification->Clear();
220 mPendingNotification = nullptr;
222 mWatchDocumentOrShadowRoot = nullptr;
223 mWatchID = nullptr;
224 mElement = nullptr;
225 mReferencingImage = false;
228 void IDTracker::ElementChanged(Element* aFrom, Element* aTo) { mElement = aTo; }
230 bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
231 void* aData) {
232 IDTracker* p = static_cast<IDTracker*>(aData);
233 if (p->mPendingNotification) {
234 p->mPendingNotification->SetTo(aNewElement);
235 } else {
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();
243 if (!keepTracking) {
244 p->mWatchDocumentOrShadowRoot = nullptr;
245 p->mWatchID = nullptr;
247 return keepTracking;
250 IDTracker::ChangeNotification::ChangeNotification(IDTracker* aTarget,
251 Element* aFrom, Element* aTo)
252 : mozilla::Runnable("IDTracker::ChangeNotification"),
253 Notification(aTarget),
254 mFrom(aFrom),
255 mTo(aTo) {}
257 IDTracker::ChangeNotification::~ChangeNotification() = default;
259 void IDTracker::ChangeNotification::SetTo(Element* aTo) { mTo = aTo; }
261 void IDTracker::ChangeNotification::Clear() {
262 Notification::Clear();
263 mFrom = nullptr;
264 mTo = nullptr;
267 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
268 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
270 NS_IMETHODIMP
271 IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
272 const char* aTopic,
273 const char16_t* aData) {
274 NS_ASSERTION(!strcmp(aTopic, "external-resource-document-created"),
275 "Unexpected topic");
276 if (mTarget) {
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);
285 return NS_OK;
288 DocumentOrShadowRoot* IDTracker::GetWatchDocOrShadowRoot() const {
289 if (!mWatchDocumentOrShadowRoot) {
290 return nullptr;
292 MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
293 mWatchDocumentOrShadowRoot->IsShadowRoot());
294 if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
295 return shadow;
297 return mWatchDocumentOrShadowRoot->AsDocument();
300 } // namespace mozilla::dom