1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * nsBaseContentList is a basic list of content nodes; nsContentList
9 * is a commonly used NodeList implementation (used for
10 * getElementsByTagName, some properties on HTMLDocument/Document, etc).
13 #include "nsContentList.h"
14 #include "nsIContent.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/ContentIterator.h"
17 #include "mozilla/dom/Element.h"
18 #include "nsWrapperCacheInlines.h"
19 #include "nsContentUtils.h"
20 #include "nsCCUncollectableMarker.h"
21 #include "nsGkAtoms.h"
22 #include "mozilla/dom/HTMLCollectionBinding.h"
23 #include "mozilla/dom/NodeListBinding.h"
24 #include "mozilla/Likely.h"
25 #include "nsGenericHTMLElement.h"
26 #include "jsfriendapi.h"
28 #include "mozilla/dom/NodeInfoInlines.h"
29 #include "mozilla/MruCache.h"
30 #include "mozilla/StaticPtr.h"
32 #include "PLDHashTable.h"
33 #include "nsTHashtable.h"
35 #ifdef DEBUG_CONTENT_LIST
36 # define ASSERT_IN_SYNC AssertInSync()
38 # define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
41 using namespace mozilla
;
42 using namespace mozilla::dom
;
44 nsBaseContentList::~nsBaseContentList() = default;
46 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsBaseContentList
)
47 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList
)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements
)
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
50 tmp
->RemoveFromCaches();
51 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList
)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements
)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
56 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList
)
57 if (nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper()) {
58 for (uint32_t i
= 0; i
< tmp
->mElements
.Length(); ++i
) {
59 nsIContent
* c
= tmp
->mElements
[i
];
63 Element::MarkNodeChildren(c
);
67 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
69 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList
)
70 return nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper();
71 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
73 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList
)
74 return nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper();
75 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
77 // QueryInterface implementation for nsBaseContentList
78 NS_INTERFACE_TABLE_HEAD(nsBaseContentList
)
79 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
80 NS_INTERFACE_TABLE(nsBaseContentList
, nsINodeList
)
81 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList
)
84 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList
)
85 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsBaseContentList
,
88 nsIContent
* nsBaseContentList::Item(uint32_t aIndex
) {
89 return mElements
.SafeElementAt(aIndex
);
92 int32_t nsBaseContentList::IndexOf(nsIContent
* aContent
, bool aDoFlush
) {
93 return mElements
.IndexOf(aContent
);
96 int32_t nsBaseContentList::IndexOf(nsIContent
* aContent
) {
97 return IndexOf(aContent
, true);
100 size_t nsBaseContentList::SizeOfIncludingThis(
101 MallocSizeOf aMallocSizeOf
) const {
102 size_t n
= aMallocSizeOf(this);
103 n
+= mElements
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
107 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList
, nsBaseContentList
,
110 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSimpleContentList
)
111 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList
)
113 NS_IMPL_ADDREF_INHERITED(nsSimpleContentList
, nsBaseContentList
)
114 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList
, nsBaseContentList
)
116 JSObject
* nsSimpleContentList::WrapObject(JSContext
* cx
,
117 JS::Handle
<JSObject
*> aGivenProto
) {
118 return NodeList_Binding::Wrap(cx
, this, aGivenProto
);
121 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsEmptyContentList
, nsBaseContentList
, mRoot
)
123 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEmptyContentList
)
124 NS_INTERFACE_MAP_ENTRY(nsIHTMLCollection
)
125 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList
)
127 NS_IMPL_ADDREF_INHERITED(nsEmptyContentList
, nsBaseContentList
)
128 NS_IMPL_RELEASE_INHERITED(nsEmptyContentList
, nsBaseContentList
)
130 JSObject
* nsEmptyContentList::WrapObject(JSContext
* cx
,
131 JS::Handle
<JSObject
*> aGivenProto
) {
132 return HTMLCollection_Binding::Wrap(cx
, this, aGivenProto
);
135 mozilla::dom::Element
* nsEmptyContentList::GetElementAt(uint32_t index
) {
139 mozilla::dom::Element
* nsEmptyContentList::GetFirstNamedElement(
140 const nsAString
& aName
, bool& aFound
) {
145 void nsEmptyContentList::GetSupportedNames(nsTArray
<nsString
>& aNames
) {}
147 nsIContent
* nsEmptyContentList::Item(uint32_t aIndex
) { return nullptr; }
149 struct ContentListCache
150 : public MruCache
<nsContentListKey
, nsContentList
*, ContentListCache
> {
151 static HashNumber
Hash(const nsContentListKey
& aKey
) {
152 return aKey
.GetHash();
154 static bool Match(const nsContentListKey
& aKey
, const nsContentList
* aVal
) {
155 return aVal
->MatchesKey(aKey
);
159 static ContentListCache sRecentlyUsedContentLists
;
161 class nsContentList::HashEntry
: public PLDHashEntryHdr
{
163 using KeyType
= const nsContentListKey
*;
164 using KeyTypePointer
= KeyType
;
166 // Note that this is creating a blank entry, so you'll have to manually
167 // initialize it after it has been inserted into the hash table.
168 explicit HashEntry(KeyTypePointer aKey
) : mContentList(nullptr) {}
170 HashEntry(HashEntry
&& aEnt
) : mContentList(std::move(aEnt
.mContentList
)) {}
174 MOZ_RELEASE_ASSERT(mContentList
->mInHashtable
);
175 mContentList
->mInHashtable
= false;
179 bool KeyEquals(KeyTypePointer aKey
) const {
180 return mContentList
->MatchesKey(*aKey
);
183 static KeyTypePointer
KeyToPointer(KeyType aKey
) { return aKey
; }
185 static PLDHashNumber
HashKey(KeyTypePointer aKey
) { return aKey
->GetHash(); }
187 nsContentList
* GetContentList() const { return mContentList
; }
188 void SetContentList(nsContentList
* aContentList
) {
189 MOZ_RELEASE_ASSERT(!mContentList
);
190 MOZ_ASSERT(aContentList
);
191 MOZ_RELEASE_ASSERT(!aContentList
->mInHashtable
);
192 mContentList
= aContentList
;
193 mContentList
->mInHashtable
= true;
196 enum { ALLOW_MEMMOVE
= true };
199 nsContentList
* MOZ_UNSAFE_REF(
200 "This entry will be removed in nsContentList::RemoveFromHashtable "
201 "before mContentList is destroyed") mContentList
;
204 // Hashtable for storing nsContentLists
205 static StaticAutoPtr
<nsTHashtable
<nsContentList::HashEntry
>>
206 gContentListHashTable
;
208 already_AddRefed
<nsContentList
> NS_GetContentList(nsINode
* aRootNode
,
209 int32_t aMatchNameSpaceId
,
210 const nsAString
& aTagname
) {
211 NS_ASSERTION(aRootNode
, "content list has to have a root");
213 RefPtr
<nsContentList
> list
;
214 nsContentListKey
hashKey(aRootNode
, aMatchNameSpaceId
, aTagname
,
215 aRootNode
->OwnerDoc()->IsHTMLDocument());
216 auto p
= sRecentlyUsedContentLists
.Lookup(hashKey
);
219 return list
.forget();
222 // Initialize the hashtable if needed.
223 if (!gContentListHashTable
) {
224 gContentListHashTable
= new nsTHashtable
<nsContentList::HashEntry
>();
227 // First we look in our hashtable. Then we create a content list if needed
228 auto entry
= gContentListHashTable
->PutEntry(&hashKey
, fallible
);
230 list
= entry
->GetContentList();
234 // We need to create a ContentList and add it to our new entry, if
236 RefPtr
<nsAtom
> xmlAtom
= NS_Atomize(aTagname
);
237 RefPtr
<nsAtom
> htmlAtom
;
238 if (aMatchNameSpaceId
== kNameSpaceID_Unknown
) {
239 nsAutoString lowercaseName
;
240 nsContentUtils::ASCIIToLower(aTagname
, lowercaseName
);
241 htmlAtom
= NS_Atomize(lowercaseName
);
245 list
= new nsContentList(aRootNode
, aMatchNameSpaceId
, htmlAtom
, xmlAtom
);
247 entry
->SetContentList(list
);
252 return list
.forget();
256 const nsCacheableFuncStringContentList::ContentListType
257 nsCachableElementsByNameNodeList::sType
=
258 nsCacheableFuncStringContentList::eNodeList
;
259 const nsCacheableFuncStringContentList::ContentListType
260 nsCacheableFuncStringHTMLCollection::sType
=
261 nsCacheableFuncStringContentList::eHTMLCollection
;
264 class nsCacheableFuncStringContentList::HashEntry
: public PLDHashEntryHdr
{
266 using KeyType
= const nsFuncStringCacheKey
*;
267 using KeyTypePointer
= KeyType
;
269 // Note that this is creating a blank entry, so you'll have to manually
270 // initialize it after it has been inserted into the hash table.
271 explicit HashEntry(KeyTypePointer aKey
) : mContentList(nullptr) {}
273 HashEntry(HashEntry
&& aEnt
) : mContentList(std::move(aEnt
.mContentList
)) {}
277 MOZ_RELEASE_ASSERT(mContentList
->mInHashtable
);
278 mContentList
->mInHashtable
= false;
282 bool KeyEquals(KeyTypePointer aKey
) const {
283 return mContentList
->Equals(aKey
);
286 static KeyTypePointer
KeyToPointer(KeyType aKey
) { return aKey
; }
288 static PLDHashNumber
HashKey(KeyTypePointer aKey
) { return aKey
->GetHash(); }
290 nsCacheableFuncStringContentList
* GetContentList() const {
293 void SetContentList(nsCacheableFuncStringContentList
* aContentList
) {
294 MOZ_RELEASE_ASSERT(!mContentList
);
295 MOZ_ASSERT(aContentList
);
296 MOZ_RELEASE_ASSERT(!aContentList
->mInHashtable
);
297 mContentList
= aContentList
;
298 mContentList
->mInHashtable
= true;
301 enum { ALLOW_MEMMOVE
= true };
304 nsCacheableFuncStringContentList
* MOZ_UNSAFE_REF(
305 "This entry will be removed in "
306 "nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable "
307 "before mContentList is destroyed") mContentList
;
310 // Hashtable for storing nsCacheableFuncStringContentList
311 static StaticAutoPtr
<nsTHashtable
<nsCacheableFuncStringContentList::HashEntry
>>
312 gFuncStringContentListHashTable
;
314 template <class ListType
>
315 already_AddRefed
<nsContentList
> GetFuncStringContentList(
316 nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
317 nsContentListDestroyFunc aDestroyFunc
,
318 nsFuncStringContentListDataAllocator aDataAllocator
,
319 const nsAString
& aString
) {
320 NS_ASSERTION(aRootNode
, "content list has to have a root");
322 RefPtr
<nsCacheableFuncStringContentList
> list
;
324 // Initialize the hashtable if needed.
325 if (!gFuncStringContentListHashTable
) {
326 gFuncStringContentListHashTable
=
327 new nsTHashtable
<nsCacheableFuncStringContentList::HashEntry
>();
330 nsCacheableFuncStringContentList::HashEntry
* entry
= nullptr;
331 // First we look in our hashtable. Then we create a content list if needed
332 if (gFuncStringContentListHashTable
) {
333 nsFuncStringCacheKey
hashKey(aRootNode
, aFunc
, aString
);
335 entry
= gFuncStringContentListHashTable
->PutEntry(&hashKey
, fallible
);
337 list
= entry
->GetContentList();
339 MOZ_ASSERT_IF(list
, list
->mType
== ListType::sType
);
345 // We need to create a ContentList and add it to our new entry, if
348 new ListType(aRootNode
, aFunc
, aDestroyFunc
, aDataAllocator
, aString
);
350 entry
->SetContentList(list
);
354 // Don't cache these lists globally
356 return list
.forget();
359 // Explicit instantiations to avoid link errors
360 template already_AddRefed
<nsContentList
>
361 GetFuncStringContentList
<nsCachableElementsByNameNodeList
>(
362 nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
363 nsContentListDestroyFunc aDestroyFunc
,
364 nsFuncStringContentListDataAllocator aDataAllocator
,
365 const nsAString
& aString
);
366 template already_AddRefed
<nsContentList
>
367 GetFuncStringContentList
<nsCacheableFuncStringHTMLCollection
>(
368 nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
369 nsContentListDestroyFunc aDestroyFunc
,
370 nsFuncStringContentListDataAllocator aDataAllocator
,
371 const nsAString
& aString
);
373 //-----------------------------------------------------
374 // nsContentList implementation
376 nsContentList::nsContentList(nsINode
* aRootNode
, int32_t aMatchNameSpaceId
,
377 nsAtom
* aHTMLMatchAtom
, nsAtom
* aXMLMatchAtom
,
378 bool aDeep
, bool aLiveList
)
379 : nsBaseContentList(),
380 mRootNode(aRootNode
),
381 mMatchNameSpaceId(aMatchNameSpaceId
),
382 mHTMLMatchAtom(aHTMLMatchAtom
),
383 mXMLMatchAtom(aXMLMatchAtom
),
384 mState(State::Dirty
),
386 mFuncMayDependOnAttr(false),
387 mIsHTMLDocument(aRootNode
->OwnerDoc()->IsHTMLDocument()),
388 mNamedItemsCacheValid(false),
389 mIsLiveList(aLiveList
),
390 mInHashtable(false) {
391 NS_ASSERTION(mRootNode
, "Must have root");
392 if (nsGkAtoms::_asterisk
== mHTMLMatchAtom
) {
393 NS_ASSERTION(mXMLMatchAtom
== nsGkAtoms::_asterisk
,
394 "HTML atom and XML atom are not both asterisk?");
399 // This is aLiveList instead of mIsLiveList to avoid Valgrind errors.
401 SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed
);
402 mRootNode
->AddMutationObserver(this);
405 // We only need to flush if we're in an non-HTML document, since the
406 // HTML5 parser doesn't need flushing. Further, if we're not in a
407 // document at all right now (in the GetUncomposedDoc() sense), we're
408 // not parser-created and don't need to be flushing stuff under us
409 // to get our kids right.
410 Document
* doc
= mRootNode
->GetUncomposedDoc();
411 mFlushesNeeded
= doc
&& !doc
->IsHTMLDocument();
414 nsContentList::nsContentList(nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
415 nsContentListDestroyFunc aDestroyFunc
, void* aData
,
416 bool aDeep
, nsAtom
* aMatchAtom
,
417 int32_t aMatchNameSpaceId
,
418 bool aFuncMayDependOnAttr
, bool aLiveList
)
419 : nsBaseContentList(),
420 mRootNode(aRootNode
),
421 mMatchNameSpaceId(aMatchNameSpaceId
),
422 mHTMLMatchAtom(aMatchAtom
),
423 mXMLMatchAtom(aMatchAtom
),
425 mDestroyFunc(aDestroyFunc
),
427 mState(State::Dirty
),
430 mFuncMayDependOnAttr(aFuncMayDependOnAttr
),
431 mIsHTMLDocument(false),
432 mNamedItemsCacheValid(false),
433 mIsLiveList(aLiveList
),
434 mInHashtable(false) {
435 NS_ASSERTION(mRootNode
, "Must have root");
436 // This is aLiveList instead of mIsLiveList to avoid Valgrind errors.
438 SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed
);
439 mRootNode
->AddMutationObserver(this);
442 // We only need to flush if we're in an non-HTML document, since the
443 // HTML5 parser doesn't need flushing. Further, if we're not in a
444 // document at all right now (in the GetUncomposedDoc() sense), we're
445 // not parser-created and don't need to be flushing stuff under us
446 // to get our kids right.
447 Document
* doc
= mRootNode
->GetUncomposedDoc();
448 mFlushesNeeded
= doc
&& !doc
->IsHTMLDocument();
451 nsContentList::~nsContentList() {
452 RemoveFromHashtable();
453 if (mIsLiveList
&& mRootNode
) {
454 mRootNode
->RemoveMutationObserver(this);
459 (*mDestroyFunc
)(mData
);
463 JSObject
* nsContentList::WrapObject(JSContext
* cx
,
464 JS::Handle
<JSObject
*> aGivenProto
) {
465 return HTMLCollection_Binding::Wrap(cx
, this, aGivenProto
);
468 NS_IMPL_ISUPPORTS_INHERITED(nsContentList
, nsBaseContentList
, nsIHTMLCollection
,
471 uint32_t nsContentList::Length(bool aDoFlush
) {
472 BringSelfUpToDate(aDoFlush
);
474 return mElements
.Length();
477 nsIContent
* nsContentList::Item(uint32_t aIndex
, bool aDoFlush
) {
478 if (mRootNode
&& aDoFlush
&& mFlushesNeeded
) {
479 // XXX sXBL/XBL2 issue
480 Document
* doc
= mRootNode
->GetUncomposedDoc();
482 // Flush pending content changes Bug 4891.
483 doc
->FlushPendingNotifications(FlushType::ContentAndNotify
);
487 if (mState
!= State::UpToDate
) {
488 PopulateSelf(std::min(aIndex
, UINT32_MAX
- 1) + 1);
492 NS_ASSERTION(!mRootNode
|| mState
!= State::Dirty
,
493 "PopulateSelf left the list in a dirty (useless) state!");
495 return mElements
.SafeElementAt(aIndex
);
498 inline void nsContentList::InsertElementInNamedItemsCache(
499 nsIContent
& aContent
) {
500 const bool hasName
= aContent
.HasName();
501 const bool hasId
= aContent
.HasID();
502 if (!hasName
&& !hasId
) {
506 Element
* el
= aContent
.AsElement();
507 MOZ_ASSERT_IF(hasName
, el
->IsHTMLElement());
510 while (BorrowedAttrInfo info
= el
->GetAttrInfoAt(i
++)) {
511 const bool valid
= (info
.mName
->Equals(nsGkAtoms::name
) && hasName
) ||
512 (info
.mName
->Equals(nsGkAtoms::id
) && hasId
);
517 if (!mNamedItemsCache
) {
518 mNamedItemsCache
= MakeUnique
<NamedItemsCache
>();
521 nsAtom
* name
= info
.mValue
->GetAtomValue();
522 // NOTE: LookupOrInsert makes sure we keep the first element we find for a
524 mNamedItemsCache
->LookupOrInsert(name
, el
);
528 inline void nsContentList::InvalidateNamedItemsCacheForAttributeChange(
529 int32_t aNamespaceID
, nsAtom
* aAttribute
) {
530 if (!mNamedItemsCacheValid
) {
533 if ((aAttribute
== nsGkAtoms::id
|| aAttribute
== nsGkAtoms::name
) &&
534 aNamespaceID
== kNameSpaceID_None
) {
535 InvalidateNamedItemsCache();
539 inline void nsContentList::InvalidateNamedItemsCacheForInsertion(
541 if (!mNamedItemsCacheValid
) {
545 InsertElementInNamedItemsCache(aElement
);
548 inline void nsContentList::InvalidateNamedItemsCacheForDeletion(
550 if (!mNamedItemsCacheValid
) {
553 if (aElement
.HasName() || aElement
.HasID()) {
554 InvalidateNamedItemsCache();
558 void nsContentList::EnsureNamedItemsCacheValid(bool aDoFlush
) {
559 BringSelfUpToDate(aDoFlush
);
561 if (mNamedItemsCacheValid
) {
565 MOZ_ASSERT(!mNamedItemsCache
);
567 // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key
568 // XXX: Blink/WebKit don't follow the spec here, and searches first-by-id,
570 for (const nsCOMPtr
<nsIContent
>& content
: mElements
) {
571 InsertElementInNamedItemsCache(*content
);
574 mNamedItemsCacheValid
= true;
577 Element
* nsContentList::NamedItem(const nsAString
& aName
, bool aDoFlush
) {
578 if (aName
.IsEmpty()) {
582 EnsureNamedItemsCacheValid(aDoFlush
);
584 if (!mNamedItemsCache
) {
588 // Typically IDs and names are atomized
589 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
590 NS_ENSURE_TRUE(name
, nullptr);
592 return mNamedItemsCache
->Get(name
);
595 void nsContentList::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
596 BringSelfUpToDate(true);
598 AutoTArray
<nsAtom
*, 8> atoms
;
599 for (uint32_t i
= 0; i
< mElements
.Length(); ++i
) {
600 nsIContent
* content
= mElements
.ElementAt(i
);
601 if (content
->HasID()) {
602 nsAtom
* id
= content
->GetID();
603 MOZ_ASSERT(id
!= nsGkAtoms::_empty
, "Empty ids don't get atomized");
604 if (!atoms
.Contains(id
)) {
605 atoms
.AppendElement(id
);
609 nsGenericHTMLElement
* el
= nsGenericHTMLElement::FromNode(content
);
611 // XXXbz should we be checking for particular tags here? How
612 // stable is this part of the spec?
613 // Note: nsINode::HasName means the name is exposed on the document,
614 // which is false for options, so we don't check it here.
615 const nsAttrValue
* val
= el
->GetParsedAttr(nsGkAtoms::name
);
616 if (val
&& val
->Type() == nsAttrValue::eAtom
) {
617 nsAtom
* name
= val
->GetAtomValue();
618 MOZ_ASSERT(name
!= nsGkAtoms::_empty
, "Empty names don't get atomized");
619 if (!atoms
.Contains(name
)) {
620 atoms
.AppendElement(name
);
626 uint32_t atomsLen
= atoms
.Length();
627 nsString
* names
= aNames
.AppendElements(atomsLen
);
628 for (uint32_t i
= 0; i
< atomsLen
; ++i
) {
629 atoms
[i
]->ToString(names
[i
]);
633 int32_t nsContentList::IndexOf(nsIContent
* aContent
, bool aDoFlush
) {
634 BringSelfUpToDate(aDoFlush
);
636 return mElements
.IndexOf(aContent
);
639 int32_t nsContentList::IndexOf(nsIContent
* aContent
) {
640 return IndexOf(aContent
, true);
643 void nsContentList::NodeWillBeDestroyed(nsINode
* aNode
) {
644 // We shouldn't do anything useful from now on
649 // We will get no more updates, so we can never know we're up to
654 void nsContentList::LastRelease() {
656 if (mIsLiveList
&& mRootNode
) {
657 mRootNode
->RemoveMutationObserver(this);
663 Element
* nsContentList::GetElementAt(uint32_t aIndex
) {
664 return static_cast<Element
*>(Item(aIndex
, true));
667 nsIContent
* nsContentList::Item(uint32_t aIndex
) {
668 return GetElementAt(aIndex
);
671 void nsContentList::AttributeChanged(Element
* aElement
, int32_t aNameSpaceID
,
672 nsAtom
* aAttribute
, int32_t aModType
,
673 const nsAttrValue
* aOldValue
) {
674 MOZ_ASSERT(aElement
, "Must have a content node to work with");
676 if (mState
== State::Dirty
||
677 !MayContainRelevantNodes(aElement
->GetParentNode()) ||
678 !nsContentUtils::IsInSameAnonymousTree(mRootNode
, aElement
)) {
679 // Either we're already dirty or aElement will never match us.
683 InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID
, aAttribute
);
685 if (!mFunc
|| !mFuncMayDependOnAttr
) {
686 // aElement might be relevant but the attribute change doesn't affect
687 // whether we match it.
691 if (Match(aElement
)) {
692 if (mElements
.IndexOf(aElement
) == mElements
.NoIndex
) {
693 // We match aElement now, and it's not in our list already. Just dirty
694 // ourselves; this is simpler than trying to figure out where to insert
699 // We no longer match aElement. Remove it from our list. If it's
700 // already not there, this is a no-op (though a potentially
701 // expensive one). Either way, no change of mState is required
703 if (mElements
.RemoveElement(aElement
)) {
704 InvalidateNamedItemsCacheForDeletion(*aElement
);
709 void nsContentList::ContentAppended(nsIContent
* aFirstNewContent
) {
710 nsIContent
* container
= aFirstNewContent
->GetParent();
711 MOZ_ASSERT(container
, "Can't get at the new content if no container!");
714 * If the state is State::Dirty then we have no useful information in our list
715 * and we want to put off doing work as much as possible.
717 * Also, if container is anonymous from our point of view, we know that we
718 * can't possibly be matching any of the kids.
720 * Optimize out also the common case when just one new node is appended and
721 * it doesn't match us.
723 if (mState
== State::Dirty
||
724 !nsContentUtils::IsInSameAnonymousTree(mRootNode
, container
) ||
725 !MayContainRelevantNodes(container
) ||
726 (!aFirstNewContent
->HasChildren() &&
727 !aFirstNewContent
->GetNextSibling() && !MatchSelf(aFirstNewContent
))) {
733 * We want to handle the case of ContentAppended by sometimes
734 * appending the content to our list, not just setting state to
735 * State::Dirty, since most of our ContentAppended notifications
736 * should come during pageload and be at the end of the document.
737 * Do a bit of work to see whether we could just append to what we
741 uint32_t ourCount
= mElements
.Length();
742 const bool appendingToList
= [&] {
746 if (mRootNode
== container
) {
749 return nsContentUtils::PositionIsBefore(mElements
.LastElement(),
753 if (!appendingToList
) {
754 // The new stuff is somewhere in the middle of our list; check
755 // whether we need to invalidate
756 for (nsIContent
* cur
= aFirstNewContent
; cur
; cur
= cur
->GetNextSibling()) {
757 if (MatchSelf(cur
)) {
758 // Uh-oh. We're gonna have to add elements into the middle
759 // of our list. That's not worth the effort.
770 * At this point we know we could append. If we're not up to
771 * date, however, that would be a bad idea -- it could miss some
772 * content that we never picked up due to being lazy. Further, we
773 * may never get asked for this content... so don't grab it yet.
775 if (mState
== State::Lazy
) {
780 * We're up to date. That means someone's actively using us; we
781 * may as well grab this content....
784 for (nsIContent
* cur
= aFirstNewContent
; cur
;
785 cur
= cur
->GetNextNode(container
)) {
786 if (cur
->IsElement() && Match(cur
->AsElement())) {
787 mElements
.AppendElement(cur
);
788 InvalidateNamedItemsCacheForInsertion(*cur
->AsElement());
792 for (nsIContent
* cur
= aFirstNewContent
; cur
; cur
= cur
->GetNextSibling()) {
793 if (cur
->IsElement() && Match(cur
->AsElement())) {
794 mElements
.AppendElement(cur
);
795 InvalidateNamedItemsCacheForInsertion(*cur
->AsElement());
803 void nsContentList::ContentInserted(nsIContent
* aChild
) {
804 // Note that aChild->GetParentNode() can be null here if we are inserting into
805 // the document itself; any attempted optimizations to this method should deal
807 if (mState
!= State::Dirty
&&
808 MayContainRelevantNodes(aChild
->GetParentNode()) &&
809 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
) &&
817 void nsContentList::ContentRemoved(nsIContent
* aChild
,
818 nsIContent
* aPreviousSibling
) {
819 if (mState
!= State::Dirty
&&
820 MayContainRelevantNodes(aChild
->GetParentNode()) &&
821 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
) &&
829 bool nsContentList::Match(Element
* aElement
) {
831 return (*mFunc
)(aElement
, mMatchNameSpaceId
, mXMLMatchAtom
, mData
);
834 if (!mXMLMatchAtom
) return false;
836 NodeInfo
* ni
= aElement
->NodeInfo();
838 bool unknown
= mMatchNameSpaceId
== kNameSpaceID_Unknown
;
839 bool wildcard
= mMatchNameSpaceId
== kNameSpaceID_Wildcard
;
840 bool toReturn
= mMatchAll
;
841 if (!unknown
&& !wildcard
) toReturn
&= ni
->NamespaceEquals(mMatchNameSpaceId
);
843 if (toReturn
) return toReturn
;
846 mIsHTMLDocument
&& aElement
->GetNameSpaceID() == kNameSpaceID_XHTML
;
849 return matchHTML
? ni
->QualifiedNameEquals(mHTMLMatchAtom
)
850 : ni
->QualifiedNameEquals(mXMLMatchAtom
);
854 return matchHTML
? ni
->Equals(mHTMLMatchAtom
) : ni
->Equals(mXMLMatchAtom
);
857 return matchHTML
? ni
->Equals(mHTMLMatchAtom
, mMatchNameSpaceId
)
858 : ni
->Equals(mXMLMatchAtom
, mMatchNameSpaceId
);
861 bool nsContentList::MatchSelf(nsIContent
* aContent
) {
862 MOZ_ASSERT(aContent
, "Can't match null stuff, you know");
863 MOZ_ASSERT(mDeep
|| aContent
->GetParentNode() == mRootNode
,
864 "MatchSelf called on a node that we can't possibly match");
866 if (!aContent
->IsElement()) {
870 if (Match(aContent
->AsElement())) return true;
872 if (!mDeep
) return false;
874 for (nsIContent
* cur
= aContent
->GetFirstChild(); cur
;
875 cur
= cur
->GetNextNode(aContent
)) {
876 if (cur
->IsElement() && Match(cur
->AsElement())) {
884 void nsContentList::PopulateSelf(uint32_t aNeededLength
,
885 uint32_t aExpectedElementsIfDirty
) {
892 uint32_t count
= mElements
.Length();
893 NS_ASSERTION(mState
!= State::Dirty
|| count
== aExpectedElementsIfDirty
,
894 "Reset() not called when setting state to State::Dirty?");
896 if (count
>= aNeededLength
) // We're all set
899 uint32_t elementsToAppend
= aNeededLength
- count
;
901 uint32_t invariant
= elementsToAppend
+ mElements
.Length();
905 // If we already have nodes start searching at the last one, otherwise
906 // start searching at the root.
907 nsINode
* cur
= count
? mElements
[count
- 1].get() : mRootNode
;
909 cur
= cur
->GetNextNode(mRootNode
);
913 if (cur
->IsElement() && Match(cur
->AsElement())) {
914 // Append AsElement() to get nsIContent instead of nsINode
915 mElements
.AppendElement(cur
->AsElement());
918 } while (elementsToAppend
);
920 nsIContent
* cur
= count
? mElements
[count
- 1]->GetNextSibling()
921 : mRootNode
->GetFirstChild();
922 for (; cur
&& elementsToAppend
; cur
= cur
->GetNextSibling()) {
923 if (cur
->IsElement() && Match(cur
->AsElement())) {
924 mElements
.AppendElement(cur
);
930 NS_ASSERTION(elementsToAppend
+ mElements
.Length() == invariant
,
931 "Something is awry!");
933 if (elementsToAppend
!= 0) {
934 mState
= State::UpToDate
;
936 mState
= State::Lazy
;
939 SetEnabledCallbacks(nsIMutationObserver::kAll
);
944 void nsContentList::RemoveFromHashtable() {
946 // nsCacheableFuncStringContentList can be in a hash table without being
947 // in gContentListHashTable, but it will have been removed from the hash
948 // table in its dtor before it runs the nsContentList dtor.
949 MOZ_RELEASE_ASSERT(!mInHashtable
);
951 // This can't be in gContentListHashTable.
955 nsDependentAtomString
str(mXMLMatchAtom
);
956 nsContentListKey
key(mRootNode
, mMatchNameSpaceId
, str
, mIsHTMLDocument
);
957 sRecentlyUsedContentLists
.Remove(key
);
959 if (gContentListHashTable
) {
960 gContentListHashTable
->RemoveEntry(&key
);
962 if (gContentListHashTable
->Count() == 0) {
963 gContentListHashTable
= nullptr;
967 MOZ_RELEASE_ASSERT(!mInHashtable
);
970 void nsContentList::BringSelfUpToDate(bool aDoFlush
) {
971 if (mFlushesNeeded
&& mRootNode
&& aDoFlush
) {
972 // XXX sXBL/XBL2 issue
973 if (Document
* doc
= mRootNode
->GetUncomposedDoc()) {
974 // Flush pending content changes Bug 4891.
975 doc
->FlushPendingNotifications(FlushType::ContentAndNotify
);
979 if (mState
!= State::UpToDate
) {
980 PopulateSelf(uint32_t(-1));
986 NS_ASSERTION(!mRootNode
|| mState
== State::UpToDate
,
987 "PopulateSelf dod not bring content list up to date!");
990 nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList() {
991 RemoveFromFuncStringHashtable();
994 void nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable() {
995 if (!gFuncStringContentListHashTable
) {
996 MOZ_RELEASE_ASSERT(!mInHashtable
);
1000 nsFuncStringCacheKey
key(mRootNode
, mFunc
, mString
);
1001 gFuncStringContentListHashTable
->RemoveEntry(&key
);
1003 if (gFuncStringContentListHashTable
->Count() == 0) {
1004 gFuncStringContentListHashTable
= nullptr;
1007 MOZ_RELEASE_ASSERT(!mInHashtable
);
1010 #ifdef DEBUG_CONTENT_LIST
1011 void nsContentList::AssertInSync() {
1012 if (mState
== State::Dirty
) {
1017 NS_ASSERTION(mElements
.Length() == 0 && mState
== State::Dirty
,
1018 "Empty iterator isn't quite empty?");
1022 // XXX This code will need to change if nsContentLists can ever match
1023 // elements that are outside of the document element.
1024 nsIContent
* root
= mRootNode
->IsDocument()
1025 ? mRootNode
->AsDocument()->GetRootElement()
1026 : mRootNode
->AsContent();
1028 PreContentIterator preOrderIter
;
1030 preOrderIter
.Init(root
);
1031 preOrderIter
.First();
1034 uint32_t cnt
= 0, index
= 0;
1036 if (cnt
== mElements
.Length() && mState
== State::Lazy
) {
1041 mDeep
? preOrderIter
.GetCurrentNode() : mRootNode
->GetChildAt(index
++);
1046 if (cur
->IsElement() && Match(cur
->AsElement())) {
1047 NS_ASSERTION(cnt
< mElements
.Length() && mElements
[cnt
] == cur
,
1048 "Elements is out of sync");
1053 preOrderIter
.Next();
1057 NS_ASSERTION(cnt
== mElements
.Length(), "Too few elements");
1061 //-----------------------------------------------------
1062 // nsCachableElementsByNameNodeList
1064 JSObject
* nsCachableElementsByNameNodeList::WrapObject(
1065 JSContext
* cx
, JS::Handle
<JSObject
*> aGivenProto
) {
1066 return NodeList_Binding::Wrap(cx
, this, aGivenProto
);
1069 void nsCachableElementsByNameNodeList::AttributeChanged(
1070 Element
* aElement
, int32_t aNameSpaceID
, nsAtom
* aAttribute
,
1071 int32_t aModType
, const nsAttrValue
* aOldValue
) {
1072 // No need to rebuild the list if the changed attribute is not the name
1074 if (aAttribute
!= nsGkAtoms::name
) {
1075 InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID
, aAttribute
);
1079 nsCacheableFuncStringContentList::AttributeChanged(
1080 aElement
, aNameSpaceID
, aAttribute
, aModType
, aOldValue
);
1083 //-----------------------------------------------------
1084 // nsCacheableFuncStringHTMLCollection
1086 JSObject
* nsCacheableFuncStringHTMLCollection::WrapObject(
1087 JSContext
* cx
, JS::Handle
<JSObject
*> aGivenProto
) {
1088 return HTMLCollection_Binding::Wrap(cx
, this, aGivenProto
);
1091 //-----------------------------------------------------
1094 JSObject
* nsLabelsNodeList::WrapObject(JSContext
* cx
,
1095 JS::Handle
<JSObject
*> aGivenProto
) {
1096 return NodeList_Binding::Wrap(cx
, this, aGivenProto
);
1099 void nsLabelsNodeList::AttributeChanged(Element
* aElement
, int32_t aNameSpaceID
,
1100 nsAtom
* aAttribute
, int32_t aModType
,
1101 const nsAttrValue
* aOldValue
) {
1102 MOZ_ASSERT(aElement
, "Must have a content node to work with");
1103 if (mState
== State::Dirty
||
1104 !nsContentUtils::IsInSameAnonymousTree(mRootNode
, aElement
)) {
1108 InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID
, aAttribute
);
1110 // We need to handle input type changes to or from "hidden".
1111 if (aElement
->IsHTMLElement(nsGkAtoms::input
) &&
1112 aAttribute
== nsGkAtoms::type
&& aNameSpaceID
== kNameSpaceID_None
) {
1118 void nsLabelsNodeList::ContentAppended(nsIContent
* aFirstNewContent
) {
1119 nsIContent
* container
= aFirstNewContent
->GetParent();
1120 // If a labelable element is moved to outside or inside of
1121 // nested associated labels, we're gonna have to modify
1122 // the content list.
1123 if (mState
!= State::Dirty
||
1124 nsContentUtils::IsInSameAnonymousTree(mRootNode
, container
)) {
1130 void nsLabelsNodeList::ContentInserted(nsIContent
* aChild
) {
1131 // If a labelable element is moved to outside or inside of
1132 // nested associated labels, we're gonna have to modify
1133 // the content list.
1134 if (mState
!= State::Dirty
||
1135 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
)) {
1141 void nsLabelsNodeList::ContentRemoved(nsIContent
* aChild
,
1142 nsIContent
* aPreviousSibling
) {
1143 // If a labelable element is removed, we're gonna have to clean
1144 // the content list.
1145 if (mState
!= State::Dirty
||
1146 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
)) {
1152 void nsLabelsNodeList::MaybeResetRoot(nsINode
* aRootNode
) {
1153 MOZ_ASSERT(aRootNode
, "Must have root");
1154 if (mRootNode
== aRootNode
) {
1158 MOZ_ASSERT(mIsLiveList
, "nsLabelsNodeList is always a live list");
1160 mRootNode
->RemoveMutationObserver(this);
1162 mRootNode
= aRootNode
;
1163 mRootNode
->AddMutationObserver(this);
1167 void nsLabelsNodeList::PopulateSelf(uint32_t aNeededLength
,
1168 uint32_t aExpectedElementsIfDirty
) {
1173 // Start searching at the root.
1174 nsINode
* cur
= mRootNode
;
1175 if (mElements
.IsEmpty() && cur
->IsElement() && Match(cur
->AsElement())) {
1176 mElements
.AppendElement(cur
->AsElement());
1177 ++aExpectedElementsIfDirty
;
1180 nsContentList::PopulateSelf(aNeededLength
, aExpectedElementsIfDirty
);