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