michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ 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: * nsBaseContentList is a basic list of content nodes; nsContentList michael@0: * is a commonly used NodeList implementation (used for michael@0: * getElementsByTagName, some properties on nsIDOMHTMLDocument, etc). michael@0: */ michael@0: michael@0: #include "nsContentList.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDocument.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "mozilla/dom/HTMLCollectionBinding.h" michael@0: #include "mozilla/dom/NodeListBinding.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "nsGenericHTMLElement.h" michael@0: #include "jsfriendapi.h" michael@0: #include michael@0: michael@0: // Form related includes michael@0: #include "nsIDOMHTMLFormElement.h" michael@0: michael@0: #include "pldhash.h" michael@0: michael@0: #ifdef DEBUG_CONTENT_LIST michael@0: #include "nsIContentIterator.h" michael@0: #define ASSERT_IN_SYNC AssertInSync() michael@0: #else michael@0: #define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsBaseContentList::~nsBaseContentList() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: tmp->RemoveFromCaches(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsBaseContentList) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList) michael@0: if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { michael@0: for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) { michael@0: nsIContent* c = tmp->mElements[i]; michael@0: if (c->IsPurple()) { michael@0: c->RemovePurple(); michael@0: } michael@0: Element::MarkNodeChildren(c); michael@0: } michael@0: return true; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList) michael@0: return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList) michael@0: return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: #define NS_CONTENT_LIST_INTERFACES(_class) \ michael@0: NS_INTERFACE_TABLE_ENTRY(_class, nsINodeList) \ michael@0: NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMNodeList) michael@0: michael@0: // QueryInterface implementation for nsBaseContentList michael@0: NS_INTERFACE_TABLE_HEAD(nsBaseContentList) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_TABLE(nsBaseContentList, nsINodeList, nsIDOMNodeList) michael@0: NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBaseContentList) michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsBaseContentList::GetLength(uint32_t* aLength) michael@0: { michael@0: *aLength = mElements.Length(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsBaseContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn) michael@0: { michael@0: nsISupports *tmp = Item(aIndex); michael@0: michael@0: if (!tmp) { michael@0: *aReturn = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: return CallQueryInterface(tmp, aReturn); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsBaseContentList::Item(uint32_t aIndex) michael@0: { michael@0: return mElements.SafeElementAt(aIndex); michael@0: } michael@0: michael@0: michael@0: int32_t michael@0: nsBaseContentList::IndexOf(nsIContent *aContent, bool aDoFlush) michael@0: { michael@0: return mElements.IndexOf(aContent); michael@0: } michael@0: michael@0: int32_t michael@0: nsBaseContentList::IndexOf(nsIContent* aContent) michael@0: { michael@0: return IndexOf(aContent, true); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList, michael@0: mRoot) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsSimpleContentList) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList) michael@0: michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList) michael@0: NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList) michael@0: michael@0: JSObject* michael@0: nsSimpleContentList::WrapObject(JSContext *cx) michael@0: { michael@0: return NodeListBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: // Hashtable for storing nsContentLists michael@0: static PLDHashTable gContentListHashTable; michael@0: michael@0: #define RECENTLY_USED_CONTENT_LIST_CACHE_SIZE 31 michael@0: static nsContentList* michael@0: sRecentlyUsedContentLists[RECENTLY_USED_CONTENT_LIST_CACHE_SIZE] = {}; michael@0: michael@0: static MOZ_ALWAYS_INLINE uint32_t michael@0: RecentlyUsedCacheIndex(const nsContentListKey& aKey) michael@0: { michael@0: return aKey.GetHash() % RECENTLY_USED_CONTENT_LIST_CACHE_SIZE; michael@0: } michael@0: michael@0: struct ContentListHashEntry : public PLDHashEntryHdr michael@0: { michael@0: nsContentList* mContentList; michael@0: }; michael@0: michael@0: static PLDHashNumber michael@0: ContentListHashtableHashKey(PLDHashTable *table, const void *key) michael@0: { michael@0: const nsContentListKey* list = static_cast(key); michael@0: return list->GetHash(); michael@0: } michael@0: michael@0: static bool michael@0: ContentListHashtableMatchEntry(PLDHashTable *table, michael@0: const PLDHashEntryHdr *entry, michael@0: const void *key) michael@0: { michael@0: const ContentListHashEntry *e = michael@0: static_cast(entry); michael@0: const nsContentList* list = e->mContentList; michael@0: const nsContentListKey* ourKey = static_cast(key); michael@0: michael@0: return list->MatchesKey(*ourKey); michael@0: } michael@0: michael@0: already_AddRefed michael@0: NS_GetContentList(nsINode* aRootNode, michael@0: int32_t aMatchNameSpaceId, michael@0: const nsAString& aTagname) michael@0: { michael@0: NS_ASSERTION(aRootNode, "content list has to have a root"); michael@0: michael@0: nsRefPtr list; michael@0: nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname); michael@0: uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(hashKey); michael@0: nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex]; michael@0: if (cachedList && cachedList->MatchesKey(hashKey)) { michael@0: list = cachedList; michael@0: return list.forget(); michael@0: } michael@0: michael@0: static const PLDHashTableOps hash_table_ops = michael@0: { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: ContentListHashtableHashKey, michael@0: ContentListHashtableMatchEntry, michael@0: PL_DHashMoveEntryStub, michael@0: PL_DHashClearEntryStub, michael@0: PL_DHashFinalizeStub michael@0: }; michael@0: michael@0: // Initialize the hashtable if needed. michael@0: if (!gContentListHashTable.ops) { michael@0: PL_DHashTableInit(&gContentListHashTable, michael@0: &hash_table_ops, nullptr, michael@0: sizeof(ContentListHashEntry), michael@0: 16); michael@0: } michael@0: michael@0: ContentListHashEntry *entry = nullptr; michael@0: // First we look in our hashtable. Then we create a content list if needed michael@0: if (gContentListHashTable.ops) { michael@0: michael@0: // A PL_DHASH_ADD is equivalent to a PL_DHASH_LOOKUP for cases michael@0: // when the entry is already in the hashtable. michael@0: entry = static_cast michael@0: (PL_DHashTableOperate(&gContentListHashTable, michael@0: &hashKey, michael@0: PL_DHASH_ADD)); michael@0: if (entry) michael@0: list = entry->mContentList; michael@0: } michael@0: michael@0: if (!list) { michael@0: // We need to create a ContentList and add it to our new entry, if michael@0: // we have an entry michael@0: nsCOMPtr xmlAtom = do_GetAtom(aTagname); michael@0: nsCOMPtr htmlAtom; michael@0: if (aMatchNameSpaceId == kNameSpaceID_Unknown) { michael@0: nsAutoString lowercaseName; michael@0: nsContentUtils::ASCIIToLower(aTagname, lowercaseName); michael@0: htmlAtom = do_GetAtom(lowercaseName); michael@0: } else { michael@0: htmlAtom = xmlAtom; michael@0: } michael@0: list = new nsContentList(aRootNode, aMatchNameSpaceId, michael@0: htmlAtom, xmlAtom); michael@0: if (entry) { michael@0: entry->mContentList = list; michael@0: } michael@0: } michael@0: michael@0: sRecentlyUsedContentLists[recentlyUsedCacheIndex] = list; michael@0: return list.forget(); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: const nsCacheableFuncStringContentList::ContentListType michael@0: nsCacheableFuncStringNodeList::sType = nsCacheableFuncStringContentList::eNodeList; michael@0: const nsCacheableFuncStringContentList::ContentListType michael@0: nsCacheableFuncStringHTMLCollection::sType = nsCacheableFuncStringContentList::eHTMLCollection; michael@0: #endif michael@0: michael@0: JSObject* michael@0: nsCacheableFuncStringNodeList::WrapObject(JSContext *cx) michael@0: { michael@0: return NodeListBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: michael@0: JSObject* michael@0: nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx) michael@0: { michael@0: return HTMLCollectionBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: // Hashtable for storing nsCacheableFuncStringContentList michael@0: static PLDHashTable gFuncStringContentListHashTable; michael@0: michael@0: struct FuncStringContentListHashEntry : public PLDHashEntryHdr michael@0: { michael@0: nsCacheableFuncStringContentList* mContentList; michael@0: }; michael@0: michael@0: static PLDHashNumber michael@0: FuncStringContentListHashtableHashKey(PLDHashTable *table, const void *key) michael@0: { michael@0: const nsFuncStringCacheKey* funcStringKey = michael@0: static_cast(key); michael@0: return funcStringKey->GetHash(); michael@0: } michael@0: michael@0: static bool michael@0: FuncStringContentListHashtableMatchEntry(PLDHashTable *table, michael@0: const PLDHashEntryHdr *entry, michael@0: const void *key) michael@0: { michael@0: const FuncStringContentListHashEntry *e = michael@0: static_cast(entry); michael@0: const nsFuncStringCacheKey* ourKey = michael@0: static_cast(key); michael@0: michael@0: return e->mContentList->Equals(ourKey); michael@0: } michael@0: michael@0: template michael@0: already_AddRefed michael@0: GetFuncStringContentList(nsINode* aRootNode, michael@0: nsContentListMatchFunc aFunc, michael@0: nsContentListDestroyFunc aDestroyFunc, michael@0: nsFuncStringContentListDataAllocator aDataAllocator, michael@0: const nsAString& aString) michael@0: { michael@0: NS_ASSERTION(aRootNode, "content list has to have a root"); michael@0: michael@0: nsRefPtr list; michael@0: michael@0: static const PLDHashTableOps hash_table_ops = michael@0: { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: FuncStringContentListHashtableHashKey, michael@0: FuncStringContentListHashtableMatchEntry, michael@0: PL_DHashMoveEntryStub, michael@0: PL_DHashClearEntryStub, michael@0: PL_DHashFinalizeStub michael@0: }; michael@0: michael@0: // Initialize the hashtable if needed. michael@0: if (!gFuncStringContentListHashTable.ops) { michael@0: PL_DHashTableInit(&gFuncStringContentListHashTable, michael@0: &hash_table_ops, nullptr, michael@0: sizeof(FuncStringContentListHashEntry), michael@0: 16); michael@0: } michael@0: michael@0: FuncStringContentListHashEntry *entry = nullptr; michael@0: // First we look in our hashtable. Then we create a content list if needed michael@0: if (gFuncStringContentListHashTable.ops) { michael@0: nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString); michael@0: michael@0: // A PL_DHASH_ADD is equivalent to a PL_DHASH_LOOKUP for cases michael@0: // when the entry is already in the hashtable. michael@0: entry = static_cast michael@0: (PL_DHashTableOperate(&gFuncStringContentListHashTable, michael@0: &hashKey, michael@0: PL_DHASH_ADD)); michael@0: if (entry) { michael@0: list = entry->mContentList; michael@0: #ifdef DEBUG michael@0: MOZ_ASSERT_IF(list, list->mType == ListType::sType); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: if (!list) { michael@0: // We need to create a ContentList and add it to our new entry, if michael@0: // we have an entry michael@0: list = new ListType(aRootNode, aFunc, aDestroyFunc, aDataAllocator, michael@0: aString); michael@0: if (entry) { michael@0: entry->mContentList = list; michael@0: } michael@0: } michael@0: michael@0: // Don't cache these lists globally michael@0: michael@0: return list.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: NS_GetFuncStringNodeList(nsINode* aRootNode, michael@0: nsContentListMatchFunc aFunc, michael@0: nsContentListDestroyFunc aDestroyFunc, michael@0: nsFuncStringContentListDataAllocator aDataAllocator, michael@0: const nsAString& aString) michael@0: { michael@0: return GetFuncStringContentList(aRootNode, michael@0: aFunc, michael@0: aDestroyFunc, michael@0: aDataAllocator, michael@0: aString); michael@0: } michael@0: michael@0: already_AddRefed michael@0: NS_GetFuncStringHTMLCollection(nsINode* aRootNode, michael@0: nsContentListMatchFunc aFunc, michael@0: nsContentListDestroyFunc aDestroyFunc, michael@0: nsFuncStringContentListDataAllocator aDataAllocator, michael@0: const nsAString& aString) michael@0: { michael@0: return GetFuncStringContentList(aRootNode, michael@0: aFunc, michael@0: aDestroyFunc, michael@0: aDataAllocator, michael@0: aString); michael@0: } michael@0: michael@0: // nsContentList implementation michael@0: michael@0: nsContentList::nsContentList(nsINode* aRootNode, michael@0: int32_t aMatchNameSpaceId, michael@0: nsIAtom* aHTMLMatchAtom, michael@0: nsIAtom* aXMLMatchAtom, michael@0: bool aDeep) michael@0: : nsBaseContentList(), michael@0: mRootNode(aRootNode), michael@0: mMatchNameSpaceId(aMatchNameSpaceId), michael@0: mHTMLMatchAtom(aHTMLMatchAtom), michael@0: mXMLMatchAtom(aXMLMatchAtom), michael@0: mFunc(nullptr), michael@0: mDestroyFunc(nullptr), michael@0: mData(nullptr), michael@0: mState(LIST_DIRTY), michael@0: mDeep(aDeep), michael@0: mFuncMayDependOnAttr(false) michael@0: { michael@0: NS_ASSERTION(mRootNode, "Must have root"); michael@0: if (nsGkAtoms::_asterix == mHTMLMatchAtom) { michael@0: NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterix, "HTML atom and XML atom are not both asterix?"); michael@0: mMatchAll = true; michael@0: } michael@0: else { michael@0: mMatchAll = false; michael@0: } michael@0: mRootNode->AddMutationObserver(this); michael@0: michael@0: // We only need to flush if we're in an non-HTML document, since the michael@0: // HTML5 parser doesn't need flushing. Further, if we're not in a michael@0: // document at all right now (in the GetCurrentDoc() sense), we're michael@0: // not parser-created and don't need to be flushing stuff under us michael@0: // to get our kids right. michael@0: nsIDocument* doc = mRootNode->GetCurrentDoc(); michael@0: mFlushesNeeded = doc && !doc->IsHTML(); michael@0: } michael@0: michael@0: nsContentList::nsContentList(nsINode* aRootNode, michael@0: nsContentListMatchFunc aFunc, michael@0: nsContentListDestroyFunc aDestroyFunc, michael@0: void* aData, michael@0: bool aDeep, michael@0: nsIAtom* aMatchAtom, michael@0: int32_t aMatchNameSpaceId, michael@0: bool aFuncMayDependOnAttr) michael@0: : nsBaseContentList(), michael@0: mRootNode(aRootNode), michael@0: mMatchNameSpaceId(aMatchNameSpaceId), michael@0: mHTMLMatchAtom(aMatchAtom), michael@0: mXMLMatchAtom(aMatchAtom), michael@0: mFunc(aFunc), michael@0: mDestroyFunc(aDestroyFunc), michael@0: mData(aData), michael@0: mState(LIST_DIRTY), michael@0: mMatchAll(false), michael@0: mDeep(aDeep), michael@0: mFuncMayDependOnAttr(aFuncMayDependOnAttr) michael@0: { michael@0: NS_ASSERTION(mRootNode, "Must have root"); michael@0: mRootNode->AddMutationObserver(this); michael@0: michael@0: // We only need to flush if we're in an non-HTML document, since the michael@0: // HTML5 parser doesn't need flushing. Further, if we're not in a michael@0: // document at all right now (in the GetCurrentDoc() sense), we're michael@0: // not parser-created and don't need to be flushing stuff under us michael@0: // to get our kids right. michael@0: nsIDocument* doc = mRootNode->GetCurrentDoc(); michael@0: mFlushesNeeded = doc && !doc->IsHTML(); michael@0: } michael@0: michael@0: nsContentList::~nsContentList() michael@0: { michael@0: RemoveFromHashtable(); michael@0: if (mRootNode) { michael@0: mRootNode->RemoveMutationObserver(this); michael@0: } michael@0: michael@0: if (mDestroyFunc) { michael@0: // Clean up mData michael@0: (*mDestroyFunc)(mData); michael@0: } michael@0: } michael@0: michael@0: JSObject* michael@0: nsContentList::WrapObject(JSContext *cx) michael@0: { michael@0: return HTMLCollectionBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList, michael@0: nsIHTMLCollection, nsIDOMHTMLCollection, michael@0: nsIMutationObserver) michael@0: michael@0: uint32_t michael@0: nsContentList::Length(bool aDoFlush) michael@0: { michael@0: BringSelfUpToDate(aDoFlush); michael@0: michael@0: return mElements.Length(); michael@0: } michael@0: michael@0: nsIContent * michael@0: nsContentList::Item(uint32_t aIndex, bool aDoFlush) michael@0: { michael@0: if (mRootNode && aDoFlush && mFlushesNeeded) { michael@0: // XXX sXBL/XBL2 issue michael@0: nsIDocument* doc = mRootNode->GetCurrentDoc(); michael@0: if (doc) { michael@0: // Flush pending content changes Bug 4891. michael@0: doc->FlushPendingNotifications(Flush_ContentAndNotify); michael@0: } michael@0: } michael@0: michael@0: if (mState != LIST_UP_TO_DATE) michael@0: PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1); michael@0: michael@0: ASSERT_IN_SYNC; michael@0: NS_ASSERTION(!mRootNode || mState != LIST_DIRTY, michael@0: "PopulateSelf left the list in a dirty (useless) state!"); michael@0: michael@0: return mElements.SafeElementAt(aIndex); michael@0: } michael@0: michael@0: Element* michael@0: nsContentList::NamedItem(const nsAString& aName, bool aDoFlush) michael@0: { michael@0: BringSelfUpToDate(aDoFlush); michael@0: michael@0: uint32_t i, count = mElements.Length(); michael@0: michael@0: // Typically IDs and names are atomized michael@0: nsCOMPtr name = do_GetAtom(aName); michael@0: NS_ENSURE_TRUE(name, nullptr); michael@0: michael@0: for (i = 0; i < count; i++) { michael@0: nsIContent *content = mElements[i]; michael@0: // XXX Should this pass eIgnoreCase? michael@0: if (content && michael@0: (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, michael@0: name, eCaseMatters) || michael@0: content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, michael@0: name, eCaseMatters))) { michael@0: return content->AsElement(); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsContentList::GetSupportedNames(unsigned aFlags, nsTArray& aNames) michael@0: { michael@0: if (!(aFlags & JSITER_HIDDEN)) { michael@0: return; michael@0: } michael@0: michael@0: BringSelfUpToDate(true); michael@0: michael@0: nsAutoTArray atoms; michael@0: for (uint32_t i = 0; i < mElements.Length(); ++i) { michael@0: nsIContent *content = mElements.ElementAt(i); michael@0: nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(content); michael@0: if (el) { michael@0: // XXXbz should we be checking for particular tags here? How michael@0: // stable is this part of the spec? michael@0: // Note: nsINode::HasName means the name is exposed on the document, michael@0: // which is false for options, so we don't check it here. michael@0: const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name); michael@0: if (val && val->Type() == nsAttrValue::eAtom) { michael@0: nsIAtom* name = val->GetAtomValue(); michael@0: if (!atoms.Contains(name)) { michael@0: atoms.AppendElement(name); michael@0: } michael@0: } michael@0: } michael@0: if (content->HasID()) { michael@0: nsIAtom* id = content->GetID(); michael@0: if (!atoms.Contains(id)) { michael@0: atoms.AppendElement(id); michael@0: } michael@0: } michael@0: } michael@0: michael@0: aNames.SetCapacity(atoms.Length()); michael@0: for (uint32_t i = 0; i < atoms.Length(); ++i) { michael@0: aNames.AppendElement(nsDependentAtomString(atoms[i])); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsContentList::IndexOf(nsIContent *aContent, bool aDoFlush) michael@0: { michael@0: BringSelfUpToDate(aDoFlush); michael@0: michael@0: return mElements.IndexOf(aContent); michael@0: } michael@0: michael@0: int32_t michael@0: nsContentList::IndexOf(nsIContent* aContent) michael@0: { michael@0: return IndexOf(aContent, true); michael@0: } michael@0: michael@0: void michael@0: nsContentList::NodeWillBeDestroyed(const nsINode* aNode) michael@0: { michael@0: // We shouldn't do anything useful from now on michael@0: michael@0: RemoveFromCaches(); michael@0: mRootNode = nullptr; michael@0: michael@0: // We will get no more updates, so we can never know we're up to michael@0: // date michael@0: SetDirty(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentList::GetLength(uint32_t* aLength) michael@0: { michael@0: *aLength = Length(true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn) michael@0: { michael@0: nsINode* node = Item(aIndex); michael@0: michael@0: if (node) { michael@0: return CallQueryInterface(node, aReturn); michael@0: } michael@0: michael@0: *aReturn = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentList::NamedItem(const nsAString& aName, nsIDOMNode** aReturn) michael@0: { michael@0: nsIContent *content = NamedItem(aName, true); michael@0: michael@0: if (content) { michael@0: return CallQueryInterface(content, aReturn); michael@0: } michael@0: michael@0: *aReturn = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: Element* michael@0: nsContentList::GetElementAt(uint32_t aIndex) michael@0: { michael@0: return static_cast(Item(aIndex, true)); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsContentList::Item(uint32_t aIndex) michael@0: { michael@0: return GetElementAt(aIndex); michael@0: } michael@0: michael@0: void michael@0: nsContentList::AttributeChanged(nsIDocument *aDocument, Element* aElement, michael@0: int32_t aNameSpaceID, nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: NS_PRECONDITION(aElement, "Must have a content node to work with"); michael@0: michael@0: if (!mFunc || !mFuncMayDependOnAttr || mState == LIST_DIRTY || michael@0: !MayContainRelevantNodes(aElement->GetParentNode()) || michael@0: !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) { michael@0: // Either we're already dirty or this notification doesn't affect michael@0: // whether we might match aElement. michael@0: return; michael@0: } michael@0: michael@0: if (Match(aElement)) { michael@0: if (mElements.IndexOf(aElement) == mElements.NoIndex) { michael@0: // We match aElement now, and it's not in our list already. Just dirty michael@0: // ourselves; this is simpler than trying to figure out where to insert michael@0: // aElement. michael@0: SetDirty(); michael@0: } michael@0: } else { michael@0: // We no longer match aElement. Remove it from our list. If it's michael@0: // already not there, this is a no-op (though a potentially michael@0: // expensive one). Either way, no change of mState is required michael@0: // here. michael@0: mElements.RemoveElement(aElement); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: NS_PRECONDITION(aContainer, "Can't get at the new content if no container!"); michael@0: michael@0: /* michael@0: * If the state is LIST_DIRTY then we have no useful information in our list michael@0: * and we want to put off doing work as much as possible. michael@0: * michael@0: * Also, if aContainer is anonymous from our point of view, we know that we michael@0: * can't possibly be matching any of the kids. michael@0: * michael@0: * Optimize out also the common case when just one new node is appended and michael@0: * it doesn't match us. michael@0: */ michael@0: if (mState == LIST_DIRTY || michael@0: !nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer) || michael@0: !MayContainRelevantNodes(aContainer) || michael@0: (!aFirstNewContent->HasChildren() && michael@0: !aFirstNewContent->GetNextSibling() && michael@0: !MatchSelf(aFirstNewContent))) { michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * We want to handle the case of ContentAppended by sometimes michael@0: * appending the content to our list, not just setting state to michael@0: * LIST_DIRTY, since most of our ContentAppended notifications michael@0: * should come during pageload and be at the end of the document. michael@0: * Do a bit of work to see whether we could just append to what we michael@0: * already have. michael@0: */ michael@0: michael@0: int32_t count = aContainer->GetChildCount(); michael@0: michael@0: if (count > 0) { michael@0: uint32_t ourCount = mElements.Length(); michael@0: bool appendToList = false; michael@0: if (ourCount == 0) { michael@0: appendToList = true; michael@0: } else { michael@0: nsIContent* ourLastContent = mElements[ourCount - 1]; michael@0: /* michael@0: * We want to append instead of invalidating if the first thing michael@0: * that got appended comes after ourLastContent. michael@0: */ michael@0: if (nsContentUtils::PositionIsBefore(ourLastContent, aFirstNewContent)) { michael@0: appendToList = true; michael@0: } michael@0: } michael@0: michael@0: michael@0: if (!appendToList) { michael@0: // The new stuff is somewhere in the middle of our list; check michael@0: // whether we need to invalidate michael@0: for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { michael@0: if (MatchSelf(cur)) { michael@0: // Uh-oh. We're gonna have to add elements into the middle michael@0: // of our list. That's not worth the effort. michael@0: SetDirty(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: ASSERT_IN_SYNC; michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * At this point we know we could append. If we're not up to michael@0: * date, however, that would be a bad idea -- it could miss some michael@0: * content that we never picked up due to being lazy. Further, we michael@0: * may never get asked for this content... so don't grab it yet. michael@0: */ michael@0: if (mState == LIST_LAZY) // be lazy michael@0: return; michael@0: michael@0: /* michael@0: * We're up to date. That means someone's actively using us; we michael@0: * may as well grab this content.... michael@0: */ michael@0: if (mDeep) { michael@0: for (nsIContent* cur = aFirstNewContent; michael@0: cur; michael@0: cur = cur->GetNextNode(aContainer)) { michael@0: if (cur->IsElement() && Match(cur->AsElement())) { michael@0: mElements.AppendElement(cur); michael@0: } michael@0: } michael@0: } else { michael@0: for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { michael@0: if (cur->IsElement() && Match(cur->AsElement())) { michael@0: mElements.AppendElement(cur); michael@0: } michael@0: } michael@0: } michael@0: michael@0: ASSERT_IN_SYNC; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentList::ContentInserted(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: // Note that aContainer can be null here if we are inserting into michael@0: // the document itself; any attempted optimizations to this method michael@0: // should deal with that. michael@0: if (mState != LIST_DIRTY && michael@0: MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) && michael@0: nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) && michael@0: MatchSelf(aChild)) { michael@0: SetDirty(); michael@0: } michael@0: michael@0: ASSERT_IN_SYNC; michael@0: } michael@0: michael@0: void michael@0: nsContentList::ContentRemoved(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: // Note that aContainer can be null here if we are removing from michael@0: // the document itself; any attempted optimizations to this method michael@0: // should deal with that. michael@0: if (mState != LIST_DIRTY && michael@0: MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) && michael@0: nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) && michael@0: MatchSelf(aChild)) { michael@0: SetDirty(); michael@0: } michael@0: michael@0: ASSERT_IN_SYNC; michael@0: } michael@0: michael@0: bool michael@0: nsContentList::Match(Element *aElement) michael@0: { michael@0: if (mFunc) { michael@0: return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData); michael@0: } michael@0: michael@0: if (!mXMLMatchAtom) michael@0: return false; michael@0: michael@0: nsINodeInfo *ni = aElement->NodeInfo(); michael@0: michael@0: bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown; michael@0: bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard; michael@0: bool toReturn = mMatchAll; michael@0: if (!unknown && !wildcard) michael@0: toReturn &= ni->NamespaceEquals(mMatchNameSpaceId); michael@0: michael@0: if (toReturn) michael@0: return toReturn; michael@0: michael@0: bool matchHTML = aElement->GetNameSpaceID() == kNameSpaceID_XHTML && michael@0: aElement->OwnerDoc()->IsHTML(); michael@0: michael@0: if (unknown) { michael@0: return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) : michael@0: ni->QualifiedNameEquals(mXMLMatchAtom); michael@0: } michael@0: michael@0: if (wildcard) { michael@0: return matchHTML ? ni->Equals(mHTMLMatchAtom) : michael@0: ni->Equals(mXMLMatchAtom); michael@0: } michael@0: michael@0: return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId) : michael@0: ni->Equals(mXMLMatchAtom, mMatchNameSpaceId); michael@0: } michael@0: michael@0: bool michael@0: nsContentList::MatchSelf(nsIContent *aContent) michael@0: { michael@0: NS_PRECONDITION(aContent, "Can't match null stuff, you know"); michael@0: NS_PRECONDITION(mDeep || aContent->GetParentNode() == mRootNode, michael@0: "MatchSelf called on a node that we can't possibly match"); michael@0: michael@0: if (!aContent->IsElement()) { michael@0: return false; michael@0: } michael@0: michael@0: if (Match(aContent->AsElement())) michael@0: return true; michael@0: michael@0: if (!mDeep) michael@0: return false; michael@0: michael@0: for (nsIContent* cur = aContent->GetFirstChild(); michael@0: cur; michael@0: cur = cur->GetNextNode(aContent)) { michael@0: if (cur->IsElement() && Match(cur->AsElement())) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsContentList::PopulateSelf(uint32_t aNeededLength) michael@0: { michael@0: if (!mRootNode) { michael@0: return; michael@0: } michael@0: michael@0: ASSERT_IN_SYNC; michael@0: michael@0: uint32_t count = mElements.Length(); michael@0: NS_ASSERTION(mState != LIST_DIRTY || count == 0, michael@0: "Reset() not called when setting state to LIST_DIRTY?"); michael@0: michael@0: if (count >= aNeededLength) // We're all set michael@0: return; michael@0: michael@0: uint32_t elementsToAppend = aNeededLength - count; michael@0: #ifdef DEBUG michael@0: uint32_t invariant = elementsToAppend + mElements.Length(); michael@0: #endif michael@0: michael@0: if (mDeep) { michael@0: // If we already have nodes start searching at the last one, otherwise michael@0: // start searching at the root. michael@0: nsINode* cur = count ? mElements[count - 1] : mRootNode; michael@0: do { michael@0: cur = cur->GetNextNode(mRootNode); michael@0: if (!cur) { michael@0: break; michael@0: } michael@0: if (cur->IsElement() && Match(cur->AsElement())) { michael@0: // Append AsElement() to get nsIContent instead of nsINode michael@0: mElements.AppendElement(cur->AsElement()); michael@0: --elementsToAppend; michael@0: } michael@0: } while (elementsToAppend); michael@0: } else { michael@0: nsIContent* cur = michael@0: count ? mElements[count-1]->GetNextSibling() : mRootNode->GetFirstChild(); michael@0: for ( ; cur && elementsToAppend; cur = cur->GetNextSibling()) { michael@0: if (cur->IsElement() && Match(cur->AsElement())) { michael@0: mElements.AppendElement(cur); michael@0: --elementsToAppend; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(elementsToAppend + mElements.Length() == invariant, michael@0: "Something is awry!"); michael@0: michael@0: if (elementsToAppend != 0) michael@0: mState = LIST_UP_TO_DATE; michael@0: else michael@0: mState = LIST_LAZY; michael@0: michael@0: ASSERT_IN_SYNC; michael@0: } michael@0: michael@0: void michael@0: nsContentList::RemoveFromHashtable() michael@0: { michael@0: if (mFunc) { michael@0: // This can't be in the table anyway michael@0: return; michael@0: } michael@0: michael@0: nsDependentAtomString str(mXMLMatchAtom); michael@0: nsContentListKey key(mRootNode, mMatchNameSpaceId, str); michael@0: uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(key); michael@0: if (sRecentlyUsedContentLists[recentlyUsedCacheIndex] == this) { michael@0: sRecentlyUsedContentLists[recentlyUsedCacheIndex] = nullptr; michael@0: } michael@0: michael@0: if (!gContentListHashTable.ops) michael@0: return; michael@0: michael@0: PL_DHashTableOperate(&gContentListHashTable, michael@0: &key, michael@0: PL_DHASH_REMOVE); michael@0: michael@0: if (gContentListHashTable.entryCount == 0) { michael@0: PL_DHashTableFinish(&gContentListHashTable); michael@0: gContentListHashTable.ops = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsContentList::BringSelfUpToDate(bool aDoFlush) michael@0: { michael@0: if (mRootNode && aDoFlush && mFlushesNeeded) { michael@0: // XXX sXBL/XBL2 issue michael@0: nsIDocument* doc = mRootNode->GetCurrentDoc(); michael@0: if (doc) { michael@0: // Flush pending content changes Bug 4891. michael@0: doc->FlushPendingNotifications(Flush_ContentAndNotify); michael@0: } michael@0: } michael@0: michael@0: if (mState != LIST_UP_TO_DATE) michael@0: PopulateSelf(uint32_t(-1)); michael@0: michael@0: ASSERT_IN_SYNC; michael@0: NS_ASSERTION(!mRootNode || mState == LIST_UP_TO_DATE, michael@0: "PopulateSelf dod not bring content list up to date!"); michael@0: } michael@0: michael@0: nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList() michael@0: { michael@0: RemoveFromFuncStringHashtable(); michael@0: } michael@0: michael@0: void michael@0: nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable() michael@0: { michael@0: if (!gFuncStringContentListHashTable.ops) { michael@0: return; michael@0: } michael@0: michael@0: nsFuncStringCacheKey key(mRootNode, mFunc, mString); michael@0: PL_DHashTableOperate(&gFuncStringContentListHashTable, michael@0: &key, michael@0: PL_DHASH_REMOVE); michael@0: michael@0: if (gFuncStringContentListHashTable.entryCount == 0) { michael@0: PL_DHashTableFinish(&gFuncStringContentListHashTable); michael@0: gFuncStringContentListHashTable.ops = nullptr; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG_CONTENT_LIST michael@0: void michael@0: nsContentList::AssertInSync() michael@0: { michael@0: if (mState == LIST_DIRTY) { michael@0: return; michael@0: } michael@0: michael@0: if (!mRootNode) { michael@0: NS_ASSERTION(mElements.Length() == 0 && mState == LIST_DIRTY, michael@0: "Empty iterator isn't quite empty?"); michael@0: return; michael@0: } michael@0: michael@0: // XXX This code will need to change if nsContentLists can ever match michael@0: // elements that are outside of the document element. michael@0: nsIContent *root; michael@0: if (mRootNode->IsNodeOfType(nsINode::eDOCUMENT)) { michael@0: root = static_cast(mRootNode)->GetRootElement(); michael@0: } michael@0: else { michael@0: root = static_cast(mRootNode); michael@0: } michael@0: michael@0: nsCOMPtr iter; michael@0: if (mDeep) { michael@0: iter = NS_NewPreContentIterator(); michael@0: iter->Init(root); michael@0: iter->First(); michael@0: } michael@0: michael@0: uint32_t cnt = 0, index = 0; michael@0: while (true) { michael@0: if (cnt == mElements.Length() && mState == LIST_LAZY) { michael@0: break; michael@0: } michael@0: michael@0: nsIContent *cur = mDeep ? iter->GetCurrentNode() : michael@0: mRootNode->GetChildAt(index++); michael@0: if (!cur) { michael@0: break; michael@0: } michael@0: michael@0: if (cur->IsElement() && Match(cur->AsElement())) { michael@0: NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur, michael@0: "Elements is out of sync"); michael@0: ++cnt; michael@0: } michael@0: michael@0: if (mDeep) { michael@0: iter->Next(); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(cnt == mElements.Length(), "Too few elements"); michael@0: } michael@0: #endif