/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsXULPrototypeDocument.h" #include "nsXULElement.h" #include "nsAString.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsIPrincipal.h" #include "nsJSPrincipals.h" #include "nsIScriptObjectPrincipal.h" #include "nsIURI.h" #include "jsapi.h" #include "jsfriendapi.h" #include "nsString.h" #include "nsDOMCID.h" #include "nsNodeInfoManager.h" #include "nsContentUtils.h" #include "nsCCUncollectableMarker.h" #include "xpcpublic.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/BindingUtils.h" #include "nsXULPrototypeCache.h" #include "mozilla/DeclarationBlock.h" #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Text.h" using namespace mozilla; using namespace mozilla::dom; using mozilla::dom::DestroyProtoAndIfaceCache; uint32_t nsXULPrototypeDocument::gRefCnt; //---------------------------------------------------------------------- // // ctors, dtors, n' stuff // nsXULPrototypeDocument::nsXULPrototypeDocument() : mRoot(nullptr), mLoaded(false), mCCGeneration(0), mWasL10nCached(false) { ++gRefCnt; } nsresult nsXULPrototypeDocument::Init() { mNodeInfoManager = new nsNodeInfoManager(nullptr); return NS_OK; } nsXULPrototypeDocument::~nsXULPrototypeDocument() { if (mRoot) mRoot->ReleaseSubtree(); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeWaiters) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeDocument) if (nsCCUncollectableMarker::InGeneration(cb, tmp->mCCGeneration)) { return NS_SUCCESS_INTERRUPTED_TRAVERSE; } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPrototypeDocument) NS_INTERFACE_MAP_ENTRY(nsISerializable) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPrototypeDocument) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPrototypeDocument) NS_IMETHODIMP NS_NewXULPrototypeDocument(nsXULPrototypeDocument** aResult) { *aResult = nullptr; RefPtr doc = new nsXULPrototypeDocument(); nsresult rv = doc->Init(); if (NS_FAILED(rv)) { return rv; } doc.forget(aResult); return rv; } //---------------------------------------------------------------------- // // nsISerializable methods // NS_IMETHODIMP nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream) { nsCOMPtr supports; nsresult rv = aStream->ReadObject(true, getter_AddRefs(supports)); if (NS_FAILED(rv)) { return rv; } mURI = do_QueryInterface(supports); // nsIPrincipal mNodeInfoManager->mPrincipal nsAutoCString JSON; rv = aStream->ReadCString(JSON); if (NS_FAILED(rv)) { return rv; } nsCOMPtr principal = mozilla::BasePrincipal::FromJSON(JSON); // Better safe than sorry.... mNodeInfoManager->SetDocumentPrincipal(principal); rv = aStream->ReadBoolean(&mWasL10nCached); if (NS_FAILED(rv)) { return rv; } mRoot = new nsXULPrototypeElement(); // mozilla::dom::NodeInfo table nsTArray> nodeInfos; uint32_t count, i; rv = aStream->Read32(&count); if (NS_FAILED(rv)) { return rv; } nsAutoString namespaceURI, prefixStr, localName; bool prefixIsNull; RefPtr prefix; for (i = 0; i < count; ++i) { rv = aStream->ReadString(namespaceURI); if (NS_FAILED(rv)) { return rv; } rv = aStream->ReadBoolean(&prefixIsNull); if (NS_FAILED(rv)) { return rv; } if (prefixIsNull) { prefix = nullptr; } else { rv = aStream->ReadString(prefixStr); if (NS_FAILED(rv)) { return rv; } prefix = NS_Atomize(prefixStr); } rv = aStream->ReadString(localName); if (NS_FAILED(rv)) { return rv; } RefPtr nodeInfo; // Using UINT16_MAX here as we don't know which nodeinfos will be // used for attributes and which for elements. And that doesn't really // matter. rv = mNodeInfoManager->GetNodeInfo(localName, prefix, namespaceURI, UINT16_MAX, getter_AddRefs(nodeInfo)); if (NS_FAILED(rv)) { return rv; } nodeInfos.AppendElement(nodeInfo); } // Document contents uint32_t type; while (NS_SUCCEEDED(rv)) { rv = aStream->Read32(&type); if (NS_FAILED(rv)) { return rv; break; } if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) { RefPtr pi = new nsXULPrototypePI(); rv = pi->Deserialize(aStream, this, mURI, &nodeInfos); if (NS_FAILED(rv)) { return rv; } rv = AddProcessingInstruction(pi); if (NS_FAILED(rv)) { return rv; } } else if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_Element) { rv = mRoot->Deserialize(aStream, this, mURI, &nodeInfos); if (NS_FAILED(rv)) { return rv; } break; } else { MOZ_ASSERT_UNREACHABLE("Unexpected prototype node type"); return NS_ERROR_FAILURE; } } return NotifyLoadDone(); } static nsresult GetNodeInfos(nsXULPrototypeElement* aPrototype, nsTArray>& aArray) { if (aArray.IndexOf(aPrototype->mNodeInfo) == aArray.NoIndex) { aArray.AppendElement(aPrototype->mNodeInfo); } // Search attributes size_t i; for (i = 0; i < aPrototype->mAttributes.Length(); ++i) { RefPtr ni; nsAttrName* name = &aPrototype->mAttributes[i].mName; if (name->IsAtom()) { ni = aPrototype->mNodeInfo->NodeInfoManager()->GetNodeInfo( name->Atom(), nullptr, kNameSpaceID_None, nsINode::ATTRIBUTE_NODE); } else { ni = name->NodeInfo(); } if (aArray.IndexOf(ni) == aArray.NoIndex) { aArray.AppendElement(ni); } } // Search children for (i = 0; i < aPrototype->mChildren.Length(); ++i) { nsXULPrototypeNode* child = aPrototype->mChildren[i]; if (child->mType == nsXULPrototypeNode::eType_Element) { nsresult rv = GetNodeInfos(static_cast(child), aArray); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } NS_IMETHODIMP nsXULPrototypeDocument::Write(nsIObjectOutputStream* aStream) { nsresult rv; rv = aStream->WriteCompoundObject(mURI, NS_GET_IID(nsIURI), true); // nsIPrincipal mNodeInfoManager->mPrincipal nsAutoCString JSON; mozilla::BasePrincipal::Cast(mNodeInfoManager->DocumentPrincipal()) ->ToJSON(JSON); nsresult tmp = aStream->WriteStringZ(JSON.get()); if (NS_FAILED(tmp)) { rv = tmp; } #ifdef DEBUG // XXX Worrisome if we're caching things without system principal. if (!mNodeInfoManager->DocumentPrincipal()->IsSystemPrincipal()) { NS_WARNING("Serializing document without system principal"); } #endif tmp = aStream->WriteBoolean(mWasL10nCached); if (NS_FAILED(tmp)) { rv = tmp; } // mozilla::dom::NodeInfo table nsTArray> nodeInfos; if (mRoot) { tmp = GetNodeInfos(mRoot, nodeInfos); if (NS_FAILED(tmp)) { rv = tmp; } } uint32_t nodeInfoCount = nodeInfos.Length(); tmp = aStream->Write32(nodeInfoCount); if (NS_FAILED(tmp)) { rv = tmp; } uint32_t i; for (i = 0; i < nodeInfoCount; ++i) { mozilla::dom::NodeInfo* nodeInfo = nodeInfos[i]; NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE); nsAutoString namespaceURI; nodeInfo->GetNamespaceURI(namespaceURI); tmp = aStream->WriteWStringZ(namespaceURI.get()); if (NS_FAILED(tmp)) { rv = tmp; } nsAutoString prefix; nodeInfo->GetPrefix(prefix); bool nullPrefix = DOMStringIsNull(prefix); tmp = aStream->WriteBoolean(nullPrefix); if (NS_FAILED(tmp)) { rv = tmp; } if (!nullPrefix) { tmp = aStream->WriteWStringZ(prefix.get()); if (NS_FAILED(tmp)) { rv = tmp; } } nsAutoString localName; nodeInfo->GetName(localName); tmp = aStream->WriteWStringZ(localName.get()); if (NS_FAILED(tmp)) { rv = tmp; } } // Now serialize the document contents uint32_t count = mProcessingInstructions.Length(); for (i = 0; i < count; ++i) { nsXULPrototypePI* pi = mProcessingInstructions[i]; tmp = pi->Serialize(aStream, this, &nodeInfos); if (NS_FAILED(tmp)) { rv = tmp; } } if (mRoot) { tmp = mRoot->Serialize(aStream, this, &nodeInfos); if (NS_FAILED(tmp)) { rv = tmp; } } return rv; } //---------------------------------------------------------------------- // nsresult nsXULPrototypeDocument::InitPrincipal(nsIURI* aURI, nsIPrincipal* aPrincipal) { NS_ENSURE_ARG_POINTER(aURI); mURI = aURI; mNodeInfoManager->SetDocumentPrincipal(aPrincipal); return NS_OK; } nsIURI* nsXULPrototypeDocument::GetURI() { NS_ASSERTION(mURI, "null URI"); return mURI; } nsXULPrototypeElement* nsXULPrototypeDocument::GetRootElement() { return mRoot; } void nsXULPrototypeDocument::SetRootElement(nsXULPrototypeElement* aElement) { mRoot = aElement; } nsresult nsXULPrototypeDocument::AddProcessingInstruction( nsXULPrototypePI* aPI) { MOZ_ASSERT(aPI, "null ptr"); // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier, or change the return type to void. mProcessingInstructions.AppendElement(aPI); return NS_OK; } const nsTArray>& nsXULPrototypeDocument::GetProcessingInstructions() const { return mProcessingInstructions; } nsIPrincipal* nsXULPrototypeDocument::DocumentPrincipal() { MOZ_ASSERT(mNodeInfoManager, "missing nodeInfoManager"); return mNodeInfoManager->DocumentPrincipal(); } void nsXULPrototypeDocument::SetDocumentPrincipal(nsIPrincipal* aPrincipal) { mNodeInfoManager->SetDocumentPrincipal(aPrincipal); } void nsXULPrototypeDocument::MarkInCCGeneration(uint32_t aCCGeneration) { mCCGeneration = aCCGeneration; } nsNodeInfoManager* nsXULPrototypeDocument::GetNodeInfoManager() { return mNodeInfoManager; } nsresult nsXULPrototypeDocument::AwaitLoadDone(Callback&& aCallback, bool* aResult) { nsresult rv = NS_OK; *aResult = mLoaded; if (!mLoaded) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier, or change the return type to void. mPrototypeWaiters.AppendElement(std::move(aCallback)); } return rv; } nsresult nsXULPrototypeDocument::NotifyLoadDone() { // Call back to each XUL document that raced to start the same // prototype document load, lost the race, but hit the XUL // prototype cache because the winner filled the cache with // the not-yet-loaded prototype object. mLoaded = true; for (uint32_t i = mPrototypeWaiters.Length(); i > 0;) { --i; mPrototypeWaiters[i](); } mPrototypeWaiters.Clear(); return NS_OK; } void nsXULPrototypeDocument::SetIsL10nCached(bool aIsCached) { mWasL10nCached = aIsCached; } void nsXULPrototypeDocument::RebuildPrototypeFromElement( nsXULPrototypeElement* aPrototype, Element* aElement, bool aDeep) { aPrototype->mHasIdAttribute = aElement->HasID(); aPrototype->mHasClassAttribute = aElement->MayHaveClass(); aPrototype->mHasStyleAttribute = aElement->MayHaveStyle(); NodeInfo* oldNodeInfo = aElement->NodeInfo(); RefPtr newNodeInfo = mNodeInfoManager->GetNodeInfo( oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(), oldNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE); aPrototype->mNodeInfo = newNodeInfo; // First replace the prototype attributes with the new ones from this element. aPrototype->mAttributes.Clear(); uint32_t count = aElement->GetAttrCount(); nsXULPrototypeAttribute* protoAttr = aPrototype->mAttributes.AppendElements(count); for (uint32_t index = 0; index < count; index++) { BorrowedAttrInfo attr = aElement->GetAttrInfoAt(index); if (attr.mName->IsAtom()) { protoAttr->mName.SetTo(attr.mName->Atom()); } else { NodeInfo* oldNodeInfo = attr.mName->NodeInfo(); RefPtr newNodeInfo = mNodeInfoManager->GetNodeInfo( oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(), oldNodeInfo->NamespaceID(), nsINode::ATTRIBUTE_NODE); protoAttr->mName.SetTo(newNodeInfo); } protoAttr->mValue.SetTo(*attr.mValue); protoAttr++; } // Make sure the mIsAtom is correct in case this prototype element has been // completely rebuilt. CustomElementData* ceData = aElement->GetCustomElementData(); nsAtom* isAtom = ceData ? ceData->GetIs(aElement) : nullptr; aPrototype->mIsAtom = isAtom; if (aDeep) { // We have to rebuild the prototype children from this element. // First release the tree under this element. aPrototype->ReleaseSubtree(); RefPtr* children = aPrototype->mChildren.AppendElements(aElement->GetChildCount()); for (nsIContent* child = aElement->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->IsElement()) { Element* element = child->AsElement(); RefPtr elemProto = new nsXULPrototypeElement; RebuildPrototypeFromElement(elemProto, element, true); *children = elemProto; } else if (child->IsText()) { Text* text = child->AsText(); RefPtr textProto = new nsXULPrototypeText(); text->AppendTextTo(textProto->mValue); *children = textProto; } else { MOZ_ASSERT(false, "We handle only elements and text nodes here."); } children++; } } } void nsXULPrototypeDocument::RebuildL10nPrototype(Element* aElement, bool aDeep) { if (mWasL10nCached) { return; } MOZ_ASSERT(aElement->HasAttr(nsGkAtoms::datal10nid)); Document* doc = aElement->OwnerDoc(); if (RefPtr proto = doc->mL10nProtoElements.Get(aElement)) { RebuildPrototypeFromElement(proto, aElement, aDeep); } }