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"
31 #include "PLDHashTable.h"
33 #ifdef DEBUG_CONTENT_LIST
34 # define ASSERT_IN_SYNC AssertInSync()
36 # define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
39 using namespace mozilla
;
40 using namespace mozilla::dom
;
42 nsBaseContentList::~nsBaseContentList() = default;
44 NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList
)
45 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList
)
46 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements
)
47 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
48 tmp
->RemoveFromCaches();
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList
)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements
)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
53 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsBaseContentList
)
55 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList
)
56 if (nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper()) {
57 for (uint32_t i
= 0; i
< tmp
->mElements
.Length(); ++i
) {
58 nsIContent
* c
= tmp
->mElements
[i
];
62 Element::MarkNodeChildren(c
);
66 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
68 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList
)
69 return nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper();
70 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
72 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList
)
73 return nsCCUncollectableMarker::sGeneration
&& tmp
->HasKnownLiveWrapper();
74 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
76 // QueryInterface implementation for nsBaseContentList
77 NS_INTERFACE_TABLE_HEAD(nsBaseContentList
)
78 NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
79 NS_INTERFACE_TABLE(nsBaseContentList
, nsINodeList
)
80 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList
)
83 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList
)
84 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsBaseContentList
,
87 nsIContent
* nsBaseContentList::Item(uint32_t aIndex
) {
88 return mElements
.SafeElementAt(aIndex
);
91 int32_t nsBaseContentList::IndexOf(nsIContent
* aContent
, bool aDoFlush
) {
92 return mElements
.IndexOf(aContent
);
95 int32_t nsBaseContentList::IndexOf(nsIContent
* aContent
) {
96 return IndexOf(aContent
, true);
99 size_t nsBaseContentList::SizeOfIncludingThis(
100 MallocSizeOf aMallocSizeOf
) const {
101 size_t n
= aMallocSizeOf(this);
102 n
+= mElements
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
106 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList
, nsBaseContentList
,
109 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSimpleContentList
)
110 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList
)
112 NS_IMPL_ADDREF_INHERITED(nsSimpleContentList
, nsBaseContentList
)
113 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList
, nsBaseContentList
)
115 JSObject
* nsSimpleContentList::WrapObject(JSContext
* cx
,
116 JS::Handle
<JSObject
*> aGivenProto
) {
117 return NodeList_Binding::Wrap(cx
, this, aGivenProto
);
120 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsEmptyContentList
, nsBaseContentList
, mRoot
)
122 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEmptyContentList
)
123 NS_INTERFACE_MAP_ENTRY(nsIHTMLCollection
)
124 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList
)
126 NS_IMPL_ADDREF_INHERITED(nsEmptyContentList
, nsBaseContentList
)
127 NS_IMPL_RELEASE_INHERITED(nsEmptyContentList
, nsBaseContentList
)
129 JSObject
* nsEmptyContentList::WrapObject(JSContext
* cx
,
130 JS::Handle
<JSObject
*> aGivenProto
) {
131 return HTMLCollection_Binding::Wrap(cx
, this, aGivenProto
);
134 mozilla::dom::Element
* nsEmptyContentList::GetElementAt(uint32_t index
) {
138 mozilla::dom::Element
* nsEmptyContentList::GetFirstNamedElement(
139 const nsAString
& aName
, bool& aFound
) {
144 void nsEmptyContentList::GetSupportedNames(nsTArray
<nsString
>& aNames
) {}
146 nsIContent
* nsEmptyContentList::Item(uint32_t aIndex
) { return nullptr; }
148 // Hashtable for storing nsContentLists
149 static PLDHashTable
* gContentListHashTable
;
151 struct ContentListCache
152 : public MruCache
<nsContentListKey
, nsContentList
*, ContentListCache
> {
153 static HashNumber
Hash(const nsContentListKey
& aKey
) {
154 return aKey
.GetHash();
156 static bool Match(const nsContentListKey
& aKey
, const nsContentList
* aVal
) {
157 return aVal
->MatchesKey(aKey
);
161 static ContentListCache sRecentlyUsedContentLists
;
163 struct ContentListHashEntry
: public PLDHashEntryHdr
{
164 nsContentList
* mContentList
;
167 static PLDHashNumber
ContentListHashtableHashKey(const void* key
) {
168 const nsContentListKey
* list
= static_cast<const nsContentListKey
*>(key
);
169 return list
->GetHash();
172 static bool ContentListHashtableMatchEntry(const PLDHashEntryHdr
* entry
,
174 const ContentListHashEntry
* e
=
175 static_cast<const ContentListHashEntry
*>(entry
);
176 const nsContentList
* list
= e
->mContentList
;
177 const nsContentListKey
* ourKey
= static_cast<const nsContentListKey
*>(key
);
179 return list
->MatchesKey(*ourKey
);
182 already_AddRefed
<nsContentList
> NS_GetContentList(nsINode
* aRootNode
,
183 int32_t aMatchNameSpaceId
,
184 const nsAString
& aTagname
) {
185 NS_ASSERTION(aRootNode
, "content list has to have a root");
187 RefPtr
<nsContentList
> list
;
188 nsContentListKey
hashKey(aRootNode
, aMatchNameSpaceId
, aTagname
,
189 aRootNode
->OwnerDoc()->IsHTMLDocument());
190 auto p
= sRecentlyUsedContentLists
.Lookup(hashKey
);
193 return list
.forget();
196 static const PLDHashTableOps hash_table_ops
= {
197 ContentListHashtableHashKey
, ContentListHashtableMatchEntry
,
198 PLDHashTable::MoveEntryStub
, PLDHashTable::ClearEntryStub
};
200 // Initialize the hashtable if needed.
201 if (!gContentListHashTable
) {
202 gContentListHashTable
=
203 new PLDHashTable(&hash_table_ops
, sizeof(ContentListHashEntry
));
206 // First we look in our hashtable. Then we create a content list if needed
207 auto entry
= static_cast<ContentListHashEntry
*>(
208 gContentListHashTable
->Add(&hashKey
, fallible
));
209 if (entry
) list
= entry
->mContentList
;
212 // We need to create a ContentList and add it to our new entry, if
214 RefPtr
<nsAtom
> xmlAtom
= NS_Atomize(aTagname
);
215 RefPtr
<nsAtom
> htmlAtom
;
216 if (aMatchNameSpaceId
== kNameSpaceID_Unknown
) {
217 nsAutoString lowercaseName
;
218 nsContentUtils::ASCIIToLower(aTagname
, lowercaseName
);
219 htmlAtom
= NS_Atomize(lowercaseName
);
223 list
= new nsContentList(aRootNode
, aMatchNameSpaceId
, htmlAtom
, xmlAtom
);
225 entry
->mContentList
= list
;
230 return list
.forget();
234 const nsCacheableFuncStringContentList::ContentListType
235 nsCachableElementsByNameNodeList::sType
=
236 nsCacheableFuncStringContentList::eNodeList
;
237 const nsCacheableFuncStringContentList::ContentListType
238 nsCacheableFuncStringHTMLCollection::sType
=
239 nsCacheableFuncStringContentList::eHTMLCollection
;
242 // Hashtable for storing nsCacheableFuncStringContentList
243 static PLDHashTable
* gFuncStringContentListHashTable
;
245 struct FuncStringContentListHashEntry
: public PLDHashEntryHdr
{
246 nsCacheableFuncStringContentList
* mContentList
;
249 static PLDHashNumber
FuncStringContentListHashtableHashKey(const void* key
) {
250 const nsFuncStringCacheKey
* funcStringKey
=
251 static_cast<const nsFuncStringCacheKey
*>(key
);
252 return funcStringKey
->GetHash();
255 static bool FuncStringContentListHashtableMatchEntry(
256 const PLDHashEntryHdr
* entry
, const void* key
) {
257 const FuncStringContentListHashEntry
* e
=
258 static_cast<const FuncStringContentListHashEntry
*>(entry
);
259 const nsFuncStringCacheKey
* ourKey
=
260 static_cast<const nsFuncStringCacheKey
*>(key
);
262 return e
->mContentList
->Equals(ourKey
);
265 template <class ListType
>
266 already_AddRefed
<nsContentList
> GetFuncStringContentList(
267 nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
268 nsContentListDestroyFunc aDestroyFunc
,
269 nsFuncStringContentListDataAllocator aDataAllocator
,
270 const nsAString
& aString
) {
271 NS_ASSERTION(aRootNode
, "content list has to have a root");
273 RefPtr
<nsCacheableFuncStringContentList
> list
;
275 static const PLDHashTableOps hash_table_ops
= {
276 FuncStringContentListHashtableHashKey
,
277 FuncStringContentListHashtableMatchEntry
, PLDHashTable::MoveEntryStub
,
278 PLDHashTable::ClearEntryStub
};
280 // Initialize the hashtable if needed.
281 if (!gFuncStringContentListHashTable
) {
282 gFuncStringContentListHashTable
= new PLDHashTable(
283 &hash_table_ops
, sizeof(FuncStringContentListHashEntry
));
286 FuncStringContentListHashEntry
* entry
= nullptr;
287 // First we look in our hashtable. Then we create a content list if needed
288 if (gFuncStringContentListHashTable
) {
289 nsFuncStringCacheKey
hashKey(aRootNode
, aFunc
, aString
);
291 entry
= static_cast<FuncStringContentListHashEntry
*>(
292 gFuncStringContentListHashTable
->Add(&hashKey
, fallible
));
294 list
= entry
->mContentList
;
296 MOZ_ASSERT_IF(list
, list
->mType
== ListType::sType
);
302 // We need to create a ContentList and add it to our new entry, if
305 new ListType(aRootNode
, aFunc
, aDestroyFunc
, aDataAllocator
, aString
);
307 entry
->mContentList
= list
;
311 // Don't cache these lists globally
313 return list
.forget();
316 // Explicit instantiations to avoid link errors
317 template already_AddRefed
<nsContentList
>
318 GetFuncStringContentList
<nsCachableElementsByNameNodeList
>(
319 nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
320 nsContentListDestroyFunc aDestroyFunc
,
321 nsFuncStringContentListDataAllocator aDataAllocator
,
322 const nsAString
& aString
);
323 template already_AddRefed
<nsContentList
>
324 GetFuncStringContentList
<nsCacheableFuncStringHTMLCollection
>(
325 nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
326 nsContentListDestroyFunc aDestroyFunc
,
327 nsFuncStringContentListDataAllocator aDataAllocator
,
328 const nsAString
& aString
);
330 //-----------------------------------------------------
331 // nsContentList implementation
333 nsContentList::nsContentList(nsINode
* aRootNode
, int32_t aMatchNameSpaceId
,
334 nsAtom
* aHTMLMatchAtom
, nsAtom
* aXMLMatchAtom
,
335 bool aDeep
, bool aLiveList
)
336 : nsBaseContentList(),
337 mRootNode(aRootNode
),
338 mMatchNameSpaceId(aMatchNameSpaceId
),
339 mHTMLMatchAtom(aHTMLMatchAtom
),
340 mXMLMatchAtom(aXMLMatchAtom
),
342 mDestroyFunc(nullptr),
346 mFuncMayDependOnAttr(false),
347 mIsHTMLDocument(aRootNode
->OwnerDoc()->IsHTMLDocument()),
348 mIsLiveList(aLiveList
) {
349 NS_ASSERTION(mRootNode
, "Must have root");
350 if (nsGkAtoms::_asterisk
== mHTMLMatchAtom
) {
351 NS_ASSERTION(mXMLMatchAtom
== nsGkAtoms::_asterisk
,
352 "HTML atom and XML atom are not both asterisk?");
358 mRootNode
->AddMutationObserver(this);
361 // We only need to flush if we're in an non-HTML document, since the
362 // HTML5 parser doesn't need flushing. Further, if we're not in a
363 // document at all right now (in the GetUncomposedDoc() sense), we're
364 // not parser-created and don't need to be flushing stuff under us
365 // to get our kids right.
366 Document
* doc
= mRootNode
->GetUncomposedDoc();
367 mFlushesNeeded
= doc
&& !doc
->IsHTMLDocument();
370 nsContentList::nsContentList(nsINode
* aRootNode
, nsContentListMatchFunc aFunc
,
371 nsContentListDestroyFunc aDestroyFunc
, void* aData
,
372 bool aDeep
, nsAtom
* aMatchAtom
,
373 int32_t aMatchNameSpaceId
,
374 bool aFuncMayDependOnAttr
, bool aLiveList
)
375 : nsBaseContentList(),
376 mRootNode(aRootNode
),
377 mMatchNameSpaceId(aMatchNameSpaceId
),
378 mHTMLMatchAtom(aMatchAtom
),
379 mXMLMatchAtom(aMatchAtom
),
381 mDestroyFunc(aDestroyFunc
),
386 mFuncMayDependOnAttr(aFuncMayDependOnAttr
),
387 mIsHTMLDocument(false),
388 mIsLiveList(aLiveList
) {
389 NS_ASSERTION(mRootNode
, "Must have root");
391 mRootNode
->AddMutationObserver(this);
394 // We only need to flush if we're in an non-HTML document, since the
395 // HTML5 parser doesn't need flushing. Further, if we're not in a
396 // document at all right now (in the GetUncomposedDoc() sense), we're
397 // not parser-created and don't need to be flushing stuff under us
398 // to get our kids right.
399 Document
* doc
= mRootNode
->GetUncomposedDoc();
400 mFlushesNeeded
= doc
&& !doc
->IsHTMLDocument();
403 nsContentList::~nsContentList() {
404 RemoveFromHashtable();
405 if (mIsLiveList
&& mRootNode
) {
406 mRootNode
->RemoveMutationObserver(this);
411 (*mDestroyFunc
)(mData
);
415 JSObject
* nsContentList::WrapObject(JSContext
* cx
,
416 JS::Handle
<JSObject
*> aGivenProto
) {
417 return HTMLCollection_Binding::Wrap(cx
, this, aGivenProto
);
420 NS_IMPL_ISUPPORTS_INHERITED(nsContentList
, nsBaseContentList
, nsIHTMLCollection
,
423 uint32_t nsContentList::Length(bool aDoFlush
) {
424 BringSelfUpToDate(aDoFlush
);
426 return mElements
.Length();
429 nsIContent
* nsContentList::Item(uint32_t aIndex
, bool aDoFlush
) {
430 if (mRootNode
&& aDoFlush
&& mFlushesNeeded
) {
431 // XXX sXBL/XBL2 issue
432 Document
* doc
= mRootNode
->GetUncomposedDoc();
434 // Flush pending content changes Bug 4891.
435 doc
->FlushPendingNotifications(FlushType::ContentAndNotify
);
439 if (mState
!= LIST_UP_TO_DATE
)
440 PopulateSelf(std::min(aIndex
, UINT32_MAX
- 1) + 1);
443 NS_ASSERTION(!mRootNode
|| mState
!= LIST_DIRTY
,
444 "PopulateSelf left the list in a dirty (useless) state!");
446 return mElements
.SafeElementAt(aIndex
);
449 Element
* nsContentList::NamedItem(const nsAString
& aName
, bool aDoFlush
) {
450 if (aName
.IsEmpty()) {
454 BringSelfUpToDate(aDoFlush
);
456 uint32_t i
, count
= mElements
.Length();
458 // Typically IDs and names are atomized
459 RefPtr
<nsAtom
> name
= NS_Atomize(aName
);
460 NS_ENSURE_TRUE(name
, nullptr);
462 for (i
= 0; i
< count
; i
++) {
463 nsIContent
* content
= mElements
[i
];
464 // XXX Should this pass eIgnoreCase?
466 ((content
->IsHTMLElement() &&
467 content
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::name
,
468 name
, eCaseMatters
)) ||
469 content
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::id
,
470 name
, eCaseMatters
))) {
471 return content
->AsElement();
478 void nsContentList::GetSupportedNames(nsTArray
<nsString
>& aNames
) {
479 BringSelfUpToDate(true);
481 AutoTArray
<nsAtom
*, 8> atoms
;
482 for (uint32_t i
= 0; i
< mElements
.Length(); ++i
) {
483 nsIContent
* content
= mElements
.ElementAt(i
);
484 if (content
->HasID()) {
485 nsAtom
* id
= content
->GetID();
486 MOZ_ASSERT(id
!= nsGkAtoms::_empty
, "Empty ids don't get atomized");
487 if (!atoms
.Contains(id
)) {
488 atoms
.AppendElement(id
);
492 nsGenericHTMLElement
* el
= nsGenericHTMLElement::FromNode(content
);
494 // XXXbz should we be checking for particular tags here? How
495 // stable is this part of the spec?
496 // Note: nsINode::HasName means the name is exposed on the document,
497 // which is false for options, so we don't check it here.
498 const nsAttrValue
* val
= el
->GetParsedAttr(nsGkAtoms::name
);
499 if (val
&& val
->Type() == nsAttrValue::eAtom
) {
500 nsAtom
* name
= val
->GetAtomValue();
501 MOZ_ASSERT(name
!= nsGkAtoms::_empty
, "Empty names don't get atomized");
502 if (!atoms
.Contains(name
)) {
503 atoms
.AppendElement(name
);
509 uint32_t atomsLen
= atoms
.Length();
510 nsString
* names
= aNames
.AppendElements(atomsLen
);
511 for (uint32_t i
= 0; i
< atomsLen
; ++i
) {
512 atoms
[i
]->ToString(names
[i
]);
516 int32_t nsContentList::IndexOf(nsIContent
* aContent
, bool aDoFlush
) {
517 BringSelfUpToDate(aDoFlush
);
519 return mElements
.IndexOf(aContent
);
522 int32_t nsContentList::IndexOf(nsIContent
* aContent
) {
523 return IndexOf(aContent
, true);
526 void nsContentList::NodeWillBeDestroyed(const nsINode
* aNode
) {
527 // We shouldn't do anything useful from now on
532 // We will get no more updates, so we can never know we're up to
537 void nsContentList::LastRelease() {
539 if (mIsLiveList
&& mRootNode
) {
540 mRootNode
->RemoveMutationObserver(this);
546 Element
* nsContentList::GetElementAt(uint32_t aIndex
) {
547 return static_cast<Element
*>(Item(aIndex
, true));
550 nsIContent
* nsContentList::Item(uint32_t aIndex
) {
551 return GetElementAt(aIndex
);
554 void nsContentList::AttributeChanged(Element
* aElement
, int32_t aNameSpaceID
,
555 nsAtom
* aAttribute
, int32_t aModType
,
556 const nsAttrValue
* aOldValue
) {
557 MOZ_ASSERT(aElement
, "Must have a content node to work with");
559 if (!mFunc
|| !mFuncMayDependOnAttr
|| mState
== LIST_DIRTY
||
560 !MayContainRelevantNodes(aElement
->GetParentNode()) ||
561 !nsContentUtils::IsInSameAnonymousTree(mRootNode
, aElement
)) {
562 // Either we're already dirty or this notification doesn't affect
563 // whether we might match aElement.
567 if (Match(aElement
)) {
568 if (mElements
.IndexOf(aElement
) == mElements
.NoIndex
) {
569 // We match aElement now, and it's not in our list already. Just dirty
570 // ourselves; this is simpler than trying to figure out where to insert
575 // We no longer match aElement. Remove it from our list. If it's
576 // already not there, this is a no-op (though a potentially
577 // expensive one). Either way, no change of mState is required
579 mElements
.RemoveElement(aElement
);
583 void nsContentList::ContentAppended(nsIContent
* aFirstNewContent
) {
584 nsIContent
* container
= aFirstNewContent
->GetParent();
585 MOZ_ASSERT(container
, "Can't get at the new content if no container!");
588 * If the state is LIST_DIRTY then we have no useful information in our list
589 * and we want to put off doing work as much as possible.
591 * Also, if container is anonymous from our point of view, we know that we
592 * can't possibly be matching any of the kids.
594 * Optimize out also the common case when just one new node is appended and
595 * it doesn't match us.
597 if (mState
== LIST_DIRTY
||
598 !nsContentUtils::IsInSameAnonymousTree(mRootNode
, container
) ||
599 !MayContainRelevantNodes(container
) ||
600 (!aFirstNewContent
->HasChildren() &&
601 !aFirstNewContent
->GetNextSibling() && !MatchSelf(aFirstNewContent
))) {
606 * We want to handle the case of ContentAppended by sometimes
607 * appending the content to our list, not just setting state to
608 * LIST_DIRTY, since most of our ContentAppended notifications
609 * should come during pageload and be at the end of the document.
610 * Do a bit of work to see whether we could just append to what we
614 uint32_t ourCount
= mElements
.Length();
615 const bool appendingToList
= [&] {
619 if (mRootNode
== container
) {
622 return nsContentUtils::PositionIsBefore(mElements
.LastElement(),
626 if (!appendingToList
) {
627 // The new stuff is somewhere in the middle of our list; check
628 // whether we need to invalidate
629 for (nsIContent
* cur
= aFirstNewContent
; cur
; cur
= cur
->GetNextSibling()) {
630 if (MatchSelf(cur
)) {
631 // Uh-oh. We're gonna have to add elements into the middle
632 // of our list. That's not worth the effort.
643 * At this point we know we could append. If we're not up to
644 * date, however, that would be a bad idea -- it could miss some
645 * content that we never picked up due to being lazy. Further, we
646 * may never get asked for this content... so don't grab it yet.
648 if (mState
== LIST_LAZY
) {
653 * We're up to date. That means someone's actively using us; we
654 * may as well grab this content....
657 for (nsIContent
* cur
= aFirstNewContent
; cur
;
658 cur
= cur
->GetNextNode(container
)) {
659 if (cur
->IsElement() && Match(cur
->AsElement())) {
660 mElements
.AppendElement(cur
);
664 for (nsIContent
* cur
= aFirstNewContent
; cur
; cur
= cur
->GetNextSibling()) {
665 if (cur
->IsElement() && Match(cur
->AsElement())) {
666 mElements
.AppendElement(cur
);
674 void nsContentList::ContentInserted(nsIContent
* aChild
) {
675 // Note that aChild->GetParentNode() can be null here if we are inserting into
676 // the document itself; any attempted optimizations to this method should deal
678 if (mState
!= LIST_DIRTY
&&
679 MayContainRelevantNodes(aChild
->GetParentNode()) &&
680 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
) &&
688 void nsContentList::ContentRemoved(nsIContent
* aChild
,
689 nsIContent
* aPreviousSibling
) {
690 if (mState
!= LIST_DIRTY
&&
691 MayContainRelevantNodes(aChild
->GetParentNode()) &&
692 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
) &&
700 bool nsContentList::Match(Element
* aElement
) {
702 return (*mFunc
)(aElement
, mMatchNameSpaceId
, mXMLMatchAtom
, mData
);
705 if (!mXMLMatchAtom
) return false;
707 NodeInfo
* ni
= aElement
->NodeInfo();
709 bool unknown
= mMatchNameSpaceId
== kNameSpaceID_Unknown
;
710 bool wildcard
= mMatchNameSpaceId
== kNameSpaceID_Wildcard
;
711 bool toReturn
= mMatchAll
;
712 if (!unknown
&& !wildcard
) toReturn
&= ni
->NamespaceEquals(mMatchNameSpaceId
);
714 if (toReturn
) return toReturn
;
717 mIsHTMLDocument
&& aElement
->GetNameSpaceID() == kNameSpaceID_XHTML
;
720 return matchHTML
? ni
->QualifiedNameEquals(mHTMLMatchAtom
)
721 : ni
->QualifiedNameEquals(mXMLMatchAtom
);
725 return matchHTML
? ni
->Equals(mHTMLMatchAtom
) : ni
->Equals(mXMLMatchAtom
);
728 return matchHTML
? ni
->Equals(mHTMLMatchAtom
, mMatchNameSpaceId
)
729 : ni
->Equals(mXMLMatchAtom
, mMatchNameSpaceId
);
732 bool nsContentList::MatchSelf(nsIContent
* aContent
) {
733 MOZ_ASSERT(aContent
, "Can't match null stuff, you know");
734 MOZ_ASSERT(mDeep
|| aContent
->GetParentNode() == mRootNode
,
735 "MatchSelf called on a node that we can't possibly match");
737 if (!aContent
->IsElement()) {
741 if (Match(aContent
->AsElement())) return true;
743 if (!mDeep
) return false;
745 for (nsIContent
* cur
= aContent
->GetFirstChild(); cur
;
746 cur
= cur
->GetNextNode(aContent
)) {
747 if (cur
->IsElement() && Match(cur
->AsElement())) {
755 void nsContentList::PopulateSelf(uint32_t aNeededLength
,
756 uint32_t aExpectedElementsIfDirty
) {
763 uint32_t count
= mElements
.Length();
764 NS_ASSERTION(mState
!= LIST_DIRTY
|| count
== aExpectedElementsIfDirty
,
765 "Reset() not called when setting state to LIST_DIRTY?");
767 if (count
>= aNeededLength
) // We're all set
770 uint32_t elementsToAppend
= aNeededLength
- count
;
772 uint32_t invariant
= elementsToAppend
+ mElements
.Length();
776 // If we already have nodes start searching at the last one, otherwise
777 // start searching at the root.
778 nsINode
* cur
= count
? mElements
[count
- 1].get() : mRootNode
;
780 cur
= cur
->GetNextNode(mRootNode
);
784 if (cur
->IsElement() && Match(cur
->AsElement())) {
785 // Append AsElement() to get nsIContent instead of nsINode
786 mElements
.AppendElement(cur
->AsElement());
789 } while (elementsToAppend
);
791 nsIContent
* cur
= count
? mElements
[count
- 1]->GetNextSibling()
792 : mRootNode
->GetFirstChild();
793 for (; cur
&& elementsToAppend
; cur
= cur
->GetNextSibling()) {
794 if (cur
->IsElement() && Match(cur
->AsElement())) {
795 mElements
.AppendElement(cur
);
801 NS_ASSERTION(elementsToAppend
+ mElements
.Length() == invariant
,
802 "Something is awry!");
804 if (elementsToAppend
!= 0)
805 mState
= LIST_UP_TO_DATE
;
812 void nsContentList::RemoveFromHashtable() {
814 // This can't be in the table anyway
818 nsDependentAtomString
str(mXMLMatchAtom
);
819 nsContentListKey
key(mRootNode
, mMatchNameSpaceId
, str
, mIsHTMLDocument
);
820 sRecentlyUsedContentLists
.Remove(key
);
822 if (!gContentListHashTable
) return;
824 gContentListHashTable
->Remove(&key
);
826 if (gContentListHashTable
->EntryCount() == 0) {
827 delete gContentListHashTable
;
828 gContentListHashTable
= nullptr;
832 void nsContentList::BringSelfUpToDate(bool aDoFlush
) {
833 if (mRootNode
&& aDoFlush
&& mFlushesNeeded
) {
834 // XXX sXBL/XBL2 issue
835 Document
* doc
= mRootNode
->GetUncomposedDoc();
837 // Flush pending content changes Bug 4891.
838 doc
->FlushPendingNotifications(FlushType::ContentAndNotify
);
842 if (mState
!= LIST_UP_TO_DATE
) PopulateSelf(uint32_t(-1));
845 NS_ASSERTION(!mRootNode
|| mState
== LIST_UP_TO_DATE
,
846 "PopulateSelf dod not bring content list up to date!");
849 nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList() {
850 RemoveFromFuncStringHashtable();
853 void nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable() {
854 if (!gFuncStringContentListHashTable
) {
858 nsFuncStringCacheKey
key(mRootNode
, mFunc
, mString
);
859 gFuncStringContentListHashTable
->Remove(&key
);
861 if (gFuncStringContentListHashTable
->EntryCount() == 0) {
862 delete gFuncStringContentListHashTable
;
863 gFuncStringContentListHashTable
= nullptr;
867 #ifdef DEBUG_CONTENT_LIST
868 void nsContentList::AssertInSync() {
869 if (mState
== LIST_DIRTY
) {
874 NS_ASSERTION(mElements
.Length() == 0 && mState
== LIST_DIRTY
,
875 "Empty iterator isn't quite empty?");
879 // XXX This code will need to change if nsContentLists can ever match
880 // elements that are outside of the document element.
881 nsIContent
* root
= mRootNode
->IsDocument()
882 ? mRootNode
->AsDocument()->GetRootElement()
883 : mRootNode
->AsContent();
885 PreContentIterator preOrderIter
;
887 preOrderIter
.Init(root
);
888 preOrderIter
.First();
891 uint32_t cnt
= 0, index
= 0;
893 if (cnt
== mElements
.Length() && mState
== LIST_LAZY
) {
898 mDeep
? preOrderIter
.GetCurrentNode() : mRootNode
->GetChildAt(index
++);
903 if (cur
->IsElement() && Match(cur
->AsElement())) {
904 NS_ASSERTION(cnt
< mElements
.Length() && mElements
[cnt
] == cur
,
905 "Elements is out of sync");
914 NS_ASSERTION(cnt
== mElements
.Length(), "Too few elements");
918 //-----------------------------------------------------
919 // nsCachableElementsByNameNodeList
921 JSObject
* nsCachableElementsByNameNodeList::WrapObject(
922 JSContext
* cx
, JS::Handle
<JSObject
*> aGivenProto
) {
923 return NodeList_Binding::Wrap(cx
, this, aGivenProto
);
926 void nsCachableElementsByNameNodeList::AttributeChanged(
927 Element
* aElement
, int32_t aNameSpaceID
, nsAtom
* aAttribute
,
928 int32_t aModType
, const nsAttrValue
* aOldValue
) {
929 // No need to rebuild the list if the changed attribute is not the name
931 if (aAttribute
!= nsGkAtoms::name
) {
935 nsCacheableFuncStringContentList::AttributeChanged(
936 aElement
, aNameSpaceID
, aAttribute
, aModType
, aOldValue
);
939 //-----------------------------------------------------
940 // nsCacheableFuncStringHTMLCollection
942 JSObject
* nsCacheableFuncStringHTMLCollection::WrapObject(
943 JSContext
* cx
, JS::Handle
<JSObject
*> aGivenProto
) {
944 return HTMLCollection_Binding::Wrap(cx
, this, aGivenProto
);
947 //-----------------------------------------------------
950 JSObject
* nsLabelsNodeList::WrapObject(JSContext
* cx
,
951 JS::Handle
<JSObject
*> aGivenProto
) {
952 return NodeList_Binding::Wrap(cx
, this, aGivenProto
);
955 void nsLabelsNodeList::AttributeChanged(Element
* aElement
, int32_t aNameSpaceID
,
956 nsAtom
* aAttribute
, int32_t aModType
,
957 const nsAttrValue
* aOldValue
) {
958 MOZ_ASSERT(aElement
, "Must have a content node to work with");
959 if (mState
== LIST_DIRTY
||
960 !nsContentUtils::IsInSameAnonymousTree(mRootNode
, aElement
)) {
964 // We need to handle input type changes to or from "hidden".
965 if (aElement
->IsHTMLElement(nsGkAtoms::input
) &&
966 aAttribute
== nsGkAtoms::type
&& aNameSpaceID
== kNameSpaceID_None
) {
972 void nsLabelsNodeList::ContentAppended(nsIContent
* aFirstNewContent
) {
973 nsIContent
* container
= aFirstNewContent
->GetParent();
974 // If a labelable element is moved to outside or inside of
975 // nested associated labels, we're gonna have to modify
977 if (mState
!= LIST_DIRTY
||
978 nsContentUtils::IsInSameAnonymousTree(mRootNode
, container
)) {
984 void nsLabelsNodeList::ContentInserted(nsIContent
* aChild
) {
985 // If a labelable element is moved to outside or inside of
986 // nested associated labels, we're gonna have to modify
988 if (mState
!= LIST_DIRTY
||
989 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
)) {
995 void nsLabelsNodeList::ContentRemoved(nsIContent
* aChild
,
996 nsIContent
* aPreviousSibling
) {
997 // If a labelable element is removed, we're gonna have to clean
999 if (mState
!= LIST_DIRTY
||
1000 nsContentUtils::IsInSameAnonymousTree(mRootNode
, aChild
)) {
1006 void nsLabelsNodeList::MaybeResetRoot(nsINode
* aRootNode
) {
1007 MOZ_ASSERT(aRootNode
, "Must have root");
1008 if (mRootNode
== aRootNode
) {
1012 MOZ_ASSERT(mIsLiveList
, "nsLabelsNodeList is always a live list");
1014 mRootNode
->RemoveMutationObserver(this);
1016 mRootNode
= aRootNode
;
1017 mRootNode
->AddMutationObserver(this);
1021 void nsLabelsNodeList::PopulateSelf(uint32_t aNeededLength
,
1022 uint32_t aExpectedElementsIfDirty
) {
1027 // Start searching at the root.
1028 nsINode
* cur
= mRootNode
;
1029 if (mElements
.IsEmpty() && cur
->IsElement() && Match(cur
->AsElement())) {
1030 mElements
.AppendElement(cur
->AsElement());
1031 ++aExpectedElementsIfDirty
;
1034 nsContentList::PopulateSelf(aNeededLength
, aExpectedElementsIfDirty
);