Source code

Revision control

Other Tools

/* -*- 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/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),
mGCNumber(0),
mWasL10nCached(false) {
++gRefCnt;
}
nsresult nsXULPrototypeDocument::Init() {
mNodeInfoManager = new nsNodeInfoManager();
return mNodeInfoManager->Init(nullptr);
}
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<nsXULPrototypeDocument> 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<nsISupports> 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<nsIPrincipal> 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<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
uint32_t count, i;
rv = aStream->Read32(&count);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoString namespaceURI, prefixStr, localName;
bool prefixIsNull;
RefPtr<nsAtom> 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<mozilla::dom::NodeInfo> 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<nsXULPrototypePI> 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<RefPtr<mozilla::dom::NodeInfo>>& 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<mozilla::dom::NodeInfo> 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<nsXULPrototypeElement*>(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<RefPtr<mozilla::dom::NodeInfo>> 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<RefPtr<nsXULPrototypePI>>&
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::TraceProtos(JSTracer* aTrc) {
// Only trace the protos once per GC if we are marking.
if (aTrc->isMarkingTracer()) {
uint32_t currentGCNumber = aTrc->gcNumberForMarking();
if (mGCNumber == currentGCNumber) {
return;
}
mGCNumber = currentGCNumber;
}
if (mRoot) {
mRoot->TraceAllScripts(aTrc);
}
}
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<NodeInfo> 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<NodeInfo> newNodeInfo = mNodeInfoManager->GetNodeInfo(
oldNodeInfo->NameAtom(), oldNodeInfo->GetPrefixAtom(),
oldNodeInfo->NamespaceID(), nsINode::ATTRIBUTE_NODE);
protoAttr->mName.SetTo(newNodeInfo);
}
protoAttr->mValue.SetTo(*attr.mValue);
protoAttr++;
}
if (aDeep) {
// We have to rebuild the prototype children from this element.
// First release the tree under this element.
aPrototype->ReleaseSubtree();
RefPtr<nsXULPrototypeNode>* children =
aPrototype->mChildren.AppendElements(aElement->GetChildCount());
for (nsIContent* child = aElement->GetFirstChild(); child;
child = child->GetNextSibling()) {
if (child->IsElement()) {
Element* element = child->AsElement();
RefPtr<nsXULPrototypeElement> elemProto = new nsXULPrototypeElement;
RebuildPrototypeFromElement(elemProto, element, true);
*children = elemProto;
} else if (child->IsText()) {
Text* text = child->AsText();
RefPtr<nsXULPrototypeText> 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;
}
Document* doc = aElement->OwnerDoc();
nsAutoString id;
MOZ_RELEASE_ASSERT(aElement->GetAttr(nsGkAtoms::datal10nid, id));
if (!doc) {
return;
}
RefPtr<nsXULPrototypeElement> proto = doc->mL10nProtoElements.Get(aElement);
MOZ_RELEASE_ASSERT(proto);
RebuildPrototypeFromElement(proto, aElement, aDeep);
}