michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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: /* michael@0: * A class for handing out nodeinfos and ensuring sharing of them as needed. michael@0: */ michael@0: michael@0: #include "nsNodeInfoManager.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "nsNodeInfo.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIURI.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsLayoutStatics.h" michael@0: #include "nsBindingManager.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: // so we can get logging even in release builds michael@0: #define FORCE_PR_LOG 1 michael@0: #endif michael@0: #include "prlog.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* gNodeInfoManagerLeakPRLog; michael@0: #endif michael@0: michael@0: PLHashNumber michael@0: nsNodeInfoManager::GetNodeInfoInnerHashValue(const void *key) michael@0: { michael@0: NS_ASSERTION(key, "Null key passed to nsNodeInfo::GetHashValue!"); michael@0: michael@0: const nsINodeInfo::nsNodeInfoInner *node = michael@0: reinterpret_cast(key); michael@0: michael@0: return node->mName ? node->mName->hash() : HashString(*(node->mNameString)); michael@0: } michael@0: michael@0: michael@0: int michael@0: nsNodeInfoManager::NodeInfoInnerKeyCompare(const void *key1, const void *key2) michael@0: { michael@0: NS_ASSERTION(key1 && key2, "Null key passed to NodeInfoInnerKeyCompare!"); michael@0: michael@0: const nsINodeInfo::nsNodeInfoInner *node1 = michael@0: reinterpret_cast(key1); michael@0: const nsINodeInfo::nsNodeInfoInner *node2 = michael@0: reinterpret_cast(key2); michael@0: michael@0: if (node1->mPrefix != node2->mPrefix || michael@0: node1->mNamespaceID != node2->mNamespaceID || michael@0: node1->mNodeType != node2->mNodeType || michael@0: node1->mExtraName != node2->mExtraName) { michael@0: return 0; michael@0: } michael@0: michael@0: if (node1->mName) { michael@0: if (node2->mName) { michael@0: return (node1->mName == node2->mName); michael@0: } michael@0: return (node1->mName->Equals(*(node2->mNameString))); michael@0: } michael@0: if (node2->mName) { michael@0: return (node2->mName->Equals(*(node1->mNameString))); michael@0: } michael@0: return (node1->mNameString->Equals(*(node2->mNameString))); michael@0: } michael@0: michael@0: michael@0: static void* PR_CALLBACK michael@0: AllocTable(void* pool, size_t size) michael@0: { michael@0: return malloc(size); michael@0: } michael@0: michael@0: static void PR_CALLBACK michael@0: FreeTable(void* pool, void* item) michael@0: { michael@0: free(item); michael@0: } michael@0: michael@0: static PLHashEntry* PR_CALLBACK michael@0: AllocEntry(void* pool, const void* key) michael@0: { michael@0: return (PLHashEntry*)malloc(sizeof(PLHashEntry)); michael@0: } michael@0: michael@0: static void PR_CALLBACK michael@0: FreeEntry(void* pool, PLHashEntry* he, unsigned flag) michael@0: { michael@0: if (flag == HT_FREE_ENTRY) { michael@0: free(he); michael@0: } michael@0: } michael@0: michael@0: static PLHashAllocOps allocOps = michael@0: { AllocTable, FreeTable, AllocEntry, FreeEntry }; michael@0: michael@0: nsNodeInfoManager::nsNodeInfoManager() michael@0: : mDocument(nullptr), michael@0: mNonDocumentNodeInfos(0), michael@0: mTextNodeInfo(nullptr), michael@0: mCommentNodeInfo(nullptr), michael@0: mDocumentNodeInfo(nullptr) michael@0: { michael@0: nsLayoutStatics::AddRef(); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (!gNodeInfoManagerLeakPRLog) michael@0: gNodeInfoManagerLeakPRLog = PR_NewLogModule("NodeInfoManagerLeak"); michael@0: michael@0: if (gNodeInfoManagerLeakPRLog) michael@0: PR_LOG(gNodeInfoManagerLeakPRLog, PR_LOG_DEBUG, michael@0: ("NODEINFOMANAGER %p created", this)); michael@0: #endif michael@0: michael@0: mNodeInfoHash = PL_NewHashTable(32, GetNodeInfoInnerHashValue, michael@0: NodeInfoInnerKeyCompare, michael@0: PL_CompareValues, &allocOps, nullptr); michael@0: } michael@0: michael@0: michael@0: nsNodeInfoManager::~nsNodeInfoManager() michael@0: { michael@0: if (mNodeInfoHash) michael@0: PL_HashTableDestroy(mNodeInfoHash); michael@0: michael@0: // Note: mPrincipal may be null here if we never got inited correctly michael@0: mPrincipal = nullptr; michael@0: michael@0: mBindingManager = nullptr; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (gNodeInfoManagerLeakPRLog) michael@0: PR_LOG(gNodeInfoManagerLeakPRLog, PR_LOG_DEBUG, michael@0: ("NODEINFOMANAGER %p destroyed", this)); michael@0: #endif michael@0: michael@0: nsLayoutStatics::Release(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager) michael@0: if (tmp->mDocument && michael@0: nsCCUncollectableMarker::InGeneration(cb, michael@0: tmp->mDocument->GetMarkedCCGeneration())) { michael@0: return NS_SUCCESS_INTERRUPTED_TRAVERSE; michael@0: } michael@0: if (tmp->mNonDocumentNodeInfos) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument) michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBindingManager) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release) michael@0: michael@0: nsresult michael@0: nsNodeInfoManager::Init(nsIDocument *aDocument) michael@0: { michael@0: NS_ENSURE_TRUE(mNodeInfoHash, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: NS_PRECONDITION(!mPrincipal, michael@0: "Being inited when we already have a principal?"); michael@0: nsresult rv; michael@0: mPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); michael@0: NS_ENSURE_TRUE(mPrincipal, rv); michael@0: michael@0: if (aDocument) { michael@0: mBindingManager = new nsBindingManager(aDocument); michael@0: } michael@0: michael@0: mDefaultPrincipal = mPrincipal; michael@0: michael@0: mDocument = aDocument; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (gNodeInfoManagerLeakPRLog) michael@0: PR_LOG(gNodeInfoManagerLeakPRLog, PR_LOG_DEBUG, michael@0: ("NODEINFOMANAGER %p Init document=%p", this, aDocument)); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: int michael@0: nsNodeInfoManager::DropNodeInfoDocument(PLHashEntry *he, int hashIndex, void *arg) michael@0: { michael@0: static_cast(he->value)->mDocument = nullptr; michael@0: return HT_ENUMERATE_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsNodeInfoManager::DropDocumentReference() michael@0: { michael@0: if (mBindingManager) { michael@0: mBindingManager->DropDocumentReference(); michael@0: } michael@0: michael@0: // This is probably not needed anymore. michael@0: PL_HashTableEnumerateEntries(mNodeInfoHash, DropNodeInfoDocument, nullptr); michael@0: michael@0: NS_ASSERTION(!mNonDocumentNodeInfos, "Shouldn't have non-document nodeinfos!"); michael@0: mDocument = nullptr; michael@0: } michael@0: michael@0: michael@0: already_AddRefed michael@0: nsNodeInfoManager::GetNodeInfo(nsIAtom *aName, nsIAtom *aPrefix, michael@0: int32_t aNamespaceID, uint16_t aNodeType, michael@0: nsIAtom* aExtraName /* = nullptr */) michael@0: { michael@0: CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName); michael@0: michael@0: nsINodeInfo::nsNodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType, michael@0: aExtraName); michael@0: michael@0: void *node = PL_HashTableLookup(mNodeInfoHash, &tmpKey); michael@0: michael@0: if (node) { michael@0: nsCOMPtr nodeInfo = static_cast(node); michael@0: michael@0: return nodeInfo.forget(); michael@0: } michael@0: michael@0: nsRefPtr newNodeInfo = michael@0: new nsNodeInfo(aName, aPrefix, aNamespaceID, aNodeType, aExtraName, this); michael@0: michael@0: DebugOnly he = michael@0: PL_HashTableAdd(mNodeInfoHash, &newNodeInfo->mInner, newNodeInfo); michael@0: MOZ_ASSERT(he, "PL_HashTableAdd() failed"); michael@0: michael@0: // Have to do the swap thing, because already_AddRefed michael@0: // doesn't cast to already_AddRefed michael@0: ++mNonDocumentNodeInfos; michael@0: if (mNonDocumentNodeInfos == 1) { michael@0: NS_IF_ADDREF(mDocument); michael@0: } michael@0: michael@0: return newNodeInfo.forget(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsIAtom *aPrefix, michael@0: int32_t aNamespaceID, uint16_t aNodeType, michael@0: nsINodeInfo** aNodeInfo) michael@0: { michael@0: #ifdef DEBUG michael@0: { michael@0: nsCOMPtr nameAtom = do_GetAtom(aName); michael@0: CheckValidNodeInfo(aNodeType, nameAtom, aNamespaceID, nullptr); michael@0: } michael@0: #endif michael@0: michael@0: nsINodeInfo::nsNodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType); michael@0: michael@0: void *node = PL_HashTableLookup(mNodeInfoHash, &tmpKey); michael@0: michael@0: if (node) { michael@0: nsINodeInfo* nodeInfo = static_cast(node); michael@0: michael@0: NS_ADDREF(*aNodeInfo = nodeInfo); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nameAtom = do_GetAtom(aName); michael@0: NS_ENSURE_TRUE(nameAtom, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsRefPtr newNodeInfo = michael@0: new nsNodeInfo(nameAtom, aPrefix, aNamespaceID, aNodeType, nullptr, this); michael@0: NS_ENSURE_TRUE(newNodeInfo, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: PLHashEntry *he; michael@0: he = PL_HashTableAdd(mNodeInfoHash, &newNodeInfo->mInner, newNodeInfo); michael@0: NS_ENSURE_TRUE(he, NS_ERROR_FAILURE); michael@0: michael@0: ++mNonDocumentNodeInfos; michael@0: if (mNonDocumentNodeInfos == 1) { michael@0: NS_IF_ADDREF(mDocument); michael@0: } michael@0: michael@0: newNodeInfo.forget(aNodeInfo); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsIAtom *aPrefix, michael@0: const nsAString& aNamespaceURI, michael@0: uint16_t aNodeType, michael@0: nsINodeInfo** aNodeInfo) michael@0: { michael@0: int32_t nsid = kNameSpaceID_None; michael@0: michael@0: if (!aNamespaceURI.IsEmpty()) { michael@0: nsresult rv = nsContentUtils::NameSpaceManager()-> michael@0: RegisterNameSpace(aNamespaceURI, nsid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return GetNodeInfo(aName, aPrefix, nsid, aNodeType, aNodeInfo); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsNodeInfoManager::GetTextNodeInfo() michael@0: { michael@0: nsCOMPtr nodeInfo; michael@0: michael@0: if (!mTextNodeInfo) { michael@0: nodeInfo = GetNodeInfo(nsGkAtoms::textTagName, nullptr, kNameSpaceID_None, michael@0: nsIDOMNode::TEXT_NODE, nullptr); michael@0: // Hold a weak ref; the nodeinfo will let us know when it goes away michael@0: mTextNodeInfo = nodeInfo; michael@0: } else { michael@0: nodeInfo = mTextNodeInfo; michael@0: } michael@0: michael@0: return nodeInfo.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsNodeInfoManager::GetCommentNodeInfo() michael@0: { michael@0: nsCOMPtr nodeInfo; michael@0: michael@0: if (!mCommentNodeInfo) { michael@0: nodeInfo = GetNodeInfo(nsGkAtoms::commentTagName, nullptr, michael@0: kNameSpaceID_None, nsIDOMNode::COMMENT_NODE, michael@0: nullptr); michael@0: // Hold a weak ref; the nodeinfo will let us know when it goes away michael@0: mCommentNodeInfo = nodeInfo; michael@0: } michael@0: else { michael@0: nodeInfo = mCommentNodeInfo; michael@0: } michael@0: michael@0: return nodeInfo.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsNodeInfoManager::GetDocumentNodeInfo() michael@0: { michael@0: nsCOMPtr nodeInfo; michael@0: michael@0: if (!mDocumentNodeInfo) { michael@0: NS_ASSERTION(mDocument, "Should have mDocument!"); michael@0: nodeInfo = GetNodeInfo(nsGkAtoms::documentNodeName, nullptr, michael@0: kNameSpaceID_None, nsIDOMNode::DOCUMENT_NODE, michael@0: nullptr); michael@0: // Hold a weak ref; the nodeinfo will let us know when it goes away michael@0: mDocumentNodeInfo = nodeInfo; michael@0: michael@0: --mNonDocumentNodeInfos; michael@0: if (!mNonDocumentNodeInfos) { michael@0: mDocument->Release(); // Don't set mDocument to null! michael@0: } michael@0: } michael@0: else { michael@0: nodeInfo = mDocumentNodeInfo; michael@0: } michael@0: michael@0: return nodeInfo.forget(); michael@0: } michael@0: michael@0: void michael@0: nsNodeInfoManager::SetDocumentPrincipal(nsIPrincipal *aPrincipal) michael@0: { michael@0: mPrincipal = nullptr; michael@0: if (!aPrincipal) { michael@0: aPrincipal = mDefaultPrincipal; michael@0: } michael@0: michael@0: NS_ASSERTION(aPrincipal, "Must have principal by this point!"); michael@0: michael@0: mPrincipal = aPrincipal; michael@0: } michael@0: michael@0: void michael@0: nsNodeInfoManager::RemoveNodeInfo(nsNodeInfo *aNodeInfo) michael@0: { michael@0: NS_PRECONDITION(aNodeInfo, "Trying to remove null nodeinfo from manager!"); michael@0: michael@0: if (aNodeInfo == mDocumentNodeInfo) { michael@0: mDocumentNodeInfo = nullptr; michael@0: mDocument = nullptr; michael@0: } else { michael@0: if (--mNonDocumentNodeInfos == 0) { michael@0: if (mDocument) { michael@0: // Note, whoever calls this method should keep NodeInfoManager alive, michael@0: // even if mDocument gets deleted. michael@0: mDocument->Release(); michael@0: } michael@0: } michael@0: // Drop weak reference if needed michael@0: if (aNodeInfo == mTextNodeInfo) { michael@0: mTextNodeInfo = nullptr; michael@0: } michael@0: else if (aNodeInfo == mCommentNodeInfo) { michael@0: mCommentNodeInfo = nullptr; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool ret = michael@0: #endif michael@0: PL_HashTableRemove(mNodeInfoHash, &aNodeInfo->mInner); michael@0: michael@0: NS_POSTCONDITION(ret, "Can't find nsINodeInfo to remove!!!"); michael@0: }