Bug 1550519 - Show a translucent parent highlight when a subgrid is highlighted....
[gecko.git] / dom / base / IDTracker.cpp
blob8437d176fe564b9e6b9640665ffd1ec55e73405f
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 "nsContentUtils.h"
11 #include "nsIURI.h"
12 #include "nsBindingManager.h"
13 #include "nsEscape.h"
14 #include "nsXBLPrototypeBinding.h"
15 #include "nsCycleCollectionParticipant.h"
17 namespace mozilla {
18 namespace dom {
20 static DocumentOrShadowRoot* DocOrShadowFromContent(nsIContent& aContent) {
21 ShadowRoot* shadow = aContent.GetContainingShadow();
23 // We never look in <svg:use> shadow trees, for backwards compat.
24 while (shadow && shadow->Host()->IsSVGElement(nsGkAtoms::use)) {
25 shadow = shadow->Host()->GetContainingShadow();
28 if (shadow) {
29 return shadow;
32 return aContent.OwnerDoc();
35 void IDTracker::ResetToURIFragmentID(nsIContent* aFromContent, nsIURI* aURI,
36 nsIURI* aReferrer,
37 uint32_t aReferrerPolicy, bool aWatch,
38 bool aReferenceImage) {
39 MOZ_ASSERT(aFromContent,
40 "ResetToURIFragmentID() expects non-null content pointer");
42 Unlink();
44 if (!aURI) return;
46 nsAutoCString refPart;
47 aURI->GetRef(refPart);
48 // Unescape %-escapes in the reference. The result will be in the
49 // document charset, hopefully...
50 NS_UnescapeURL(refPart);
52 // Get the thing to observe changes to.
53 Document* doc = aFromContent->OwnerDoc();
54 DocumentOrShadowRoot* docOrShadow = DocOrShadowFromContent(*aFromContent);
55 auto encoding = doc->GetDocumentCharacterSet();
57 nsAutoString ref;
58 nsresult rv = encoding->DecodeWithoutBOMHandling(refPart, ref);
59 if (NS_FAILED(rv) || ref.IsEmpty()) {
60 return;
62 rv = NS_OK;
64 nsIContent* bindingParent = aFromContent->GetBindingParent();
65 if (bindingParent && !aFromContent->IsInShadowTree()) {
66 nsXBLBinding* binding = bindingParent->GetXBLBinding();
67 if (!binding) {
68 // This happens, for example, if aFromContent is part of the content
69 // inserted by a call to Document::InsertAnonymousContent, which we
70 // also want to handle. (It also happens for <use>'s anonymous
71 // content etc.)
72 Element* anonRoot =
73 doc->GetAnonRootIfInAnonymousContentContainer(aFromContent);
74 if (anonRoot) {
75 mElement = nsContentUtils::MatchElementId(anonRoot, ref);
76 // We don't have watching working yet for anonymous content, so bail out
77 // here.
78 return;
80 } else {
81 bool isEqualExceptRef;
82 rv = aURI->EqualsExceptRef(binding->PrototypeBinding()->DocURI(),
83 &isEqualExceptRef);
84 if (NS_SUCCEEDED(rv) && isEqualExceptRef) {
85 // XXX sXBL/XBL2 issue
86 // Our content is an anonymous XBL element from a binding inside the
87 // same document that the referenced URI points to. In order to avoid
88 // the risk of ID collisions we restrict ourselves to anonymous
89 // elements from this binding; specifically, URIs that are relative to
90 // the binding document should resolve to the copy of the target
91 // element that has been inserted into the bound document.
92 // If the URI points to a different document we don't need this
93 // restriction.
94 nsINodeList* anonymousChildren =
95 doc->BindingManager()->GetAnonymousNodesFor(bindingParent);
97 if (anonymousChildren) {
98 uint32_t length = anonymousChildren->Length();
99 for (uint32_t i = 0; i < length && !mElement; ++i) {
100 mElement =
101 nsContentUtils::MatchElementId(anonymousChildren->Item(i), ref);
105 // We don't have watching working yet for XBL, so bail out here.
106 return;
111 bool isEqualExceptRef;
112 rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef);
113 if (NS_FAILED(rv) || !isEqualExceptRef) {
114 RefPtr<Document::ExternalResourceLoad> load;
115 doc = doc->RequestExternalResource(aURI, aReferrer, aReferrerPolicy,
116 aFromContent, getter_AddRefs(load));
117 docOrShadow = doc;
118 if (!doc) {
119 if (!load || !aWatch) {
120 // Nothing will ever happen here
121 return;
124 DocumentLoadNotification* observer =
125 new DocumentLoadNotification(this, ref);
126 mPendingNotification = observer;
127 load->AddObserver(observer);
128 // Keep going so we set up our watching stuff a bit
132 if (aWatch) {
133 mWatchID = NS_Atomize(ref);
136 mReferencingImage = aReferenceImage;
137 HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, ref);
140 void IDTracker::ResetWithID(Element& aFrom, nsAtom* aID, bool aWatch) {
141 MOZ_ASSERT(aID);
143 if (aWatch) {
144 mWatchID = aID;
147 mReferencingImage = false;
149 DocumentOrShadowRoot* docOrShadow = DocOrShadowFromContent(aFrom);
150 HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, nsDependentAtomString(aID));
153 void IDTracker::HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot* aDocOrShadow,
154 bool aWatch, const nsString& aRef) {
155 if (aWatch) {
156 mWatchDocumentOrShadowRoot = nullptr;
157 if (aDocOrShadow) {
158 mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
159 mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this,
160 mReferencingImage);
162 return;
165 if (!aDocOrShadow) {
166 return;
169 Element* e = mReferencingImage ? aDocOrShadow->LookupImageElement(aRef)
170 : aDocOrShadow->GetElementById(aRef);
171 if (e) {
172 mElement = e;
176 void IDTracker::Traverse(nsCycleCollectionTraversalCallback* aCB) {
177 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocumentOrShadowRoot");
178 aCB->NoteXPCOMChild(mWatchDocumentOrShadowRoot);
179 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mElement");
180 aCB->NoteXPCOMChild(mElement);
183 void IDTracker::Unlink() {
184 if (mWatchID) {
185 if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
186 docOrShadow->RemoveIDTargetObserver(mWatchID, Observe, this,
187 mReferencingImage);
190 if (mPendingNotification) {
191 mPendingNotification->Clear();
192 mPendingNotification = nullptr;
194 mWatchDocumentOrShadowRoot = nullptr;
195 mWatchID = nullptr;
196 mElement = nullptr;
197 mReferencingImage = false;
200 bool IDTracker::Observe(Element* aOldElement, Element* aNewElement,
201 void* aData) {
202 IDTracker* p = static_cast<IDTracker*>(aData);
203 if (p->mPendingNotification) {
204 p->mPendingNotification->SetTo(aNewElement);
205 } else {
206 NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!");
207 ChangeNotification* watcher =
208 new ChangeNotification(p, aOldElement, aNewElement);
209 p->mPendingNotification = watcher;
210 nsContentUtils::AddScriptRunner(watcher);
212 bool keepTracking = p->IsPersistent();
213 if (!keepTracking) {
214 p->mWatchDocumentOrShadowRoot = nullptr;
215 p->mWatchID = nullptr;
217 return keepTracking;
220 NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
221 NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
223 NS_IMETHODIMP
224 IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
225 const char* aTopic,
226 const char16_t* aData) {
227 NS_ASSERTION(PL_strcmp(aTopic, "external-resource-document-created") == 0,
228 "Unexpected topic");
229 if (mTarget) {
230 nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
231 mTarget->mPendingNotification = nullptr;
232 NS_ASSERTION(!mTarget->mElement, "Why do we have content here?");
233 // If we got here, that means we had Reset*() called with
234 // aWatch == true. So keep watching if IsPersistent().
235 mTarget->HaveNewDocumentOrShadowRoot(doc, mTarget->IsPersistent(), mRef);
236 mTarget->ElementChanged(nullptr, mTarget->mElement);
238 return NS_OK;
241 } // namespace dom
242 } // namespace mozilla