Mozilla/mozilla/content/xul/document/src/nsElementMap.cpp
dbaron%fas.harvard.edu 8f2c550aff Convert users of nsAReadable[C]String and nsAWritable[C]String typedefs to [const] nsA[C]String. b=131899 r=scc sr=jag a=asa
git-svn-id: svn://10.0.0.236/trunk@117347 18797224-902f-48f8-a5cc-f745e15eee43
2002-03-23 22:46:13 +00:00

456 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Waterson <waterson@netscape.com>
*
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
Maps IDs to elements.
To turn on logging for this module, set:
NS_PR_LOG_MODULES nsElementMap:5
*/
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsElementMap.h"
#include "nsISupportsArray.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "prlog.h"
#ifdef PR_LOGGING
static PRLogModuleInfo* gMapLog;
#endif
static void* PR_CALLBACK AllocTable(void* aPool, PRSize aSize)
{
return new char[aSize];
}
static void PR_CALLBACK FreeTable(void* aPool, void* aItem)
{
delete[] NS_STATIC_CAST(char*, aItem);
}
static PLHashEntry* PR_CALLBACK AllocEntry(void* aPool, const void* aKey)
{
nsFixedSizeAllocator* pool = NS_STATIC_CAST(nsFixedSizeAllocator*, aPool);
PLHashEntry* entry = NS_STATIC_CAST(PLHashEntry*, pool->Alloc(sizeof(PLHashEntry)));
return entry;
}
static void PR_CALLBACK FreeEntry(void* aPool, PLHashEntry* aEntry, PRUintn aFlag)
{
nsFixedSizeAllocator* pool = NS_STATIC_CAST(nsFixedSizeAllocator*, aPool);
if (aFlag == HT_FREE_ENTRY)
pool->Free(aEntry, sizeof(PLHashEntry));
}
PLHashAllocOps nsElementMap::gAllocOps = {
AllocTable, FreeTable, AllocEntry, FreeEntry };
nsElementMap::nsElementMap()
{
MOZ_COUNT_CTOR(nsElementMap);
// Create a table for mapping IDs to elements in the content tree.
static const size_t kBucketSizes[] = {
sizeof(PLHashEntry), sizeof(ContentListItem)
};
static const PRInt32 kNumBuckets = sizeof(kBucketSizes) / sizeof(size_t);
static const PRInt32 kInitialNumElements = 64;
// Per news://news.mozilla.org/39BEC105.5090206%40netscape.com
static const PRInt32 kInitialPoolSize = 512;
mPool.Init("nsElementMap", kBucketSizes, kNumBuckets, kInitialPoolSize);
mMap = PL_NewHashTable(kInitialNumElements,
Hash,
Compare,
PL_CompareValues,
&gAllocOps,
&mPool);
NS_ASSERTION(mMap != nsnull, "could not create hash table for resources");
#ifdef PR_LOGGING
if (! gMapLog)
gMapLog = PR_NewLogModule("nsElementMap");
PR_LOG(gMapLog, PR_LOG_ALWAYS,
("xulelemap(%p) created", this));
#endif
}
nsElementMap::~nsElementMap()
{
MOZ_COUNT_DTOR(nsElementMap);
if (mMap) {
PL_HashTableEnumerateEntries(mMap, ReleaseContentList, this);
PL_HashTableDestroy(mMap);
}
PR_LOG(gMapLog, PR_LOG_ALWAYS,
("xulelemap(%p) destroyed", this));
}
PRIntn
nsElementMap::ReleaseContentList(PLHashEntry* aHashEntry, PRIntn aIndex, void* aClosure)
{
nsElementMap* self = NS_STATIC_CAST(nsElementMap*, aClosure);
PRUnichar* id =
NS_REINTERPRET_CAST(PRUnichar*, NS_CONST_CAST(void*, aHashEntry->key));
nsMemory::Free(id);
ContentListItem* head =
NS_REINTERPRET_CAST(ContentListItem*, aHashEntry->value);
while (head) {
ContentListItem* doomed = head;
head = head->mNext;
ContentListItem::Destroy(self->mPool, doomed);
}
return HT_ENUMERATE_NEXT;
}
nsresult
nsElementMap::Add(const nsAString& aID, nsIContent* aContent)
{
NS_PRECONDITION(mMap != nsnull, "not initialized");
if (! mMap)
return NS_ERROR_NOT_INITIALIZED;
const nsPromiseFlatString& flatID = PromiseFlatString(aID);
const PRUnichar *id = flatID.get();
ContentListItem* head =
NS_STATIC_CAST(ContentListItem*, PL_HashTableLookup(mMap, id));
if (! head) {
head = ContentListItem::Create(mPool, aContent);
if (! head)
return NS_ERROR_OUT_OF_MEMORY;
PRUnichar* key = ToNewUnicode(aID);
if (! key)
return NS_ERROR_OUT_OF_MEMORY;
PL_HashTableAdd(mMap, key, head);
}
else {
while (1) {
if (head->mContent.get() == aContent) {
// This can happen if an element that was created via
// frame construction code is then "appended" to the
// content model with aNotify == PR_TRUE. If you see
// this warning, it's an indication that you're
// unnecessarily notifying the frame system, and
// potentially causing unnecessary reflow.
//NS_ERROR("element was already in the map");
#ifdef PR_LOGGING
if (PR_LOG_TEST(gMapLog, PR_LOG_ALWAYS)) {
nsresult rv;
nsCOMPtr<nsIAtom> tag;
rv = aContent->GetTag(*getter_AddRefs(tag));
if (NS_FAILED(rv)) return rv;
nsAutoString tagname;
rv = tag->ToString(tagname);
if (NS_FAILED(rv)) return rv;
nsCAutoString tagnameC, aidC;
tagnameC.AssignWithConversion(tagname);
aidC.AssignWithConversion(id, aID.Length());
PR_LOG(gMapLog, PR_LOG_ALWAYS,
("xulelemap(%p) dup %s[%p] <-- %s\n",
this,
tagnameC.get(),
aContent,
aidC.get()));
}
#endif
return NS_OK;
}
if (! head->mNext)
break;
head = head->mNext;
}
head->mNext = nsElementMap::ContentListItem::Create(mPool, aContent);
if (! head->mNext)
return NS_ERROR_OUT_OF_MEMORY;
}
#ifdef PR_LOGGING
if (PR_LOG_TEST(gMapLog, PR_LOG_ALWAYS)) {
nsresult rv;
nsCOMPtr<nsIAtom> tag;
rv = aContent->GetTag(*getter_AddRefs(tag));
if (NS_FAILED(rv)) return rv;
nsAutoString tagname;
rv = tag->ToString(tagname);
if (NS_FAILED(rv)) return rv;
nsCAutoString tagnameC, aidC;
tagnameC.AssignWithConversion(tagname);
aidC.AssignWithConversion(id, aID.Length());
PR_LOG(gMapLog, PR_LOG_ALWAYS,
("xulelemap(%p) add %s[%p] <-- %s\n",
this,
tagnameC.get(),
aContent,
aidC.get()));
}
#endif
return NS_OK;
}
nsresult
nsElementMap::Remove(const nsAString& aID, nsIContent* aContent)
{
NS_PRECONDITION(mMap != nsnull, "not initialized");
if (! mMap)
return NS_ERROR_NOT_INITIALIZED;
const nsPromiseFlatString& flatID = PromiseFlatString(aID);
const PRUnichar *id = flatID.get();
#ifdef PR_LOGGING
if (PR_LOG_TEST(gMapLog, PR_LOG_ALWAYS)) {
nsresult rv;
nsCOMPtr<nsIAtom> tag;
rv = aContent->GetTag(*getter_AddRefs(tag));
if (NS_FAILED(rv)) return rv;
nsAutoString tagname;
rv = tag->ToString(tagname);
if (NS_FAILED(rv)) return rv;
nsCAutoString tagnameC, aidC;
tagnameC.AssignWithConversion(tagname);
aidC.AssignWithConversion(id);
PR_LOG(gMapLog, PR_LOG_ALWAYS,
("xulelemap(%p) remove %s[%p] <-- %s\n",
this,
tagnameC.get(),
aContent,
aidC.get()));
}
#endif
PLHashEntry** hep = PL_HashTableRawLookup(mMap,
Hash(id),
id);
// XXX Don't comment out this assert: if you get here, something
// has gone dreadfully, horribly wrong. Curse. Scream. File a bug
// against waterson@netscape.com.
NS_ASSERTION(hep != nsnull && *hep != nsnull, "attempt to remove an element that was never added");
if (!hep || !*hep)
return NS_OK;
ContentListItem* head = NS_REINTERPRET_CAST(ContentListItem*, (*hep)->value);
if (head->mContent.get() == aContent) {
ContentListItem* next = head->mNext;
if (next) {
(*hep)->value = next;
}
else {
// It was the last reference in the table
PRUnichar* key = NS_REINTERPRET_CAST(PRUnichar*, NS_CONST_CAST(void*, (*hep)->key));
PL_HashTableRawRemove(mMap, hep, *hep);
nsMemory::Free(key);
}
ContentListItem::Destroy(mPool, head);
}
else {
ContentListItem* item = head->mNext;
while (item) {
if (item->mContent.get() == aContent) {
head->mNext = item->mNext;
ContentListItem::Destroy(mPool, item);
break;
}
head = item;
item = item->mNext;
}
}
return NS_OK;
}
nsresult
nsElementMap::Find(const nsAString& aID, nsISupportsArray* aResults)
{
NS_PRECONDITION(mMap != nsnull, "not initialized");
if (! mMap)
return NS_ERROR_NOT_INITIALIZED;
aResults->Clear();
ContentListItem* item =
NS_REINTERPRET_CAST(ContentListItem*, PL_HashTableLookup(mMap, (const PRUnichar *)PromiseFlatString(aID).get()));
while (item) {
aResults->AppendElement(item->mContent);
item = item->mNext;
}
return NS_OK;
}
nsresult
nsElementMap::FindFirst(const nsAString& aID, nsIContent** aResult)
{
NS_PRECONDITION(mMap != nsnull, "not initialized");
if (! mMap)
return NS_ERROR_NOT_INITIALIZED;
ContentListItem* item =
NS_REINTERPRET_CAST(ContentListItem*, PL_HashTableLookup(mMap, (const PRUnichar *)PromiseFlatString(aID).get()));
if (item) {
*aResult = item->mContent;
NS_ADDREF(*aResult);
}
else {
*aResult = nsnull;
}
return NS_OK;
}
nsresult
nsElementMap::Enumerate(nsElementMapEnumerator aEnumerator, void* aClosure)
{
EnumerateClosure closure = { this, aEnumerator, aClosure };
PL_HashTableEnumerateEntries(mMap, EnumerateImpl, &closure);
return NS_OK;
}
PRIntn
nsElementMap::EnumerateImpl(PLHashEntry* aHashEntry, PRIntn aIndex, void* aClosure)
{
// This routine will be called once for each key in the
// hashtable. It will in turn call the enumerator that the user
// has passed in via Enumerate() once for each element that's been
// mapped to this ID.
EnumerateClosure* closure = NS_REINTERPRET_CAST(EnumerateClosure*, aClosure);
const PRUnichar* id =
NS_REINTERPRET_CAST(const PRUnichar*, aHashEntry->key);
// 'link' holds a pointer to the previous element's link field.
ContentListItem** link =
NS_REINTERPRET_CAST(ContentListItem**, &aHashEntry->value);
ContentListItem* item = *link;
// Iterate through each content node that's been mapped to this ID
while (item) {
ContentListItem* current = item;
item = item->mNext;
PRIntn result = (*closure->mEnumerator)(id, current->mContent, closure->mClosure);
if (result == HT_ENUMERATE_REMOVE) {
// If the user wants to remove the current, then deal.
*link = item;
ContentListItem::Destroy(closure->mSelf->mPool, current);
if ((! *link) && (link == NS_REINTERPRET_CAST(ContentListItem**, &aHashEntry->value))) {
// It's the last content node that was mapped to this
// ID. Unhash it.
PRUnichar* key = NS_CONST_CAST(PRUnichar*, id);
nsMemory::Free(key);
return HT_ENUMERATE_REMOVE;
}
}
else {
link = &current->mNext;
}
}
return HT_ENUMERATE_NEXT;
}
PLHashNumber
nsElementMap::Hash(const void* aKey)
{
PLHashNumber result = 0;
const PRUnichar* s = NS_REINTERPRET_CAST(const PRUnichar*, aKey);
while (*s != nsnull) {
result = (result >> 28) ^ (result << 4) ^ *s;
++s;
}
return result;
}
PRIntn
nsElementMap::Compare(const void* aLeft, const void* aRight)
{
return 0 == nsCRT::strcmp(NS_REINTERPRET_CAST(const PRUnichar*, aLeft),
NS_REINTERPRET_CAST(const PRUnichar*, aRight));
}