/src/mozilla-central/dom/xbl/nsXBLBinding.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "nsCOMPtr.h" |
8 | | #include "nsAtom.h" |
9 | | #include "nsXBLDocumentInfo.h" |
10 | | #include "nsIInputStream.h" |
11 | | #include "nsNameSpaceManager.h" |
12 | | #include "nsIURI.h" |
13 | | #include "nsIURL.h" |
14 | | #include "nsIChannel.h" |
15 | | #include "nsString.h" |
16 | | #include "nsReadableUtils.h" |
17 | | #include "plstr.h" |
18 | | #include "nsIContent.h" |
19 | | #include "nsIDocument.h" |
20 | | #include "nsContentUtils.h" |
21 | | #include "ChildIterator.h" |
22 | | #ifdef MOZ_XUL |
23 | | #include "XULDocument.h" |
24 | | #endif |
25 | | #include "nsIXMLContentSink.h" |
26 | | #include "nsContentCID.h" |
27 | | #include "mozilla/dom/XMLDocument.h" |
28 | | #include "jsapi.h" |
29 | | #include "nsXBLService.h" |
30 | | #include "nsIXPConnect.h" |
31 | | #include "nsIScriptContext.h" |
32 | | #include "nsCRT.h" |
33 | | |
34 | | // Event listeners |
35 | | #include "mozilla/EventListenerManager.h" |
36 | | #include "nsIDOMEventListener.h" |
37 | | #include "nsAttrName.h" |
38 | | |
39 | | #include "nsGkAtoms.h" |
40 | | |
41 | | #include "nsXBLPrototypeHandler.h" |
42 | | |
43 | | #include "nsXBLPrototypeBinding.h" |
44 | | #include "nsXBLBinding.h" |
45 | | #include "nsIPrincipal.h" |
46 | | #include "nsIScriptSecurityManager.h" |
47 | | #include "mozilla/dom/XBLChildrenElement.h" |
48 | | |
49 | | #include "nsNodeUtils.h" |
50 | | #include "nsJSUtils.h" |
51 | | |
52 | | #include "mozilla/DeferredFinalize.h" |
53 | | #include "mozilla/dom/Element.h" |
54 | | #include "mozilla/dom/ScriptSettings.h" |
55 | | |
56 | | using namespace mozilla; |
57 | | using namespace mozilla::dom; |
58 | | |
59 | | // Helper classes |
60 | | |
61 | | /***********************************************************************/ |
62 | | // |
63 | | // The JS class for XBLBinding |
64 | | // |
65 | | static void |
66 | | XBLFinalize(JSFreeOp *fop, JSObject *obj) |
67 | 0 | { |
68 | 0 | nsXBLDocumentInfo* docInfo = |
69 | 0 | static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj)); |
70 | 0 | DeferredFinalize(docInfo); |
71 | 0 | } |
72 | | |
73 | | static bool |
74 | | XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj) |
75 | 0 | { |
76 | 0 | nsXBLPrototypeBinding* protoBinding = |
77 | 0 | static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate()); |
78 | 0 | MOZ_ASSERT(protoBinding); |
79 | 0 |
|
80 | 0 | return protoBinding->ResolveAllFields(cx, obj); |
81 | 0 | } |
82 | | |
83 | | static const JSClassOps gPrototypeJSClassOps = { |
84 | | nullptr, nullptr, |
85 | | XBLEnumerate, nullptr, nullptr, |
86 | | nullptr, XBLFinalize, |
87 | | nullptr, nullptr, nullptr, nullptr |
88 | | }; |
89 | | |
90 | | static const JSClass gPrototypeJSClass = { |
91 | | "XBL prototype JSClass", |
92 | | JSCLASS_HAS_PRIVATE | |
93 | | JSCLASS_PRIVATE_IS_NSISUPPORTS | |
94 | | JSCLASS_FOREGROUND_FINALIZE | |
95 | | // Our one reserved slot holds the relevant nsXBLPrototypeBinding |
96 | | JSCLASS_HAS_RESERVED_SLOTS(1), |
97 | | &gPrototypeJSClassOps |
98 | | }; |
99 | | |
100 | | // Implementation ///////////////////////////////////////////////////////////////// |
101 | | |
102 | | // Constructors/Destructors |
103 | | nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding) |
104 | | : mMarkedForDeath(false) |
105 | | , mUsingContentXBLScope(false) |
106 | | , mPrototypeBinding(aBinding) |
107 | | , mBoundElement(nullptr) |
108 | 0 | { |
109 | 0 | NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!"); |
110 | 0 | // Grab a ref to the document info so the prototype binding won't die |
111 | 0 | NS_ADDREF(mPrototypeBinding->XBLDocumentInfo()); |
112 | 0 | } |
113 | | |
114 | | nsXBLBinding::~nsXBLBinding() |
115 | 0 | { |
116 | 0 | if (mContent) { |
117 | 0 | nsXBLBinding::UnbindAnonymousContent(mContent->OwnerDoc(), mContent); |
118 | 0 | } |
119 | 0 | nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo(); |
120 | 0 | NS_RELEASE(info); |
121 | 0 | } |
122 | | |
123 | | NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding) |
124 | | |
125 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding) |
126 | 0 | // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because |
127 | 0 | // mPrototypeBinding is weak. |
128 | 0 | if (tmp->mContent) { |
129 | 0 | nsXBLBinding::UnbindAnonymousContent(tmp->mContent->OwnerDoc(), |
130 | 0 | tmp->mContent); |
131 | 0 | } |
132 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent) |
133 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding) |
134 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint) |
135 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints) |
136 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList) |
137 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
138 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLBinding) |
139 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, |
140 | 0 | "mPrototypeBinding->XBLDocumentInfo()"); |
141 | 0 | cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo()); |
142 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent) |
143 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextBinding) |
144 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultInsertionPoint) |
145 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInsertionPoints) |
146 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContentList) |
147 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
148 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef) |
149 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release) |
150 | | |
151 | | void |
152 | | nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding) |
153 | 0 | { |
154 | 0 | if (mNextBinding) { |
155 | 0 | NS_ERROR("Base XBL binding is already defined!"); |
156 | 0 | return; |
157 | 0 | } |
158 | 0 |
|
159 | 0 | mNextBinding = aBinding; // Comptr handles rel/add |
160 | 0 | } |
161 | | |
162 | | nsXBLBinding* |
163 | | nsXBLBinding::GetBindingWithContent() |
164 | 0 | { |
165 | 0 | if (mContent) { |
166 | 0 | return this; |
167 | 0 | } |
168 | 0 | |
169 | 0 | return mNextBinding ? mNextBinding->GetBindingWithContent() : nullptr; |
170 | 0 | } |
171 | | |
172 | | void |
173 | | nsXBLBinding::BindAnonymousContent(nsIContent* aAnonParent, |
174 | | nsIContent* aElement, |
175 | | bool aChromeOnlyContent) |
176 | 0 | { |
177 | 0 | // We need to ensure two things. |
178 | 0 | // (1) The anonymous content should be fooled into thinking it's in the bound |
179 | 0 | // element's document, assuming that the bound element is in a document |
180 | 0 | // Note that we don't change the current doc of aAnonParent here, since that |
181 | 0 | // quite simply does not matter. aAnonParent is just a way of keeping refs |
182 | 0 | // to all its kids, which are anonymous content from the point of view of |
183 | 0 | // aElement. |
184 | 0 | // (2) The children's parent back pointer should not be to this synthetic root |
185 | 0 | // but should instead point to the enclosing parent element. |
186 | 0 | nsIDocument* doc = aElement->GetUncomposedDoc(); |
187 | 0 |
|
188 | 0 | nsAutoScriptBlocker scriptBlocker; |
189 | 0 | for (nsIContent* child = aAnonParent->GetFirstChild(); |
190 | 0 | child; |
191 | 0 | child = child->GetNextSibling()) { |
192 | 0 | child->UnbindFromTree(); |
193 | 0 | if (aChromeOnlyContent) { |
194 | 0 | child->SetFlags(NODE_CHROME_ONLY_ACCESS | |
195 | 0 | NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS); |
196 | 0 | } |
197 | 0 | child->SetFlags(NODE_IS_ANONYMOUS_ROOT); |
198 | 0 | nsresult rv = |
199 | 0 | child->BindToTree(doc, aElement, mBoundElement); |
200 | 0 | if (NS_FAILED(rv)) { |
201 | 0 | // Oh, well... Just give up. |
202 | 0 | // XXXbz This really shouldn't be a void method! |
203 | 0 | child->UnbindFromTree(); |
204 | 0 | return; |
205 | 0 | } |
206 | 0 | |
207 | 0 | #ifdef MOZ_XUL |
208 | 0 | // To make XUL templates work (and other goodies that happen when |
209 | 0 | // an element is added to a XUL document), we need to notify the |
210 | 0 | // XUL document using its special API. |
211 | 0 | if (doc && doc->IsXULDocument()) { |
212 | 0 | doc->AsXULDocument()->AddSubtreeToDocument(child); |
213 | 0 | } |
214 | 0 | #endif |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | | void |
219 | | nsXBLBinding::UnbindAnonymousContent(nsIDocument* aDocument, |
220 | | nsIContent* aAnonParent, |
221 | | bool aNullParent) |
222 | 0 | { |
223 | 0 | nsAutoScriptBlocker scriptBlocker; |
224 | 0 | // Hold a strong ref while doing this, just in case. |
225 | 0 | nsCOMPtr<nsIContent> anonParent = aAnonParent; |
226 | 0 | #ifdef MOZ_XUL |
227 | 0 | const bool isXULDocument = aDocument && aDocument->IsXULDocument(); |
228 | 0 | #endif |
229 | 0 | for (nsIContent* child = aAnonParent->GetFirstChild(); |
230 | 0 | child; |
231 | 0 | child = child->GetNextSibling()) { |
232 | 0 | child->UnbindFromTree(true, aNullParent); |
233 | 0 | #ifdef MOZ_XUL |
234 | 0 | if (isXULDocument) { |
235 | 0 | aDocument->AsXULDocument()->RemoveSubtreeFromDocument(child); |
236 | 0 | } |
237 | 0 | #endif |
238 | 0 | } |
239 | 0 | } |
240 | | |
241 | | void |
242 | | nsXBLBinding::SetBoundElement(Element* aElement) |
243 | 0 | { |
244 | 0 | mBoundElement = aElement; |
245 | 0 | if (mNextBinding) |
246 | 0 | mNextBinding->SetBoundElement(aElement); |
247 | 0 |
|
248 | 0 | if (!mBoundElement) { |
249 | 0 | return; |
250 | 0 | } |
251 | 0 | |
252 | 0 | // Compute whether we're using an XBL scope. |
253 | 0 | // |
254 | 0 | // We disable XBL scopes for remote XUL, where we care about compat more |
255 | 0 | // than security. So we need to know whether we're using an XBL scope so that |
256 | 0 | // we can decide what to do about untrusted events when "allowuntrusted" |
257 | 0 | // is not given in the handler declaration. |
258 | 0 | nsCOMPtr<nsIGlobalObject> go = mBoundElement->OwnerDoc()->GetScopeObject(); |
259 | 0 | NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject()); |
260 | 0 | mUsingContentXBLScope = xpc::UseContentXBLScope(JS::GetObjectRealmOrNull(go->GetGlobalJSObject())); |
261 | 0 | } |
262 | | |
263 | | bool |
264 | | nsXBLBinding::HasStyleSheets() const |
265 | 0 | { |
266 | 0 | // Find out if we need to re-resolve style. We'll need to do this |
267 | 0 | // if we have additional stylesheets in our binding document. |
268 | 0 | if (mPrototypeBinding->HasStyleSheets()) |
269 | 0 | return true; |
270 | 0 | |
271 | 0 | return mNextBinding ? mNextBinding->HasStyleSheets() : false; |
272 | 0 | } |
273 | | |
274 | | void |
275 | | nsXBLBinding::GenerateAnonymousContent() |
276 | 0 | { |
277 | 0 | NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), |
278 | 0 | "Someone forgot a script blocker"); |
279 | 0 |
|
280 | 0 | // Fetch the content element for this binding. |
281 | 0 | Element* content = |
282 | 0 | mPrototypeBinding->GetImmediateChild(nsGkAtoms::content); |
283 | 0 |
|
284 | 0 | if (!content) { |
285 | 0 | // We have no anonymous content. |
286 | 0 | if (mNextBinding) |
287 | 0 | mNextBinding->GenerateAnonymousContent(); |
288 | 0 |
|
289 | 0 | return; |
290 | 0 | } |
291 | 0 |
|
292 | 0 | // Find out if we're really building kids or if we're just |
293 | 0 | // using the attribute-setting shorthand hack. |
294 | 0 | uint32_t contentCount = content->GetChildCount(); |
295 | 0 |
|
296 | 0 | // Plan to build the content by default. |
297 | 0 | bool hasContent = (contentCount > 0); |
298 | 0 | if (hasContent) { |
299 | 0 | nsIDocument* doc = mBoundElement->OwnerDoc(); |
300 | 0 |
|
301 | 0 | nsCOMPtr<nsINode> clonedNode = |
302 | 0 | nsNodeUtils::Clone(content, true, doc->NodeInfoManager(), nullptr, |
303 | 0 | IgnoreErrors()); |
304 | 0 | // FIXME: Bug 1399558, Why is this code OK assuming that nsNodeUtils::Clone |
305 | 0 | // never fails? |
306 | 0 | mContent = clonedNode->AsElement(); |
307 | 0 |
|
308 | 0 | // Search for <xbl:children> elements in the XBL content. In the presence |
309 | 0 | // of multiple default insertion points, we use the last one in document |
310 | 0 | // order. |
311 | 0 | for (nsIContent* child = mContent; child; child = child->GetNextNode(mContent)) { |
312 | 0 | if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { |
313 | 0 | XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child); |
314 | 0 | if (point->IsDefaultInsertion()) { |
315 | 0 | mDefaultInsertionPoint = point; |
316 | 0 | } else { |
317 | 0 | mInsertionPoints.AppendElement(point); |
318 | 0 | } |
319 | 0 | } |
320 | 0 | } |
321 | 0 |
|
322 | 0 | // Do this after looking for <children> as this messes up the parent |
323 | 0 | // pointer which would make the GetNextNode call above fail |
324 | 0 | BindAnonymousContent(mContent, mBoundElement, |
325 | 0 | mPrototypeBinding->ChromeOnlyContent()); |
326 | 0 |
|
327 | 0 | // Insert explicit children into insertion points |
328 | 0 | if (mDefaultInsertionPoint && mInsertionPoints.IsEmpty()) { |
329 | 0 | ExplicitChildIterator iter(mBoundElement); |
330 | 0 | for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { |
331 | 0 | // Pass aNotify = false because we're just setting up the whole thing. |
332 | 0 | // Furthermore we do it from frame construction, so passing true here |
333 | 0 | // would reenter into it which is... not great. |
334 | 0 | mDefaultInsertionPoint->AppendInsertedChild(child, false); |
335 | 0 | } |
336 | 0 | } else { |
337 | 0 | // It is odd to come into this code if mInsertionPoints is not empty, but |
338 | 0 | // we need to make sure to do the compatibility hack below if the bound |
339 | 0 | // node has any non <xul:template> or <xul:observes> children. |
340 | 0 | ExplicitChildIterator iter(mBoundElement); |
341 | 0 | for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { |
342 | 0 | XBLChildrenElement* point = FindInsertionPointForInternal(child); |
343 | 0 | if (point) { |
344 | 0 | // Pass aNotify = false because we're just setting up the whole thing. |
345 | 0 | // (see the similar call above for more details). |
346 | 0 | point->AppendInsertedChild(child, false); |
347 | 0 | } else { |
348 | 0 | NodeInfo *ni = child->NodeInfo(); |
349 | 0 | if (!child->TextIsOnlyWhitespace() && |
350 | 0 | (ni->NamespaceID() != kNameSpaceID_XUL || |
351 | 0 | (!ni->Equals(nsGkAtoms::_template) && |
352 | 0 | !ni->Equals(nsGkAtoms::observes))) |
353 | 0 | ) { |
354 | 0 | // Compatibility hack. For some reason the original XBL |
355 | 0 | // implementation dropped the content of a binding if any child of |
356 | 0 | // the bound element didn't match any of the <children> in the |
357 | 0 | // binding. This became a pseudo-API that we have to maintain. |
358 | 0 |
|
359 | 0 | // Undo BindAnonymousContent |
360 | 0 | UnbindAnonymousContent(doc, mContent); |
361 | 0 |
|
362 | 0 | // Clear out our children elements to avoid dangling references. |
363 | 0 | ClearInsertionPoints(); |
364 | 0 |
|
365 | 0 | // Pretend as though there was no content in the binding. |
366 | 0 | mContent = nullptr; |
367 | 0 | return; |
368 | 0 | } |
369 | 0 | } |
370 | 0 | } |
371 | 0 | } |
372 | 0 |
|
373 | 0 | // Set binding parent on default content if need |
374 | 0 | if (mDefaultInsertionPoint) { |
375 | 0 | mDefaultInsertionPoint->MaybeSetupDefaultContent(); |
376 | 0 | } |
377 | 0 | for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) { |
378 | 0 | mInsertionPoints[i]->MaybeSetupDefaultContent(); |
379 | 0 | } |
380 | 0 |
|
381 | 0 | mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent); |
382 | 0 | } |
383 | 0 |
|
384 | 0 | // Always check the content element for potential attributes. |
385 | 0 | // This shorthand hack always happens, even when we didn't |
386 | 0 | // build anonymous content. |
387 | 0 | BorrowedAttrInfo attrInfo; |
388 | 0 | for (uint32_t i = 0; (attrInfo = content->GetAttrInfoAt(i)); ++i) { |
389 | 0 | int32_t namespaceID = attrInfo.mName->NamespaceID(); |
390 | 0 | // Hold a strong reference here so that the atom doesn't go away during |
391 | 0 | // UnsetAttr. |
392 | 0 | RefPtr<nsAtom> name = attrInfo.mName->LocalName(); |
393 | 0 |
|
394 | 0 | if (name != nsGkAtoms::includes) { |
395 | 0 | if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) { |
396 | 0 | nsAutoString value2; |
397 | 0 | attrInfo.mValue->ToString(value2); |
398 | 0 | mBoundElement->SetAttr(namespaceID, name, attrInfo.mName->GetPrefix(), |
399 | 0 | value2, false); |
400 | 0 | } |
401 | 0 | } |
402 | 0 |
|
403 | 0 | // Conserve space by wiping the attributes off the clone. |
404 | 0 | // |
405 | 0 | // FIXME(emilio): It'd be nice to make `mContent` a `RefPtr<Element>`. |
406 | 0 | if (mContent) |
407 | 0 | mContent->AsElement()->UnsetAttr(namespaceID, name, false); |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | nsIURI* |
412 | | nsXBLBinding::GetSourceDocURI() |
413 | 0 | { |
414 | 0 | nsIContent* targetContent = |
415 | 0 | mPrototypeBinding->GetImmediateChild(nsGkAtoms::content); |
416 | 0 | if (!targetContent) { |
417 | 0 | return nullptr; |
418 | 0 | } |
419 | 0 | |
420 | 0 | return targetContent->OwnerDoc()->GetDocumentURI(); |
421 | 0 | } |
422 | | |
423 | | XBLChildrenElement* |
424 | | nsXBLBinding::FindInsertionPointFor(nsIContent* aChild) |
425 | 0 | { |
426 | 0 | // XXX We should get rid of this function as it causes us to traverse the |
427 | 0 | // binding chain multiple times |
428 | 0 | if (mContent) { |
429 | 0 | return FindInsertionPointForInternal(aChild); |
430 | 0 | } |
431 | 0 | |
432 | 0 | return mNextBinding ? mNextBinding->FindInsertionPointFor(aChild) |
433 | 0 | : nullptr; |
434 | 0 | } |
435 | | |
436 | | XBLChildrenElement* |
437 | | nsXBLBinding::FindInsertionPointForInternal(nsIContent* aChild) |
438 | 0 | { |
439 | 0 | for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) { |
440 | 0 | XBLChildrenElement* point = mInsertionPoints[i]; |
441 | 0 | if (point->Includes(aChild)) { |
442 | 0 | return point; |
443 | 0 | } |
444 | 0 | } |
445 | 0 |
|
446 | 0 | return mDefaultInsertionPoint; |
447 | 0 | } |
448 | | |
449 | | void |
450 | | nsXBLBinding::ClearInsertionPoints() |
451 | 0 | { |
452 | 0 | if (mDefaultInsertionPoint) { |
453 | 0 | mDefaultInsertionPoint->ClearInsertedChildren(); |
454 | 0 | } |
455 | 0 |
|
456 | 0 | for (const auto& insertionPoint : mInsertionPoints) { |
457 | 0 | insertionPoint->ClearInsertedChildren(); |
458 | 0 | } |
459 | 0 | } |
460 | | |
461 | | nsAnonymousContentList* |
462 | | nsXBLBinding::GetAnonymousNodeList() |
463 | 0 | { |
464 | 0 | if (!mContent) { |
465 | 0 | return mNextBinding ? mNextBinding->GetAnonymousNodeList() : nullptr; |
466 | 0 | } |
467 | 0 |
|
468 | 0 | if (!mAnonymousContentList) { |
469 | 0 | mAnonymousContentList = new nsAnonymousContentList(mContent); |
470 | 0 | } |
471 | 0 |
|
472 | 0 | return mAnonymousContentList; |
473 | 0 | } |
474 | | |
475 | | void |
476 | | nsXBLBinding::InstallEventHandlers() |
477 | 0 | { |
478 | 0 | // Don't install handlers if scripts aren't allowed. |
479 | 0 | if (AllowScripts()) { |
480 | 0 | // Fetch the handlers prototypes for this binding. |
481 | 0 | nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers(); |
482 | 0 |
|
483 | 0 | if (handlerChain) { |
484 | 0 | EventListenerManager* manager = mBoundElement->GetOrCreateListenerManager(); |
485 | 0 | if (!manager) |
486 | 0 | return; |
487 | 0 | |
488 | 0 | bool isChromeDoc = |
489 | 0 | nsContentUtils::IsChromeDoc(mBoundElement->OwnerDoc()); |
490 | 0 | bool isChromeBinding = mPrototypeBinding->IsChrome(); |
491 | 0 | nsXBLPrototypeHandler* curr; |
492 | 0 | for (curr = handlerChain; curr; curr = curr->GetNextHandler()) { |
493 | 0 | // Fetch the event type. |
494 | 0 | RefPtr<nsAtom> eventAtom = curr->GetEventName(); |
495 | 0 | if (!eventAtom || |
496 | 0 | eventAtom == nsGkAtoms::keyup || |
497 | 0 | eventAtom == nsGkAtoms::keydown || |
498 | 0 | eventAtom == nsGkAtoms::keypress) |
499 | 0 | continue; |
500 | 0 | |
501 | 0 | nsXBLEventHandler* handler = curr->GetEventHandler(); |
502 | 0 | if (handler) { |
503 | 0 | // Figure out if we're using capturing or not. |
504 | 0 | EventListenerFlags flags; |
505 | 0 | flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING); |
506 | 0 |
|
507 | 0 | // If this is a command, add it in the system event group |
508 | 0 | if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | |
509 | 0 | NS_HANDLER_TYPE_SYSTEM)) && |
510 | 0 | (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) { |
511 | 0 | flags.mInSystemGroup = true; |
512 | 0 | } |
513 | 0 |
|
514 | 0 | bool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr(); |
515 | 0 | if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) || |
516 | 0 | (!hasAllowUntrustedAttr && !isChromeDoc && !mUsingContentXBLScope)) { |
517 | 0 | flags.mAllowUntrustedEvents = true; |
518 | 0 | } |
519 | 0 |
|
520 | 0 | manager->AddEventListenerByType(handler, |
521 | 0 | nsDependentAtomString(eventAtom), |
522 | 0 | flags); |
523 | 0 | } |
524 | 0 | } |
525 | 0 |
|
526 | 0 | const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers = |
527 | 0 | mPrototypeBinding->GetKeyEventHandlers(); |
528 | 0 | int32_t i; |
529 | 0 | for (i = 0; i < keyHandlers->Count(); ++i) { |
530 | 0 | nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i); |
531 | 0 | handler->SetIsBoundToChrome(isChromeDoc); |
532 | 0 | handler->SetUsingContentXBLScope(mUsingContentXBLScope); |
533 | 0 |
|
534 | 0 | nsAutoString type; |
535 | 0 | handler->GetEventName(type); |
536 | 0 |
|
537 | 0 | // If this is a command, add it in the system event group, otherwise |
538 | 0 | // add it to the standard event group. |
539 | 0 |
|
540 | 0 | // Figure out if we're using capturing or not. |
541 | 0 | EventListenerFlags flags; |
542 | 0 | flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING); |
543 | 0 |
|
544 | 0 | if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | |
545 | 0 | NS_HANDLER_TYPE_SYSTEM)) && |
546 | 0 | (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) { |
547 | 0 | flags.mInSystemGroup = true; |
548 | 0 | } |
549 | 0 |
|
550 | 0 | // For key handlers we have to set mAllowUntrustedEvents flag. |
551 | 0 | // Whether the handling of the event is allowed or not is handled in |
552 | 0 | // nsXBLKeyEventHandler::HandleEvent |
553 | 0 | flags.mAllowUntrustedEvents = true; |
554 | 0 |
|
555 | 0 | manager->AddEventListenerByType(handler, type, flags); |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | 0 |
|
560 | 0 | if (mNextBinding) |
561 | 0 | mNextBinding->InstallEventHandlers(); |
562 | 0 | } |
563 | | |
564 | | nsresult |
565 | | nsXBLBinding::InstallImplementation() |
566 | 0 | { |
567 | 0 | // Always install the base class properties first, so that |
568 | 0 | // derived classes can reference the base class properties. |
569 | 0 |
|
570 | 0 | if (mNextBinding) { |
571 | 0 | nsresult rv = mNextBinding->InstallImplementation(); |
572 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
573 | 0 | } |
574 | 0 |
|
575 | 0 | // iterate through each property in the prototype's list and install the property. |
576 | 0 | if (AllowScripts()) |
577 | 0 | return mPrototypeBinding->InstallImplementation(this); |
578 | 0 | |
579 | 0 | return NS_OK; |
580 | 0 | } |
581 | | |
582 | | nsAtom* |
583 | | nsXBLBinding::GetBaseTag(int32_t* aNameSpaceID) |
584 | 0 | { |
585 | 0 | nsAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID); |
586 | 0 | if (!tag && mNextBinding) |
587 | 0 | return mNextBinding->GetBaseTag(aNameSpaceID); |
588 | 0 | |
589 | 0 | return tag; |
590 | 0 | } |
591 | | |
592 | | void |
593 | | nsXBLBinding::AttributeChanged(nsAtom* aAttribute, int32_t aNameSpaceID, |
594 | | bool aRemoveFlag, bool aNotify) |
595 | 0 | { |
596 | 0 | // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content |
597 | 0 | if (!mContent) { |
598 | 0 | if (mNextBinding) |
599 | 0 | mNextBinding->AttributeChanged(aAttribute, aNameSpaceID, |
600 | 0 | aRemoveFlag, aNotify); |
601 | 0 | } else { |
602 | 0 | mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag, |
603 | 0 | mBoundElement, mContent, aNotify); |
604 | 0 | } |
605 | 0 | } |
606 | | |
607 | | void |
608 | | nsXBLBinding::ExecuteAttachedHandler() |
609 | 0 | { |
610 | 0 | if (mNextBinding) |
611 | 0 | mNextBinding->ExecuteAttachedHandler(); |
612 | 0 |
|
613 | 0 | if (AllowScripts()) |
614 | 0 | mPrototypeBinding->BindingAttached(mBoundElement); |
615 | 0 | } |
616 | | |
617 | | void |
618 | | nsXBLBinding::ExecuteDetachedHandler() |
619 | 0 | { |
620 | 0 | if (AllowScripts()) |
621 | 0 | mPrototypeBinding->BindingDetached(mBoundElement); |
622 | 0 |
|
623 | 0 | if (mNextBinding) |
624 | 0 | mNextBinding->ExecuteDetachedHandler(); |
625 | 0 | } |
626 | | |
627 | | void |
628 | | nsXBLBinding::UnhookEventHandlers() |
629 | 0 | { |
630 | 0 | nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers(); |
631 | 0 |
|
632 | 0 | if (handlerChain) { |
633 | 0 | EventListenerManager* manager = mBoundElement->GetExistingListenerManager(); |
634 | 0 | if (!manager) { |
635 | 0 | return; |
636 | 0 | } |
637 | 0 | |
638 | 0 | bool isChromeBinding = mPrototypeBinding->IsChrome(); |
639 | 0 | nsXBLPrototypeHandler* curr; |
640 | 0 | for (curr = handlerChain; curr; curr = curr->GetNextHandler()) { |
641 | 0 | nsXBLEventHandler* handler = curr->GetCachedEventHandler(); |
642 | 0 | if (!handler) { |
643 | 0 | continue; |
644 | 0 | } |
645 | 0 | |
646 | 0 | RefPtr<nsAtom> eventAtom = curr->GetEventName(); |
647 | 0 | if (!eventAtom || |
648 | 0 | eventAtom == nsGkAtoms::keyup || |
649 | 0 | eventAtom == nsGkAtoms::keydown || |
650 | 0 | eventAtom == nsGkAtoms::keypress) |
651 | 0 | continue; |
652 | 0 | |
653 | 0 | // Figure out if we're using capturing or not. |
654 | 0 | EventListenerFlags flags; |
655 | 0 | flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING); |
656 | 0 |
|
657 | 0 | // If this is a command, remove it from the system event group, |
658 | 0 | // otherwise remove it from the standard event group. |
659 | 0 |
|
660 | 0 | if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | |
661 | 0 | NS_HANDLER_TYPE_SYSTEM)) && |
662 | 0 | (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) { |
663 | 0 | flags.mInSystemGroup = true; |
664 | 0 | } |
665 | 0 |
|
666 | 0 | manager->RemoveEventListenerByType(handler, |
667 | 0 | nsDependentAtomString(eventAtom), |
668 | 0 | flags); |
669 | 0 | } |
670 | 0 |
|
671 | 0 | const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers = |
672 | 0 | mPrototypeBinding->GetKeyEventHandlers(); |
673 | 0 | int32_t i; |
674 | 0 | for (i = 0; i < keyHandlers->Count(); ++i) { |
675 | 0 | nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i); |
676 | 0 |
|
677 | 0 | nsAutoString type; |
678 | 0 | handler->GetEventName(type); |
679 | 0 |
|
680 | 0 | // Figure out if we're using capturing or not. |
681 | 0 | EventListenerFlags flags; |
682 | 0 | flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING); |
683 | 0 |
|
684 | 0 | // If this is a command, remove it from the system event group, otherwise |
685 | 0 | // remove it from the standard event group. |
686 | 0 |
|
687 | 0 | if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) && |
688 | 0 | (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) { |
689 | 0 | flags.mInSystemGroup = true; |
690 | 0 | } |
691 | 0 |
|
692 | 0 | manager->RemoveEventListenerByType(handler, type, flags); |
693 | 0 | } |
694 | 0 | } |
695 | 0 | } |
696 | | |
697 | | void |
698 | | nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument) |
699 | 0 | { |
700 | 0 | if (aOldDocument == aNewDocument) |
701 | 0 | return; |
702 | 0 | |
703 | 0 | // Now the binding dies. Unhook our prototypes. |
704 | 0 | if (mPrototypeBinding->HasImplementation()) { |
705 | 0 | AutoJSAPI jsapi; |
706 | 0 | // Init might fail here if we've cycle-collected the global object, since |
707 | 0 | // the Unlink phase of cycle collection happens after JS GC finalization. |
708 | 0 | // But in that case, we don't care about fixing the prototype chain, since |
709 | 0 | // everything's going away immediately. |
710 | 0 | if (jsapi.Init(aOldDocument->GetScopeObject())) { |
711 | 0 | JSContext* cx = jsapi.cx(); |
712 | 0 |
|
713 | 0 | JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper()); |
714 | 0 | if (scriptObject) { |
715 | 0 | // XXX Stay in sync! What if a layered binding has an |
716 | 0 | // <interface>?! |
717 | 0 | // XXXbz what does that comment mean, really? It seems to date |
718 | 0 | // back to when there was such a thing as an <interface>, whever |
719 | 0 | // that was... |
720 | 0 |
|
721 | 0 | // Find the right prototype. |
722 | 0 | JSAutoRealm ar(cx, scriptObject); |
723 | 0 |
|
724 | 0 | JS::Rooted<JSObject*> base(cx, scriptObject); |
725 | 0 | JS::Rooted<JSObject*> proto(cx); |
726 | 0 | for ( ; true; base = proto) { // Will break out on null proto |
727 | 0 | if (!JS_GetPrototype(cx, base, &proto)) { |
728 | 0 | return; |
729 | 0 | } |
730 | 0 | if (!proto) { |
731 | 0 | break; |
732 | 0 | } |
733 | 0 | |
734 | 0 | if (JS_GetClass(proto) != &gPrototypeJSClass) { |
735 | 0 | // Clearly not the right class |
736 | 0 | continue; |
737 | 0 | } |
738 | 0 | |
739 | 0 | RefPtr<nsXBLDocumentInfo> docInfo = |
740 | 0 | static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(proto)); |
741 | 0 | if (!docInfo) { |
742 | 0 | // Not the proto we seek |
743 | 0 | continue; |
744 | 0 | } |
745 | 0 | |
746 | 0 | JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0); |
747 | 0 |
|
748 | 0 | if (protoBinding.toPrivate() != mPrototypeBinding) { |
749 | 0 | // Not the right binding |
750 | 0 | continue; |
751 | 0 | } |
752 | 0 | |
753 | 0 | // Alright! This is the right prototype. Pull it out of the |
754 | 0 | // proto chain. |
755 | 0 | JS::Rooted<JSObject*> grandProto(cx); |
756 | 0 | if (!JS_GetPrototype(cx, proto, &grandProto)) { |
757 | 0 | return; |
758 | 0 | } |
759 | 0 | ::JS_SetPrototype(cx, base, grandProto); |
760 | 0 | break; |
761 | 0 | } |
762 | 0 |
|
763 | 0 | mPrototypeBinding->UndefineFields(cx, scriptObject); |
764 | 0 |
|
765 | 0 | // Don't remove the reference from the document to the |
766 | 0 | // wrapper here since it'll be removed by the element |
767 | 0 | // itself when that's taken out of the document. |
768 | 0 | } |
769 | 0 | } |
770 | 0 | } |
771 | 0 |
|
772 | 0 | // Remove our event handlers |
773 | 0 | UnhookEventHandlers(); |
774 | 0 |
|
775 | 0 | { |
776 | 0 | nsAutoScriptBlocker scriptBlocker; |
777 | 0 |
|
778 | 0 | // Then do our ancestors. This reverses the construction order, so that at |
779 | 0 | // all times things are consistent as far as everyone is concerned. |
780 | 0 | if (mNextBinding) { |
781 | 0 | mNextBinding->ChangeDocument(aOldDocument, aNewDocument); |
782 | 0 | } |
783 | 0 |
|
784 | 0 | // Update the anonymous content. |
785 | 0 | // XXXbz why not only for style bindings? |
786 | 0 | if (mContent) { |
787 | 0 | nsXBLBinding::UnbindAnonymousContent(aOldDocument, mContent); |
788 | 0 | } |
789 | 0 |
|
790 | 0 | ClearInsertionPoints(); |
791 | 0 | } |
792 | 0 | } |
793 | | |
794 | | bool |
795 | | nsXBLBinding::InheritsStyle() const |
796 | 0 | { |
797 | 0 | // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content. |
798 | 0 | // Most derived binding with anonymous content determines style inheritance for now. |
799 | 0 |
|
800 | 0 | // XXX What about bindings with <content> but no kids, e.g., my treecell-text binding? |
801 | 0 | if (mContent) |
802 | 0 | return mPrototypeBinding->InheritsStyle(); |
803 | 0 | |
804 | 0 | if (mNextBinding) |
805 | 0 | return mNextBinding->InheritsStyle(); |
806 | 0 | |
807 | 0 | return true; |
808 | 0 | } |
809 | | |
810 | | |
811 | | const RawServoAuthorStyles* |
812 | | nsXBLBinding::GetServoStyles() const |
813 | 0 | { |
814 | 0 | return mPrototypeBinding->GetServoStyles(); |
815 | 0 | } |
816 | | |
817 | | // Internal helper methods //////////////////////////////////////////////////////////////// |
818 | | |
819 | | // Get or create a WeakMap object on a given XBL-hosting global. |
820 | | // |
821 | | // The scheme is as follows. XBL-hosting globals (either privileged content |
822 | | // Windows or XBL scopes) get two lazily-defined WeakMap properties. Each |
823 | | // WeakMap is keyed by the grand-proto - i.e. the original prototype of the |
824 | | // content before it was bound, and the prototype of the class object that we |
825 | | // splice in. The values in the WeakMap are simple dictionary-style objects, |
826 | | // mapping from XBL class names to class objects. |
827 | | static JSObject* |
828 | | GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName) |
829 | 0 | { |
830 | 0 | AssertSameCompartment(cx, scope); |
831 | 0 | MOZ_ASSERT(JS_IsGlobalObject(scope)); |
832 | 0 | MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope)); |
833 | 0 |
|
834 | 0 | // First, see if the map is already defined. |
835 | 0 | JS::Rooted<JS::PropertyDescriptor> desc(cx); |
836 | 0 | if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, &desc)) { |
837 | 0 | return nullptr; |
838 | 0 | } |
839 | 0 | if (desc.object() && desc.value().isObject() && |
840 | 0 | JS::IsWeakMapObject(&desc.value().toObject())) { |
841 | 0 | return &desc.value().toObject(); |
842 | 0 | } |
843 | 0 | |
844 | 0 | // It's not there. Create and define it. |
845 | 0 | JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx)); |
846 | 0 | if (!map || !JS_DefineProperty(cx, scope, mapName, map, |
847 | 0 | JSPROP_PERMANENT | JSPROP_READONLY)) |
848 | 0 | { |
849 | 0 | return nullptr; |
850 | 0 | } |
851 | 0 | return map; |
852 | 0 | } |
853 | | |
854 | | static JSObject* |
855 | | GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto) |
856 | 0 | { |
857 | 0 | AssertSameCompartment(cx, proto); |
858 | 0 | // We want to hang our class objects off the XBL scope. But since we also |
859 | 0 | // hoist anonymous content into the XBL scope, this creates the potential for |
860 | 0 | // tricky collisions, since we can simultaneously have a bound in-content |
861 | 0 | // node with grand-proto HTMLDivElement and a bound anonymous node whose |
862 | 0 | // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement. |
863 | 0 | // Since we have to wrap the WeakMap keys into its scope, this distinction |
864 | 0 | // would be lost if we don't do something about it. |
865 | 0 | // |
866 | 0 | // So we define two maps - one class objects that live in content (prototyped |
867 | 0 | // to content prototypes), and the other for class objects that live in the |
868 | 0 | // XBL scope (prototyped to cross-compartment-wrapped content prototypes). |
869 | 0 | const char* name = xpc::IsInContentXBLScope(proto) ? "__ContentClassObjectMap__" |
870 | 0 | : "__XBLClassObjectMap__"; |
871 | 0 |
|
872 | 0 | // Now, enter the XBL scope, since that's where we need to operate, and wrap |
873 | 0 | // the proto accordingly. We hang the map off of the content XBL scope for |
874 | 0 | // content, and the Window for chrome (whether add-ons are involved or not). |
875 | 0 | JS::Rooted<JSObject*> scope(cx, |
876 | 0 | xpc::GetXBLScopeOrGlobal(cx, JS::CurrentGlobalOrNull(cx))); |
877 | 0 | NS_ENSURE_TRUE(scope, nullptr); |
878 | 0 | MOZ_ASSERT(JS_IsGlobalObject(scope)); |
879 | 0 |
|
880 | 0 | JS::Rooted<JSObject*> wrappedProto(cx, proto); |
881 | 0 | JSAutoRealm ar(cx, scope); |
882 | 0 | if (!JS_WrapObject(cx, &wrappedProto)) { |
883 | 0 | return nullptr; |
884 | 0 | } |
885 | 0 | |
886 | 0 | // Grab the appropriate WeakMap. |
887 | 0 | JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name)); |
888 | 0 | if (!map) { |
889 | 0 | return nullptr; |
890 | 0 | } |
891 | 0 | |
892 | 0 | // See if we already have a map entry for that prototype. |
893 | 0 | JS::Rooted<JS::Value> val(cx); |
894 | 0 | if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) { |
895 | 0 | return nullptr; |
896 | 0 | } |
897 | 0 | if (val.isObject()) { |
898 | 0 | return &val.toObject(); |
899 | 0 | } |
900 | 0 | |
901 | 0 | // We don't have an entry. Create one and stick it in the map. |
902 | 0 | JS::Rooted<JSObject*> entry(cx); |
903 | 0 | entry = JS_NewObjectWithGivenProto(cx, nullptr, nullptr); |
904 | 0 | if (!entry) { |
905 | 0 | return nullptr; |
906 | 0 | } |
907 | 0 | JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry)); |
908 | 0 | if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) { |
909 | 0 | NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap " |
910 | 0 | "key. XBL binding will fail for this element."); |
911 | 0 | return nullptr; |
912 | 0 | } |
913 | 0 | return entry; |
914 | 0 | } |
915 | | |
916 | | static |
917 | | nsXBLPrototypeBinding* |
918 | | GetProtoBindingFromClassObject(JSObject* obj) |
919 | 0 | { |
920 | 0 | MOZ_ASSERT(JS_GetClass(obj) == &gPrototypeJSClass); |
921 | 0 | return static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate()); |
922 | 0 | } |
923 | | |
924 | | |
925 | | // static |
926 | | nsresult |
927 | | nsXBLBinding::DoInitJSClass(JSContext *cx, |
928 | | JS::Handle<JSObject*> obj, |
929 | | const nsString& aClassName, |
930 | | nsXBLPrototypeBinding* aProtoBinding, |
931 | | JS::MutableHandle<JSObject*> aClassObject, |
932 | | bool* aNew) |
933 | 0 | { |
934 | 0 | MOZ_ASSERT(obj); |
935 | 0 |
|
936 | 0 | // Note that, now that NAC reflectors are created in the XBL scope, the |
937 | 0 | // reflector is not necessarily same-compartment with the document. So we'll |
938 | 0 | // end up creating a separate instance of the oddly-named XBL class object |
939 | 0 | // and defining it as a property on the XBL scope's global. This works fine, |
940 | 0 | // but we need to make sure never to assume that the the reflector and |
941 | 0 | // prototype are same-compartment with the bound document. |
942 | 0 | JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(obj)); |
943 | 0 |
|
944 | 0 | // We must be in obj's realm. |
945 | 0 | MOZ_ASSERT(JS::CurrentGlobalOrNull(cx) == global); |
946 | 0 |
|
947 | 0 | // We never store class objects in add-on scopes. |
948 | 0 | JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global)); |
949 | 0 | NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED); |
950 | 0 |
|
951 | 0 | JS::Rooted<JSObject*> parent_proto(cx); |
952 | 0 | { |
953 | 0 | JS::RootedObject wrapped(cx, obj); |
954 | 0 | JSAutoRealm ar(cx, xblScope); |
955 | 0 | if (!JS_WrapObject(cx, &wrapped)) { |
956 | 0 | return NS_ERROR_FAILURE; |
957 | 0 | } |
958 | 0 | if (!JS_GetPrototype(cx, wrapped, &parent_proto)) { |
959 | 0 | return NS_ERROR_FAILURE; |
960 | 0 | } |
961 | 0 | } |
962 | 0 | if (!JS_WrapObject(cx, &parent_proto)) { |
963 | 0 | return NS_ERROR_FAILURE; |
964 | 0 | } |
965 | 0 | |
966 | 0 | // Get the map entry for the parent prototype. In the one-off case that the |
967 | 0 | // parent prototype is null, we somewhat hackily just use the WeakMap itself |
968 | 0 | // as a property holder. |
969 | 0 | JS::Rooted<JSObject*> holder(cx); |
970 | 0 | if (parent_proto) { |
971 | 0 | holder = GetOrCreateMapEntryForPrototype(cx, parent_proto); |
972 | 0 | } else { |
973 | 0 | JSAutoRealm innerAR(cx, xblScope); |
974 | 0 | holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__"); |
975 | 0 | } |
976 | 0 | if (NS_WARN_IF(!holder)) { |
977 | 0 | return NS_ERROR_FAILURE; |
978 | 0 | } |
979 | 0 | js::AssertSameCompartment(holder, xblScope); |
980 | 0 | JSAutoRealm ar(cx, holder); |
981 | 0 |
|
982 | 0 | // Look up the class on the property holder. The only properties on the |
983 | 0 | // holder should be class objects. If we don't find the class object, we need |
984 | 0 | // to create and define it. |
985 | 0 | JS::Rooted<JSObject*> proto(cx); |
986 | 0 | JS::Rooted<JS::PropertyDescriptor> desc(cx); |
987 | 0 | if (!JS_GetOwnUCPropertyDescriptor(cx, holder, aClassName.get(), &desc)) { |
988 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
989 | 0 | } |
990 | 0 | *aNew = !desc.object(); |
991 | 0 | if (desc.object()) { |
992 | 0 | proto = &desc.value().toObject(); |
993 | 0 | DebugOnly<nsXBLPrototypeBinding*> cachedBinding = |
994 | 0 | GetProtoBindingFromClassObject(js::UncheckedUnwrap(proto)); |
995 | 0 | MOZ_ASSERT(cachedBinding == aProtoBinding); |
996 | 0 | } else { |
997 | 0 |
|
998 | 0 | // We need to create the prototype. First, enter the realm where it's |
999 | 0 | // going to live, and create it. |
1000 | 0 | JSAutoRealm ar2(cx, global); |
1001 | 0 | proto = JS_NewObjectWithGivenProto(cx, &gPrototypeJSClass, parent_proto); |
1002 | 0 | if (!proto) { |
1003 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1004 | 0 | } |
1005 | 0 | |
1006 | 0 | // Keep this proto binding alive while we're alive. Do this first so that |
1007 | 0 | // we can guarantee that in XBLFinalize this will be non-null. |
1008 | 0 | // Note that we can't just store aProtoBinding in the private and |
1009 | 0 | // addref/release the nsXBLDocumentInfo through it, because cycle |
1010 | 0 | // collection doesn't seem to work right if the private is not an |
1011 | 0 | // nsISupports. |
1012 | 0 | nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo(); |
1013 | 0 | ::JS_SetPrivate(proto, docInfo); |
1014 | 0 | NS_ADDREF(docInfo); |
1015 | 0 | RecordReplayRegisterDeferredFinalize(docInfo); |
1016 | 0 | JS_SetReservedSlot(proto, 0, JS::PrivateValue(aProtoBinding)); |
1017 | 0 |
|
1018 | 0 | // Next, enter the realm of the property holder, wrap the proto, and |
1019 | 0 | // stick it on. |
1020 | 0 | JSAutoRealm ar3(cx, holder); |
1021 | 0 | if (!JS_WrapObject(cx, &proto) || |
1022 | 0 | !JS_DefineUCProperty(cx, holder, aClassName.get(), -1, proto, |
1023 | 0 | JSPROP_READONLY | JSPROP_PERMANENT)) |
1024 | 0 | { |
1025 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1026 | 0 | } |
1027 | 0 | } |
1028 | 0 | |
1029 | 0 | // Whew. We have the proto. Wrap it back into the realm of |obj|, |
1030 | 0 | // splice it in, and return it. |
1031 | 0 | JSAutoRealm ar4(cx, obj); |
1032 | 0 | if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) { |
1033 | 0 | return NS_ERROR_FAILURE; |
1034 | 0 | } |
1035 | 0 | aClassObject.set(proto); |
1036 | 0 | return NS_OK; |
1037 | 0 | } |
1038 | | |
1039 | | bool |
1040 | | nsXBLBinding::AllowScripts() |
1041 | 0 | { |
1042 | 0 | return mBoundElement && mPrototypeBinding->GetAllowScripts(); |
1043 | 0 | } |
1044 | | |
1045 | | nsXBLBinding* |
1046 | | nsXBLBinding::RootBinding() |
1047 | 0 | { |
1048 | 0 | if (mNextBinding) |
1049 | 0 | return mNextBinding->RootBinding(); |
1050 | 0 | |
1051 | 0 | return this; |
1052 | 0 | } |
1053 | | |
1054 | | bool |
1055 | | nsXBLBinding::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const |
1056 | 0 | { |
1057 | 0 | if (!mPrototypeBinding->ResolveAllFields(cx, obj)) { |
1058 | 0 | return false; |
1059 | 0 | } |
1060 | 0 | |
1061 | 0 | if (mNextBinding) { |
1062 | 0 | return mNextBinding->ResolveAllFields(cx, obj); |
1063 | 0 | } |
1064 | 0 | |
1065 | 0 | return true; |
1066 | 0 | } |
1067 | | |
1068 | | bool |
1069 | | nsXBLBinding::LookupMember(JSContext* aCx, JS::Handle<jsid> aId, |
1070 | | JS::MutableHandle<JS::PropertyDescriptor> aDesc) |
1071 | 0 | { |
1072 | 0 | // We should never enter this function with a pre-filled property descriptor. |
1073 | 0 | MOZ_ASSERT(!aDesc.object()); |
1074 | 0 |
|
1075 | 0 | // Get the string as an nsString before doing anything, so we can make |
1076 | 0 | // convenient comparisons during our search. |
1077 | 0 | if (!JSID_IS_STRING(aId)) { |
1078 | 0 | return true; |
1079 | 0 | } |
1080 | 0 | nsAutoJSString name; |
1081 | 0 | if (!name.init(aCx, JSID_TO_STRING(aId))) { |
1082 | 0 | return false; |
1083 | 0 | } |
1084 | 0 | |
1085 | 0 | // We have a weak reference to our bound element, so make sure it's alive. |
1086 | 0 | if (!mBoundElement || !mBoundElement->GetWrapper()) { |
1087 | 0 | return false; |
1088 | 0 | } |
1089 | 0 | |
1090 | 0 | // Get the scope of mBoundElement and the associated XBL scope. We should only |
1091 | 0 | // be calling into this machinery if we're running in a separate XBL scope. |
1092 | 0 | // |
1093 | 0 | // Note that we only end up in LookupMember for XrayWrappers from XBL scopes |
1094 | 0 | // into content. So for NAC reflectors that live in the XBL scope, we should |
1095 | 0 | // never get here. But on the off-chance that someone adds new callsites to |
1096 | 0 | // LookupMember, we do a release-mode assertion as belt-and-braces. |
1097 | 0 | // We do a release-mode assertion here to be extra safe. |
1098 | 0 | // |
1099 | 0 | // This code is only called for content XBL, so we don't have to worry about |
1100 | 0 | // add-on scopes here. |
1101 | 0 | JS::Rooted<JSObject*> boundScope(aCx, |
1102 | 0 | JS::GetNonCCWObjectGlobal(mBoundElement->GetWrapper())); |
1103 | 0 | MOZ_RELEASE_ASSERT(!xpc::IsInContentXBLScope(boundScope)); |
1104 | 0 | JS::Rooted<JSObject*> xblScope(aCx, xpc::GetXBLScope(aCx, boundScope)); |
1105 | 0 | NS_ENSURE_TRUE(xblScope, false); |
1106 | 0 | MOZ_ASSERT(boundScope != xblScope); |
1107 | 0 |
|
1108 | 0 | // Enter the xbl scope and invoke the internal version. |
1109 | 0 | { |
1110 | 0 | JSAutoRealm ar(aCx, xblScope); |
1111 | 0 | JS::Rooted<jsid> id(aCx, aId); |
1112 | 0 | if (!LookupMemberInternal(aCx, name, id, aDesc, xblScope)) { |
1113 | 0 | return false; |
1114 | 0 | } |
1115 | 0 | } |
1116 | 0 | |
1117 | 0 | // Wrap into the caller's scope. |
1118 | 0 | return JS_WrapPropertyDescriptor(aCx, aDesc); |
1119 | 0 | } |
1120 | | |
1121 | | bool |
1122 | | nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName, |
1123 | | JS::Handle<jsid> aNameAsId, |
1124 | | JS::MutableHandle<JS::PropertyDescriptor> aDesc, |
1125 | | JS::Handle<JSObject*> aXBLScope) |
1126 | 0 | { |
1127 | 0 | // First, see if we have an implementation. If we don't, it means that this |
1128 | 0 | // binding doesn't have a class object, and thus doesn't have any members. |
1129 | 0 | // Skip it. |
1130 | 0 | if (!PrototypeBinding()->HasImplementation()) { |
1131 | 0 | if (!mNextBinding) { |
1132 | 0 | return true; |
1133 | 0 | } |
1134 | 0 | return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, |
1135 | 0 | aDesc, aXBLScope); |
1136 | 0 | } |
1137 | 0 | |
1138 | 0 | // Find our class object. It's in a protected scope and permanent just in case, |
1139 | 0 | // so should be there no matter what. |
1140 | 0 | JS::Rooted<JS::Value> classObject(aCx); |
1141 | 0 | if (!JS_GetUCProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(), |
1142 | 0 | -1, &classObject)) { |
1143 | 0 | return false; |
1144 | 0 | } |
1145 | 0 | |
1146 | 0 | // The bound element may have been adoped by a document and have a different |
1147 | 0 | // wrapper (and different xbl scope) than when the binding was applied, in |
1148 | 0 | // this case getting the class object will fail. Behave as if the class |
1149 | 0 | // object did not exist. |
1150 | 0 | if (classObject.isUndefined()) { |
1151 | 0 | return true; |
1152 | 0 | } |
1153 | 0 | |
1154 | 0 | MOZ_ASSERT(classObject.isObject()); |
1155 | 0 |
|
1156 | 0 | // Look for the property on this binding. If it's not there, try the next |
1157 | 0 | // binding on the chain. |
1158 | 0 | nsXBLProtoImpl* impl = mPrototypeBinding->GetImplementation(); |
1159 | 0 | JS::Rooted<JSObject*> object(aCx, &classObject.toObject()); |
1160 | 0 | if (impl && !impl->LookupMember(aCx, aName, aNameAsId, aDesc, object)) { |
1161 | 0 | return false; |
1162 | 0 | } |
1163 | 0 | if (aDesc.object() || !mNextBinding) { |
1164 | 0 | return true; |
1165 | 0 | } |
1166 | 0 | |
1167 | 0 | return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, aDesc, |
1168 | 0 | aXBLScope); |
1169 | 0 | } |
1170 | | |
1171 | | bool |
1172 | | nsXBLBinding::HasField(nsString& aName) |
1173 | 0 | { |
1174 | 0 | // See if this binding has such a field. |
1175 | 0 | return mPrototypeBinding->FindField(aName) || |
1176 | 0 | (mNextBinding && mNextBinding->HasField(aName)); |
1177 | 0 | } |
1178 | | |
1179 | | void |
1180 | | nsXBLBinding::MarkForDeath() |
1181 | 0 | { |
1182 | 0 | mMarkedForDeath = true; |
1183 | 0 | ExecuteDetachedHandler(); |
1184 | 0 | } |
1185 | | |
1186 | | bool |
1187 | | nsXBLBinding::ImplementsInterface(REFNSIID aIID) const |
1188 | 0 | { |
1189 | 0 | return mPrototypeBinding->ImplementsInterface(aIID) || |
1190 | 0 | (mNextBinding && mNextBinding->ImplementsInterface(aIID)); |
1191 | 0 | } |