dom/xbl/nsXBLPrototypeBinding.cpp
author Bogdan Tara <btara@mozilla.com>
Fri, 28 Sep 2018 02:42:20 +0300
changeset 438612 6cc26ea43938b62443c992908f0fbdd9d4333c29
parent 423109 110e4b10e2eaca929de9ec715da66fe26d52445f
child 439584 0b8bb97cf1d2f2cb811fe511488ab91474db0d1a
permissions -rw-r--r--
Backed out changeset ba1fef7b14eb (bug 1493955) for GTest failures CLOSED TREE

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/ArrayUtils.h"

#include "nsCOMPtr.h"
#include "nsAtom.h"
#include "nsIInputStream.h"
#include "nsNameSpaceManager.h"
#include "nsIURI.h"
#include "nsIURIMutator.h"
#include "nsIURL.h"
#include "nsIChannel.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsNetUtil.h"
#include "plstr.h"
#include "nsContentCreatorFunctions.h"
#include "nsIDocument.h"
#include "nsIXMLContentSink.h"
#include "nsContentCID.h"
#include "mozilla/dom/XMLDocument.h"
#include "nsXBLService.h"
#include "nsXBLBinding.h"
#include "nsXBLPrototypeBinding.h"
#include "nsXBLContentSink.h"
#include "xptinfo.h"
#include "nsIDocumentObserver.h"
#include "nsGkAtoms.h"
#include "nsXBLProtoImpl.h"
#include "nsCRT.h"
#include "nsContentUtils.h"
#include "nsTextFragment.h"
#include "nsTextNode.h"
#include "nsIScriptError.h"

#include "nsXBLResourceLoader.h"
#include "mozilla/dom/CDATASection.h"
#include "mozilla/dom/CharacterData.h"
#include "mozilla/dom/Comment.h"
#include "mozilla/dom/Element.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"

#ifdef MOZ_XUL
#include "nsXULElement.h"
#endif

using namespace mozilla;
using namespace mozilla::dom;

// Helper Classes =====================================================================

// nsXBLAttributeEntry and helpers.  This class is used to efficiently handle
// attribute changes in anonymous content.

class nsXBLAttributeEntry {
public:
  nsXBLAttributeEntry(nsAtom* aSrcAtom, nsAtom* aDstAtom,
                      int32_t aDstNameSpace, Element* aElement)
    : mElement(aElement),
      mSrcAttribute(aSrcAtom),
      mDstAttribute(aDstAtom),
      mDstNameSpace(aDstNameSpace),
      mNext(nullptr) { }

  ~nsXBLAttributeEntry() {
    NS_CONTENT_DELETE_LIST_MEMBER(nsXBLAttributeEntry, this, mNext);
  }

  nsAtom* GetSrcAttribute() { return mSrcAttribute; }
  nsAtom* GetDstAttribute() { return mDstAttribute; }
  int32_t GetDstNameSpace() { return mDstNameSpace; }

  Element* GetElement() { return mElement; }

  nsXBLAttributeEntry* GetNext() { return mNext; }
  const nsXBLAttributeEntry* GetNext() const { return mNext; }
  void SetNext(nsXBLAttributeEntry* aEntry) { mNext = aEntry; }

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;

protected:
  Element* mElement;

  RefPtr<nsAtom> mSrcAttribute;
  RefPtr<nsAtom> mDstAttribute;
  int32_t mDstNameSpace;
  nsXBLAttributeEntry* mNext;
};

size_t
nsXBLAttributeEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t n = 0;
  for (const nsXBLAttributeEntry* entry = this;
       entry; entry = entry->GetNext()) {
    n += aMallocSizeOf(entry);
  }
  return n;
}

// =============================================================================

// Implementation /////////////////////////////////////////////////////////////////

// Constructors/Destructors
nsXBLPrototypeBinding::nsXBLPrototypeBinding()
: mImplementation(nullptr),
  mBaseBinding(nullptr),
  mInheritStyle(true),
  mCheckedBaseProto(false),
  mKeyHandlersRegistered(false),
  mChromeOnlyContent(false),
  mBindToUntrustedContent(false),
  mSimpleScopeChain(false),
  mResources(nullptr),
  mXBLDocInfoWeak(nullptr),
  mBaseNameSpaceID(kNameSpaceID_None)
{
  MOZ_COUNT_CTOR(nsXBLPrototypeBinding);
}

nsresult
nsXBLPrototypeBinding::Init(const nsACString& aID,
                            nsXBLDocumentInfo* aInfo,
                            Element* aElement,
                            bool aFirstBinding)
{
  nsresult rv;
  nsCOMPtr<nsIURI> bindingURI = aInfo->DocumentURI();

  // The binding URI might be an immutable URI (e.g. for about: URIs). In that case,
  // we'll fail in SetRef below, but that doesn't matter much for now.
  if (aFirstBinding) {
    mAlternateBindingURI = bindingURI;
  }
  rv = NS_MutateURI(bindingURI)
        .SetRef(aID)
        .Finalize(mBindingURI);
  if (NS_FAILED(rv)) {
    // If SetRef failed, mBindingURI should be a clone.
    mBindingURI = bindingURI;
  }

  mXBLDocInfoWeak = aInfo;

  // aElement will be null when reading from the cache, but the element will
  // still be set later.
  if (aElement) {
    SetBindingElement(aElement);
  }
  return NS_OK;
}

bool nsXBLPrototypeBinding::CompareBindingURI(nsIURI* aURI) const
{
  bool equal = false;
  mBindingURI->Equals(aURI, &equal);
  if (!equal && mAlternateBindingURI) {
    mAlternateBindingURI->Equals(aURI, &equal);
  }
  return equal;
}

void
nsXBLPrototypeBinding::Traverse(nsCycleCollectionTraversalCallback &cb) const
{
  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "proto mBinding");
  cb.NoteXPCOMChild(mBinding);
  if (mResources) {
    mResources->Traverse(cb);
  }
  ImplCycleCollectionTraverse(cb, mInterfaceTable, "proto mInterfaceTable");
}

void
nsXBLPrototypeBinding::Unlink()
{
  if (mImplementation) {
    mImplementation->UnlinkJSObjects();
  }

  if (mResources) {
    mResources->Unlink();
  }
}

void
nsXBLPrototypeBinding::Trace(const TraceCallbacks& aCallbacks, void *aClosure) const
{
  if (mImplementation)
    mImplementation->Trace(aCallbacks, aClosure);
}

void
nsXBLPrototypeBinding::Initialize()
{
  if (Element* content = GetImmediateChild(nsGkAtoms::content)) {
    ConstructAttributeTable(content);
  }
}

nsXBLPrototypeBinding::~nsXBLPrototypeBinding(void)
{
  delete mImplementation;
  MOZ_COUNT_DTOR(nsXBLPrototypeBinding);
}

