diff -r 000000000000 -r 6474c204b198 dom/xbl/nsXBLBinding.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/xbl/nsXBLBinding.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1211 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=79: */ +/* 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 "nsNetUtil.h" +#include "plstr.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsContentUtils.h" +#include "ChildIterator.h" +#include "nsCxPusher.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/dom/Element.h" +#include "mozilla/dom/ShadowRoot.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(::JS_GetPrivate(obj)); + nsContentUtils::DeferredFinalize(docInfo); +} + +static bool +XBLEnumerate(JSContext *cx, JS::Handle obj) +{ + nsXBLPrototypeBinding* protoBinding = + static_cast(::JS_GetReservedSlot(obj, 0).toPrivate()); + MOZ_ASSERT(protoBinding); + + return protoBinding->ResolveAllFields(cx, obj); +} + +static const JSClass gPrototypeJSClass = { + "XBL prototype JSClass", + JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | + JSCLASS_NEW_RESOLVE | + // Our one reserved slot holds the relevant nsXBLPrototypeBinding + JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, JS_DeletePropertyStub, + JS_PropertyStub, JS_StrictPropertyStub, + XBLEnumerate, JS_ResolveStub, + JS_ConvertStub, XBLFinalize, + nullptr, nullptr, nullptr, nullptr +}; + +// Implementation ///////////////////////////////////////////////////////////////// + +// Constructors/Destructors +nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding) + : mMarkedForDeath(false) + , mUsingXBLScope(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), + mUsingXBLScope(false), + 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) { + 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) { + 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->GetCurrentDoc(); + 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 xuldoc(do_QueryInterface(doc)); + if (xuldoc) + xuldoc->AddSubtreeToDocument(child); +#endif + } +} + +void +nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument, + nsIContent* aAnonParent) +{ + if (aAnonParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) { + // 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. + return; + } + + nsAutoScriptBlocker scriptBlocker; + // Hold a strong ref while doing this, just in case. + nsCOMPtr anonParent = aAnonParent; +#ifdef MOZ_XUL + nsCOMPtr 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 go = mBoundElement->OwnerDoc()->GetScopeObject(); + NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject()); + mUsingXBLScope = xpc::UseXBLScope(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 clonedNode; + nsCOMArray nodesWithProperties; + nsNodeUtils::Clone(content, true, doc->NodeInfoManager(), + nodesWithProperties, getter_AddRefs(clonedNode)); + mContent = clonedNode->AsElement(); + + // Search for 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(child); + if (point->IsDefaultInsertion()) { + mDefaultInsertionPoint = point; + } else { + mInsertionPoints.AppendElement(point); + } + } + } + + // Do this after looking for 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 or children. + ExplicitChildIterator iter(mBoundElement); + for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { + XBLChildrenElement* point = FindInsertionPointForInternal(child); + if (point) { + point->AppendInsertedChild(child); + } else { + nsINodeInfo *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 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. + const nsAttrName* attrName; + for (uint32_t i = 0; (attrName = content->GetAttrNameAt(i)); ++i) { + int32_t namespaceID = attrName->NamespaceID(); + // Hold a strong reference here so that the atom doesn't go away during + // UnsetAttr. + nsCOMPtr name = attrName->LocalName(); + + if (name != nsGkAtoms::includes) { + if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) { + nsAutoString value2; + content->GetAttr(namespaceID, name, value2); + mBoundElement->SetAttr(namespaceID, name, attrName->GetPrefix(), + value2, false); + } + } + + // Conserve space by wiping the attributes off the clone. + if (mContent) + mContent->UnsetAttr(namespaceID, name, false); + } +} + +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 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 && !mUsingXBLScope)) { + flags.mAllowUntrustedEvents = true; + } + + manager->AddEventListenerByType(handler, + nsDependentAtomString(eventAtom), + flags); + } + } + + const nsCOMArray* keyHandlers = + mPrototypeBinding->GetKeyEventHandlers(); + int32_t i; + for (i = 0; i < keyHandlers->Count(); ++i) { + nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i); + handler->SetIsBoundToChrome(isChromeDoc); + handler->SetUsingXBLScope(mUsingXBLScope); + + 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 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* 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->mInsertedChildren[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 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()) { + nsCOMPtr global = do_QueryInterface( + aOldDocument->GetScopeObject()); + if (global) { + nsCOMPtr context = global->GetContext(); + if (context) { + JSContext *cx = context->GetNativeContext(); + + nsCxPusher pusher; + pusher.Push(cx); + + // scope might be null 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. + JS::Rooted scope(cx, global->GetGlobalJSObject()); + JS::Rooted scriptObject(cx, mBoundElement->GetWrapper()); + if (scope && scriptObject) { + // XXX Stay in sync! What if a layered binding has an + // ?! + // XXXbz what does that comment mean, really? It seems to date + // back to when there was such a thing as an , whever + // that was... + + // Find the right prototype. + JSAutoCompartment ac(cx, scriptObject); + + JS::Rooted base(cx, scriptObject); + JS::Rooted 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; + } + + nsRefPtr docInfo = + static_cast(::JS_GetPrivate(proto)); + if (!docInfo) { + // Not the proto we seek + continue; + } + + JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0); + + if (JSVAL_TO_PRIVATE(protoBinding) != mPrototypeBinding) { + // Not the right binding + continue; + } + + // Alright! This is the right prototype. Pull it out of the + // proto chain. + JS::Rooted 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) { + 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 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 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 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 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 map(cx, JS::NewWeakMapObject(cx)); + if (!map || !JS_DefineProperty(cx, scope, mapName, map, + JSPROP_PERMANENT | JSPROP_READONLY, + JS_PropertyStub, JS_StrictPropertyStub)) + { + return nullptr; + } + return map; +} + +static JSObject* +GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle 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::IsInXBLScope(proto) ? "__ContentClassObjectMap__" + : "__XBLClassObjectMap__"; + + // Now, enter the XBL scope, since that's where we need to operate, and wrap + // the proto accordingly. + JS::Rooted scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto)); + NS_ENSURE_TRUE(scope, nullptr); + JS::Rooted wrappedProto(cx, proto); + JSAutoCompartment ac(cx, scope); + if (!JS_WrapObject(cx, &wrappedProto)) { + return nullptr; + } + + // Grab the appropriate WeakMap. + JS::Rooted map(cx, GetOrCreateClassObjectMap(cx, scope, name)); + if (!map) { + return nullptr; + } + + // See if we already have a map entry for that prototype. + JS::Rooted 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 entry(cx); + entry = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scope); + if (!entry) { + return nullptr; + } + JS::Rooted 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 +nsresult +nsXBLBinding::DoInitJSClass(JSContext *cx, + JS::Handle obj, + const nsAFlatCString& aClassName, + nsXBLPrototypeBinding* aProtoBinding, + JS::MutableHandle 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 global(cx, js::GetGlobalForObjectCrossCompartment(obj)); + JS::Rooted xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global)); + NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED); + + JS::Rooted 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 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 proto(cx); + JS::Rooted desc(cx); + if (!JS_GetOwnPropertyDescriptor(cx, holder, aClassName.get(), &desc)) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aNew = !desc.object(); + if (desc.object()) { + proto = &desc.value().toObject(); + MOZ_ASSERT(JS_GetClass(js::UncheckedUnwrap(proto)) == &gPrototypeJSClass); + } 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, global); + 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, PRIVATE_TO_JSVAL(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_DefineProperty(cx, holder, aClassName.get(), proto, + JSPROP_READONLY | JSPROP_PERMANENT, + JS_PropertyStub, JS_StrictPropertyStub)) + { + 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 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 aId, + JS::MutableHandle 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; + } + nsDependentJSString name(aId); + + // 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. + JS::Rooted boundScope(aCx, + js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper())); + MOZ_RELEASE_ASSERT(!xpc::IsInXBLScope(boundScope)); + JS::Rooted 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 id(aCx, aId); + if (!JS_WrapId(aCx, &id) || + !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 aNameAsId, + JS::MutableHandle aDesc, + JS::Handle 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 classObject(aCx); + if (!JS_GetProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(), + &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 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)); +}