Revision fe3239aec3da54e7ce66751ad6d0a98e2bc102e3 authored by ffxbld on 02 May 2015, 10:19:37 UTC, committed by ffxbld on 02 May 2015, 10:19:37 UTC
1 parent e6082e0
Raw File
nsXBLBinding.cpp
/* -*- 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<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
  nsContentUtils::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 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<nsIXULDocument> 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<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());
  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<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 {
          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 <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.
  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<nsIAtom> 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<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 && !mUsingXBLScope)) {
            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->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<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->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 <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()) {
    nsCOMPtr<nsIScriptGlobalObject> global =  do_QueryInterface(
                                                                aOldDocument->GetScopeObject());
    if (global) {
      nsCOMPtr<nsIScriptContext> 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<JSObject*> scope(cx, global->GetGlobalJSObject());
        JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper());
        if (scope && 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;
            }

            nsRefPtr<nsXBLDocumentInfo> docInfo =
              static_cast<nsXBLDocumentInfo*>(::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<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) {
      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<JSPropertyDescriptor> 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_PropertyStub, JS_StrictPropertyStub))
  {
    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::IsInXBLScope(proto) ? "__ContentClassObjectMap__"
                                              : "__XBLClassObjectMap__";

  // Now, enter the XBL scope, since that's where we need to operate, and wrap
  // the proto accordingly.
  JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
  NS_ENSURE_TRUE(scope, nullptr);
  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, JS::NullPtr(), scope);
  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
nsresult
nsXBLBinding::DoInitJSClass(JSContext *cx,
                            JS::Handle<JSObject*> obj,
                            const nsAFlatCString& 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));
  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<JSPropertyDescriptor> 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<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<JSPropertyDescriptor> 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<JSObject*> boundScope(aCx,
    js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper()));
  MOZ_RELEASE_ASSERT(!xpc::IsInXBLScope(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 (!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<jsid> aNameAsId,
                                   JS::MutableHandle<JSPropertyDescriptor> 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_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<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));
}
back to top