void
nsXBLPrototypeBinding::SetBasePrototype(nsXBLPrototypeBinding* aBinding)
{
  if (mBaseBinding == aBinding)
    return;

  if (mBaseBinding) {
    NS_ERROR("Base XBL prototype binding is already defined!");
    return;
  }

  mBaseBinding = aBinding;
}

void
nsXBLPrototypeBinding::SetBindingElement(Element* aElement)
{
  mBinding = aElement;
  if (mBinding->AttrValueIs(kNameSpaceID_None, nsGkAtoms::inheritstyle,
                            nsGkAtoms::_false, eCaseMatters))
    mInheritStyle = false;

  mChromeOnlyContent = mBinding->AttrValueIs(kNameSpaceID_None,
                                             nsGkAtoms::chromeOnlyContent,
                                             nsGkAtoms::_true, eCaseMatters);

  mBindToUntrustedContent = mBinding->AttrValueIs(kNameSpaceID_None,
                                                  nsGkAtoms::bindToUntrustedContent,
                                                  nsGkAtoms::_true, eCaseMatters);

  // TODO(emilio): Should we imply mBindToUntrustedContent -> mSimpleScopeChain?
  mSimpleScopeChain = mBinding->AttrValueIs(kNameSpaceID_None,
                                            nsGkAtoms::simpleScopeChain,
                                            nsGkAtoms::_true, eCaseMatters);
}

bool
nsXBLPrototypeBinding::GetAllowScripts() const
{
  return mXBLDocInfoWeak->GetScriptAccess();
}

bool
nsXBLPrototypeBinding::LoadResources(nsIContent* aBoundElement)
{
  if (mResources) {
    return mResources->LoadResources(aBoundElement);
  }

  return true;
}

