/* -*- 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/BasePrincipal.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/NodeInfo.h" #include "mozilla/dom/NodeInfoInlines.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/NullPrincipal.h" #include "mozilla/StaticPrefs_dom.h" #include "nsCOMPtr.h" #include "nsString.h" #include "nsAtom.h" #include "nsIPrincipal.h" #include "nsContentUtils.h" #include "nsReadableUtils.h" #include "nsGkAtoms.h" #include "nsComponentManagerUtils.h" #include "nsLayoutStatics.h" #include "nsHashKeys.h" #include "nsCCUncollectableMarker.h" #include "nsNameSpaceManager.h" #include "nsWindowSizes.h" using namespace mozilla; using mozilla::dom::NodeInfo; #include "mozilla/Logging.h" static LazyLogModule gNodeInfoManagerLeakPRLog("NodeInfoManagerLeak"); static const uint32_t kInitialNodeInfoHashSize = 32; nsNodeInfoManager::nsNodeInfoManager() : mNodeInfoHash(kInitialNodeInfoHashSize), mDocument(nullptr), mNonDocumentNodeInfos(0), mTextNodeInfo(nullptr), mCommentNodeInfo(nullptr), mDocumentNodeInfo(nullptr), mRecentlyUsedNodeInfos(), mArena(nullptr) { nsLayoutStatics::AddRef(); if (gNodeInfoManagerLeakPRLog) MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug, ("NODEINFOMANAGER %p created", this)); } nsNodeInfoManager::~nsNodeInfoManager() { // Note: mPrincipal may be null here if we never got inited correctly mPrincipal = nullptr; mArena = 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_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(mozilla::dom::Document) ->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(mozilla::dom::Document) ->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(mozilla::dom::Document) ->CanSkipThis(tmp->mDocument); } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END nsresult nsNodeInfoManager::Init(mozilla::dom::Document* aDocument) { MOZ_ASSERT(!mPrincipal, "Being inited when we already have a principal?"); mPrincipal = NullPrincipal::CreateWithoutOriginAttributes(); mDefaultPrincipal = mPrincipal; mDocument = aDocument; if (gNodeInfoManagerLeakPRLog) MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug, ("NODEINFOMANAGER %p Init document=%p", this, aDocument)); return NS_OK; } void nsNodeInfoManager::DropDocumentReference() { // This is probably not needed anymore. for (auto iter = mNodeInfoHash.Iter(); !iter.Done(); iter.Next()) { iter.Data()->mDocument = 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); auto p = mRecentlyUsedNodeInfos.Lookup(tmpKey); if (p) { RefPtr nodeInfo = p.Data(); return nodeInfo.forget(); } // We don't use LookupForAdd here as that would end up storing the temporary // key instead of using `mInner`. RefPtr nodeInfo = mNodeInfoHash.Get(&tmpKey); if (!nodeInfo) { ++mNonDocumentNodeInfos; if (mNonDocumentNodeInfos == 1) { NS_IF_ADDREF(mDocument); } nodeInfo = new NodeInfo(aName, aPrefix, aNamespaceID, aNodeType, aExtraName, this); mNodeInfoHash.Put(&nodeInfo->mInner, nodeInfo); } // Have to do the swap thing, because already_AddRefed // doesn't cast to already_AddRefed p.Set(nodeInfo); return nodeInfo.forget(); } nsresult nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsAtom* aPrefix, int32_t aNamespaceID, uint16_t aNodeType, NodeInfo** aNodeInfo) { // TODO(erahm): Combine this with the atom version. #ifdef DEBUG { RefPtr nameAtom = NS_Atomize(aName); CheckValidNodeInfo(aNodeType, nameAtom, aNamespaceID, nullptr); } #endif NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType); auto p = mRecentlyUsedNodeInfos.Lookup(tmpKey); if (p) { RefPtr nodeInfo = p.Data(); nodeInfo.forget(aNodeInfo); return NS_OK; } RefPtr nodeInfo = mNodeInfoHash.Get(&tmpKey); if (!nodeInfo) { ++mNonDocumentNodeInfos; if (mNonDocumentNodeInfos == 1) { NS_IF_ADDREF(mDocument); } RefPtr nameAtom = NS_Atomize(aName); nodeInfo = new NodeInfo(nameAtom, aPrefix, aNamespaceID, aNodeType, nullptr, this); mNodeInfoHash.Put(&nodeInfo->mInner, nodeInfo); } p.Set(nodeInfo); nodeInfo.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::Allocate(size_t aSize) { if (!mHasAllocated) { if (mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) { if (!mArena) { mozilla::dom::DocGroup* docGroup = GetDocument()->GetDocGroupOrCreate(); if (docGroup) { MOZ_ASSERT(!GetDocument()->HasChildren()); mArena = docGroup->ArenaAllocator(); } } #ifdef DEBUG else { mozilla::dom::DocGroup* docGroup = GetDocument()->GetDocGroup(); MOZ_ASSERT(docGroup); MOZ_ASSERT(mArena == docGroup->ArenaAllocator()); } #endif } mHasAllocated = true; } #ifdef DEBUG if (!mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()) { MOZ_ASSERT(!mArena, "mArena should not set if the pref is not on"); }; #endif if (mArena) { return mArena->Allocate(aSize); } return malloc(aSize); } void nsNodeInfoManager::SetArenaAllocator(mozilla::dom::DOMArena* aArena) { MOZ_DIAGNOSTIC_ASSERT_IF(mArena, mArena == aArena); MOZ_DIAGNOSTIC_ASSERT(!mHasAllocated); MOZ_DIAGNOSTIC_ASSERT( mozilla::StaticPrefs::dom_arena_allocator_enabled_AtStartup()); if (!mArena) { mArena = aArena; } } 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) { MOZ_ASSERT(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; } } mRecentlyUsedNodeInfos.Remove(aNodeInfo->mInner); DebugOnly ret = mNodeInfoHash.Remove(&aNodeInfo->mInner); MOZ_ASSERT(ret, "Can't find mozilla::dom::NodeInfo to remove!!!"); } static bool IsSystemOrAddonOrAboutPrincipal(nsIPrincipal* aPrincipal) { return aPrincipal->IsSystemPrincipal() || BasePrincipal::Cast(aPrincipal)->AddonPolicy() || // NOTE: about:blank and about:srcdoc inherit the principal of their // parent, so aPrincipal->SchemeIs("about") returns false for them. aPrincipal->SchemeIs("about"); } bool nsNodeInfoManager::InternalSVGEnabled() { MOZ_ASSERT(!mSVGEnabled, "Caller should use the cached mSVGEnabled!"); // 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->LoadInfo(); } } // We allow SVG (regardless of the pref) if this is a system or add-on // principal or about: page, or if this load was requested for a system or // add-on principal or about: page (e.g. a remote image being served as part // of system or add-on UI or about: page) bool conclusion = (SVGEnabled || IsSystemOrAddonOrAboutPrincipal(mPrincipal) || (loadInfo && (loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_IMAGE || loadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_OTHER) && (IsSystemOrAddonOrAboutPrincipal(loadInfo->GetLoadingPrincipal()) || IsSystemOrAddonOrAboutPrincipal(loadInfo->TriggeringPrincipal())))); mSVGEnabled = Some(conclusion); return conclusion; } bool nsNodeInfoManager::InternalMathMLEnabled() { MOZ_ASSERT(!mMathMLEnabled, "Caller should use the cached mMathMLEnabled!"); // 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) || mPrincipal->IsSystemPrincipal()); mMathMLEnabled = Some(conclusion); return conclusion; } void nsNodeInfoManager::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const { aSizes.mDOMOtherSize += aSizes.mState.mMallocSizeOf(this); // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - mNodeInfoHash }