summaryrefslogtreecommitdiffstats
path: root/dom/xbl/nsXBLBinding.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xbl/nsXBLBinding.cpp')
-rw-r--r--dom/xbl/nsXBLBinding.cpp1247
1 files changed, 1247 insertions, 0 deletions
diff --git a/dom/xbl/nsXBLBinding.cpp b/dom/xbl/nsXBLBinding.cpp
new file mode 100644
index 000000000..6ae17c4c0
--- /dev/null
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -0,0 +1,1247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsIInputStream.h"
+#include "nsNameSpaceManager.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "plstr.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "ChildIterator.h"
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif
+#include "nsIXMLContentSink.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "jsapi.h"
+#include "nsXBLService.h"
+#include "nsIXPConnect.h"
+#include "nsIScriptContext.h"
+#include "nsCRT.h"
+
+// Event listeners
+#include "mozilla/EventListenerManager.h"
+#include "nsIDOMEventListener.h"
+#include "nsAttrName.h"
+
+#include "nsGkAtoms.h"
+
+#include "nsXBLPrototypeHandler.h"
+
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLBinding.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozilla/dom/XBLChildrenElement.h"
+
+#include "prprf.h"
+#include "nsNodeUtils.h"
+#include "nsJSUtils.h"
+
+// Nasty hack. Maybe we could move some of the classinfo utility methods
+// (e.g. WrapNative) over to nsContentUtils?
+#include "nsDOMClassInfo.h"
+
+#include "mozilla/DeferredFinalize.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/ServoStyleSet.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Helper classes
+
+/***********************************************************************/
+//
+// The JS class for XBLBinding
+//
+static void
+XBLFinalize(JSFreeOp *fop, JSObject *obj)
+{
+ nsXBLDocumentInfo* docInfo =
+ static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
+ DeferredFinalize(docInfo);
+}
+
+static bool
+XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
+{
+ nsXBLPrototypeBinding* protoBinding =
+ static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
+ MOZ_ASSERT(protoBinding);
+
+ return protoBinding->ResolveAllFields(cx, obj);
+}
+
+static const JSClassOps gPrototypeJSClassOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ XBLEnumerate, nullptr,
+ nullptr, XBLFinalize,
+ nullptr, nullptr, nullptr, nullptr
+};
+
+static const JSClass gPrototypeJSClass = {
+ "XBL prototype JSClass",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_PRIVATE_IS_NSISUPPORTS |
+ JSCLASS_FOREGROUND_FINALIZE |
+ // Our one reserved slot holds the relevant nsXBLPrototypeBinding
+ JSCLASS_HAS_RESERVED_SLOTS(1),
+ &gPrototypeJSClassOps
+};
+
+// Implementation /////////////////////////////////////////////////////////////////
+
+// Constructors/Destructors
+nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
+ : mMarkedForDeath(false)
+ , mUsingContentXBLScope(false)
+ , mIsShadowRootBinding(false)
+ , mPrototypeBinding(aBinding)
+{
+ NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
+ // Grab a ref to the document info so the prototype binding won't die
+ NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
+}
+
+// Constructor used by web components.
+nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
+ : mMarkedForDeath(false),
+ mUsingContentXBLScope(false),
+ mIsShadowRootBinding(true),
+ mPrototypeBinding(aBinding),
+ mContent(aShadowRoot)
+{
+ NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
+ // Grab a ref to the document info so the prototype binding won't die
+ NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
+}
+
+nsXBLBinding::~nsXBLBinding(void)
+{
+ if (mContent && !mIsShadowRootBinding) {
+ // It is unnecessary to uninstall anonymous content in a shadow tree
+ // because the ShadowRoot itself is a DocumentFragment and does not
+ // need any additional cleanup.
+ nsXBLBinding::UninstallAnonymousContent(mContent->OwnerDoc(), mContent);
+ }
+ nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo();
+ NS_RELEASE(info);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding)
+ // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because
+ // mPrototypeBinding is weak.
+ if (tmp->mContent && !tmp->mIsShadowRootBinding) {
+ nsXBLBinding::UninstallAnonymousContent(tmp->mContent->OwnerDoc(),
+ tmp->mContent);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLBinding)
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mPrototypeBinding->XBLDocumentInfo()");
+ cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo());
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextBinding)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultInsertionPoint)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInsertionPoints)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContentList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release)
+
+void
+nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding)
+{
+ if (mNextBinding) {
+ NS_ERROR("Base XBL binding is already defined!");
+ return;
+ }
+
+ mNextBinding = aBinding; // Comptr handles rel/add
+}
+
+nsXBLBinding*
+nsXBLBinding::GetBindingWithContent()
+{
+ if (mContent) {
+ return this;
+ }
+
+ return mNextBinding ? mNextBinding->GetBindingWithContent() : nullptr;
+}
+
+void
+nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement,
+ bool aChromeOnlyContent)
+{
+ // We need to ensure two things.
+ // (1) The anonymous content should be fooled into thinking it's in the bound
+ // element's document, assuming that the bound element is in a document
+ // Note that we don't change the current doc of aAnonParent here, since that
+ // quite simply does not matter. aAnonParent is just a way of keeping refs
+ // to all its kids, which are anonymous content from the point of view of
+ // aElement.
+ // (2) The children's parent back pointer should not be to this synthetic root
+ // but should instead point to the enclosing parent element.
+ nsIDocument* doc = aElement->GetUncomposedDoc();
+ bool allowScripts = AllowScripts();
+
+ nsAutoScriptBlocker scriptBlocker;
+ for (nsIContent* child = aAnonParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree();
+ if (aChromeOnlyContent) {
+ child->SetFlags(NODE_CHROME_ONLY_ACCESS |
+ NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS);
+ }
+ nsresult rv =
+ child->BindToTree(doc, aElement, mBoundElement, allowScripts);
+ if (NS_FAILED(rv)) {
+ // Oh, well... Just give up.
+ // XXXbz This really shouldn't be a void method!
+ child->UnbindFromTree();
+ return;
+ }
+
+ child->SetFlags(NODE_IS_ANONYMOUS_ROOT);
+
+#ifdef MOZ_XUL
+ // To make XUL templates work (and other goodies that happen when
+ // an element is added to a XUL document), we need to notify the
+ // XUL document using its special API.
+ nsCOMPtr<nsIXULDocument> xuldoc(do_QueryInterface(doc));
+ if (xuldoc)
+ xuldoc->AddSubtreeToDocument(child);
+#endif
+ }
+}
+
+void
+nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
+ nsIContent* aAnonParent)
+{
+ nsAutoScriptBlocker scriptBlocker;
+ // Hold a strong ref while doing this, just in case.
+ nsCOMPtr<nsIContent> anonParent = aAnonParent;
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIXULDocument> xuldoc =
+ do_QueryInterface(aDocument);
+#endif
+ for (nsIContent* child = aAnonParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree();
+#ifdef MOZ_XUL
+ if (xuldoc) {
+ xuldoc->RemoveSubtreeFromDocument(child);
+ }
+#endif
+ }
+}
+
+void
+nsXBLBinding::SetBoundElement(nsIContent* aElement)
+{
+ mBoundElement = aElement;
+ if (mNextBinding)
+ mNextBinding->SetBoundElement(aElement);
+
+ if (!mBoundElement) {
+ return;
+ }
+
+ // Compute whether we're using an XBL scope.
+ //
+ // We disable XBL scopes for remote XUL, where we care about compat more
+ // than security. So we need to know whether we're using an XBL scope so that
+ // we can decide what to do about untrusted events when "allowuntrusted"
+ // is not given in the handler declaration.
+ nsCOMPtr<nsIGlobalObject> go = mBoundElement->OwnerDoc()->GetScopeObject();
+ NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject());
+ mUsingContentXBLScope = xpc::UseContentXBLScope(js::GetObjectCompartment(go->GetGlobalJSObject()));
+}
+
+bool
+nsXBLBinding::HasStyleSheets() const
+{
+ // Find out if we need to re-resolve style. We'll need to do this
+ // if we have additional stylesheets in our binding document.
+ if (mPrototypeBinding->HasStyleSheets())
+ return true;
+
+ return mNextBinding ? mNextBinding->HasStyleSheets() : false;
+}
+
+void
+nsXBLBinding::GenerateAnonymousContent()
+{
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker");
+
+ // Fetch the content element for this binding.
+ nsIContent* content =
+ mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
+
+ if (!content) {
+ // We have no anonymous content.
+ if (mNextBinding)
+ mNextBinding->GenerateAnonymousContent();
+
+ return;
+ }
+
+ // Find out if we're really building kids or if we're just
+ // using the attribute-setting shorthand hack.
+ uint32_t contentCount = content->GetChildCount();
+
+ // Plan to build the content by default.
+ bool hasContent = (contentCount > 0);
+ if (hasContent) {
+ nsIDocument* doc = mBoundElement->OwnerDoc();
+
+ nsCOMPtr<nsINode> clonedNode;
+ nsCOMArray<nsINode> nodesWithProperties;
+ nsNodeUtils::Clone(content, true, doc->NodeInfoManager(),
+ nodesWithProperties, getter_AddRefs(clonedNode));
+ mContent = clonedNode->AsElement();
+
+ // Search for <xbl:children> elements in the XBL content. In the presence
+ // of multiple default insertion points, we use the last one in document
+ // order.
+ for (nsIContent* child = mContent; child; child = child->GetNextNode(mContent)) {
+ if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
+ if (point->IsDefaultInsertion()) {
+ mDefaultInsertionPoint = point;
+ } else {
+ mInsertionPoints.AppendElement(point);
+ }
+ }
+ }
+
+ // Do this after looking for <children> as this messes up the parent
+ // pointer which would make the GetNextNode call above fail
+ InstallAnonymousContent(mContent, mBoundElement,
+ mPrototypeBinding->ChromeOnlyContent());
+
+ // Insert explicit children into insertion points
+ if (mDefaultInsertionPoint && mInsertionPoints.IsEmpty()) {
+ ExplicitChildIterator iter(mBoundElement);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ mDefaultInsertionPoint->AppendInsertedChild(child);
+ }
+ } else {
+ // It is odd to come into this code if mInsertionPoints is not empty, but
+ // we need to make sure to do the compatibility hack below if the bound
+ // node has any non <xul:template> or <xul:observes> children.
+ ExplicitChildIterator iter(mBoundElement);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ XBLChildrenElement* point = FindInsertionPointForInternal(child);
+ if (point) {
+ point->AppendInsertedChild(child);
+ } else {
+ NodeInfo *ni = child->NodeInfo();
+ if (ni->NamespaceID() != kNameSpaceID_XUL ||
+ (!ni->Equals(nsGkAtoms::_template) &&
+ !ni->Equals(nsGkAtoms::observes))) {
+ // Compatibility hack. For some reason the original XBL
+ // implementation dropped the content of a binding if any child of
+ // the bound element didn't match any of the <children> in the
+ // binding. This became a pseudo-API that we have to maintain.
+
+ // Undo InstallAnonymousContent
+ UninstallAnonymousContent(doc, mContent);
+
+ // Clear out our children elements to avoid dangling references.
+ ClearInsertionPoints();
+
+ // Pretend as though there was no content in the binding.
+ mContent = nullptr;
+ return;
+ }
+ }
+ }
+ }
+
+ // Set binding parent on default content if need
+ if (mDefaultInsertionPoint) {
+ mDefaultInsertionPoint->MaybeSetupDefaultContent();
+ }
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ mInsertionPoints[i]->MaybeSetupDefaultContent();
+ }
+
+ mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent);
+ }
+
+ // Always check the content element for potential attributes.
+ // This shorthand hack always happens, even when we didn't
+ // build anonymous content.
+ BorrowedAttrInfo attrInfo;
+ for (uint32_t i = 0; (attrInfo = content->GetAttrInfoAt(i)); ++i) {
+ int32_t namespaceID = attrInfo.mName->NamespaceID();
+ // Hold a strong reference here so that the atom doesn't go away during
+ // UnsetAttr.
+ nsCOMPtr<nsIAtom> name = attrInfo.mName->LocalName();
+
+ if (name != nsGkAtoms::includes) {
+ if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) {
+ nsAutoString value2;
+ attrInfo.mValue->ToString(value2);
+ mBoundElement->SetAttr(namespaceID, name, attrInfo.mName->GetPrefix(),
+ value2, false);
+ }
+ }
+
+ // Conserve space by wiping the attributes off the clone.
+ if (mContent)
+ mContent->UnsetAttr(namespaceID, name, false);
+ }
+
+ // Now that we've finished shuffling the tree around, go ahead and restyle it
+ // since frame construction is about to happen.
+ nsIPresShell* presShell = mBoundElement->OwnerDoc()->GetShell();
+ ServoStyleSet* servoSet = presShell->StyleSet()->GetAsServo();
+ if (servoSet) {
+ mBoundElement->SetHasDirtyDescendantsForServo();
+ servoSet->StyleNewChildren(mBoundElement);
+ }
+}
+
+nsIURI*
+nsXBLBinding::GetSourceDocURI()
+{
+ nsIContent* targetContent =
+ mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
+ if (!targetContent) {
+ return nullptr;
+ }
+
+ return targetContent->OwnerDoc()->GetDocumentURI();
+}
+
+XBLChildrenElement*
+nsXBLBinding::FindInsertionPointFor(nsIContent* aChild)
+{
+ // XXX We should get rid of this function as it causes us to traverse the
+ // binding chain multiple times
+ if (mContent) {
+ return FindInsertionPointForInternal(aChild);
+ }
+
+ return mNextBinding ? mNextBinding->FindInsertionPointFor(aChild)
+ : nullptr;
+}
+
+XBLChildrenElement*
+nsXBLBinding::FindInsertionPointForInternal(nsIContent* aChild)
+{
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ XBLChildrenElement* point = mInsertionPoints[i];
+ if (point->Includes(aChild)) {
+ return point;
+ }
+ }
+
+ return mDefaultInsertionPoint;
+}
+
+void
+nsXBLBinding::ClearInsertionPoints()
+{
+ if (mDefaultInsertionPoint) {
+ mDefaultInsertionPoint->ClearInsertedChildren();
+ }
+
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ mInsertionPoints[i]->ClearInsertedChildren();
+ }
+}
+
+nsAnonymousContentList*
+nsXBLBinding::GetAnonymousNodeList()
+{
+ if (!mContent) {
+ return mNextBinding ? mNextBinding->GetAnonymousNodeList() : nullptr;
+ }
+
+ if (!mAnonymousContentList) {
+ mAnonymousContentList = new nsAnonymousContentList(mContent);
+ }
+
+ return mAnonymousContentList;
+}
+
+void
+nsXBLBinding::InstallEventHandlers()
+{
+ // Don't install handlers if scripts aren't allowed.
+ if (AllowScripts()) {
+ // Fetch the handlers prototypes for this binding.
+ nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
+
+ if (handlerChain) {
+ EventListenerManager* manager = mBoundElement->GetOrCreateListenerManager();
+ if (!manager)
+ return;
+
+ bool isChromeDoc =
+ nsContentUtils::IsChromeDoc(mBoundElement->OwnerDoc());
+ bool isChromeBinding = mPrototypeBinding->IsChrome();
+ nsXBLPrototypeHandler* curr;
+ for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
+ // Fetch the event type.
+ nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
+ if (!eventAtom ||
+ eventAtom == nsGkAtoms::keyup ||
+ eventAtom == nsGkAtoms::keydown ||
+ eventAtom == nsGkAtoms::keypress)
+ continue;
+
+ nsXBLEventHandler* handler = curr->GetEventHandler();
+ if (handler) {
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
+
+ // If this is a command, add it in the system event group
+ if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
+ NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ bool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr();
+ if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) ||
+ (!hasAllowUntrustedAttr && !isChromeDoc && !mUsingContentXBLScope)) {
+ flags.mAllowUntrustedEvents = true;
+ }
+
+ manager->AddEventListenerByType(handler,
+ nsDependentAtomString(eventAtom),
+ flags);
+ }
+ }
+
+ const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
+ mPrototypeBinding->GetKeyEventHandlers();
+ int32_t i;
+ for (i = 0; i < keyHandlers->Count(); ++i) {
+ nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
+ handler->SetIsBoundToChrome(isChromeDoc);
+ handler->SetUsingContentXBLScope(mUsingContentXBLScope);
+
+ nsAutoString type;
+ handler->GetEventName(type);
+
+ // If this is a command, add it in the system event group, otherwise
+ // add it to the standard event group.
+
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
+
+ if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
+ NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ // For key handlers we have to set mAllowUntrustedEvents flag.
+ // Whether the handling of the event is allowed or not is handled in
+ // nsXBLKeyEventHandler::HandleEvent
+ flags.mAllowUntrustedEvents = true;
+
+ manager->AddEventListenerByType(handler, type, flags);
+ }
+ }
+ }
+
+ if (mNextBinding)
+ mNextBinding->InstallEventHandlers();
+}
+
+nsresult
+nsXBLBinding::InstallImplementation()
+{
+ // Always install the base class properties first, so that
+ // derived classes can reference the base class properties.
+
+ if (mNextBinding) {
+ nsresult rv = mNextBinding->InstallImplementation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // iterate through each property in the prototype's list and install the property.
+ if (AllowScripts())
+ return mPrototypeBinding->InstallImplementation(this);
+
+ return NS_OK;
+}
+
+nsIAtom*
+nsXBLBinding::GetBaseTag(int32_t* aNameSpaceID)
+{
+ nsIAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID);
+ if (!tag && mNextBinding)
+ return mNextBinding->GetBaseTag(aNameSpaceID);
+
+ return tag;
+}
+
+void
+nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
+ bool aRemoveFlag, bool aNotify)
+{
+ // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content
+ if (!mContent) {
+ if (mNextBinding)
+ mNextBinding->AttributeChanged(aAttribute, aNameSpaceID,
+ aRemoveFlag, aNotify);
+ } else {
+ mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag,
+ mBoundElement, mContent, aNotify);
+ }
+}
+
+void
+nsXBLBinding::ExecuteAttachedHandler()
+{
+ if (mNextBinding)
+ mNextBinding->ExecuteAttachedHandler();
+
+ if (AllowScripts())
+ mPrototypeBinding->BindingAttached(mBoundElement);
+}
+
+void
+nsXBLBinding::ExecuteDetachedHandler()
+{
+ if (AllowScripts())
+ mPrototypeBinding->BindingDetached(mBoundElement);
+
+ if (mNextBinding)
+ mNextBinding->ExecuteDetachedHandler();
+}
+
+void
+nsXBLBinding::UnhookEventHandlers()
+{
+ nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
+
+ if (handlerChain) {
+ EventListenerManager* manager = mBoundElement->GetExistingListenerManager();
+ if (!manager) {
+ return;
+ }
+
+ bool isChromeBinding = mPrototypeBinding->IsChrome();
+ nsXBLPrototypeHandler* curr;
+ for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
+ nsXBLEventHandler* handler = curr->GetCachedEventHandler();
+ if (!handler) {
+ continue;
+ }
+
+ nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
+ if (!eventAtom ||
+ eventAtom == nsGkAtoms::keyup ||
+ eventAtom == nsGkAtoms::keydown ||
+ eventAtom == nsGkAtoms::keypress)
+ continue;
+
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
+
+ // If this is a command, remove it from the system event group,
+ // otherwise remove it from the standard event group.
+
+ if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
+ NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ manager->RemoveEventListenerByType(handler,
+ nsDependentAtomString(eventAtom),
+ flags);
+ }
+
+ const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
+ mPrototypeBinding->GetKeyEventHandlers();
+ int32_t i;
+ for (i = 0; i < keyHandlers->Count(); ++i) {
+ nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
+
+ nsAutoString type;
+ handler->GetEventName(type);
+
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
+
+ // If this is a command, remove it from the system event group, otherwise
+ // remove it from the standard event group.
+
+ if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ manager->RemoveEventListenerByType(handler, type, flags);
+ }
+ }
+}
+
+static void
+UpdateInsertionParent(XBLChildrenElement* aPoint,
+ nsIContent* aOldBoundElement)
+{
+ if (aPoint->IsDefaultInsertion()) {
+ return;
+ }
+
+ for (size_t i = 0; i < aPoint->InsertedChildrenLength(); ++i) {
+ nsIContent* child = aPoint->InsertedChild(i);
+
+ MOZ_ASSERT(child->GetParentNode());
+
+ // Here, we're iterating children that we inserted. There are two cases:
+ // either |child| is an explicit child of |aOldBoundElement| and is no
+ // longer inserted anywhere or it's a child of a <children> element
+ // parented to |aOldBoundElement|. In the former case, the child is no
+ // longer inserted anywhere, so we set its insertion parent to null. In the
+ // latter case, the child is now inserted into |aOldBoundElement| from some
+ // binding above us, so we set its insertion parent to aOldBoundElement.
+ if (child->GetParentNode() == aOldBoundElement) {
+ child->SetXBLInsertionParent(nullptr);
+ } else {
+ child->SetXBLInsertionParent(aOldBoundElement);
+ }
+ }
+}
+
+void
+nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument)
+{
+ if (aOldDocument == aNewDocument)
+ return;
+
+ // Now the binding dies. Unhook our prototypes.
+ if (mPrototypeBinding->HasImplementation()) {
+ AutoJSAPI jsapi;
+ // Init might fail here if we've cycle-collected the global object, since
+ // the Unlink phase of cycle collection happens after JS GC finalization.
+ // But in that case, we don't care about fixing the prototype chain, since
+ // everything's going away immediately.
+ if (jsapi.Init(aOldDocument->GetScopeObject())) {
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper());
+ if (scriptObject) {
+ // XXX Stay in sync! What if a layered binding has an
+ // <interface>?!
+ // XXXbz what does that comment mean, really? It seems to date
+ // back to when there was such a thing as an <interface>, whever
+ // that was...
+
+ // Find the right prototype.
+ JSAutoCompartment ac(cx, scriptObject);
+
+ JS::Rooted<JSObject*> base(cx, scriptObject);
+ JS::Rooted<JSObject*> proto(cx);
+ for ( ; true; base = proto) { // Will break out on null proto
+ if (!JS_GetPrototype(cx, base, &proto)) {
+ return;
+ }
+ if (!proto) {
+ break;
+ }
+
+ if (JS_GetClass(proto) != &gPrototypeJSClass) {
+ // Clearly not the right class
+ continue;
+ }
+
+ RefPtr<nsXBLDocumentInfo> docInfo =
+ static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(proto));
+ if (!docInfo) {
+ // Not the proto we seek
+ continue;
+ }
+
+ JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0);
+
+ if (protoBinding.toPrivate() != mPrototypeBinding) {
+ // Not the right binding
+ continue;
+ }
+
+ // Alright! This is the right prototype. Pull it out of the
+ // proto chain.
+ JS::Rooted<JSObject*> grandProto(cx);
+ if (!JS_GetPrototype(cx, proto, &grandProto)) {
+ return;
+ }
+ ::JS_SetPrototype(cx, base, grandProto);
+ break;
+ }
+
+ mPrototypeBinding->UndefineFields(cx, scriptObject);
+
+ // Don't remove the reference from the document to the
+ // wrapper here since it'll be removed by the element
+ // itself when that's taken out of the document.
+ }
+ }
+ }
+
+ // Remove our event handlers
+ UnhookEventHandlers();
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Then do our ancestors. This reverses the construction order, so that at
+ // all times things are consistent as far as everyone is concerned.
+ if (mNextBinding) {
+ mNextBinding->ChangeDocument(aOldDocument, aNewDocument);
+ }
+
+ // Update the anonymous content.
+ // XXXbz why not only for style bindings?
+ if (mContent && !mIsShadowRootBinding) {
+ nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
+ }
+
+ // Now that we've unbound our anonymous content from the tree and updated
+ // its binding parent, update the insertion parent for content inserted
+ // into our <children> elements.
+ if (mDefaultInsertionPoint) {
+ UpdateInsertionParent(mDefaultInsertionPoint, mBoundElement);
+ }
+
+ for (size_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ UpdateInsertionParent(mInsertionPoints[i], mBoundElement);
+ }
+
+ // Now that our inserted children no longer think they're inserted
+ // anywhere, make sure our internal state reflects that as well.
+ ClearInsertionPoints();
+ }
+}
+
+bool
+nsXBLBinding::InheritsStyle() const
+{
+ // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content.
+ // Most derived binding with anonymous content determines style inheritance for now.
+
+ // XXX What about bindings with <content> but no kids, e.g., my treecell-text binding?
+ if (mContent)
+ return mPrototypeBinding->InheritsStyle();
+
+ if (mNextBinding)
+ return mNextBinding->InheritsStyle();
+
+ return true;
+}
+
+void
+nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
+{
+ if (mNextBinding)
+ mNextBinding->WalkRules(aFunc, aData);
+
+ nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor();
+ if (rules)
+ (*aFunc)(rules, aData);
+}
+
+// Internal helper methods ////////////////////////////////////////////////////////////////
+
+// Get or create a WeakMap object on a given XBL-hosting global.
+//
+// The scheme is as follows. XBL-hosting globals (either privileged content
+// Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
+// WeakMap is keyed by the grand-proto - i.e. the original prototype of the
+// content before it was bound, and the prototype of the class object that we
+// splice in. The values in the WeakMap are simple dictionary-style objects,
+// mapping from XBL class names to class objects.
+static JSObject*
+GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
+{
+ AssertSameCompartment(cx, scope);
+ MOZ_ASSERT(JS_IsGlobalObject(scope));
+ MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
+
+ // First, see if the map is already defined.
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, &desc)) {
+ return nullptr;
+ }
+ if (desc.object() && desc.value().isObject() &&
+ JS::IsWeakMapObject(&desc.value().toObject())) {
+ return &desc.value().toObject();
+ }
+
+ // It's not there. Create and define it.
+ JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
+ if (!map || !JS_DefineProperty(cx, scope, mapName, map,
+ JSPROP_PERMANENT | JSPROP_READONLY,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ return nullptr;
+ }
+ return map;
+}
+
+static JSObject*
+GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
+{
+ AssertSameCompartment(cx, proto);
+ // We want to hang our class objects off the XBL scope. But since we also
+ // hoist anonymous content into the XBL scope, this creates the potential for
+ // tricky collisions, since we can simultaneously have a bound in-content
+ // node with grand-proto HTMLDivElement and a bound anonymous node whose
+ // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
+ // Since we have to wrap the WeakMap keys into its scope, this distinction
+ // would be lost if we don't do something about it.
+ //
+ // So we define two maps - one class objects that live in content (prototyped
+ // to content prototypes), and the other for class objects that live in the
+ // XBL scope (prototyped to cross-compartment-wrapped content prototypes).
+ const char* name = xpc::IsInContentXBLScope(proto) ? "__ContentClassObjectMap__"
+ : "__XBLClassObjectMap__";
+
+ // Now, enter the XBL scope, since that's where we need to operate, and wrap
+ // the proto accordingly. We hang the map off of the content XBL scope for
+ // content, and the Window for chrome (whether add-ons are involved or not).
+ JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
+ NS_ENSURE_TRUE(scope, nullptr);
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(scope) == scope);
+
+ JS::Rooted<JSObject*> wrappedProto(cx, proto);
+ JSAutoCompartment ac(cx, scope);
+ if (!JS_WrapObject(cx, &wrappedProto)) {
+ return nullptr;
+ }
+
+ // Grab the appropriate WeakMap.
+ JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
+ if (!map) {
+ return nullptr;
+ }
+
+ // See if we already have a map entry for that prototype.
+ JS::Rooted<JS::Value> val(cx);
+ if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
+ return nullptr;
+ }
+ if (val.isObject()) {
+ return &val.toObject();
+ }
+
+ // We don't have an entry. Create one and stick it in the map.
+ JS::Rooted<JSObject*> entry(cx);
+ entry = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
+ if (!entry) {
+ return nullptr;
+ }
+ JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
+ if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
+ NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap "
+ "key. XBL binding will fail for this element.");
+ return nullptr;
+ }
+ return entry;
+}
+
+static
+nsXBLPrototypeBinding*
+GetProtoBindingFromClassObject(JSObject* obj)
+{
+ MOZ_ASSERT(JS_GetClass(obj) == &gPrototypeJSClass);
+ return static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
+}
+
+
+// static
+nsresult
+nsXBLBinding::DoInitJSClass(JSContext *cx,
+ JS::Handle<JSObject*> obj,
+ const nsAFlatString& aClassName,
+ nsXBLPrototypeBinding* aProtoBinding,
+ JS::MutableHandle<JSObject*> aClassObject,
+ bool* aNew)
+{
+ MOZ_ASSERT(obj);
+
+ // Note that, now that NAC reflectors are created in the XBL scope, the
+ // reflector is not necessarily same-compartment with the document. So we'll
+ // end up creating a separate instance of the oddly-named XBL class object
+ // and defining it as a property on the XBL scope's global. This works fine,
+ // but we need to make sure never to assume that the the reflector and
+ // prototype are same-compartment with the bound document.
+ JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
+
+ // We never store class objects in add-on scopes.
+ JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
+ NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
+
+ JS::Rooted<JSObject*> parent_proto(cx);
+ if (!JS_GetPrototype(cx, obj, &parent_proto)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the map entry for the parent prototype. In the one-off case that the
+ // parent prototype is null, we somewhat hackily just use the WeakMap itself
+ // as a property holder.
+ JS::Rooted<JSObject*> holder(cx);
+ if (parent_proto) {
+ holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
+ } else {
+ JSAutoCompartment innerAC(cx, xblScope);
+ holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
+ }
+ if (NS_WARN_IF(!holder)) {
+ return NS_ERROR_FAILURE;
+ }
+ js::AssertSameCompartment(holder, xblScope);
+ JSAutoCompartment ac(cx, holder);
+
+ // Look up the class on the property holder. The only properties on the
+ // holder should be class objects. If we don't find the class object, we need
+ // to create and define it.
+ JS::Rooted<JSObject*> proto(cx);
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetOwnUCPropertyDescriptor(cx, holder, aClassName.get(), &desc)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aNew = !desc.object();
+ if (desc.object()) {
+ proto = &desc.value().toObject();
+ DebugOnly<nsXBLPrototypeBinding*> cachedBinding =
+ GetProtoBindingFromClassObject(js::UncheckedUnwrap(proto));
+ MOZ_ASSERT(cachedBinding == aProtoBinding);
+ } else {
+
+ // We need to create the prototype. First, enter the compartment where it's
+ // going to live, and create it.
+ JSAutoCompartment ac2(cx, global);
+ proto = JS_NewObjectWithGivenProto(cx, &gPrototypeJSClass, parent_proto);
+ if (!proto) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Keep this proto binding alive while we're alive. Do this first so that
+ // we can guarantee that in XBLFinalize this will be non-null.
+ // Note that we can't just store aProtoBinding in the private and
+ // addref/release the nsXBLDocumentInfo through it, because cycle
+ // collection doesn't seem to work right if the private is not an
+ // nsISupports.
+ nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
+ ::JS_SetPrivate(proto, docInfo);
+ NS_ADDREF(docInfo);
+ JS_SetReservedSlot(proto, 0, JS::PrivateValue(aProtoBinding));
+
+ // Next, enter the compartment of the property holder, wrap the proto, and
+ // stick it on.
+ JSAutoCompartment ac3(cx, holder);
+ if (!JS_WrapObject(cx, &proto) ||
+ !JS_DefineUCProperty(cx, holder, aClassName.get(), -1, proto,
+ JSPROP_READONLY | JSPROP_PERMANENT,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // Whew. We have the proto. Wrap it back into the compartment of |obj|,
+ // splice it in, and return it.
+ JSAutoCompartment ac4(cx, obj);
+ if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
+ return NS_ERROR_FAILURE;
+ }
+ aClassObject.set(proto);
+ return NS_OK;
+}
+
+bool
+nsXBLBinding::AllowScripts()
+{
+ return mBoundElement && mPrototypeBinding->GetAllowScripts();
+}
+
+nsXBLBinding*
+nsXBLBinding::RootBinding()
+{
+ if (mNextBinding)
+ return mNextBinding->RootBinding();
+
+ return this;
+}
+
+bool
+nsXBLBinding::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
+{
+ if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
+ return false;
+ }
+
+ if (mNextBinding) {
+ return mNextBinding->ResolveAllFields(cx, obj);
+ }
+
+ return true;
+}
+
+bool
+nsXBLBinding::LookupMember(JSContext* aCx, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc)
+{
+ // We should never enter this function with a pre-filled property descriptor.
+ MOZ_ASSERT(!aDesc.object());
+
+ // Get the string as an nsString before doing anything, so we can make
+ // convenient comparisons during our search.
+ if (!JSID_IS_STRING(aId)) {
+ return true;
+ }
+ nsAutoJSString name;
+ if (!name.init(aCx, JSID_TO_STRING(aId))) {
+ return false;
+ }
+
+ // We have a weak reference to our bound element, so make sure it's alive.
+ if (!mBoundElement || !mBoundElement->GetWrapper()) {
+ return false;
+ }
+
+ // Get the scope of mBoundElement and the associated XBL scope. We should only
+ // be calling into this machinery if we're running in a separate XBL scope.
+ //
+ // Note that we only end up in LookupMember for XrayWrappers from XBL scopes
+ // into content. So for NAC reflectors that live in the XBL scope, we should
+ // never get here. But on the off-chance that someone adds new callsites to
+ // LookupMember, we do a release-mode assertion as belt-and-braces.
+ // We do a release-mode assertion here to be extra safe.
+ //
+ // This code is only called for content XBL, so we don't have to worry about
+ // add-on scopes here.
+ JS::Rooted<JSObject*> boundScope(aCx,
+ js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper()));
+ MOZ_RELEASE_ASSERT(!xpc::IsInAddonScope(boundScope));
+ MOZ_RELEASE_ASSERT(!xpc::IsInContentXBLScope(boundScope));
+ JS::Rooted<JSObject*> xblScope(aCx, xpc::GetXBLScope(aCx, boundScope));
+ NS_ENSURE_TRUE(xblScope, false);
+ MOZ_ASSERT(boundScope != xblScope);
+
+ // Enter the xbl scope and invoke the internal version.
+ {
+ JSAutoCompartment ac(aCx, xblScope);
+ JS::Rooted<jsid> id(aCx, aId);
+ if (!LookupMemberInternal(aCx, name, id, aDesc, xblScope)) {
+ return false;
+ }
+ }
+
+ // Wrap into the caller's scope.
+ return JS_WrapPropertyDescriptor(aCx, aDesc);
+}
+
+bool
+nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
+ JS::Handle<jsid> aNameAsId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+ JS::Handle<JSObject*> aXBLScope)
+{
+ // First, see if we have an implementation. If we don't, it means that this
+ // binding doesn't have a class object, and thus doesn't have any members.
+ // Skip it.
+ if (!PrototypeBinding()->HasImplementation()) {
+ if (!mNextBinding) {
+ return true;
+ }
+ return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId,
+ aDesc, aXBLScope);
+ }
+
+ // Find our class object. It's in a protected scope and permanent just in case,
+ // so should be there no matter what.
+ JS::Rooted<JS::Value> classObject(aCx);
+ if (!JS_GetUCProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(),
+ -1, &classObject)) {
+ return false;
+ }
+
+ // The bound element may have been adoped by a document and have a different
+ // wrapper (and different xbl scope) than when the binding was applied, in
+ // this case getting the class object will fail. Behave as if the class
+ // object did not exist.
+ if (classObject.isUndefined()) {
+ return true;
+ }
+
+ MOZ_ASSERT(classObject.isObject());
+
+ // Look for the property on this binding. If it's not there, try the next
+ // binding on the chain.
+ nsXBLProtoImpl* impl = mPrototypeBinding->GetImplementation();
+ JS::Rooted<JSObject*> object(aCx, &classObject.toObject());
+ if (impl && !impl->LookupMember(aCx, aName, aNameAsId, aDesc, object)) {
+ return false;
+ }
+ if (aDesc.object() || !mNextBinding) {
+ return true;
+ }
+
+ return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, aDesc,
+ aXBLScope);
+}
+
+bool
+nsXBLBinding::HasField(nsString& aName)
+{
+ // See if this binding has such a field.
+ return mPrototypeBinding->FindField(aName) ||
+ (mNextBinding && mNextBinding->HasField(aName));
+}
+
+void
+nsXBLBinding::MarkForDeath()
+{
+ mMarkedForDeath = true;
+ ExecuteDetachedHandler();
+}
+
+bool
+nsXBLBinding::ImplementsInterface(REFNSIID aIID) const
+{
+ return mPrototypeBinding->ImplementsInterface(aIID) ||
+ (mNextBinding && mNextBinding->ImplementsInterface(aIID));
+}