michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "plstr.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIXMLContentSink.h" michael@0: #include "nsContentCID.h" michael@0: #include "mozilla/dom/XMLDocument.h" michael@0: #include "nsXBLService.h" michael@0: #include "nsXBLBinding.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsXBLContentSink.h" michael@0: #include "xptinfo.h" michael@0: #include "nsIInterfaceInfoManager.h" michael@0: #include "nsIDocumentObserver.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsXBLProtoImpl.h" michael@0: #include "nsCRT.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsTextNode.h" michael@0: #include "nsIInterfaceInfo.h" michael@0: #include "nsIScriptError.h" michael@0: michael@0: #include "nsIStyleRuleProcessor.h" michael@0: #include "nsXBLResourceLoader.h" michael@0: #include "mozilla/dom/CDATASection.h" michael@0: #include "mozilla/dom/Comment.h" michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: #ifdef MOZ_XUL michael@0: #include "nsXULElement.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: // Helper Classes ===================================================================== michael@0: michael@0: // nsXBLAttributeEntry and helpers. This class is used to efficiently handle michael@0: // attribute changes in anonymous content. michael@0: michael@0: class nsXBLAttributeEntry { michael@0: public: michael@0: nsXBLAttributeEntry(nsIAtom* aSrcAtom, nsIAtom* aDstAtom, michael@0: int32_t aDstNameSpace, nsIContent* aContent) michael@0: : mElement(aContent), michael@0: mSrcAttribute(aSrcAtom), michael@0: mDstAttribute(aDstAtom), michael@0: mDstNameSpace(aDstNameSpace), michael@0: mNext(nullptr) { } michael@0: michael@0: ~nsXBLAttributeEntry() { michael@0: NS_CONTENT_DELETE_LIST_MEMBER(nsXBLAttributeEntry, this, mNext); michael@0: } michael@0: michael@0: nsIAtom* GetSrcAttribute() { return mSrcAttribute; } michael@0: nsIAtom* GetDstAttribute() { return mDstAttribute; } michael@0: int32_t GetDstNameSpace() { return mDstNameSpace; } michael@0: michael@0: nsIContent* GetElement() { return mElement; } michael@0: michael@0: nsXBLAttributeEntry* GetNext() { return mNext; } michael@0: void SetNext(nsXBLAttributeEntry* aEntry) { mNext = aEntry; } michael@0: michael@0: protected: michael@0: nsIContent* mElement; michael@0: michael@0: nsCOMPtr mSrcAttribute; michael@0: nsCOMPtr mDstAttribute; michael@0: int32_t mDstNameSpace; michael@0: nsXBLAttributeEntry* mNext; michael@0: }; michael@0: michael@0: // ============================================================================= michael@0: michael@0: // Implementation ///////////////////////////////////////////////////////////////// michael@0: michael@0: // Constructors/Destructors michael@0: nsXBLPrototypeBinding::nsXBLPrototypeBinding() michael@0: : mImplementation(nullptr), michael@0: mBaseBinding(nullptr), michael@0: mInheritStyle(true), michael@0: mCheckedBaseProto(false), michael@0: mKeyHandlersRegistered(false), michael@0: mChromeOnlyContent(false), michael@0: mResources(nullptr), michael@0: mBaseNameSpaceID(kNameSpaceID_None) michael@0: { michael@0: MOZ_COUNT_CTOR(nsXBLPrototypeBinding); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::Init(const nsACString& aID, michael@0: nsXBLDocumentInfo* aInfo, michael@0: nsIContent* aElement, michael@0: bool aFirstBinding) michael@0: { michael@0: nsresult rv = aInfo->DocumentURI()->Clone(getter_AddRefs(mBindingURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // The binding URI might be an immutable URI (e.g. for about: URIs). In that case, michael@0: // we'll fail in SetRef below, but that doesn't matter much for now. michael@0: if (aFirstBinding) { michael@0: rv = mBindingURI->Clone(getter_AddRefs(mAlternateBindingURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: mBindingURI->SetRef(aID); michael@0: michael@0: mXBLDocInfoWeak = aInfo; michael@0: michael@0: // aElement will be null when reading from the cache, but the element will michael@0: // still be set later. michael@0: if (aElement) { michael@0: SetBindingElement(aElement); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsXBLPrototypeBinding::CompareBindingURI(nsIURI* aURI) const michael@0: { michael@0: bool equal = false; michael@0: mBindingURI->Equals(aURI, &equal); michael@0: if (!equal && mAlternateBindingURI) { michael@0: mAlternateBindingURI->Equals(aURI, &equal); michael@0: } michael@0: return equal; michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::Traverse(nsCycleCollectionTraversalCallback &cb) const michael@0: { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "proto mBinding"); michael@0: cb.NoteXPCOMChild(mBinding); michael@0: if (mResources) { michael@0: mResources->Traverse(cb); michael@0: } michael@0: ImplCycleCollectionTraverse(cb, mInterfaceTable, "proto mInterfaceTable"); michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::UnlinkJSObjects() michael@0: { michael@0: if (mImplementation) michael@0: mImplementation->UnlinkJSObjects(); michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::Trace(const TraceCallbacks& aCallbacks, void *aClosure) const michael@0: { michael@0: if (mImplementation) michael@0: mImplementation->Trace(aCallbacks, aClosure); michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::Initialize() michael@0: { michael@0: nsIContent* content = GetImmediateChild(nsGkAtoms::content); michael@0: if (content) { michael@0: ConstructAttributeTable(content); michael@0: } michael@0: } michael@0: michael@0: nsXBLPrototypeBinding::~nsXBLPrototypeBinding(void) michael@0: { michael@0: delete mImplementation; michael@0: MOZ_COUNT_DTOR(nsXBLPrototypeBinding); michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::SetBasePrototype(nsXBLPrototypeBinding* aBinding) michael@0: { michael@0: if (mBaseBinding == aBinding) michael@0: return; michael@0: michael@0: if (mBaseBinding) { michael@0: NS_ERROR("Base XBL prototype binding is already defined!"); michael@0: return; michael@0: } michael@0: michael@0: mBaseBinding = aBinding; michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::SetBindingElement(nsIContent* aElement) michael@0: { michael@0: mBinding = aElement; michael@0: if (mBinding->AttrValueIs(kNameSpaceID_None, nsGkAtoms::inheritstyle, michael@0: nsGkAtoms::_false, eCaseMatters)) michael@0: mInheritStyle = false; michael@0: michael@0: mChromeOnlyContent = mBinding->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::chromeOnlyContent, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: } michael@0: michael@0: bool michael@0: nsXBLPrototypeBinding::GetAllowScripts() const michael@0: { michael@0: return mXBLDocInfoWeak->GetScriptAccess(); michael@0: } michael@0: michael@0: bool michael@0: nsXBLPrototypeBinding::LoadResources() michael@0: { michael@0: if (mResources) { michael@0: bool result; michael@0: mResources->LoadResources(&result); michael@0: return result; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::AddResource(nsIAtom* aResourceType, const nsAString& aSrc) michael@0: { michael@0: if (!mResources) { michael@0: mResources = new nsXBLPrototypeResources(this); michael@0: } michael@0: michael@0: mResources->AddResource(aResourceType, aSrc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::FlushSkinSheets() michael@0: { michael@0: if (mResources) michael@0: return mResources->FlushSkinSheets(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::BindingAttached(nsIContent* aBoundElement) michael@0: { michael@0: if (mImplementation && mImplementation->CompiledMembers() && michael@0: mImplementation->mConstructor) michael@0: return mImplementation->mConstructor->Execute(aBoundElement); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::BindingDetached(nsIContent* aBoundElement) michael@0: { michael@0: if (mImplementation && mImplementation->CompiledMembers() && michael@0: mImplementation->mDestructor) michael@0: return mImplementation->mDestructor->Execute(aBoundElement); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsXBLProtoImplAnonymousMethod* michael@0: nsXBLPrototypeBinding::GetConstructor() michael@0: { michael@0: if (mImplementation) michael@0: return mImplementation->mConstructor; michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsXBLProtoImplAnonymousMethod* michael@0: nsXBLPrototypeBinding::GetDestructor() michael@0: { michael@0: if (mImplementation) michael@0: return mImplementation->mDestructor; michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::SetConstructor(nsXBLProtoImplAnonymousMethod* aMethod) michael@0: { michael@0: if (!mImplementation) michael@0: return NS_ERROR_FAILURE; michael@0: mImplementation->mConstructor = aMethod; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::SetDestructor(nsXBLProtoImplAnonymousMethod* aMethod) michael@0: { michael@0: if (!mImplementation) michael@0: return NS_ERROR_FAILURE; michael@0: mImplementation->mDestructor = aMethod; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::InstallImplementation(nsXBLBinding* aBinding) michael@0: { michael@0: if (mImplementation) michael@0: return mImplementation->InstallImplementation(this, aBinding); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXXbz this duplicates lots of SetAttrs michael@0: void michael@0: nsXBLPrototypeBinding::AttributeChanged(nsIAtom* aAttribute, michael@0: int32_t aNameSpaceID, michael@0: bool aRemoveFlag, michael@0: nsIContent* aChangedElement, michael@0: nsIContent* aAnonymousContent, michael@0: bool aNotify) michael@0: { michael@0: if (!mAttributeTable) michael@0: return; michael@0: michael@0: InnerAttributeTable *attributesNS = mAttributeTable->Get(aNameSpaceID); michael@0: if (!attributesNS) michael@0: return; michael@0: michael@0: nsXBLAttributeEntry* xblAttr = attributesNS->Get(aAttribute); michael@0: if (!xblAttr) michael@0: return; michael@0: michael@0: // Iterate over the elements in the array. michael@0: nsCOMPtr content = GetImmediateChild(nsGkAtoms::content); michael@0: while (xblAttr) { michael@0: nsIContent* element = xblAttr->GetElement(); michael@0: michael@0: nsCOMPtr realElement = LocateInstance(aChangedElement, content, michael@0: aAnonymousContent, michael@0: element); michael@0: michael@0: if (realElement) { michael@0: // Hold a strong reference here so that the atom doesn't go away during michael@0: // UnsetAttr. michael@0: nsCOMPtr dstAttr = xblAttr->GetDstAttribute(); michael@0: int32_t dstNs = xblAttr->GetDstNameSpace(); michael@0: michael@0: if (aRemoveFlag) michael@0: realElement->UnsetAttr(dstNs, dstAttr, aNotify); michael@0: else { michael@0: bool attrPresent = true; michael@0: nsAutoString value; michael@0: // Check to see if the src attribute is xbl:text. If so, then we need to obtain the michael@0: // children of the real element and get the text nodes' values. michael@0: if (aAttribute == nsGkAtoms::text && aNameSpaceID == kNameSpaceID_XBL) { michael@0: if (!nsContentUtils::GetNodeTextContent(aChangedElement, false, value)) { michael@0: NS_RUNTIMEABORT("OOM"); michael@0: } michael@0: value.StripChar(char16_t('\n')); michael@0: value.StripChar(char16_t('\r')); michael@0: nsAutoString stripVal(value); michael@0: stripVal.StripWhitespace(); michael@0: if (stripVal.IsEmpty()) michael@0: attrPresent = false; michael@0: } michael@0: else { michael@0: attrPresent = aChangedElement->GetAttr(aNameSpaceID, aAttribute, value); michael@0: } michael@0: michael@0: if (attrPresent) michael@0: realElement->SetAttr(dstNs, dstAttr, value, aNotify); michael@0: } michael@0: michael@0: // See if we're the tag in XUL, and see if value is being michael@0: // set or unset on us. We may also be a tag that is having michael@0: // xbl:text set on us. michael@0: michael@0: if ((dstAttr == nsGkAtoms::text && dstNs == kNameSpaceID_XBL) || michael@0: (realElement->NodeInfo()->Equals(nsGkAtoms::html, michael@0: kNameSpaceID_XUL) && michael@0: dstAttr == nsGkAtoms::value)) { michael@0: // Flush out all our kids. michael@0: uint32_t childCount = realElement->GetChildCount(); michael@0: for (uint32_t i = 0; i < childCount; i++) michael@0: realElement->RemoveChildAt(0, aNotify); michael@0: michael@0: if (!aRemoveFlag) { michael@0: // Construct a new text node and insert it. michael@0: nsAutoString value; michael@0: aChangedElement->GetAttr(aNameSpaceID, aAttribute, value); michael@0: if (!value.IsEmpty()) { michael@0: nsRefPtr textContent = michael@0: new nsTextNode(realElement->NodeInfo()->NodeInfoManager()); michael@0: michael@0: textContent->SetText(value, true); michael@0: realElement->AppendChildTo(textContent, true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: xblAttr = xblAttr->GetNext(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::SetBaseTag(int32_t aNamespaceID, nsIAtom* aTag) michael@0: { michael@0: mBaseNameSpaceID = aNamespaceID; michael@0: mBaseTag = aTag; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsXBLPrototypeBinding::GetBaseTag(int32_t* aNamespaceID) michael@0: { michael@0: if (mBaseTag) { michael@0: *aNamespaceID = mBaseNameSpaceID; michael@0: return mBaseTag; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsXBLPrototypeBinding::ImplementsInterface(REFNSIID aIID) const michael@0: { michael@0: // Check our IID table. michael@0: return !!mInterfaceTable.GetWeak(aIID); michael@0: } michael@0: michael@0: // Internal helpers /////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsIContent* michael@0: nsXBLPrototypeBinding::GetImmediateChild(nsIAtom* aTag) michael@0: { michael@0: for (nsIContent* child = mBinding->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (child->NodeInfo()->Equals(aTag, kNameSpaceID_XBL)) { michael@0: return child; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::InitClass(const nsCString& aClassName, michael@0: JSContext * aContext, michael@0: JS::Handle aScriptObject, michael@0: JS::MutableHandle aClassObject, michael@0: bool* aNew) michael@0: { michael@0: return nsXBLBinding::DoInitJSClass(aContext, aScriptObject, michael@0: aClassName, this, aClassObject, aNew); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsXBLPrototypeBinding::LocateInstance(nsIContent* aBoundElement, michael@0: nsIContent* aTemplRoot, michael@0: nsIContent* aCopyRoot, michael@0: nsIContent* aTemplChild) michael@0: { michael@0: // XXX We will get in trouble if the binding instantiation deviates from the template michael@0: // in the prototype. michael@0: if (aTemplChild == aTemplRoot || !aTemplChild) michael@0: return nullptr; michael@0: michael@0: nsIContent* templParent = aTemplChild->GetParent(); michael@0: michael@0: // We may be disconnected from our parent during cycle collection. michael@0: if (!templParent) michael@0: return nullptr; michael@0: michael@0: nsIContent *copyParent = michael@0: templParent == aTemplRoot ? aCopyRoot : michael@0: LocateInstance(aBoundElement, aTemplRoot, aCopyRoot, templParent); michael@0: michael@0: if (!copyParent) michael@0: return nullptr; michael@0: michael@0: return copyParent->GetChildAt(templParent->IndexOf(aTemplChild)); michael@0: } michael@0: michael@0: struct nsXBLAttrChangeData michael@0: { michael@0: nsXBLPrototypeBinding* mProto; michael@0: nsIContent* mBoundElement; michael@0: nsIContent* mContent; michael@0: int32_t mSrcNamespace; michael@0: michael@0: nsXBLAttrChangeData(nsXBLPrototypeBinding* aProto, michael@0: nsIContent* aElt, nsIContent* aContent) michael@0: :mProto(aProto), mBoundElement(aElt), mContent(aContent) {} michael@0: }; michael@0: michael@0: // XXXbz this duplicates lots of AttributeChanged michael@0: static PLDHashOperator michael@0: SetAttrs(nsISupports* aKey, nsXBLAttributeEntry* aEntry, void* aClosure) michael@0: { michael@0: nsXBLAttrChangeData* changeData = static_cast(aClosure); michael@0: michael@0: nsIAtom* src = aEntry->GetSrcAttribute(); michael@0: int32_t srcNs = changeData->mSrcNamespace; michael@0: nsAutoString value; michael@0: bool attrPresent = true; michael@0: michael@0: if (src == nsGkAtoms::text && srcNs == kNameSpaceID_XBL) { michael@0: if (!nsContentUtils::GetNodeTextContent(changeData->mBoundElement, false, michael@0: value)) { michael@0: NS_RUNTIMEABORT("OOM"); michael@0: } michael@0: value.StripChar(char16_t('\n')); michael@0: value.StripChar(char16_t('\r')); michael@0: nsAutoString stripVal(value); michael@0: stripVal.StripWhitespace(); michael@0: michael@0: if (stripVal.IsEmpty()) michael@0: attrPresent = false; michael@0: } michael@0: else { michael@0: attrPresent = changeData->mBoundElement->GetAttr(srcNs, src, value); michael@0: } michael@0: michael@0: if (attrPresent) { michael@0: nsIContent* content = michael@0: changeData->mProto->GetImmediateChild(nsGkAtoms::content); michael@0: michael@0: nsXBLAttributeEntry* curr = aEntry; michael@0: while (curr) { michael@0: nsIAtom* dst = curr->GetDstAttribute(); michael@0: int32_t dstNs = curr->GetDstNameSpace(); michael@0: nsIContent* element = curr->GetElement(); michael@0: michael@0: nsIContent *realElement = michael@0: changeData->mProto->LocateInstance(changeData->mBoundElement, content, michael@0: changeData->mContent, element); michael@0: michael@0: if (realElement) { michael@0: realElement->SetAttr(dstNs, dst, value, false); michael@0: michael@0: // XXXndeakin shouldn't this be done in lieu of SetAttr? michael@0: if ((dst == nsGkAtoms::text && dstNs == kNameSpaceID_XBL) || michael@0: (realElement->NodeInfo()->Equals(nsGkAtoms::html, michael@0: kNameSpaceID_XUL) && michael@0: dst == nsGkAtoms::value && !value.IsEmpty())) { michael@0: michael@0: nsRefPtr textContent = michael@0: new nsTextNode(realElement->NodeInfo()->NodeInfoManager()); michael@0: michael@0: textContent->SetText(value, false); michael@0: realElement->AppendChildTo(textContent, false); michael@0: } michael@0: } michael@0: michael@0: curr = curr->GetNext(); michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: SetAttrsNS(const uint32_t &aNamespace, michael@0: nsXBLPrototypeBinding::InnerAttributeTable* aXBLAttributes, michael@0: void* aClosure) michael@0: { michael@0: if (aXBLAttributes && aClosure) { michael@0: nsXBLAttrChangeData* changeData = static_cast(aClosure); michael@0: changeData->mSrcNamespace = aNamespace; michael@0: aXBLAttributes->EnumerateRead(SetAttrs, aClosure); michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::SetInitialAttributes(nsIContent* aBoundElement, nsIContent* aAnonymousContent) michael@0: { michael@0: if (mAttributeTable) { michael@0: nsXBLAttrChangeData data(this, aBoundElement, aAnonymousContent); michael@0: mAttributeTable->EnumerateRead(SetAttrsNS, &data); michael@0: } michael@0: } michael@0: michael@0: nsIStyleRuleProcessor* michael@0: nsXBLPrototypeBinding::GetRuleProcessor() michael@0: { michael@0: if (mResources) { michael@0: return mResources->mRuleProcessor; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsXBLPrototypeResources::sheet_array_type* michael@0: nsXBLPrototypeBinding::GetOrCreateStyleSheets() michael@0: { michael@0: if (!mResources) { michael@0: mResources = new nsXBLPrototypeResources(this); michael@0: } michael@0: michael@0: return &mResources->mStyleSheetList; michael@0: } michael@0: michael@0: nsXBLPrototypeResources::sheet_array_type* michael@0: nsXBLPrototypeBinding::GetStyleSheets() michael@0: { michael@0: if (mResources) { michael@0: return &mResources->mStyleSheetList; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::EnsureAttributeTable() michael@0: { michael@0: if (!mAttributeTable) { michael@0: mAttributeTable = new nsClassHashtable(4); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::AddToAttributeTable(int32_t aSourceNamespaceID, nsIAtom* aSourceTag, michael@0: int32_t aDestNamespaceID, nsIAtom* aDestTag, michael@0: nsIContent* aContent) michael@0: { michael@0: InnerAttributeTable* attributesNS = mAttributeTable->Get(aSourceNamespaceID); michael@0: if (!attributesNS) { michael@0: attributesNS = new InnerAttributeTable(4); michael@0: mAttributeTable->Put(aSourceNamespaceID, attributesNS); michael@0: } michael@0: michael@0: nsXBLAttributeEntry* xblAttr = michael@0: new nsXBLAttributeEntry(aSourceTag, aDestTag, aDestNamespaceID, aContent); michael@0: michael@0: nsXBLAttributeEntry* entry = attributesNS->Get(aSourceTag); michael@0: if (!entry) { michael@0: attributesNS->Put(aSourceTag, xblAttr); michael@0: } else { michael@0: while (entry->GetNext()) michael@0: entry = entry->GetNext(); michael@0: entry->SetNext(xblAttr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::ConstructAttributeTable(nsIContent* aElement) michael@0: { michael@0: // Don't add entries for elements, since those will get michael@0: // removed from the DOM when we construct the insertion point table. michael@0: if (!aElement->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) { michael@0: nsAutoString inherits; michael@0: aElement->GetAttr(kNameSpaceID_XBL, nsGkAtoms::inherits, inherits); michael@0: michael@0: if (!inherits.IsEmpty()) { michael@0: EnsureAttributeTable(); michael@0: michael@0: // The user specified at least one attribute. michael@0: char* str = ToNewCString(inherits); michael@0: char* newStr; michael@0: // XXX We should use a strtok function that tokenizes PRUnichars michael@0: // so that we don't have to convert from Unicode to ASCII and then back michael@0: michael@0: char* token = nsCRT::strtok( str, ", ", &newStr ); michael@0: while( token != nullptr ) { michael@0: // Build an atom out of this attribute. michael@0: nsCOMPtr atom; michael@0: int32_t atomNsID = kNameSpaceID_None; michael@0: nsCOMPtr attribute; michael@0: int32_t attributeNsID = kNameSpaceID_None; michael@0: michael@0: // Figure out if this token contains a :. michael@0: nsAutoString attrTok; attrTok.AssignWithConversion(token); michael@0: int32_t index = attrTok.Find("=", true); michael@0: nsresult rv; michael@0: if (index != -1) { michael@0: // This attribute maps to something different. michael@0: nsAutoString left, right; michael@0: attrTok.Left(left, index); michael@0: attrTok.Right(right, attrTok.Length()-index-1); michael@0: michael@0: rv = nsContentUtils::SplitQName(aElement, left, &attributeNsID, michael@0: getter_AddRefs(attribute)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: rv = nsContentUtils::SplitQName(aElement, right, &atomNsID, michael@0: getter_AddRefs(atom)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: } michael@0: else { michael@0: nsAutoString tok; michael@0: tok.AssignWithConversion(token); michael@0: rv = nsContentUtils::SplitQName(aElement, tok, &atomNsID, michael@0: getter_AddRefs(atom)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: attribute = atom; michael@0: attributeNsID = atomNsID; michael@0: } michael@0: michael@0: AddToAttributeTable(atomNsID, atom, attributeNsID, attribute, aElement); michael@0: michael@0: // Now remove the inherits attribute from the element so that it doesn't michael@0: // show up on clones of the element. It is used michael@0: // by the template only, and we don't need it anymore. michael@0: // XXXdwh Don't do this for XUL elements, since it faults them into heavyweight michael@0: // elements. Should nuke from the prototype instead. michael@0: // aElement->UnsetAttr(kNameSpaceID_XBL, nsGkAtoms::inherits, false); michael@0: michael@0: token = nsCRT::strtok( newStr, ", ", &newStr ); michael@0: } michael@0: michael@0: nsMemory::Free(str); michael@0: } michael@0: } michael@0: michael@0: // Recur into our children. michael@0: for (nsIContent* child = aElement->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: ConstructAttributeTable(child); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::ConstructInterfaceTable(const nsAString& aImpls) michael@0: { michael@0: if (!aImpls.IsEmpty()) { michael@0: // Obtain the interface info manager that can tell us the IID michael@0: // for a given interface name. michael@0: nsCOMPtr michael@0: infoManager(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID)); michael@0: if (!infoManager) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // The user specified at least one attribute. michael@0: NS_ConvertUTF16toUTF8 utf8impl(aImpls); michael@0: char* str = utf8impl.BeginWriting(); michael@0: char* newStr; michael@0: // XXX We should use a strtok function that tokenizes PRUnichars michael@0: // so that we don't have to convert from Unicode to ASCII and then back michael@0: michael@0: char* token = nsCRT::strtok( str, ", ", &newStr ); michael@0: while( token != nullptr ) { michael@0: // get the InterfaceInfo for the name michael@0: nsCOMPtr iinfo; michael@0: infoManager->GetInfoForName(token, getter_AddRefs(iinfo)); michael@0: michael@0: if (iinfo) { michael@0: // obtain an IID. michael@0: const nsIID* iid = nullptr; michael@0: iinfo->GetIIDShared(&iid); michael@0: michael@0: if (iid) { michael@0: // We found a valid iid. Add it to our table. michael@0: mInterfaceTable.Put(*iid, mBinding); michael@0: michael@0: // this block adds the parent interfaces of each interface michael@0: // defined in the xbl definition (implements="nsI...") michael@0: nsCOMPtr parentInfo; michael@0: // if it has a parent, add it to the table michael@0: while (NS_SUCCEEDED(iinfo->GetParent(getter_AddRefs(parentInfo))) && parentInfo) { michael@0: // get the iid michael@0: parentInfo->GetIIDShared(&iid); michael@0: michael@0: // don't add nsISupports to the table michael@0: if (!iid || iid->Equals(NS_GET_IID(nsISupports))) michael@0: break; michael@0: michael@0: // add the iid to the table michael@0: mInterfaceTable.Put(*iid, mBinding); michael@0: michael@0: // look for the next parent michael@0: iinfo = parentInfo; michael@0: } michael@0: } michael@0: } michael@0: michael@0: token = nsCRT::strtok( newStr, ", ", &newStr ); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::AddResourceListener(nsIContent* aBoundElement) michael@0: { michael@0: if (!mResources) michael@0: return NS_ERROR_FAILURE; // Makes no sense to add a listener when the binding michael@0: // has no resources. michael@0: michael@0: mResources->AddResourceListener(aBoundElement); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXBLPrototypeBinding::CreateKeyHandlers() michael@0: { michael@0: nsXBLPrototypeHandler* curr = mPrototypeHandler; michael@0: while (curr) { michael@0: nsCOMPtr eventAtom = curr->GetEventName(); michael@0: if (eventAtom == nsGkAtoms::keyup || michael@0: eventAtom == nsGkAtoms::keydown || michael@0: eventAtom == nsGkAtoms::keypress) { michael@0: uint8_t phase = curr->GetPhase(); michael@0: uint8_t type = curr->GetType(); michael@0: michael@0: int32_t count = mKeyHandlers.Count(); michael@0: int32_t i; michael@0: nsXBLKeyEventHandler* handler = nullptr; michael@0: for (i = 0; i < count; ++i) { michael@0: handler = mKeyHandlers[i]; michael@0: if (handler->Matches(eventAtom, phase, type)) michael@0: break; michael@0: } michael@0: michael@0: if (i == count) { michael@0: nsRefPtr newHandler; michael@0: NS_NewXBLKeyEventHandler(eventAtom, phase, type, michael@0: getter_AddRefs(newHandler)); michael@0: if (newHandler) michael@0: mKeyHandlers.AppendObject(newHandler); michael@0: handler = newHandler; michael@0: } michael@0: michael@0: if (handler) michael@0: handler->AddProtoHandler(curr); michael@0: } michael@0: michael@0: curr = curr->GetNextHandler(); michael@0: } michael@0: } michael@0: michael@0: class XBLPrototypeSetupCleanup michael@0: { michael@0: public: michael@0: XBLPrototypeSetupCleanup(nsXBLDocumentInfo* aDocInfo, const nsACString& aID) michael@0: : mDocInfo(aDocInfo), mID(aID) {} michael@0: michael@0: ~XBLPrototypeSetupCleanup() michael@0: { michael@0: if (mDocInfo) { michael@0: mDocInfo->RemovePrototypeBinding(mID); michael@0: } michael@0: } michael@0: michael@0: void Disconnect() michael@0: { michael@0: mDocInfo = nullptr; michael@0: } michael@0: michael@0: nsXBLDocumentInfo* mDocInfo; michael@0: nsAutoCString mID; michael@0: }; michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::Read(nsIObjectInputStream* aStream, michael@0: nsXBLDocumentInfo* aDocInfo, michael@0: nsIDocument* aDocument, michael@0: uint8_t aFlags) michael@0: { michael@0: mInheritStyle = (aFlags & XBLBinding_Serialize_InheritStyle) ? true : false; michael@0: mChromeOnlyContent = michael@0: (aFlags & XBLBinding_Serialize_ChromeOnlyContent) ? true : false; michael@0: michael@0: // nsXBLContentSink::ConstructBinding doesn't create a binding with an empty michael@0: // id, so we don't here either. michael@0: nsAutoCString id; michael@0: nsresult rv = aStream->ReadCString(id); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(!id.IsEmpty(), NS_ERROR_FAILURE); michael@0: michael@0: nsAutoCString baseBindingURI; michael@0: rv = aStream->ReadCString(baseBindingURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mCheckedBaseProto = true; michael@0: michael@0: if (!baseBindingURI.IsEmpty()) { michael@0: rv = NS_NewURI(getter_AddRefs(mBaseBindingURI), baseBindingURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = ReadNamespace(aStream, mBaseNameSpaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString baseTag; michael@0: rv = aStream->ReadString(baseTag); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!baseTag.IsEmpty()) { michael@0: mBaseTag = do_GetAtom(baseTag); michael@0: } michael@0: michael@0: aDocument->CreateElem(NS_LITERAL_STRING("binding"), nullptr, kNameSpaceID_XBL, michael@0: getter_AddRefs(mBinding)); michael@0: michael@0: nsCOMPtr child; michael@0: rv = ReadContentNode(aStream, aDocument, aDocument->NodeInfoManager(), getter_AddRefs(child)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: Element* rootElement = aDocument->GetRootElement(); michael@0: if (rootElement) michael@0: rootElement->AppendChildTo(mBinding, false); michael@0: michael@0: if (child) { michael@0: mBinding->AppendChildTo(child, false); michael@0: } michael@0: michael@0: uint32_t interfaceCount; michael@0: rv = aStream->Read32(&interfaceCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (; interfaceCount > 0; interfaceCount--) { michael@0: nsIID iid; michael@0: aStream->ReadID(&iid); michael@0: mInterfaceTable.Put(iid, mBinding); michael@0: } michael@0: michael@0: AutoSafeJSContext cx; michael@0: JS::Rooted compilationGlobal(cx, xpc::GetCompilationScope()); michael@0: NS_ENSURE_TRUE(compilationGlobal, NS_ERROR_UNEXPECTED); michael@0: JSAutoCompartment ac(cx, compilationGlobal); michael@0: michael@0: bool isFirstBinding = aFlags & XBLBinding_Serialize_IsFirstBinding; michael@0: rv = Init(id, aDocInfo, nullptr, isFirstBinding); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We need to set the prototype binding before reading the nsXBLProtoImpl, michael@0: // as it may be retrieved within. michael@0: rv = aDocInfo->SetPrototypeBinding(id, this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: XBLPrototypeSetupCleanup cleanup(aDocInfo, id); michael@0: michael@0: nsAutoCString className; michael@0: rv = aStream->ReadCString(className); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!className.IsEmpty()) { michael@0: nsXBLProtoImpl* impl; // NS_NewXBLProtoImpl will set mImplementation for us michael@0: NS_NewXBLProtoImpl(this, NS_ConvertUTF8toUTF16(className).get(), &impl); michael@0: michael@0: // This needs to happen after SetPrototypeBinding as calls are made to michael@0: // retrieve the mapped bindings from within here. However, if an error michael@0: // occurs, the mapping should be removed again so that we don't keep an michael@0: // invalid binding around. michael@0: rv = mImplementation->Read(aStream, this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Next read in the handlers. michael@0: nsXBLPrototypeHandler* previousHandler = nullptr; michael@0: michael@0: do { michael@0: XBLBindingSerializeDetails type; michael@0: rv = aStream->Read8(&type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (type == XBLBinding_Serialize_NoMoreItems) michael@0: break; michael@0: michael@0: NS_ASSERTION((type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Handler, michael@0: "invalid handler type"); michael@0: michael@0: nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(this); michael@0: rv = handler->Read(aStream); michael@0: if (NS_FAILED(rv)) { michael@0: delete handler; michael@0: return rv; michael@0: } michael@0: michael@0: if (previousHandler) { michael@0: previousHandler->SetNextHandler(handler); michael@0: } michael@0: else { michael@0: SetPrototypeHandlers(handler); michael@0: } michael@0: previousHandler = handler; michael@0: } while (1); michael@0: michael@0: if (mBinding) { michael@0: while (true) { michael@0: XBLBindingSerializeDetails type; michael@0: rv = aStream->Read8(&type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (type != XBLBinding_Serialize_Attribute) { michael@0: break; michael@0: } michael@0: michael@0: int32_t attrNamespace; michael@0: rv = ReadNamespace(aStream, attrNamespace); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString attrPrefix, attrName, attrValue; michael@0: rv = aStream->ReadString(attrPrefix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->ReadString(attrName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->ReadString(attrValue); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr atomPrefix = do_GetAtom(attrPrefix); michael@0: nsCOMPtr atomName = do_GetAtom(attrName); michael@0: mBinding->SetAttr(attrNamespace, atomName, atomPrefix, attrValue, false); michael@0: } michael@0: } michael@0: michael@0: // Finally, read in the resources. michael@0: while (true) { michael@0: XBLBindingSerializeDetails type; michael@0: rv = aStream->Read8(&type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (type == XBLBinding_Serialize_NoMoreItems) michael@0: break; michael@0: michael@0: NS_ASSERTION((type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Stylesheet || michael@0: (type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Image, "invalid resource type"); michael@0: michael@0: nsAutoString src; michael@0: rv = aStream->ReadString(src); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: AddResource(type == XBLBinding_Serialize_Stylesheet ? nsGkAtoms::stylesheet : michael@0: nsGkAtoms::image, src); michael@0: } michael@0: michael@0: if (isFirstBinding) { michael@0: aDocInfo->SetFirstPrototypeBinding(this); michael@0: } michael@0: michael@0: cleanup.Disconnect(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: nsXBLPrototypeBinding::ReadNewBinding(nsIObjectInputStream* aStream, michael@0: nsXBLDocumentInfo* aDocInfo, michael@0: nsIDocument* aDocument, michael@0: uint8_t aFlags) michael@0: { michael@0: // If the Read() succeeds, |binding| will end up being owned by aDocInfo's michael@0: // binding table. Otherwise, we must manually delete it. michael@0: nsXBLPrototypeBinding* binding = new nsXBLPrototypeBinding(); michael@0: nsresult rv = binding->Read(aStream, aDocInfo, aDocument, aFlags); michael@0: if (NS_FAILED(rv)) { michael@0: delete binding; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: WriteInterfaceID(const nsIID& aKey, nsIContent* aData, void* aClosure) michael@0: { michael@0: // We can just write out the ids. The cache will be invalidated when a michael@0: // different build is used, so we don't need to worry about ids changing. michael@0: static_cast(aClosure)->WriteID(aKey); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::Write(nsIObjectOutputStream* aStream) michael@0: { michael@0: // This writes out the binding. Note that mCheckedBaseProto, michael@0: // mKeyHandlersRegistered and mKeyHandlers are not serialized as they are michael@0: // computed on demand. michael@0: michael@0: AutoSafeJSContext cx; michael@0: JS::Rooted compilationGlobal(cx, xpc::GetCompilationScope()); michael@0: NS_ENSURE_TRUE(compilationGlobal, NS_ERROR_UNEXPECTED); michael@0: JSAutoCompartment ac(cx, compilationGlobal); michael@0: michael@0: uint8_t flags = mInheritStyle ? XBLBinding_Serialize_InheritStyle : 0; michael@0: michael@0: // mAlternateBindingURI is only set on the first binding. michael@0: if (mAlternateBindingURI) { michael@0: flags |= XBLBinding_Serialize_IsFirstBinding; michael@0: } michael@0: michael@0: if (mChromeOnlyContent) { michael@0: flags |= XBLBinding_Serialize_ChromeOnlyContent; michael@0: } michael@0: michael@0: nsresult rv = aStream->Write8(flags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString id; michael@0: mBindingURI->GetRef(id); michael@0: rv = aStream->WriteStringZ(id.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // write out the extends and display attribute values michael@0: nsAutoCString extends; michael@0: ResolveBaseBinding(); michael@0: if (mBaseBindingURI) michael@0: mBaseBindingURI->GetSpec(extends); michael@0: michael@0: rv = aStream->WriteStringZ(extends.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = WriteNamespace(aStream, mBaseNameSpaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString baseTag; michael@0: if (mBaseTag) { michael@0: mBaseTag->ToString(baseTag); michael@0: } michael@0: rv = aStream->WriteWStringZ(baseTag.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsIContent* content = GetImmediateChild(nsGkAtoms::content); michael@0: if (content) { michael@0: rv = WriteContentNode(aStream, content); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: // Write a marker to indicate that there is no content. michael@0: rv = aStream->Write8(XBLBinding_Serialize_NoContent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Enumerate and write out the implemented interfaces. michael@0: rv = aStream->Write32(mInterfaceTable.Count()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mInterfaceTable.EnumerateRead(WriteInterfaceID, aStream); michael@0: michael@0: // Write out the implementation details. michael@0: if (mImplementation) { michael@0: rv = mImplementation->Write(aStream, this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: // Write out an empty classname. This indicates that the binding does not michael@0: // define an implementation. michael@0: rv = aStream->WriteWStringZ(EmptyString().get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Write out the handlers. michael@0: nsXBLPrototypeHandler* handler = mPrototypeHandler; michael@0: while (handler) { michael@0: rv = handler->Write(aStream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: handler = handler->GetNextHandler(); michael@0: } michael@0: michael@0: aStream->Write8(XBLBinding_Serialize_NoMoreItems); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mBinding) { michael@0: uint32_t attributes = mBinding->GetAttrCount(); michael@0: nsAutoString attrValue; michael@0: for (uint32_t i = 0; i < attributes; ++i) { michael@0: const nsAttrName* attr = mBinding->GetAttrNameAt(i); michael@0: nsDependentAtomString attrName = attr->LocalName(); michael@0: mBinding->GetAttr(attr->NamespaceID(), attr->LocalName(), attrValue); michael@0: rv = aStream->Write8(XBLBinding_Serialize_Attribute); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = WriteNamespace(aStream, attr->NamespaceID()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsIAtom* prefix = attr->GetPrefix(); michael@0: nsAutoString prefixString; michael@0: if (prefix) { michael@0: prefix->ToString(prefixString); michael@0: } michael@0: michael@0: rv = aStream->WriteWStringZ(prefixString.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(attrName.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(attrValue.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: aStream->Write8(XBLBinding_Serialize_NoMoreItems); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Write out the resources michael@0: if (mResources) { michael@0: rv = mResources->Write(aStream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Write out an end mark at the end. michael@0: return aStream->Write8(XBLBinding_Serialize_NoMoreItems); michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::ReadContentNode(nsIObjectInputStream* aStream, michael@0: nsIDocument* aDocument, michael@0: nsNodeInfoManager* aNim, michael@0: nsIContent** aContent) michael@0: { michael@0: *aContent = nullptr; michael@0: michael@0: int32_t namespaceID; michael@0: nsresult rv = ReadNamespace(aStream, namespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // There is no content to read so just return. michael@0: if (namespaceID == XBLBinding_Serialize_NoContent) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr content; michael@0: michael@0: // If this is a text type, just read the string and return. michael@0: if (namespaceID == XBLBinding_Serialize_TextNode || michael@0: namespaceID == XBLBinding_Serialize_CDATANode || michael@0: namespaceID == XBLBinding_Serialize_CommentNode) { michael@0: switch (namespaceID) { michael@0: case XBLBinding_Serialize_TextNode: michael@0: content = new nsTextNode(aNim); michael@0: break; michael@0: case XBLBinding_Serialize_CDATANode: michael@0: content = new CDATASection(aNim); michael@0: break; michael@0: case XBLBinding_Serialize_CommentNode: michael@0: content = new Comment(aNim); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: nsAutoString text; michael@0: rv = aStream->ReadString(text); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: content->SetText(text, false); michael@0: content.swap(*aContent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise, it's an element, so read its tag, attributes and children. michael@0: nsAutoString prefix, tag; michael@0: rv = aStream->ReadString(prefix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr prefixAtom; michael@0: if (!prefix.IsEmpty()) michael@0: prefixAtom = do_GetAtom(prefix); michael@0: michael@0: rv = aStream->ReadString(tag); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr tagAtom = do_GetAtom(tag); michael@0: nsCOMPtr nodeInfo = michael@0: aNim->GetNodeInfo(tagAtom, prefixAtom, namespaceID, nsIDOMNode::ELEMENT_NODE); michael@0: michael@0: uint32_t attrCount; michael@0: rv = aStream->Read32(&attrCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create XUL prototype elements, or regular elements for other namespaces. michael@0: // This needs to match the code in nsXBLContentSink::CreateElement. michael@0: #ifdef MOZ_XUL michael@0: if (namespaceID == kNameSpaceID_XUL) { michael@0: nsIURI* documentURI = aDocument->GetDocumentURI(); michael@0: michael@0: nsRefPtr prototype = new nsXULPrototypeElement(); michael@0: NS_ENSURE_TRUE(prototype, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: prototype->mNodeInfo = nodeInfo; michael@0: michael@0: nsXULPrototypeAttribute* attrs = nullptr; michael@0: if (attrCount > 0) { michael@0: attrs = new nsXULPrototypeAttribute[attrCount]; michael@0: } michael@0: michael@0: prototype->mAttributes = attrs; michael@0: prototype->mNumAttributes = attrCount; michael@0: michael@0: for (uint32_t i = 0; i < attrCount; i++) { michael@0: rv = ReadNamespace(aStream, namespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString prefix, name, val; michael@0: rv = aStream->ReadString(prefix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->ReadString(name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->ReadString(val); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr nameAtom = do_GetAtom(name); michael@0: if (namespaceID == kNameSpaceID_None) { michael@0: attrs[i].mName.SetTo(nameAtom); michael@0: } michael@0: else { michael@0: nsCOMPtr prefixAtom; michael@0: if (!prefix.IsEmpty()) michael@0: prefixAtom = do_GetAtom(prefix); michael@0: michael@0: nsCOMPtr ni = michael@0: aNim->GetNodeInfo(nameAtom, prefixAtom, michael@0: namespaceID, nsIDOMNode::ATTRIBUTE_NODE); michael@0: attrs[i].mName.SetTo(ni); michael@0: } michael@0: michael@0: rv = prototype->SetAttrAt(i, val, documentURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr result; michael@0: nsresult rv = michael@0: nsXULElement::Create(prototype, aDocument, false, false, getter_AddRefs(result)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: content = result; michael@0: } michael@0: else { michael@0: #endif michael@0: nsCOMPtr element; michael@0: NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), NOT_FROM_PARSER); michael@0: content = element; michael@0: michael@0: for (uint32_t i = 0; i < attrCount; i++) { michael@0: rv = ReadNamespace(aStream, namespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString prefix, name, val; michael@0: rv = aStream->ReadString(prefix); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->ReadString(name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->ReadString(val); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr prefixAtom; michael@0: if (!prefix.IsEmpty()) michael@0: prefixAtom = do_GetAtom(prefix); michael@0: michael@0: nsCOMPtr nameAtom = do_GetAtom(name); michael@0: content->SetAttr(namespaceID, nameAtom, prefixAtom, val, false); michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: } michael@0: #endif michael@0: michael@0: // Now read the attribute forwarding entries (xbl:inherits) michael@0: michael@0: int32_t srcNamespaceID, destNamespaceID; michael@0: rv = ReadNamespace(aStream, srcNamespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (srcNamespaceID != XBLBinding_Serialize_NoMoreAttributes) { michael@0: nsAutoString srcAttribute, destAttribute; michael@0: rv = aStream->ReadString(srcAttribute); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = ReadNamespace(aStream, destNamespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aStream->ReadString(destAttribute); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr srcAtom = do_GetAtom(srcAttribute); michael@0: nsCOMPtr destAtom = do_GetAtom(destAttribute); michael@0: michael@0: EnsureAttributeTable(); michael@0: AddToAttributeTable(srcNamespaceID, srcAtom, destNamespaceID, destAtom, content); michael@0: michael@0: rv = ReadNamespace(aStream, srcNamespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Finally, read in the child nodes. michael@0: uint32_t childCount; michael@0: rv = aStream->Read32(&childCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (uint32_t i = 0; i < childCount; i++) { michael@0: nsCOMPtr child; michael@0: ReadContentNode(aStream, aDocument, aNim, getter_AddRefs(child)); michael@0: michael@0: // Child may be null if this was a comment for example and can just be ignored. michael@0: if (child) { michael@0: content->AppendChildTo(child, false); michael@0: } michael@0: } michael@0: michael@0: content.swap(*aContent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This structure holds information about a forwarded attribute that needs to be michael@0: // written out. This is used because we need several fields passed within the michael@0: // enumeration closure. michael@0: struct WriteAttributeData michael@0: { michael@0: nsXBLPrototypeBinding* binding; michael@0: nsIObjectOutputStream* stream; michael@0: nsIContent* content; michael@0: int32_t srcNamespace; michael@0: michael@0: WriteAttributeData(nsXBLPrototypeBinding* aBinding, michael@0: nsIObjectOutputStream* aStream, michael@0: nsIContent* aContent) michael@0: : binding(aBinding), stream(aStream), content(aContent) michael@0: { } michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: WriteAttribute(nsISupports* aKey, nsXBLAttributeEntry* aEntry, void* aClosure) michael@0: { michael@0: WriteAttributeData* data = static_cast(aClosure); michael@0: nsIObjectOutputStream* stream = data->stream; michael@0: const int32_t srcNamespace = data->srcNamespace; michael@0: michael@0: do { michael@0: if (aEntry->GetElement() == data->content) { michael@0: data->binding->WriteNamespace(stream, srcNamespace); michael@0: stream->WriteWStringZ(nsDependentAtomString(aEntry->GetSrcAttribute()).get()); michael@0: data->binding->WriteNamespace(stream, aEntry->GetDstNameSpace()); michael@0: stream->WriteWStringZ(nsDependentAtomString(aEntry->GetDstAttribute()).get()); michael@0: } michael@0: michael@0: aEntry = aEntry->GetNext(); michael@0: } while (aEntry); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // WriteAttributeNS is the callback to enumerate over the attribute michael@0: // forwarding entries. Since these are stored in a hash of hashes, michael@0: // we need to iterate over the inner hashes, calling WriteAttribute michael@0: // to do the actual work. michael@0: static PLDHashOperator michael@0: WriteAttributeNS(const uint32_t &aNamespace, michael@0: nsXBLPrototypeBinding::InnerAttributeTable* aXBLAttributes, michael@0: void* aClosure) michael@0: { michael@0: WriteAttributeData* data = static_cast(aClosure); michael@0: data->srcNamespace = aNamespace; michael@0: aXBLAttributes->EnumerateRead(WriteAttribute, data); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::WriteContentNode(nsIObjectOutputStream* aStream, michael@0: nsIContent* aNode) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!aNode->IsElement()) { michael@0: // Text is writen out as a single byte for the type, followed by the text. michael@0: uint8_t type = XBLBinding_Serialize_NoContent; michael@0: switch (aNode->NodeType()) { michael@0: case nsIDOMNode::TEXT_NODE: michael@0: type = XBLBinding_Serialize_TextNode; michael@0: break; michael@0: case nsIDOMNode::CDATA_SECTION_NODE: michael@0: type = XBLBinding_Serialize_CDATANode; michael@0: break; michael@0: case nsIDOMNode::COMMENT_NODE: michael@0: type = XBLBinding_Serialize_CommentNode; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: rv = aStream->Write8(type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString content; michael@0: aNode->GetText()->AppendTo(content); michael@0: return aStream->WriteWStringZ(content.get()); michael@0: } michael@0: michael@0: // Otherwise, this is an element. michael@0: michael@0: // Write the namespace id followed by the tag name michael@0: rv = WriteNamespace(aStream, aNode->GetNameSpaceID()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString prefixStr; michael@0: aNode->NodeInfo()->GetPrefix(prefixStr); michael@0: rv = aStream->WriteWStringZ(prefixStr.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(nsDependentAtomString(aNode->Tag()).get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Write attributes michael@0: uint32_t count = aNode->GetAttrCount(); michael@0: rv = aStream->Write32(count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t i; michael@0: for (i = 0; i < count; i++) { michael@0: // Write out the namespace id, the namespace prefix, the local tag name, michael@0: // and the value, in that order. michael@0: michael@0: const nsAttrName* attr = aNode->GetAttrNameAt(i); michael@0: michael@0: // XXXndeakin don't write out xbl:inherits? michael@0: int32_t namespaceID = attr->NamespaceID(); michael@0: rv = WriteNamespace(aStream, namespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString prefixStr; michael@0: nsIAtom* prefix = attr->GetPrefix(); michael@0: if (prefix) michael@0: prefix->ToString(prefixStr); michael@0: rv = aStream->WriteWStringZ(prefixStr.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aStream->WriteWStringZ(nsDependentAtomString(attr->LocalName()).get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString val; michael@0: aNode->GetAttr(attr->NamespaceID(), attr->LocalName(), val); michael@0: rv = aStream->WriteWStringZ(val.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Write out the attribute fowarding information michael@0: if (mAttributeTable) { michael@0: WriteAttributeData data(this, aStream, aNode); michael@0: mAttributeTable->EnumerateRead(WriteAttributeNS, &data); michael@0: } michael@0: rv = aStream->Write8(XBLBinding_Serialize_NoMoreAttributes); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Finally, write out the child nodes. michael@0: count = aNode->GetChildCount(); michael@0: rv = aStream->Write32(count); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (i = 0; i < count; i++) { michael@0: rv = WriteContentNode(aStream, aNode->GetChildAt(i)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::ReadNamespace(nsIObjectInputStream* aStream, michael@0: int32_t& aNameSpaceID) michael@0: { michael@0: uint8_t namespaceID; michael@0: nsresult rv = aStream->Read8(&namespaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (namespaceID == XBLBinding_Serialize_CustomNamespace) { michael@0: nsAutoString namesp; michael@0: rv = aStream->ReadString(namesp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsContentUtils::NameSpaceManager()->RegisterNameSpace(namesp, aNameSpaceID); michael@0: } michael@0: else { michael@0: aNameSpaceID = namespaceID; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::WriteNamespace(nsIObjectOutputStream* aStream, michael@0: int32_t aNameSpaceID) michael@0: { michael@0: // Namespaces are stored as a single byte id for well-known namespaces. michael@0: // This saves time and space as other namespaces aren't very common in michael@0: // XBL. If another namespace is used however, the namespace id will be michael@0: // XBLBinding_Serialize_CustomNamespace and the string namespace written michael@0: // out directly afterwards. michael@0: nsresult rv; michael@0: michael@0: if (aNameSpaceID <= kNameSpaceID_LastBuiltin) { michael@0: rv = aStream->Write8((int8_t)aNameSpaceID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: rv = aStream->Write8(XBLBinding_Serialize_CustomNamespace); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString namesp; michael@0: nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, namesp); michael@0: aStream->WriteWStringZ(namesp.get()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: bool CheckTagNameWhiteList(int32_t aNameSpaceID, nsIAtom *aTagName) michael@0: { michael@0: static nsIContent::AttrValuesArray kValidXULTagNames[] = { michael@0: &nsGkAtoms::autorepeatbutton, &nsGkAtoms::box, &nsGkAtoms::browser, michael@0: &nsGkAtoms::button, &nsGkAtoms::hbox, &nsGkAtoms::image, &nsGkAtoms::menu, michael@0: &nsGkAtoms::menubar, &nsGkAtoms::menuitem, &nsGkAtoms::menupopup, michael@0: &nsGkAtoms::row, &nsGkAtoms::slider, &nsGkAtoms::spacer, michael@0: &nsGkAtoms::splitter, &nsGkAtoms::text, &nsGkAtoms::tree, nullptr}; michael@0: michael@0: uint32_t i; michael@0: if (aNameSpaceID == kNameSpaceID_XUL) { michael@0: for (i = 0; kValidXULTagNames[i]; ++i) { michael@0: if (aTagName == *(kValidXULTagNames[i])) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: else if (aNameSpaceID == kNameSpaceID_SVG && michael@0: aTagName == nsGkAtoms::generic_) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsXBLPrototypeBinding::ResolveBaseBinding() michael@0: { michael@0: if (mCheckedBaseProto) michael@0: return NS_OK; michael@0: mCheckedBaseProto = true; michael@0: michael@0: nsCOMPtr doc = mXBLDocInfoWeak->GetDocument(); michael@0: michael@0: // Check for the presence of 'extends' and 'display' attributes michael@0: nsAutoString display, extends; michael@0: mBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::extends, extends); michael@0: if (extends.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: mBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::display, display); michael@0: bool hasDisplay = !display.IsEmpty(); michael@0: michael@0: nsAutoString value(extends); michael@0: michael@0: // Now slice 'em up to see what we've got. michael@0: nsAutoString prefix; michael@0: int32_t offset; michael@0: if (hasDisplay) { michael@0: offset = display.FindChar(':'); michael@0: if (-1 != offset) { michael@0: display.Left(prefix, offset); michael@0: display.Cut(0, offset+1); michael@0: } michael@0: } michael@0: else { michael@0: offset = extends.FindChar(':'); michael@0: if (-1 != offset) { michael@0: extends.Left(prefix, offset); michael@0: extends.Cut(0, offset+1); michael@0: display = extends; michael@0: } michael@0: } michael@0: michael@0: nsAutoString nameSpace; michael@0: michael@0: if (!prefix.IsEmpty()) { michael@0: mBinding->LookupNamespaceURI(prefix, nameSpace); michael@0: if (!nameSpace.IsEmpty()) { michael@0: int32_t nameSpaceID = michael@0: nsContentUtils::NameSpaceManager()->GetNameSpaceID(nameSpace); michael@0: michael@0: nsCOMPtr tagName = do_GetAtom(display); michael@0: // Check the white list michael@0: if (!CheckTagNameWhiteList(nameSpaceID, tagName)) { michael@0: const char16_t* params[] = { display.get() }; michael@0: nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, michael@0: NS_LITERAL_CSTRING("XBL"), nullptr, michael@0: nsContentUtils::eXBL_PROPERTIES, michael@0: "InvalidExtendsBinding", michael@0: params, ArrayLength(params), michael@0: doc->GetDocumentURI()); michael@0: NS_ASSERTION(!nsXBLService::IsChromeOrResourceURI(doc->GetDocumentURI()), michael@0: "Invalid extends value"); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: SetBaseTag(nameSpaceID, tagName); michael@0: } michael@0: } michael@0: michael@0: if (hasDisplay || nameSpace.IsEmpty()) { michael@0: mBinding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::extends, false); michael@0: mBinding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::display, false); michael@0: michael@0: return NS_NewURI(getter_AddRefs(mBaseBindingURI), value, michael@0: doc->GetDocumentCharacterSet().get(), michael@0: doc->GetDocBaseURI()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: }