|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et tw=78: */ |
|
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/. */ |
|
6 |
|
7 #include "nsReferencedElement.h" |
|
8 #include "nsContentUtils.h" |
|
9 #include "nsIURI.h" |
|
10 #include "nsBindingManager.h" |
|
11 #include "nsEscape.h" |
|
12 #include "nsXBLPrototypeBinding.h" |
|
13 #include "nsIDOMNode.h" |
|
14 #include "nsIDOMElement.h" |
|
15 #include "nsCycleCollectionParticipant.h" |
|
16 |
|
17 void |
|
18 nsReferencedElement::Reset(nsIContent* aFromContent, nsIURI* aURI, |
|
19 bool aWatch, bool aReferenceImage) |
|
20 { |
|
21 NS_ABORT_IF_FALSE(aFromContent, "Reset() expects non-null content pointer"); |
|
22 |
|
23 Unlink(); |
|
24 |
|
25 if (!aURI) |
|
26 return; |
|
27 |
|
28 nsAutoCString refPart; |
|
29 aURI->GetRef(refPart); |
|
30 // Unescape %-escapes in the reference. The result will be in the |
|
31 // origin charset of the URL, hopefully... |
|
32 NS_UnescapeURL(refPart); |
|
33 |
|
34 nsAutoCString charset; |
|
35 aURI->GetOriginCharset(charset); |
|
36 nsAutoString ref; |
|
37 nsresult rv = nsContentUtils::ConvertStringFromEncoding(charset, |
|
38 refPart, |
|
39 ref); |
|
40 if (NS_FAILED(rv)) { |
|
41 // XXX Eww. If fallible malloc failed, using a conversion method that |
|
42 // assumes UTF-8 and doesn't handle UTF-8 errors. |
|
43 // https://bugzilla.mozilla.org/show_bug.cgi?id=951082 |
|
44 CopyUTF8toUTF16(refPart, ref); |
|
45 } |
|
46 if (ref.IsEmpty()) |
|
47 return; |
|
48 |
|
49 // Get the current document |
|
50 nsIDocument *doc = aFromContent->GetCurrentDoc(); |
|
51 if (!doc) |
|
52 return; |
|
53 |
|
54 nsIContent* bindingParent = aFromContent->GetBindingParent(); |
|
55 if (bindingParent) { |
|
56 nsXBLBinding* binding = bindingParent->GetXBLBinding(); |
|
57 if (binding) { |
|
58 bool isEqualExceptRef; |
|
59 rv = aURI->EqualsExceptRef(binding->PrototypeBinding()->DocURI(), |
|
60 &isEqualExceptRef); |
|
61 if (NS_SUCCEEDED(rv) && isEqualExceptRef) { |
|
62 // XXX sXBL/XBL2 issue |
|
63 // Our content is an anonymous XBL element from a binding inside the |
|
64 // same document that the referenced URI points to. In order to avoid |
|
65 // the risk of ID collisions we restrict ourselves to anonymous |
|
66 // elements from this binding; specifically, URIs that are relative to |
|
67 // the binding document should resolve to the copy of the target |
|
68 // element that has been inserted into the bound document. |
|
69 // If the URI points to a different document we don't need this |
|
70 // restriction. |
|
71 nsINodeList* anonymousChildren = |
|
72 doc->BindingManager()->GetAnonymousNodesFor(bindingParent); |
|
73 |
|
74 if (anonymousChildren) { |
|
75 uint32_t length; |
|
76 anonymousChildren->GetLength(&length); |
|
77 for (uint32_t i = 0; i < length && !mElement; ++i) { |
|
78 mElement = |
|
79 nsContentUtils::MatchElementId(anonymousChildren->Item(i), ref); |
|
80 } |
|
81 } |
|
82 |
|
83 // We don't have watching working yet for XBL, so bail out here. |
|
84 return; |
|
85 } |
|
86 } |
|
87 } |
|
88 |
|
89 bool isEqualExceptRef; |
|
90 rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef); |
|
91 if (NS_FAILED(rv) || !isEqualExceptRef) { |
|
92 nsRefPtr<nsIDocument::ExternalResourceLoad> load; |
|
93 doc = doc->RequestExternalResource(aURI, aFromContent, |
|
94 getter_AddRefs(load)); |
|
95 if (!doc) { |
|
96 if (!load || !aWatch) { |
|
97 // Nothing will ever happen here |
|
98 return; |
|
99 } |
|
100 |
|
101 DocumentLoadNotification* observer = |
|
102 new DocumentLoadNotification(this, ref); |
|
103 mPendingNotification = observer; |
|
104 if (observer) { |
|
105 load->AddObserver(observer); |
|
106 } |
|
107 // Keep going so we set up our watching stuff a bit |
|
108 } |
|
109 } |
|
110 |
|
111 if (aWatch) { |
|
112 nsCOMPtr<nsIAtom> atom = do_GetAtom(ref); |
|
113 if (!atom) |
|
114 return; |
|
115 atom.swap(mWatchID); |
|
116 } |
|
117 |
|
118 mReferencingImage = aReferenceImage; |
|
119 |
|
120 HaveNewDocument(doc, aWatch, ref); |
|
121 } |
|
122 |
|
123 void |
|
124 nsReferencedElement::ResetWithID(nsIContent* aFromContent, const nsString& aID, |
|
125 bool aWatch) |
|
126 { |
|
127 nsIDocument *doc = aFromContent->GetCurrentDoc(); |
|
128 if (!doc) |
|
129 return; |
|
130 |
|
131 // XXX Need to take care of XBL/XBL2 |
|
132 |
|
133 if (aWatch) { |
|
134 nsCOMPtr<nsIAtom> atom = do_GetAtom(aID); |
|
135 if (!atom) |
|
136 return; |
|
137 atom.swap(mWatchID); |
|
138 } |
|
139 |
|
140 mReferencingImage = false; |
|
141 |
|
142 HaveNewDocument(doc, aWatch, aID); |
|
143 } |
|
144 |
|
145 void |
|
146 nsReferencedElement::HaveNewDocument(nsIDocument* aDocument, bool aWatch, |
|
147 const nsString& aRef) |
|
148 { |
|
149 if (aWatch) { |
|
150 mWatchDocument = aDocument; |
|
151 if (mWatchDocument) { |
|
152 mElement = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this, |
|
153 mReferencingImage); |
|
154 } |
|
155 return; |
|
156 } |
|
157 |
|
158 if (!aDocument) { |
|
159 return; |
|
160 } |
|
161 |
|
162 Element *e = mReferencingImage ? aDocument->LookupImageElement(aRef) : |
|
163 aDocument->GetElementById(aRef); |
|
164 if (e) { |
|
165 mElement = e; |
|
166 } |
|
167 } |
|
168 |
|
169 void |
|
170 nsReferencedElement::Traverse(nsCycleCollectionTraversalCallback* aCB) |
|
171 { |
|
172 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocument"); |
|
173 aCB->NoteXPCOMChild(mWatchDocument); |
|
174 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mContent"); |
|
175 aCB->NoteXPCOMChild(mElement); |
|
176 } |
|
177 |
|
178 void |
|
179 nsReferencedElement::Unlink() |
|
180 { |
|
181 if (mWatchDocument && mWatchID) { |
|
182 mWatchDocument->RemoveIDTargetObserver(mWatchID, Observe, this, |
|
183 mReferencingImage); |
|
184 } |
|
185 if (mPendingNotification) { |
|
186 mPendingNotification->Clear(); |
|
187 mPendingNotification = nullptr; |
|
188 } |
|
189 mWatchDocument = nullptr; |
|
190 mWatchID = nullptr; |
|
191 mElement = nullptr; |
|
192 mReferencingImage = false; |
|
193 } |
|
194 |
|
195 bool |
|
196 nsReferencedElement::Observe(Element* aOldElement, |
|
197 Element* aNewElement, void* aData) |
|
198 { |
|
199 nsReferencedElement* p = static_cast<nsReferencedElement*>(aData); |
|
200 if (p->mPendingNotification) { |
|
201 p->mPendingNotification->SetTo(aNewElement); |
|
202 } else { |
|
203 NS_ASSERTION(aOldElement == p->mElement, "Failed to track content!"); |
|
204 ChangeNotification* watcher = |
|
205 new ChangeNotification(p, aOldElement, aNewElement); |
|
206 p->mPendingNotification = watcher; |
|
207 nsContentUtils::AddScriptRunner(watcher); |
|
208 } |
|
209 bool keepTracking = p->IsPersistent(); |
|
210 if (!keepTracking) { |
|
211 p->mWatchDocument = nullptr; |
|
212 p->mWatchID = nullptr; |
|
213 } |
|
214 return keepTracking; |
|
215 } |
|
216 |
|
217 NS_IMPL_ISUPPORTS_INHERITED0(nsReferencedElement::ChangeNotification, |
|
218 nsRunnable) |
|
219 |
|
220 NS_IMPL_ISUPPORTS(nsReferencedElement::DocumentLoadNotification, |
|
221 nsIObserver) |
|
222 |
|
223 NS_IMETHODIMP |
|
224 nsReferencedElement::DocumentLoadNotification::Observe(nsISupports* aSubject, |
|
225 const char* aTopic, |
|
226 const char16_t* aData) |
|
227 { |
|
228 NS_ASSERTION(PL_strcmp(aTopic, "external-resource-document-created") == 0, |
|
229 "Unexpected topic"); |
|
230 if (mTarget) { |
|
231 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aSubject); |
|
232 mTarget->mPendingNotification = nullptr; |
|
233 NS_ASSERTION(!mTarget->mElement, "Why do we have content here?"); |
|
234 // If we got here, that means we had Reset() called with aWatch == |
|
235 // true. So keep watching if IsPersistent(). |
|
236 mTarget->HaveNewDocument(doc, mTarget->IsPersistent(), mRef); |
|
237 mTarget->ElementChanged(nullptr, mTarget->mElement); |
|
238 } |
|
239 return NS_OK; |
|
240 } |