/* -*- 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/. */ /* * A class for handing out nodeinfos and ensuring sharing of them as needed. */ #include "nsNodeInfoManager.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/NodeInfo.h" #include "mozilla/dom/NodeInfoInlines.h" #include "nsCOMPtr.h" #include "nsString.h" #include "nsAtom.h" #include "nsIDocument.h" #include "nsIPrincipal.h" #include "nsIURI.h" #include "nsContentUtils.h" #include "nsReadableUtils.h" #include "nsGkAtoms.h" #include "nsComponentManagerUtils.h" #include "nsLayoutStatics.h" #include "nsBindingManager.h" #include "nsHashKeys.h" #include "nsCCUncollectableMarker.h" #include "nsNameSpaceManager.h" #include "nsDocument.h" #include "nsWindowSizes.h" #include "NullPrincipal.h" using namespace mozilla; using mozilla::dom::NodeInfo; #include "mozilla/Logging.h" static LazyLogModule gNodeInfoManagerLeakPRLog("NodeInfoManagerLeak"); PLHashNumber nsNodeInfoManager::GetNodeInfoInnerHashValue(const void *key) { MOZ_ASSERT(key, "Null key passed to NodeInfo::GetHashValue!"); auto *node = const_cast (reinterpret_cast(key)); if (!node->mHashInitialized) { node->mHash = node->mName ? node->mName->hash() : HashString(*(node->mNameString)); node->mHashInitialized = true; } return node->mHash; } int nsNodeInfoManager::NodeInfoInnerKeyCompare(const void *key1, const void *key2) { MOZ_ASSERT(key1 && key2, "Null key passed to NodeInfoInnerKeyCompare!"); auto *node1 = reinterpret_cast(key1); auto *node2 = reinterpret_cast(key2); if (node1->mPrefix != node2->mPrefix || node1->mNamespaceID != node2->mNamespaceID || node1->mNodeType != node2->mNodeType || node1->mExtraName != node2->mExtraName) { return 0; } if (node1->mName) { if (node2->mName) { return (node1->mName == node2->mName); } return (node1->mName->Equals(*(node2->mNameString))); } if (node2->mName) { return (node2->mName->Equals(*(node1->mNameString))); } return (node1->mNameString->Equals(*(node2->mNameString))); } static void* PR_CALLBACK AllocTable(void* pool, size_t size) { return malloc(size); } static void PR_CALLBACK FreeTable(void* pool, void* item) { free(item); } static PLHashEntry* PR_CALLBACK AllocEntry(void* pool, const void* key) { return (PLHashEntry*)malloc(sizeof(PLHashEntry)); } static void PR_CALLBACK FreeEntry(void* pool, PLHashEntry* he, unsigned flag) { if (flag == HT_FREE_ENTRY) { free(he); } } static PLHashAllocOps allocOps = { AllocTable, FreeTable, AllocEntry, FreeEntry }; nsNodeInfoManager::nsNodeInfoManager() : mDocument(nullptr), mNonDocumentNodeInfos(0), mTextNodeInfo(nullptr), mCommentNodeInfo(nullptr), mDocumentNodeInfo(nullptr), mRecentlyUsedNodeInfos{}, mSVGEnabled(eTriUnset), mMathMLEnabled(eTriUnset) { nsLayoutStatics::AddRef(); if (gNodeInfoManagerLeakPRLog) MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug, ("NODEINFOMANAGER %p created", this)); mNodeInfoHash = PL_NewHashTable(32, GetNodeInfoInnerHashValue, NodeInfoInnerKeyCompare, PL_CompareValues, &allocOps, nullptr); } nsNodeInfoManager::~nsNodeInfoManager() { if (mNodeInfoHash) PL_HashTableDestroy(mNodeInfoHash); // Note: mPrincipal may be null here if we never got inited correctly mPrincipal = nullptr; mBindingManager = nullptr; if (gNodeInfoManagerLeakPRLog) MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug, ("NODEINFOMANAGER %p destroyed", this)); nsLayoutStatics::Release(); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager) if (tmp->mNonDocumentNodeInfos) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument) } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBindingManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release) NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsNodeInfoManager) if (tmp->mDocument) { return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkip(tmp->mDocument, aRemovingAllowed); } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsNodeInfoManager) if (tmp->mDocument) { return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkipInCC(tmp->mDocument); } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsNodeInfoManager) if (tmp->mDocument) { return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkipThis(tmp->mDocument); } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END nsresult nsNodeInfoManager::Init(nsIDocument *aDocument) { NS_ENSURE_TRUE(mNodeInfoHash, NS_ERROR_OUT_OF_MEMORY); NS_PRECONDITION(!mPrincipal, "Being inited when we already have a principal?"); mPrincipal = NullPrincipal::Create(); if (aDocument) { mBindingManager = new nsBindingManager(aDocument); } mDefaultPrincipal = mPrincipal; mDocument = aDocument; if (gNodeInfoManagerLeakPRLog) MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug, ("NODEINFOMANAGER %p Init document=%p", this, aDocument)); return NS_OK; } // static int nsNodeInfoManager::DropNodeInfoDocument(PLHashEntry *he, int hashIndex, void *arg) { static_cast(he->value)->mDocument = nullptr; return HT_ENUMERATE_NEXT; } void nsNodeInfoManager::DropDocumentReference() { if (mBindingManager) { mBindingManager->DropDocumentReference(); } // This is probably not needed anymore. PL_HashTableEnumerateEntries(mNodeInfoHash, DropNodeInfoDocument, nullptr); NS_ASSERTION(!mNonDocumentNodeInfos, "Shouldn't have non-document nodeinfos!"); mDocument = nullptr; } already_AddRefed nsNodeInfoManager::GetNodeInfo(nsAtom *aName, nsAtom *aPrefix, int32_t aNamespaceID, uint16_t aNodeType, nsAtom* aExtraName /* = nullptr */) { CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName); NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType, aExtraName); uint32_t index = GetNodeInfoInnerHashValue(&tmpKey) % RECENTLY_USED_NODEINFOS_SIZE; NodeInfo* ni = mRecentlyUsedNodeInfos[index]; if (ni && NodeInfoInnerKeyCompare(&(ni->mInner), &tmpKey)) { RefPtr nodeInfo = ni; return nodeInfo.forget(); } void *node = PL_HashTableLookup(mNodeInfoHash, &tmpKey); if (node) { RefPtr nodeInfo = static_cast(node); mRecentlyUsedNodeInfos[index] = nodeInfo; return nodeInfo.forget(); } RefPtr newNodeInfo = new NodeInfo(aName, aPrefix, aNamespaceID, aNodeType, aExtraName, this); DebugOnly he = PL_HashTableAdd(mNodeInfoHash, &newNodeInfo->mInner, newNodeInfo); MOZ_ASSERT(he, "PL_HashTableAdd() failed"); // Have to do the swap thing, because already_AddRefed // doesn't cast to already_AddRefed ++mNonDocumentNodeInfos; if (mNonDocumentNodeInfos == 1) { NS_IF_ADDREF(mDocument); } mRecentlyUsedNodeInfos[index] = newNodeInfo; return newNodeInfo.forget(); } nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom *aPrefix, int32_t aNamespaceID, uint16_t aNodeType, NodeInfo** aNodeInfo) { #ifdef DEBUG { RefPtr nameAtom = NS_Atomize(aName); CheckValidNodeInfo(aNodeType, nameAtom, aNamespaceID, nullptr); } #endif NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType); uint32_t index = GetNodeInfoInnerHashValue(&tmpKey) % RECENTLY_USED_NODEINFOS_SIZE; NodeInfo* ni = mRecentlyUsedNodeInfos[index]; if (ni && NodeInfoInnerKeyCompare(&(ni->mInner), &tmpKey)) { RefPtr nodeInfo = ni; nodeInfo.forget(aNodeInfo); return NS_OK; } void *node = PL_HashTableLookup(mNodeInfoHash, &tmpKey); if (node) { RefPtr nodeInfo = static_cast(node); mRecentlyUsedNodeInfos[index] = nodeInfo; nodeInfo.forget(aNodeInfo); return NS_OK; } RefPtr nameAtom = NS_Atomize(aName); NS_ENSURE_TRUE(nameAtom, NS_ERROR_OUT_OF_MEMORY); RefPtr newNodeInfo = new NodeInfo(nameAtom, aPrefix, aNamespaceID, aNodeType, nullptr, this); NS_ENSURE_TRUE(newNodeInfo, NS_ERROR_OUT_OF_MEMORY); PLHashEntry *he; he = PL_HashTableAdd(mNodeInfoHash, &newNodeInfo->mInner, newNodeInfo); NS_ENSURE_TRUE(he, NS_ERROR_FAILURE); ++mNonDocumentNodeInfos; if (mNonDocumentNodeInfos == 1) { NS_IF_ADDREF(mDocument); } mRecentlyUsedNodeInfos[index] = newNodeInfo; newNodeInfo.forget(aNodeInfo); return NS_OK; } nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom *aPrefix, const nsAString& aNamespaceURI, uint16_t aNodeType, NodeInfo** aNodeInfo) { int32_t nsid = kNameSpaceID_None; if (!aNamespaceURI.IsEmpty()) { nsresult rv = nsContentUtils::NameSpaceManager()-> RegisterNameSpace(aNamespaceURI, nsid); NS_ENSURE_SUCCESS(rv, rv); } return GetNodeInfo(aName, aPrefix, nsid, aNodeType, aNodeInfo); } already_AddRefed nsNodeInfoManager::GetTextNodeInfo() { RefPtr nodeInfo; if (!mTextNodeInfo) { nodeInfo = GetNodeInfo(nsGkAtoms::textTagName, nullptr, kNameSpaceID_None, nsINode::TEXT_NODE, nullptr); // Hold a weak ref; the nodeinfo will let us know when it goes away mTextNodeInfo = nodeInfo; } else { nodeInfo = mTextNodeInfo; } return nodeInfo.forget(); } already_AddRefed nsNodeInfoManager::GetCommentNodeInfo() { RefPtr nodeInfo; if (!mCommentNodeInfo) { nodeInfo = GetNodeInfo(nsGkAtoms::commentTagName, nullptr, kNameSpaceID_None, nsINode::COMMENT_NODE, nullptr); // Hold a weak ref; the nodeinfo will let us know when it goes away mCommentNodeInfo = nodeInfo; } else { nodeInfo = mCommentNodeInfo; } return nodeInfo.forget(); } already_AddRefed nsNodeInfoManager::GetDocumentNodeInfo() { RefPtr nodeInfo; if (!mDocumentNodeInfo) { NS_ASSERTION(mDocument, "Should have mDocument!"); nodeInfo = GetNodeInfo(nsGkAtoms::documentNodeName, nullptr, kNameSpaceID_None, nsINode::DOCUMENT_NODE, nullptr); // Hold a weak ref; the nodeinfo will let us know when it goes away mDocumentNodeInfo = nodeInfo; --mNonDocumentNodeInfos; if (!mNonDocumentNodeInfos) { mDocument->Release(); // Don't set mDocument to null! } } else { nodeInfo = mDocumentNodeInfo; } return nodeInfo.forget(); } void nsNodeInfoManager::SetDocumentPrincipal(nsIPrincipal *aPrincipal) { mPrincipal = nullptr; if (!aPrincipal) { aPrincipal = mDefaultPrincipal; } NS_ASSERTION(aPrincipal, "Must have principal by this point!"); MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsExpandedPrincipal(aPrincipal), "Documents shouldn't have an expanded principal"); mPrincipal = aPrincipal; } void nsNodeInfoManager::RemoveNodeInfo(NodeInfo *aNodeInfo) { NS_PRECONDITION(aNodeInfo, "Trying to remove null nodeinfo from manager!"); if (aNodeInfo == mDocumentNodeInfo) { mDocumentNodeInfo = nullptr; mDocument = nullptr; } else { if (--mNonDocumentNodeInfos == 0) { if (mDocument) { // Note, whoever calls this method should keep NodeInfoManager alive, // even if mDocument gets deleted. mDocument->Release(); } } // Drop weak reference if needed if (aNodeInfo == mTextNodeInfo) { mTextNodeInfo = nullptr; } else if (aNodeInfo == mCommentNodeInfo) { mCommentNodeInfo = nullptr; } } uint32_t index = GetNodeInfoInnerHashValue(&aNodeInfo->mInner) % RECENTLY_USED_NODEINFOS_SIZE; if (mRecentlyUsedNodeInfos[index] == aNodeInfo) { mRecentlyUsedNodeInfos[index] = nullptr; } #ifdef DEBUG bool ret = #endif PL_HashTableRemove(mNodeInfoHash, &aNodeInfo->mInner); MOZ_ASSERT(ret, "Can't find mozilla::dom::NodeInfo to remove!!!"); } bool nsNodeInfoManager::InternalSVGEnabled() { // If the svg.disabled pref. is true, convert all SVG nodes into // disabled SVG nodes by swapping the namespace. nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance(); nsCOMPtr loadInfo; bool SVGEnabled = false; if (nsmgr && !nsmgr->mSVGDisabled) { SVGEnabled = true; } else { nsCOMPtr channel = mDocument->GetChannel(); // We don't have a channel for SVGs constructed inside a SVG script if (channel) { loadInfo = channel->GetLoadInfo(); } } bool conclusion = (SVGEnabled || nsContentUtils::IsSystemPrincipal(mPrincipal) || (loadInfo && (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_IMAGE || loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_OTHER) && (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) || nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal())))); mSVGEnabled = conclusion ? eTriTrue : eTriFalse; return conclusion; } bool nsNodeInfoManager::InternalMathMLEnabled() { // If the mathml.disabled pref. is true, convert all MathML nodes into // disabled MathML nodes by swapping the namespace. nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance(); bool conclusion = ((nsmgr && !nsmgr->mMathMLDisabled) || nsContentUtils::IsSystemPrincipal(mPrincipal)); mMathMLEnabled = conclusion ? eTriTrue : eTriFalse; return conclusion; } void nsNodeInfoManager::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const { aSizes.mDOMOtherSize += aSizes.mState.mMallocSizeOf(this); if (mBindingManager) { aSizes.mBindingsSize += mBindingManager->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); } // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - mNodeInfoHash }