nsresult
nsXBLPrototypeBinding::AddResource(nsAtom* aResourceType, const nsAString& aSrc)
{
  EnsureResources();

  mResources->AddResource(aResourceType, aSrc);
  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::FlushSkinSheets()
{
  if (mResources)
    return mResources->FlushSkinSheets();
  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::BindingAttached(nsIContent* aBoundElement)
{
  if (mImplementation && mImplementation->CompiledMembers() &&
      mImplementation->mConstructor)
    return mImplementation->mConstructor->Execute(aBoundElement, *this);
  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::BindingDetached(nsIContent* aBoundElement)
{
  if (mImplementation && mImplementation->CompiledMembers() &&
      mImplementation->mDestructor)
    return mImplementation->mDestructor->Execute(aBoundElement, *this);
  return NS_OK;
}

nsXBLProtoImplAnonymousMethod*
nsXBLPrototypeBinding::GetConstructor()
{
  if (mImplementation)
    return mImplementation->mConstructor;

  return nullptr;
}

nsXBLProtoImplAnonymousMethod*
nsXBLPrototypeBinding::GetDestructor()
{
  if (mImplementation)
    return mImplementation->mDestructor;

  return nullptr;
}

nsresult
nsXBLPrototypeBinding::SetConstructor(nsXBLProtoImplAnonymousMethod* aMethod)
{
  if (!mImplementation)
    return NS_ERROR_FAILURE;
  mImplementation->mConstructor = aMethod;
  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::SetDestructor(nsXBLProtoImplAnonymousMethod* aMethod)
{
  if (!mImplementation)
    return NS_ERROR_FAILURE;
  mImplementation->mDestructor = aMethod;
  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::InstallImplementation(nsXBLBinding* aBinding)
{
  if (mImplementation)
    return mImplementation->InstallImplementation(this, aBinding);
  return NS_OK;
}

// XXXbz this duplicates lots of SetAttrs
void
nsXBLPrototypeBinding::AttributeChanged(nsAtom* aAttribute,
                                        int32_t aNameSpaceID,
                                        bool aRemoveFlag,
                                        Element* aChangedElement,
                                        nsIContent* aAnonymousContent,
                                        bool aNotify)
{
  if (!mAttributeTable)
    return;

  InnerAttributeTable *attributesNS = mAttributeTable->Get(aNameSpaceID);
  if (!attributesNS)
    return;

  nsXBLAttributeEntry* xblAttr = attributesNS->Get(aAttribute);
  if (!xblAttr)
    return;

  // Iterate over the elements in the array.
  RefPtr<Element> content = GetImmediateChild(nsGkAtoms::content);
  while (xblAttr) {
    Element* element = xblAttr->GetElement();

    RefPtr<Element> realElement =
      LocateInstance(aChangedElement, content, aAnonymousContent, element);

    if (realElement) {
      // Hold a strong reference here so that the atom doesn't go away during
      // UnsetAttr.
      RefPtr<nsAtom> dstAttr = xblAttr->GetDstAttribute();
      int32_t dstNs = xblAttr->GetDstNameSpace();

      if (aRemoveFlag)
        realElement->UnsetAttr(dstNs, dstAttr, aNotify);
      else {
        bool attrPresent = true;
        nsAutoString value;
        // Check to see if the src attribute is xbl:text.  If so, then we need to obtain the
        // children of the real element and get the text nodes' values.
        if (aAttribute == nsGkAtoms::text && aNameSpaceID == kNameSpaceID_XBL) {
          nsContentUtils::GetNodeTextContent(aChangedElement, false, value);
          value.StripChar(char16_t('\n'));
          value.StripChar(char16_t('\r'));
          nsAutoString stripVal(value);
          stripVal.StripWhitespace();
          if (stripVal.IsEmpty())
            attrPresent = false;
        }
        else {
          attrPresent = aChangedElement->GetAttr(aNameSpaceID, aAttribute, value);
        }

        if (attrPresent)
          realElement->SetAttr(dstNs, dstAttr, value, aNotify);
      }

      // See if we're the <html> tag in XUL, and see if value is being
      // set or unset on us.  We may also be a tag that is having
      // xbl:text set on us.

      if ((dstAttr == nsGkAtoms::text && dstNs == kNameSpaceID_XBL) ||
          (realElement->NodeInfo()->Equals(nsGkAtoms::html,
                                           kNameSpaceID_XUL) &&
           dstAttr == nsGkAtoms::value)) {
        // Flush out all our kids.
        while (realElement->HasChildren()) {
          realElement->RemoveChildNode(realElement->GetFirstChild(), aNotify);
        }

        if (!aRemoveFlag) {
          // Construct a new text node and insert it.
          nsAutoString value;
          aChangedElement->GetAttr(aNameSpaceID, aAttribute, value);
          if (!value.IsEmpty()) {
            RefPtr<nsTextNode> textContent =
              new nsTextNode(realElement->NodeInfo()->NodeInfoManager());

            textContent->SetText(value, true);
            realElement->AppendChildTo(textContent, true);
          }
        }
      }
    }

    xblAttr = xblAttr->GetNext();
  }
}

void
nsXBLPrototypeBinding::SetBaseTag(int32_t aNamespaceID, nsAtom* aTag)
{
  mBaseNameSpaceID = aNamespaceID;
  mBaseTag = aTag;
}

nsAtom*
nsXBLPrototypeBinding::GetBaseTag(int32_t* aNamespaceID)
{
  if (mBaseTag) {
    *aNamespaceID = mBaseNameSpaceID;
    return mBaseTag;
  }

  return nullptr;
}

bool
nsXBLPrototypeBinding::ImplementsInterface(REFNSIID aIID) const
{
  // Check our IID table.
  return !!mInterfaceTable.GetWeak(aIID);
}

// Internal helpers ///////////////////////////////////////////////////////////////////////

Element*
nsXBLPrototypeBinding::GetImmediateChild(nsAtom* aTag)
{
  for (nsIContent* child = mBinding->GetFirstChild();
       child;
       child = child->GetNextSibling()) {
    if (child->NodeInfo()->Equals(aTag, kNameSpaceID_XBL)) {
      return child->AsElement();
    }
  }

  return nullptr;
}

nsresult
nsXBLPrototypeBinding::InitClass(const nsString& aClassName,
                                 JSContext * aContext,
                                 JS::Handle<JSObject*> aScriptObject,
                                 JS::MutableHandle<JSObject*> aClassObject,
                                 bool* aNew)
{
  return nsXBLBinding::DoInitJSClass(aContext, aScriptObject,
                                     aClassName, this, aClassObject, aNew);
}

Element*
nsXBLPrototypeBinding::LocateInstance(Element* aBoundElement,
                                      nsIContent* aTemplRoot,
                                      nsIContent* aCopyRoot,
                                      Element* aTemplChild)
{
  // XXX We will get in trouble if the binding instantiation deviates from the template
  // in the prototype.
  if (aTemplChild == aTemplRoot || !aTemplChild)
    return nullptr;

  Element* templParent = aTemplChild->GetParentElement();

  // We may be disconnected from our parent during cycle collection.
  if (!templParent)
    return nullptr;

  nsIContent *copyParent =
    templParent == aTemplRoot ? aCopyRoot :
                   LocateInstance(aBoundElement, aTemplRoot, aCopyRoot, templParent);

  if (!copyParent)
    return nullptr;

  nsIContent* child =
    copyParent->GetChildAt_Deprecated(templParent->ComputeIndexOf(aTemplChild));
  if (child && child->IsElement()) {
    return child->AsElement();
  }
  return nullptr;
}

void
nsXBLPrototypeBinding::SetInitialAttributes(
    Element* aBoundElement,
    nsIContent* aAnonymousContent)
{
  if (!mAttributeTable) {
    return;
  }

  for (auto iter1 = mAttributeTable->Iter(); !iter1.Done(); iter1.Next()) {
    InnerAttributeTable* xblAttributes = iter1.UserData();
    if (xblAttributes) {
      int32_t srcNamespace = iter1.Key();

      for (auto iter2 = xblAttributes->Iter(); !iter2.Done(); iter2.Next()) {
        // XXXbz this duplicates lots of AttributeChanged
        nsXBLAttributeEntry* entry = iter2.UserData();
        nsAtom* src = entry->GetSrcAttribute();
        nsAutoString value;
        bool attrPresent = true;

        if (src == nsGkAtoms::text && srcNamespace == kNameSpaceID_XBL) {
          nsContentUtils::GetNodeTextContent(aBoundElement, false, value);
          value.StripChar(char16_t('\n'));
          value.StripChar(char16_t('\r'));
          nsAutoString stripVal(value);
          stripVal.StripWhitespace();

          if (stripVal.IsEmpty()) {
            attrPresent = false;
          }
        } else {
          attrPresent = aBoundElement->GetAttr(srcNamespace, src, value);
        }

        if (attrPresent) {
          nsIContent* content = GetImmediateChild(nsGkAtoms::content);

          nsXBLAttributeEntry* curr = entry;
          while (curr) {
            nsAtom* dst = curr->GetDstAttribute();
            int32_t dstNs = curr->GetDstNameSpace();
            Element* element = curr->GetElement();

            Element* realElement =
              LocateInstance(aBoundElement, content,
                             aAnonymousContent, element);

            if (realElement) {
              realElement->SetAttr(dstNs, dst, value, false);

              // XXXndeakin shouldn't this be done in lieu of SetAttr?
              if ((dst == nsGkAtoms::text && dstNs == kNameSpaceID_XBL) ||
                  (realElement->NodeInfo()->Equals(nsGkAtoms::html,
                                                   kNameSpaceID_XUL) &&
                   dst == nsGkAtoms::value && !value.IsEmpty())) {

                RefPtr<nsTextNode> textContent =
                  new nsTextNode(realElement->NodeInfo()->NodeInfoManager());

                textContent->SetText(value, false);
                realElement->AppendChildTo(textContent, false);
              }
            }

            curr = curr->GetNext();
          }
        }
      }
    }
  }
}


void
nsXBLPrototypeBinding::EnsureAttributeTable()
{
  if (!mAttributeTable) {
    mAttributeTable =
        new nsClassHashtable<nsUint32HashKey, InnerAttributeTable>(2);
  }
}

void
nsXBLPrototypeBinding::AddToAttributeTable(int32_t aSourceNamespaceID, nsAtom* aSourceTag,
                                           int32_t aDestNamespaceID, nsAtom* aDestTag,
                                           Element* aElement)
{
    InnerAttributeTable* attributesNS = mAttributeTable->Get(aSourceNamespaceID);
    if (!attributesNS) {
      attributesNS = new InnerAttributeTable(2);
      mAttributeTable->Put(aSourceNamespaceID, attributesNS);
    }

    nsXBLAttributeEntry* xblAttr =
      new nsXBLAttributeEntry(aSourceTag, aDestTag, aDestNamespaceID, aElement);

    nsXBLAttributeEntry* entry = attributesNS->Get(aSourceTag);
    if (!entry) {
      attributesNS->Put(aSourceTag, xblAttr);
    } else {
      while (entry->GetNext())
        entry = entry->GetNext();
      entry->SetNext(xblAttr);
    }
}

void
nsXBLPrototypeBinding::ConstructAttributeTable(Element* aElement)
{
  // Don't add entries for <children> elements, since those will get
  // removed from the DOM when we construct the insertion point table.
  if (!aElement->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
    nsAutoString inherits;
    aElement->GetAttr(kNameSpaceID_XBL, nsGkAtoms::inherits, inherits);

    if (!inherits.IsEmpty()) {
      EnsureAttributeTable();

      // The user specified at least one attribute.
      char* str = ToNewCString(inherits);
      char* newStr;
      // XXX We should use a strtok function that tokenizes PRUnichars
      // so that we don't have to convert from Unicode to ASCII and then back

      char* token = nsCRT::strtok( str, ", ", &newStr );
      while( token != nullptr ) {
        // Build an atom out of this attribute.
        RefPtr<nsAtom> atom;
        int32_t atomNsID = kNameSpaceID_None;
        RefPtr<nsAtom> attribute;
        int32_t attributeNsID = kNameSpaceID_None;

        // Figure out if this token contains a :.
        NS_ConvertASCIItoUTF16 attrTok(token);
        int32_t index = attrTok.Find("=", true);
        nsresult rv;
        if (index != -1) {
          // This attribute maps to something different.
          nsAutoString left, right;
          attrTok.Left(left, index);
          attrTok.Right(right, attrTok.Length()-index-1);

          rv = nsContentUtils::SplitQName(aElement, left, &attributeNsID,
                                          getter_AddRefs(attribute));
          if (NS_FAILED(rv))
            return;

          rv = nsContentUtils::SplitQName(aElement, right, &atomNsID,
                                          getter_AddRefs(atom));
          if (NS_FAILED(rv))
            return;
        }
        else {
          NS_ConvertASCIItoUTF16 tok(token);
          rv = nsContentUtils::SplitQName(aElement, tok, &atomNsID,
                                          getter_AddRefs(atom));
          if (NS_FAILED(rv))
            return;
          attribute = atom;
          attributeNsID = atomNsID;
        }

        AddToAttributeTable(atomNsID, atom, attributeNsID, attribute, aElement);

        // Now remove the inherits attribute from the element so that it doesn't
        // show up on clones of the element.  It is used
        // by the template only, and we don't need it anymore.
        // XXXdwh Don't do this for XUL elements, since it faults them into heavyweight
        // elements. Should nuke from the prototype instead.
        // aElement->UnsetAttr(kNameSpaceID_XBL, nsGkAtoms::inherits, false);

        token = nsCRT::strtok( newStr, ", ", &newStr );
      }

      free(str);
    }
  }

  // Recur into our children.
  for (nsIContent* child = aElement->GetFirstChild();
       child;
       child = child->GetNextSibling()) {
    if (child->IsElement()) {
      ConstructAttributeTable(child->AsElement());
    }
  }
}

nsresult
nsXBLPrototypeBinding::ConstructInterfaceTable(const nsAString& aImpls)
{
  if (!aImpls.IsEmpty()) {
    // The user specified at least one attribute.
    NS_ConvertUTF16toUTF8 utf8impl(aImpls);
    char* str = utf8impl.BeginWriting();
    char* newStr;
    // XXX We should use a strtok function that tokenizes PRUnichars
    // so that we don't have to convert from Unicode to ASCII and then back

    char* token = nsCRT::strtok( str, ", ", &newStr );
    while( token != nullptr ) {
      // get the InterfaceInfo for the name
      const nsXPTInterfaceInfo* iinfo = nsXPTInterfaceInfo::ByName(token);

      if (iinfo) {
        // obtain an IID.
        const nsIID* iid = nullptr;
        iinfo->GetIIDShared(&iid);

        if (iid) {
          // We found a valid iid.  Add it to our table.
          mInterfaceTable.Put(*iid, mBinding);

          // this block adds the parent interfaces of each interface
          // defined in the xbl definition (implements="nsI...")
          const nsXPTInterfaceInfo* parentInfo;
          // if it has a parent, add it to the table
          while (NS_SUCCEEDED(iinfo->GetParent(&parentInfo)) && parentInfo) {
            // get the iid
            parentInfo->GetIIDShared(&iid);

            // don't add nsISupports to the table
            if (!iid || iid->Equals(NS_GET_IID(nsISupports)))
              break;

            // add the iid to the table
            mInterfaceTable.Put(*iid, mBinding);

            // look for the next parent
            iinfo = parentInfo;
          }
        }
      }

      token = nsCRT::strtok( newStr, ", ", &newStr );
    }
  }

  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::AddResourceListener(nsIContent* aBoundElement)
{
  if (!mResources)
    return NS_ERROR_FAILURE; // Makes no sense to add a listener when the binding
                             // has no resources.

  mResources->AddResourceListener(aBoundElement);
  return NS_OK;
}

void
nsXBLPrototypeBinding::CreateKeyHandlers()
{
  nsXBLPrototypeHandler* curr = mPrototypeHandler;
  while (curr) {
    RefPtr<nsAtom> eventAtom = curr->GetEventName();
    if (eventAtom == nsGkAtoms::keyup ||
        eventAtom == nsGkAtoms::keydown ||
        eventAtom == nsGkAtoms::keypress) {
      uint8_t phase = curr->GetPhase();
      uint8_t type = curr->GetType();

      int32_t count = mKeyHandlers.Count();
      int32_t i;
      nsXBLKeyEventHandler* handler = nullptr;
      for (i = 0; i < count; ++i) {
        handler = mKeyHandlers[i];
        if (handler->Matches(eventAtom, phase, type))
          break;
      }

      if (i == count) {
        RefPtr<nsXBLKeyEventHandler> newHandler =
          new nsXBLKeyEventHandler(eventAtom, phase, type);
        mKeyHandlers.AppendObject(newHandler);
        handler = newHandler;
      }

      if (handler)
        handler->AddProtoHandler(curr);
    }

    curr = curr->GetNextHandler();
  }
}

class XBLPrototypeSetupCleanup
{
public:
  XBLPrototypeSetupCleanup(nsXBLDocumentInfo* aDocInfo, const nsACString& aID)
  : mDocInfo(aDocInfo), mID(aID) {}

  ~XBLPrototypeSetupCleanup()
  {
    if (mDocInfo) {
      mDocInfo->RemovePrototypeBinding(mID);
    }
  }

  void Disconnect()
  {
    mDocInfo = nullptr;
  }

  nsXBLDocumentInfo* mDocInfo;
  nsAutoCString mID;
};

nsresult
nsXBLPrototypeBinding::Read(nsIObjectInputStream* aStream,
                            nsXBLDocumentInfo* aDocInfo,
                            nsIDocument* aDocument,
                            uint8_t aFlags)
{
  mInheritStyle = (aFlags & XBLBinding_Serialize_InheritStyle) ? true : false;
  mChromeOnlyContent =
    (aFlags & XBLBinding_Serialize_ChromeOnlyContent) ? true : false;
  mBindToUntrustedContent =
    (aFlags & XBLBinding_Serialize_BindToUntrustedContent) ? true : false;
  mSimpleScopeChain =
    (aFlags & XBLBinding_Serialize_SimpleScopeChain) ? true : false;

  // nsXBLContentSink::ConstructBinding doesn't create a binding with an empty
  // id, so we don't here either.
  nsAutoCString id;
  nsresult rv = aStream->ReadCString(id);

  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(!id.IsEmpty(), NS_ERROR_FAILURE);

  nsAutoCString baseBindingURI;
  rv = aStream->ReadCString(baseBindingURI);
  NS_ENSURE_SUCCESS(rv, rv);
  mCheckedBaseProto = true;

  if (!baseBindingURI.IsEmpty()) {
    rv = NS_NewURI(getter_AddRefs(mBaseBindingURI), baseBindingURI);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = ReadNamespace(aStream, mBaseNameSpaceID);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString baseTag;
  rv = aStream->ReadString(baseTag);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!baseTag.IsEmpty()) {
    mBaseTag = NS_Atomize(baseTag);
  }

  mBinding = aDocument->CreateElem(NS_LITERAL_STRING("binding"), nullptr,
                                   kNameSpaceID_XBL);

  nsCOMPtr<nsIContent> child;
  rv = ReadContentNode(aStream, aDocument, aDocument->NodeInfoManager(), getter_AddRefs(child));
  NS_ENSURE_SUCCESS(rv, rv);

  Element* rootElement = aDocument->GetRootElement();
  if (rootElement)
    rootElement->AppendChildTo(mBinding, false);

  if (child) {
    mBinding->AppendChildTo(child, false);
  }

  uint32_t interfaceCount;
  rv = aStream->Read32(&interfaceCount);
  NS_ENSURE_SUCCESS(rv, rv);

  for (; interfaceCount > 0; interfaceCount--) {
    nsIID iid;
    rv = aStream->ReadID(&iid);
    NS_ENSURE_SUCCESS(rv, rv);
    mInterfaceTable.Put(iid, mBinding);
  }

  // We're not directly using this AutoJSAPI here, but callees use it via
  // AutoJSContext.
  AutoJSAPI jsapi;
  if (!jsapi.Init(xpc::CompilationScope())) {
    return NS_ERROR_UNEXPECTED;
  }

  bool isFirstBinding = aFlags & XBLBinding_Serialize_IsFirstBinding;
  rv = Init(id, aDocInfo, nullptr, isFirstBinding);
  NS_ENSURE_SUCCESS(rv, rv);

  // We need to set the prototype binding before reading the nsXBLProtoImpl,
  // as it may be retrieved within.
  rv = aDocInfo->SetPrototypeBinding(id, this);
  NS_ENSURE_SUCCESS(rv, rv);

  XBLPrototypeSetupCleanup cleanup(aDocInfo, id);

  nsAutoCString className;
  rv = aStream->ReadCString(className);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!className.IsEmpty()) {
    nsXBLProtoImpl* impl; // NS_NewXBLProtoImpl will set mImplementation for us
    NS_NewXBLProtoImpl(this, NS_ConvertUTF8toUTF16(className).get(), &impl);

    // This needs to happen after SetPrototypeBinding as calls are made to
    // retrieve the mapped bindings from within here. However, if an error
    // occurs, the mapping should be removed again so that we don't keep an
    // invalid binding around.
    rv = mImplementation->Read(aStream, this);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Next read in the handlers.
  nsXBLPrototypeHandler* previousHandler = nullptr;

  do {
    XBLBindingSerializeDetails type;
    rv = aStream->Read8(&type);
    NS_ENSURE_SUCCESS(rv, rv);

    if (type == XBLBinding_Serialize_NoMoreItems)
      break;

    NS_ASSERTION((type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Handler,
                 "invalid handler type");

    nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(this);
    rv = handler->Read(aStream);
    if (NS_FAILED(rv)) {
      delete handler;
      return rv;
    }

    if (previousHandler) {
      previousHandler->SetNextHandler(handler);
    }
    else {
      SetPrototypeHandlers(handler);
    }
    previousHandler = handler;
  } while (1);

  if (mBinding) {
    while (true) {
      XBLBindingSerializeDetails type;
      rv = aStream->Read8(&type);
      NS_ENSURE_SUCCESS(rv, rv);

      if (type != XBLBinding_Serialize_Attribute) {
        break;
      }

      int32_t attrNamespace;
      rv = ReadNamespace(aStream, attrNamespace);
      NS_ENSURE_SUCCESS(rv, rv);

      nsAutoString attrPrefix, attrName, attrValue;
      rv = aStream->ReadString(attrPrefix);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = aStream->ReadString(attrName);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = aStream->ReadString(attrValue);
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<nsAtom> atomPrefix = NS_Atomize(attrPrefix);
      RefPtr<nsAtom> atomName = NS_Atomize(attrName);
      mBinding->SetAttr(attrNamespace, atomName, atomPrefix, attrValue, false);
    }
  }

  // Finally, read in the resources.
  while (true) {
    XBLBindingSerializeDetails type;
    rv = aStream->Read8(&type);
    NS_ENSURE_SUCCESS(rv, rv);

    if (type == XBLBinding_Serialize_NoMoreItems)
      break;

    NS_ASSERTION((type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Stylesheet ||
                 (type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Image, "invalid resource type");

    nsAutoString src;
    rv = aStream->ReadString(src);
    NS_ENSURE_SUCCESS(rv, rv);

    AddResource(type == XBLBinding_Serialize_Stylesheet ? nsGkAtoms::stylesheet :
                                                          nsGkAtoms::image, src);
  }

  if (isFirstBinding) {
    aDocInfo->SetFirstPrototypeBinding(this);
  }

  cleanup.Disconnect();
  return NS_OK;
}

// static
nsresult
nsXBLPrototypeBinding::ReadNewBinding(nsIObjectInputStream* aStream,
                                      nsXBLDocumentInfo* aDocInfo,
                                      nsIDocument* aDocument,
                                      uint8_t aFlags)
{
  // If the Read() succeeds, |binding| will end up being owned by aDocInfo's
  // binding table. Otherwise, we must manually delete it.
  nsXBLPrototypeBinding* binding = new nsXBLPrototypeBinding();
  nsresult rv = binding->Read(aStream, aDocInfo, aDocument, aFlags);
  if (NS_FAILED(rv)) {
    delete binding;
  }
  return rv;
}

nsresult
nsXBLPrototypeBinding::Write(nsIObjectOutputStream* aStream)
{
  // This writes out the binding. Note that mCheckedBaseProto,
  // mKeyHandlersRegistered and mKeyHandlers are not serialized as they are
  // computed on demand.

  // We're not directly using this AutoJSAPI here, but callees use it via
  // AutoJSContext.
  AutoJSAPI jsapi;
  if (!jsapi.Init(xpc::CompilationScope())) {
    return NS_ERROR_UNEXPECTED;
  }

  uint8_t flags = mInheritStyle ? XBLBinding_Serialize_InheritStyle : 0;

  // mAlternateBindingURI is only set on the first binding.
  if (mAlternateBindingURI) {
    flags |= XBLBinding_Serialize_IsFirstBinding;
  }

  if (mChromeOnlyContent) {
    flags |= XBLBinding_Serialize_ChromeOnlyContent;
  }

  if (mBindToUntrustedContent) {
    flags |= XBLBinding_Serialize_BindToUntrustedContent;
  }

  if (mSimpleScopeChain) {
    flags |= XBLBinding_Serialize_SimpleScopeChain;
  }

  nsresult rv = aStream->Write8(flags);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString id;
  mBindingURI->GetRef(id);
  rv = aStream->WriteStringZ(id.get());
  NS_ENSURE_SUCCESS(rv, rv);

  // write out the extends and display attribute values
  nsAutoCString extends;
  ResolveBaseBinding();
  if (mBaseBindingURI) {
    rv = mBaseBindingURI->GetSpec(extends);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = aStream->WriteStringZ(extends.get());
  NS_ENSURE_SUCCESS(rv, rv);

  rv = WriteNamespace(aStream, mBaseNameSpaceID);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString baseTag;
  if (mBaseTag) {
    mBaseTag->ToString(baseTag);
  }
  rv = aStream->WriteWStringZ(baseTag.get());
  NS_ENSURE_SUCCESS(rv, rv);

  nsIContent* content = GetImmediateChild(nsGkAtoms::content);
  if (content) {
    rv = WriteContentNode(aStream, content);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else {
    // Write a marker to indicate that there is no content.
    rv = aStream->Write8(XBLBinding_Serialize_NoContent);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Enumerate and write out the implemented interfaces.
  rv = aStream->Write32(mInterfaceTable.Count());
  NS_ENSURE_SUCCESS(rv, rv);

  for (auto iter = mInterfaceTable.Iter(); !iter.Done(); iter.Next()) {
    // We can just write out the ids. The cache will be invalidated when a
    // different build is used, so we don't need to worry about ids changing.
    aStream->WriteID(iter.Key());
  }

  // Write out the implementation details.
  if (mImplementation) {
    rv = mImplementation->Write(aStream, this);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else {
    // Write out an empty classname. This indicates that the binding does not
    // define an implementation.
    rv = aStream->WriteUtf8Z(EmptyString().get());
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Write out the handlers.
  nsXBLPrototypeHandler* handler = mPrototypeHandler;
  while (handler) {
    rv = handler->Write(aStream);
    NS_ENSURE_SUCCESS(rv, rv);

    handler = handler->GetNextHandler();
  }

  aStream->Write8(XBLBinding_Serialize_NoMoreItems);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mBinding) {
    uint32_t attributes = mBinding->GetAttrCount();
    nsAutoString attrValue;
    for (uint32_t i = 0; i < attributes; ++i) {
      BorrowedAttrInfo attrInfo = mBinding->GetAttrInfoAt(i);
      const nsAttrName* name = attrInfo.mName;
      nsDependentAtomString attrName(attrInfo.mName->LocalName());
      attrInfo.mValue->ToString(attrValue);

      rv = aStream->Write8(XBLBinding_Serialize_Attribute);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = WriteNamespace(aStream, name->NamespaceID());
      NS_ENSURE_SUCCESS(rv, rv);

      nsAtom* prefix = name->GetPrefix();
      nsAutoString prefixString;
      if (prefix) {
        prefix->ToString(prefixString);
      }

      rv = aStream->WriteWStringZ(prefixString.get());
      NS_ENSURE_SUCCESS(rv, rv);

      rv = aStream->WriteWStringZ(attrName.get());
      NS_ENSURE_SUCCESS(rv, rv);

      rv = aStream->WriteWStringZ(attrValue.get());
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  aStream->Write8(XBLBinding_Serialize_NoMoreItems);
  NS_ENSURE_SUCCESS(rv, rv);

  // Write out the resources
  if (mResources) {
    rv = mResources->Write(aStream);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Write out an end mark at the end.
  return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
}

nsresult
nsXBLPrototypeBinding::ReadContentNode(nsIObjectInputStream* aStream,
                                       nsIDocument* aDocument,
                                       nsNodeInfoManager* aNim,
                                       nsIContent** aContent)
{
  *aContent = nullptr;

  int32_t namespaceID;
  nsresult rv = ReadNamespace(aStream, namespaceID);
  NS_ENSURE_SUCCESS(rv, rv);

  // There is no content to read so just return.
  if (namespaceID == XBLBinding_Serialize_NoContent)
    return NS_OK;

  // If this is a text type, just read the string and return.
  if (namespaceID == XBLBinding_Serialize_TextNode ||
      namespaceID == XBLBinding_Serialize_CDATANode ||
      namespaceID == XBLBinding_Serialize_CommentNode) {
    RefPtr<CharacterData> content;
    switch (namespaceID) {
      case XBLBinding_Serialize_TextNode:
        content = new nsTextNode(aNim);
        break;
      case XBLBinding_Serialize_CDATANode:
        content = new CDATASection(aNim);
        break;
      case XBLBinding_Serialize_CommentNode:
        content = new Comment(aNim);
        break;
      default:
        break;
    }

    nsAutoString text;
    rv = aStream->ReadString(text);
    NS_ENSURE_SUCCESS(rv, rv);

    content->SetText(text, false);
    content.forget(aContent);
    return NS_OK;
  }

  // Otherwise, it's an element, so read its tag, attributes and children.
  nsAutoString prefix, tag;
  rv = aStream->ReadString(prefix);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<nsAtom> prefixAtom;
  if (!prefix.IsEmpty())
    prefixAtom = NS_Atomize(prefix);

  rv = aStream->ReadString(tag);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<nsAtom> tagAtom = NS_Atomize(tag);
  RefPtr<NodeInfo> nodeInfo =
    aNim->GetNodeInfo(tagAtom, prefixAtom, namespaceID, nsINode::ELEMENT_NODE);

  uint32_t attrCount;
  rv = aStream->Read32(&attrCount);
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<Element> element;
  // Create XUL prototype elements, or regular elements for other namespaces.
  // This needs to match the code in nsXBLContentSink::CreateElement.
#ifdef MOZ_XUL
  if (namespaceID == kNameSpaceID_XUL) {
    nsIURI* documentURI = aDocument->GetDocumentURI();

    RefPtr<nsXULPrototypeElement> prototype = new nsXULPrototypeElement();

    prototype->mNodeInfo = nodeInfo;

    nsXULPrototypeAttribute* attrs = nullptr;
    if (attrCount > 0) {
      attrs = new nsXULPrototypeAttribute[attrCount];
    }

    prototype->mAttributes = attrs;
    prototype->mNumAttributes = attrCount;

    for (uint32_t i = 0; i < attrCount; i++) {
      rv = ReadNamespace(aStream, namespaceID);
      NS_ENSURE_SUCCESS(rv, rv);

      nsAutoString prefix, name, val;
      rv = aStream->ReadString(prefix);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = aStream->ReadString(name);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = aStream->ReadString(val);
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<nsAtom> nameAtom = NS_Atomize(name);
      if (namespaceID == kNameSpaceID_None) {
        attrs[i].mName.SetTo(nameAtom);
      }
      else {
        RefPtr<nsAtom> prefixAtom;
        if (!prefix.IsEmpty())
          prefixAtom = NS_Atomize(prefix);

        RefPtr<NodeInfo> ni =
          aNim->GetNodeInfo(nameAtom, prefixAtom,
                            namespaceID, nsINode::ATTRIBUTE_NODE);
        attrs[i].mName.SetTo(ni);
      }

      rv = prototype->SetAttrAt(i, val, documentURI);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    nsresult rv =
      nsXULElement::CreateFromPrototype(prototype, aDocument, false, false, getter_AddRefs(element));
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
#endif
    NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), NOT_FROM_PARSER);

    for (uint32_t i = 0; i < attrCount; i++) {
      rv = ReadNamespace(aStream, namespaceID);
      NS_ENSURE_SUCCESS(rv, rv);

      nsAutoString prefix, name, val;
      rv = aStream->ReadString(prefix);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = aStream->ReadString(name);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = aStream->ReadString(val);
      NS_ENSURE_SUCCESS(rv, rv);

      RefPtr<nsAtom> prefixAtom;
      if (!prefix.IsEmpty())
        prefixAtom = NS_Atomize(prefix);

      RefPtr<nsAtom> nameAtom = NS_Atomize(name);
      element->SetAttr(namespaceID, nameAtom, prefixAtom, val, false);
    }

#ifdef MOZ_XUL
  }
#endif

  // Now read the attribute forwarding entries (xbl:inherits)

  int32_t srcNamespaceID, destNamespaceID;
  rv = ReadNamespace(aStream, srcNamespaceID);
  NS_ENSURE_SUCCESS(rv, rv);

  while (srcNamespaceID != XBLBinding_Serialize_NoMoreAttributes) {
    nsAutoString srcAttribute, destAttribute;
    rv = aStream->ReadString(srcAttribute);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = ReadNamespace(aStream, destNamespaceID);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = aStream->ReadString(destAttribute);
    NS_ENSURE_SUCCESS(rv, rv);

    RefPtr<nsAtom> srcAtom = NS_Atomize(srcAttribute);
    RefPtr<nsAtom> destAtom = NS_Atomize(destAttribute);

    EnsureAttributeTable();
    AddToAttributeTable(srcNamespaceID, srcAtom, destNamespaceID, destAtom, element);

    rv = ReadNamespace(aStream, srcNamespaceID);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Finally, read in the child nodes.
  uint32_t childCount;
  rv = aStream->Read32(&childCount);
  NS_ENSURE_SUCCESS(rv, rv);

  for (uint32_t i = 0; i < childCount; i++) {
    nsCOMPtr<nsIContent> child;
    ReadContentNode(aStream, aDocument, aNim, getter_AddRefs(child));

    // Child may be null if this was a comment for example and can just be ignored.
    if (child) {
      element->AppendChildTo(child, false);
    }
  }

  element.forget(aContent);
  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::WriteContentNode(nsIObjectOutputStream* aStream,
                                        nsIContent* aNode)
{
  nsresult rv;

  if (!aNode->IsElement()) {
    // Text is writen out as a single byte for the type, followed by the text.
    uint8_t type = XBLBinding_Serialize_NoContent;
    switch (aNode->NodeType()) {
      case nsINode::TEXT_NODE:
        type = XBLBinding_Serialize_TextNode;
        break;
      case nsINode::CDATA_SECTION_NODE:
        type = XBLBinding_Serialize_CDATANode;
        break;
      case nsINode::COMMENT_NODE:
        type = XBLBinding_Serialize_CommentNode;
        break;
      default:
        break;
    }

    rv = aStream->Write8(type);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString content;
    aNode->GetText()->AppendTo(content);
    return aStream->WriteWStringZ(content.get());
  }

  // Otherwise, this is an element.
  Element* element = aNode->AsElement();

  // Write the namespace id followed by the tag name
  rv = WriteNamespace(aStream, element->GetNameSpaceID());
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoString prefixStr;
  element->NodeInfo()->GetPrefix(prefixStr);
  rv = aStream->WriteWStringZ(prefixStr.get());
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aStream->WriteWStringZ(nsDependentAtomString(element->NodeInfo()->NameAtom()).get());
  NS_ENSURE_SUCCESS(rv, rv);

  // Write attributes
  uint32_t count = element->GetAttrCount();
  rv = aStream->Write32(count);
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t i;
  for (i = 0; i < count; i++) {
    // Write out the namespace id, the namespace prefix, the local tag name,
    // and the value, in that order.

    const BorrowedAttrInfo attrInfo = element->GetAttrInfoAt(i);
    const nsAttrName* name = attrInfo.mName;

    // XXXndeakin don't write out xbl:inherits?
    int32_t namespaceID = name->NamespaceID();
    rv = WriteNamespace(aStream, namespaceID);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString prefixStr;
    nsAtom* prefix = name->GetPrefix();
    if (prefix)
      prefix->ToString(prefixStr);
    rv = aStream->WriteWStringZ(prefixStr.get());
    NS_ENSURE_SUCCESS(rv, rv);

    rv = aStream->WriteWStringZ(nsDependentAtomString(name->LocalName()).get());
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString val;
    attrInfo.mValue->ToString(val);
    rv = aStream->WriteWStringZ(val.get());
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Write out the attribute fowarding information
  if (mAttributeTable) {
    for (auto iter1 = mAttributeTable->Iter(); !iter1.Done(); iter1.Next()) {
      int32_t srcNamespace = iter1.Key();
      InnerAttributeTable* xblAttributes = iter1.UserData();

      for (auto iter2 = xblAttributes->Iter(); !iter2.Done(); iter2.Next()) {
        nsXBLAttributeEntry* entry = iter2.UserData();

        do {
          if (entry->GetElement() == element) {
            WriteNamespace(aStream, srcNamespace);
            aStream->WriteWStringZ(
              nsDependentAtomString(entry->GetSrcAttribute()).get());
            WriteNamespace(aStream, entry->GetDstNameSpace());
            aStream->WriteWStringZ(
              nsDependentAtomString(entry->GetDstAttribute()).get());
          }

          entry = entry->GetNext();
        } while (entry);
      }
    }
  }
  rv = aStream->Write8(XBLBinding_Serialize_NoMoreAttributes);
  NS_ENSURE_SUCCESS(rv, rv);

  // Finally, write out the child nodes.
  count = element->GetChildCount();
  rv = aStream->Write32(count);
  NS_ENSURE_SUCCESS(rv, rv);

  for (i = 0; i < count; i++) {
    rv = WriteContentNode(aStream, element->GetChildAt_Deprecated(i));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::ReadNamespace(nsIObjectInputStream* aStream,
                                     int32_t& aNameSpaceID)
{
  uint8_t namespaceID;
  nsresult rv = aStream->Read8(&namespaceID);
  NS_ENSURE_SUCCESS(rv, rv);

  if (namespaceID == XBLBinding_Serialize_CustomNamespace) {
    nsAutoString namesp;
    rv = aStream->ReadString(namesp);
    NS_ENSURE_SUCCESS(rv, rv);

    nsContentUtils::NameSpaceManager()->RegisterNameSpace(namesp, aNameSpaceID);
  }
  else {
    aNameSpaceID = namespaceID;
  }

  return NS_OK;
}

nsresult
nsXBLPrototypeBinding::WriteNamespace(nsIObjectOutputStream* aStream,
                                      int32_t aNameSpaceID)
{
  // Namespaces are stored as a single byte id for well-known namespaces.
  // This saves time and space as other namespaces aren't very common in
  // XBL. If another namespace is used however, the namespace id will be
  // XBLBinding_Serialize_CustomNamespace and the string namespace written
  // out directly afterwards.
  nsresult rv;

  if (aNameSpaceID <= kNameSpaceID_LastBuiltin) {
    rv = aStream->Write8((int8_t)aNameSpaceID);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else {
    rv = aStream->Write8(XBLBinding_Serialize_CustomNamespace);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString namesp;
    nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, namesp);
    aStream->WriteWStringZ(namesp.get());
  }

  return NS_OK;
}


bool CheckTagNameWhiteList(int32_t aNameSpaceID, nsAtom *aTagName)
{
  static Element::AttrValuesArray kValidXULTagNames[] =  {
    &nsGkAtoms::box, &nsGkAtoms::browser,
    &nsGkAtoms::button, &nsGkAtoms::hbox, &nsGkAtoms::image, &nsGkAtoms::menu,
    &nsGkAtoms::menubar, &nsGkAtoms::menuitem, &nsGkAtoms::menupopup,
    &nsGkAtoms::row, &nsGkAtoms::slider, &nsGkAtoms::spacer,
    &nsGkAtoms::splitter, &nsGkAtoms::text, &nsGkAtoms::tree, nullptr};

  uint32_t i;
  if (aNameSpaceID == kNameSpaceID_XUL) {
    for (i = 0; kValidXULTagNames[i]; ++i) {
      if (aTagName == *(kValidXULTagNames[i])) {
        return true;
      }
    }
  }
  else if (aNameSpaceID == kNameSpaceID_SVG &&
           aTagName == nsGkAtoms::generic_) {
    return true;
  }

  return false;
}

nsresult
nsXBLPrototypeBinding::ResolveBaseBinding()
{
  if (mCheckedBaseProto)
    return NS_OK;
  mCheckedBaseProto = true;

  nsCOMPtr<nsIDocument> doc = mXBLDocInfoWeak->GetDocument();

  // Check for the presence of 'extends' and 'display' attributes
  nsAutoString display, extends;
  mBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::extends, extends);
  if (extends.IsEmpty())
    return NS_OK;

  mBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::display, display);
  bool hasDisplay = !display.IsEmpty();

  nsAutoString value(extends);

  // Now slice 'em up to see what we've got.
  nsAutoString prefix;
  int32_t offset;
  if (hasDisplay) {
    offset = display.FindChar(':');
    if (-1 != offset) {
      display.Left(prefix, offset);
      display.Cut(0, offset+1);
    }
  }
  else {
    offset = extends.FindChar(':');
    if (-1 != offset) {
      extends.Left(prefix, offset);
      extends.Cut(0, offset+1);
      display = extends;
    }
  }

  nsAutoString nameSpace;

  if (!prefix.IsEmpty()) {
    mBinding->LookupNamespaceURI(prefix, nameSpace);
    if (!nameSpace.IsEmpty()) {
      int32_t nameSpaceID =
        nsContentUtils::NameSpaceManager()->GetNameSpaceID(nameSpace,
                                                           nsContentUtils::IsChromeDoc(doc));

      RefPtr<nsAtom> tagName = NS_Atomize(display);
      // Check the white list
      if (!CheckTagNameWhiteList(nameSpaceID, tagName)) {
        const char16_t* params[] = { display.get() };
        nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
                                        NS_LITERAL_CSTRING("XBL"), nullptr,
                                        nsContentUtils::eXBL_PROPERTIES,
                                       "InvalidExtendsBinding",
                                        params, ArrayLength(params),
                                        doc->GetDocumentURI());
        NS_ASSERTION(!nsXBLService::IsChromeOrResourceURI(doc->GetDocumentURI()),
                     "Invalid extends value");
        return NS_ERROR_ILLEGAL_VALUE;
      }

      SetBaseTag(nameSpaceID, tagName);
    }
  }

  if (hasDisplay || nameSpace.IsEmpty()) {
    mBinding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::extends, false);
    mBinding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::display, false);

    return NS_NewURI(getter_AddRefs(mBaseBindingURI), value,
                     doc->GetDocumentCharacterSet(),
                     doc->GetDocBaseURI());
  }

  return NS_OK;
}

void
nsXBLPrototypeBinding::EnsureResources()
{
  if (!mResources) {
    mResources = new nsXBLPrototypeResources(this);
  }
}

void
nsXBLPrototypeBinding::AppendStyleSheet(StyleSheet* aSheet)
{
  EnsureResources();
  mResources->AppendStyleSheet(aSheet);
}

void
nsXBLPrototypeBinding::RemoveStyleSheet(StyleSheet* aSheet)
{
  if (!mResources) {
    MOZ_ASSERT(false, "Trying to remove a sheet that does not exist.");
    return;
  }

  mResources->RemoveStyleSheet(aSheet);
}
void
nsXBLPrototypeBinding::InsertStyleSheetAt(size_t aIndex, StyleSheet* aSheet)
{
  EnsureResources();
  mResources->InsertStyleSheetAt(aIndex, aSheet);
}

StyleSheet*
nsXBLPrototypeBinding::StyleSheetAt(size_t aIndex) const
{
  MOZ_ASSERT(mResources);
  return mResources->StyleSheetAt(aIndex);
}

size_t
nsXBLPrototypeBinding::SheetCount() const
{
  return mResources ? mResources->SheetCount() : 0;
}

bool
nsXBLPrototypeBinding::HasStyleSheets() const
{
  return mResources && mResources->HasStyleSheets();
}

void
nsXBLPrototypeBinding::AppendStyleSheetsTo(
                                      nsTArray<StyleSheet*>& aResult) const
{
  if (mResources) {
    mResources->AppendStyleSheetsTo(aResult);
  }
}

size_t
nsXBLPrototypeBinding::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);
  n += mPrototypeHandler
    ? mPrototypeHandler->SizeOfIncludingThis(aMallocSizeOf) : 0;
  n += mResources ? mResources->SizeOfIncludingThis(aMallocSizeOf) : 0;

  if (mAttributeTable) {
    n += mAttributeTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
    for (auto iter = mAttributeTable->Iter(); !iter.Done(); iter.Next()) {
      InnerAttributeTable* table = iter.UserData();
      n += table->ShallowSizeOfIncludingThis(aMallocSizeOf);
      for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
        n += iter2.UserData()->SizeOfIncludingThis(aMallocSizeOf);
      }
    }
  }

  n += mInterfaceTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
  n += mKeyHandlers.ShallowSizeOfExcludingThis(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it
  // is worthwhile:
  // - mBindingURI
  // - mAlternateBindingURI
  // - mBaseBindingURI
  // - mImplementation

  return n;
}