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