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