Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
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/. */
7 /*
8 * nsBaseContentList is a basic list of content nodes; nsContentList
9 * is a commonly used NodeList implementation (used for
10 * getElementsByTagName, some properties on nsIDOMHTMLDocument, etc).
11 */
13 #include "nsContentList.h"
14 #include "nsIContent.h"
15 #include "nsIDOMNode.h"
16 #include "nsIDocument.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"
27 #include <algorithm>
29 // Form related includes
30 #include "nsIDOMHTMLFormElement.h"
32 #include "pldhash.h"
34 #ifdef DEBUG_CONTENT_LIST
35 #include "nsIContentIterator.h"
36 #define ASSERT_IN_SYNC AssertInSync()
37 #else
38 #define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
39 #endif
41 using namespace mozilla;
42 using namespace mozilla::dom;
44 nsBaseContentList::~nsBaseContentList()
45 {
46 }
48 NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList)
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList)
50 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
51 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
52 tmp->RemoveFromCaches();
53 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
58 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsBaseContentList)
60 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList)
61 if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) {
62 for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) {
63 nsIContent* c = tmp->mElements[i];
64 if (c->IsPurple()) {
65 c->RemovePurple();
66 }
67 Element::MarkNodeChildren(c);
68 }
69 return true;
70 }
71 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
73 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList)
74 return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
75 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
77 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList)
78 return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
79 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
81 #define NS_CONTENT_LIST_INTERFACES(_class) \
82 NS_INTERFACE_TABLE_ENTRY(_class, nsINodeList) \
83 NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMNodeList)
85 // QueryInterface implementation for nsBaseContentList
86 NS_INTERFACE_TABLE_HEAD(nsBaseContentList)
87 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
88 NS_INTERFACE_TABLE(nsBaseContentList, nsINodeList, nsIDOMNodeList)
89 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList)
90 NS_INTERFACE_MAP_END
93 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList)
94 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBaseContentList)
97 NS_IMETHODIMP
98 nsBaseContentList::GetLength(uint32_t* aLength)
99 {
100 *aLength = mElements.Length();
102 return NS_OK;
103 }
105 NS_IMETHODIMP
106 nsBaseContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
107 {
108 nsISupports *tmp = Item(aIndex);
110 if (!tmp) {
111 *aReturn = nullptr;
113 return NS_OK;
114 }
116 return CallQueryInterface(tmp, aReturn);
117 }
119 nsIContent*
120 nsBaseContentList::Item(uint32_t aIndex)
121 {
122 return mElements.SafeElementAt(aIndex);
123 }
126 int32_t
127 nsBaseContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
128 {
129 return mElements.IndexOf(aContent);
130 }
132 int32_t
133 nsBaseContentList::IndexOf(nsIContent* aContent)
134 {
135 return IndexOf(aContent, true);
136 }
138 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList,
139 mRoot)
141 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsSimpleContentList)
142 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList)
145 NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList)
146 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
148 JSObject*
149 nsSimpleContentList::WrapObject(JSContext *cx)
150 {
151 return NodeListBinding::Wrap(cx, this);
152 }
154 // Hashtable for storing nsContentLists
155 static PLDHashTable gContentListHashTable;
157 #define RECENTLY_USED_CONTENT_LIST_CACHE_SIZE 31
158 static nsContentList*
159 sRecentlyUsedContentLists[RECENTLY_USED_CONTENT_LIST_CACHE_SIZE] = {};
161 static MOZ_ALWAYS_INLINE uint32_t
162 RecentlyUsedCacheIndex(const nsContentListKey& aKey)
163 {
164 return aKey.GetHash() % RECENTLY_USED_CONTENT_LIST_CACHE_SIZE;
165 }
167 struct ContentListHashEntry : public PLDHashEntryHdr
168 {
169 nsContentList* mContentList;
170 };
172 static PLDHashNumber
173 ContentListHashtableHashKey(PLDHashTable *table, const void *key)
174 {
175 const nsContentListKey* list = static_cast<const nsContentListKey *>(key);
176 return list->GetHash();
177 }
179 static bool
180 ContentListHashtableMatchEntry(PLDHashTable *table,
181 const PLDHashEntryHdr *entry,
182 const void *key)
183 {
184 const ContentListHashEntry *e =
185 static_cast<const ContentListHashEntry *>(entry);
186 const nsContentList* list = e->mContentList;
187 const nsContentListKey* ourKey = static_cast<const nsContentListKey *>(key);
189 return list->MatchesKey(*ourKey);
190 }
192 already_AddRefed<nsContentList>
193 NS_GetContentList(nsINode* aRootNode,
194 int32_t aMatchNameSpaceId,
195 const nsAString& aTagname)
196 {
197 NS_ASSERTION(aRootNode, "content list has to have a root");
199 nsRefPtr<nsContentList> list;
200 nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname);
201 uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(hashKey);
202 nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex];
203 if (cachedList && cachedList->MatchesKey(hashKey)) {
204 list = cachedList;
205 return list.forget();
206 }
208 static const PLDHashTableOps hash_table_ops =
209 {
210 PL_DHashAllocTable,
211 PL_DHashFreeTable,
212 ContentListHashtableHashKey,
213 ContentListHashtableMatchEntry,
214 PL_DHashMoveEntryStub,
215 PL_DHashClearEntryStub,
216 PL_DHashFinalizeStub
217 };
219 // Initialize the hashtable if needed.
220 if (!gContentListHashTable.ops) {
221 PL_DHashTableInit(&gContentListHashTable,
222 &hash_table_ops, nullptr,
223 sizeof(ContentListHashEntry),
224 16);
225 }
227 ContentListHashEntry *entry = nullptr;
228 // First we look in our hashtable. Then we create a content list if needed
229 if (gContentListHashTable.ops) {
231 // A PL_DHASH_ADD is equivalent to a PL_DHASH_LOOKUP for cases
232 // when the entry is already in the hashtable.
233 entry = static_cast<ContentListHashEntry *>
234 (PL_DHashTableOperate(&gContentListHashTable,
235 &hashKey,
236 PL_DHASH_ADD));
237 if (entry)
238 list = entry->mContentList;
239 }
241 if (!list) {
242 // We need to create a ContentList and add it to our new entry, if
243 // we have an entry
244 nsCOMPtr<nsIAtom> xmlAtom = do_GetAtom(aTagname);
245 nsCOMPtr<nsIAtom> htmlAtom;
246 if (aMatchNameSpaceId == kNameSpaceID_Unknown) {
247 nsAutoString lowercaseName;
248 nsContentUtils::ASCIIToLower(aTagname, lowercaseName);
249 htmlAtom = do_GetAtom(lowercaseName);
250 } else {
251 htmlAtom = xmlAtom;
252 }
253 list = new nsContentList(aRootNode, aMatchNameSpaceId,
254 htmlAtom, xmlAtom);
255 if (entry) {
256 entry->mContentList = list;
257 }
258 }
260 sRecentlyUsedContentLists[recentlyUsedCacheIndex] = list;
261 return list.forget();
262 }
264 #ifdef DEBUG
265 const nsCacheableFuncStringContentList::ContentListType
266 nsCacheableFuncStringNodeList::sType = nsCacheableFuncStringContentList::eNodeList;
267 const nsCacheableFuncStringContentList::ContentListType
268 nsCacheableFuncStringHTMLCollection::sType = nsCacheableFuncStringContentList::eHTMLCollection;
269 #endif
271 JSObject*
272 nsCacheableFuncStringNodeList::WrapObject(JSContext *cx)
273 {
274 return NodeListBinding::Wrap(cx, this);
275 }
278 JSObject*
279 nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx)
280 {
281 return HTMLCollectionBinding::Wrap(cx, this);
282 }
284 // Hashtable for storing nsCacheableFuncStringContentList
285 static PLDHashTable gFuncStringContentListHashTable;
287 struct FuncStringContentListHashEntry : public PLDHashEntryHdr
288 {
289 nsCacheableFuncStringContentList* mContentList;
290 };
292 static PLDHashNumber
293 FuncStringContentListHashtableHashKey(PLDHashTable *table, const void *key)
294 {
295 const nsFuncStringCacheKey* funcStringKey =
296 static_cast<const nsFuncStringCacheKey *>(key);
297 return funcStringKey->GetHash();
298 }
300 static bool
301 FuncStringContentListHashtableMatchEntry(PLDHashTable *table,
302 const PLDHashEntryHdr *entry,
303 const void *key)
304 {
305 const FuncStringContentListHashEntry *e =
306 static_cast<const FuncStringContentListHashEntry *>(entry);
307 const nsFuncStringCacheKey* ourKey =
308 static_cast<const nsFuncStringCacheKey *>(key);
310 return e->mContentList->Equals(ourKey);
311 }
313 template<class ListType>
314 already_AddRefed<nsContentList>
315 GetFuncStringContentList(nsINode* aRootNode,
316 nsContentListMatchFunc aFunc,
317 nsContentListDestroyFunc aDestroyFunc,
318 nsFuncStringContentListDataAllocator aDataAllocator,
319 const nsAString& aString)
320 {
321 NS_ASSERTION(aRootNode, "content list has to have a root");
323 nsRefPtr<nsCacheableFuncStringContentList> list;
325 static const PLDHashTableOps hash_table_ops =
326 {
327 PL_DHashAllocTable,
328 PL_DHashFreeTable,
329 FuncStringContentListHashtableHashKey,
330 FuncStringContentListHashtableMatchEntry,
331 PL_DHashMoveEntryStub,
332 PL_DHashClearEntryStub,
333 PL_DHashFinalizeStub
334 };
336 // Initialize the hashtable if needed.
337 if (!gFuncStringContentListHashTable.ops) {
338 PL_DHashTableInit(&gFuncStringContentListHashTable,
339 &hash_table_ops, nullptr,
340 sizeof(FuncStringContentListHashEntry),
341 16);
342 }
344 FuncStringContentListHashEntry *entry = nullptr;
345 // First we look in our hashtable. Then we create a content list if needed
346 if (gFuncStringContentListHashTable.ops) {
347 nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
349 // A PL_DHASH_ADD is equivalent to a PL_DHASH_LOOKUP for cases
350 // when the entry is already in the hashtable.
351 entry = static_cast<FuncStringContentListHashEntry *>
352 (PL_DHashTableOperate(&gFuncStringContentListHashTable,
353 &hashKey,
354 PL_DHASH_ADD));
355 if (entry) {
356 list = entry->mContentList;
357 #ifdef DEBUG
358 MOZ_ASSERT_IF(list, list->mType == ListType::sType);
359 #endif
360 }
361 }
363 if (!list) {
364 // We need to create a ContentList and add it to our new entry, if
365 // we have an entry
366 list = new ListType(aRootNode, aFunc, aDestroyFunc, aDataAllocator,
367 aString);
368 if (entry) {
369 entry->mContentList = list;
370 }
371 }
373 // Don't cache these lists globally
375 return list.forget();
376 }
378 already_AddRefed<nsContentList>
379 NS_GetFuncStringNodeList(nsINode* aRootNode,
380 nsContentListMatchFunc aFunc,
381 nsContentListDestroyFunc aDestroyFunc,
382 nsFuncStringContentListDataAllocator aDataAllocator,
383 const nsAString& aString)
384 {
385 return GetFuncStringContentList<nsCacheableFuncStringNodeList>(aRootNode,
386 aFunc,
387 aDestroyFunc,
388 aDataAllocator,
389 aString);
390 }
392 already_AddRefed<nsContentList>
393 NS_GetFuncStringHTMLCollection(nsINode* aRootNode,
394 nsContentListMatchFunc aFunc,
395 nsContentListDestroyFunc aDestroyFunc,
396 nsFuncStringContentListDataAllocator aDataAllocator,
397 const nsAString& aString)
398 {
399 return GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>(aRootNode,
400 aFunc,
401 aDestroyFunc,
402 aDataAllocator,
403 aString);
404 }
406 // nsContentList implementation
408 nsContentList::nsContentList(nsINode* aRootNode,
409 int32_t aMatchNameSpaceId,
410 nsIAtom* aHTMLMatchAtom,
411 nsIAtom* aXMLMatchAtom,
412 bool aDeep)
413 : nsBaseContentList(),
414 mRootNode(aRootNode),
415 mMatchNameSpaceId(aMatchNameSpaceId),
416 mHTMLMatchAtom(aHTMLMatchAtom),
417 mXMLMatchAtom(aXMLMatchAtom),
418 mFunc(nullptr),
419 mDestroyFunc(nullptr),
420 mData(nullptr),
421 mState(LIST_DIRTY),
422 mDeep(aDeep),
423 mFuncMayDependOnAttr(false)
424 {
425 NS_ASSERTION(mRootNode, "Must have root");
426 if (nsGkAtoms::_asterix == mHTMLMatchAtom) {
427 NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterix, "HTML atom and XML atom are not both asterix?");
428 mMatchAll = true;
429 }
430 else {
431 mMatchAll = false;
432 }
433 mRootNode->AddMutationObserver(this);
435 // We only need to flush if we're in an non-HTML document, since the
436 // HTML5 parser doesn't need flushing. Further, if we're not in a
437 // document at all right now (in the GetCurrentDoc() sense), we're
438 // not parser-created and don't need to be flushing stuff under us
439 // to get our kids right.
440 nsIDocument* doc = mRootNode->GetCurrentDoc();
441 mFlushesNeeded = doc && !doc->IsHTML();
442 }
444 nsContentList::nsContentList(nsINode* aRootNode,
445 nsContentListMatchFunc aFunc,
446 nsContentListDestroyFunc aDestroyFunc,
447 void* aData,
448 bool aDeep,
449 nsIAtom* aMatchAtom,
450 int32_t aMatchNameSpaceId,
451 bool aFuncMayDependOnAttr)
452 : nsBaseContentList(),
453 mRootNode(aRootNode),
454 mMatchNameSpaceId(aMatchNameSpaceId),
455 mHTMLMatchAtom(aMatchAtom),
456 mXMLMatchAtom(aMatchAtom),
457 mFunc(aFunc),
458 mDestroyFunc(aDestroyFunc),
459 mData(aData),
460 mState(LIST_DIRTY),
461 mMatchAll(false),
462 mDeep(aDeep),
463 mFuncMayDependOnAttr(aFuncMayDependOnAttr)
464 {
465 NS_ASSERTION(mRootNode, "Must have root");
466 mRootNode->AddMutationObserver(this);
468 // We only need to flush if we're in an non-HTML document, since the
469 // HTML5 parser doesn't need flushing. Further, if we're not in a
470 // document at all right now (in the GetCurrentDoc() sense), we're
471 // not parser-created and don't need to be flushing stuff under us
472 // to get our kids right.
473 nsIDocument* doc = mRootNode->GetCurrentDoc();
474 mFlushesNeeded = doc && !doc->IsHTML();
475 }
477 nsContentList::~nsContentList()
478 {
479 RemoveFromHashtable();
480 if (mRootNode) {
481 mRootNode->RemoveMutationObserver(this);
482 }
484 if (mDestroyFunc) {
485 // Clean up mData
486 (*mDestroyFunc)(mData);
487 }
488 }
490 JSObject*
491 nsContentList::WrapObject(JSContext *cx)
492 {
493 return HTMLCollectionBinding::Wrap(cx, this);
494 }
496 NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList,
497 nsIHTMLCollection, nsIDOMHTMLCollection,
498 nsIMutationObserver)
500 uint32_t
501 nsContentList::Length(bool aDoFlush)
502 {
503 BringSelfUpToDate(aDoFlush);
505 return mElements.Length();
506 }
508 nsIContent *
509 nsContentList::Item(uint32_t aIndex, bool aDoFlush)
510 {
511 if (mRootNode && aDoFlush && mFlushesNeeded) {
512 // XXX sXBL/XBL2 issue
513 nsIDocument* doc = mRootNode->GetCurrentDoc();
514 if (doc) {
515 // Flush pending content changes Bug 4891.
516 doc->FlushPendingNotifications(Flush_ContentAndNotify);
517 }
518 }
520 if (mState != LIST_UP_TO_DATE)
521 PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1);
523 ASSERT_IN_SYNC;
524 NS_ASSERTION(!mRootNode || mState != LIST_DIRTY,
525 "PopulateSelf left the list in a dirty (useless) state!");
527 return mElements.SafeElementAt(aIndex);
528 }
530 Element*
531 nsContentList::NamedItem(const nsAString& aName, bool aDoFlush)
532 {
533 BringSelfUpToDate(aDoFlush);
535 uint32_t i, count = mElements.Length();
537 // Typically IDs and names are atomized
538 nsCOMPtr<nsIAtom> name = do_GetAtom(aName);
539 NS_ENSURE_TRUE(name, nullptr);
541 for (i = 0; i < count; i++) {
542 nsIContent *content = mElements[i];
543 // XXX Should this pass eIgnoreCase?
544 if (content &&
545 (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
546 name, eCaseMatters) ||
547 content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
548 name, eCaseMatters))) {
549 return content->AsElement();
550 }
551 }
553 return nullptr;
554 }
556 void
557 nsContentList::GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames)
558 {
559 if (!(aFlags & JSITER_HIDDEN)) {
560 return;
561 }
563 BringSelfUpToDate(true);
565 nsAutoTArray<nsIAtom*, 8> atoms;
566 for (uint32_t i = 0; i < mElements.Length(); ++i) {
567 nsIContent *content = mElements.ElementAt(i);
568 nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(content);
569 if (el) {
570 // XXXbz should we be checking for particular tags here? How
571 // stable is this part of the spec?
572 // Note: nsINode::HasName means the name is exposed on the document,
573 // which is false for options, so we don't check it here.
574 const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
575 if (val && val->Type() == nsAttrValue::eAtom) {
576 nsIAtom* name = val->GetAtomValue();
577 if (!atoms.Contains(name)) {
578 atoms.AppendElement(name);
579 }
580 }
581 }
582 if (content->HasID()) {
583 nsIAtom* id = content->GetID();
584 if (!atoms.Contains(id)) {
585 atoms.AppendElement(id);
586 }
587 }
588 }
590 aNames.SetCapacity(atoms.Length());
591 for (uint32_t i = 0; i < atoms.Length(); ++i) {
592 aNames.AppendElement(nsDependentAtomString(atoms[i]));
593 }
594 }
596 int32_t
597 nsContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
598 {
599 BringSelfUpToDate(aDoFlush);
601 return mElements.IndexOf(aContent);
602 }
604 int32_t
605 nsContentList::IndexOf(nsIContent* aContent)
606 {
607 return IndexOf(aContent, true);
608 }
610 void
611 nsContentList::NodeWillBeDestroyed(const nsINode* aNode)
612 {
613 // We shouldn't do anything useful from now on
615 RemoveFromCaches();
616 mRootNode = nullptr;
618 // We will get no more updates, so we can never know we're up to
619 // date
620 SetDirty();
621 }
623 NS_IMETHODIMP
624 nsContentList::GetLength(uint32_t* aLength)
625 {
626 *aLength = Length(true);
628 return NS_OK;
629 }
631 NS_IMETHODIMP
632 nsContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
633 {
634 nsINode* node = Item(aIndex);
636 if (node) {
637 return CallQueryInterface(node, aReturn);
638 }
640 *aReturn = nullptr;
642 return NS_OK;
643 }
645 NS_IMETHODIMP
646 nsContentList::NamedItem(const nsAString& aName, nsIDOMNode** aReturn)
647 {
648 nsIContent *content = NamedItem(aName, true);
650 if (content) {
651 return CallQueryInterface(content, aReturn);
652 }
654 *aReturn = nullptr;
656 return NS_OK;
657 }
659 Element*
660 nsContentList::GetElementAt(uint32_t aIndex)
661 {
662 return static_cast<Element*>(Item(aIndex, true));
663 }
665 nsIContent*
666 nsContentList::Item(uint32_t aIndex)
667 {
668 return GetElementAt(aIndex);
669 }
671 void
672 nsContentList::AttributeChanged(nsIDocument *aDocument, Element* aElement,
673 int32_t aNameSpaceID, nsIAtom* aAttribute,
674 int32_t aModType)
675 {
676 NS_PRECONDITION(aElement, "Must have a content node to work with");
678 if (!mFunc || !mFuncMayDependOnAttr || mState == LIST_DIRTY ||
679 !MayContainRelevantNodes(aElement->GetParentNode()) ||
680 !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
681 // Either we're already dirty or this notification doesn't affect
682 // whether we might match aElement.
683 return;
684 }
686 if (Match(aElement)) {
687 if (mElements.IndexOf(aElement) == mElements.NoIndex) {
688 // We match aElement now, and it's not in our list already. Just dirty
689 // ourselves; this is simpler than trying to figure out where to insert
690 // aElement.
691 SetDirty();
692 }
693 } else {
694 // We no longer match aElement. Remove it from our list. If it's
695 // already not there, this is a no-op (though a potentially
696 // expensive one). Either way, no change of mState is required
697 // here.
698 mElements.RemoveElement(aElement);
699 }
700 }
702 void
703 nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
704 nsIContent* aFirstNewContent,
705 int32_t aNewIndexInContainer)
706 {
707 NS_PRECONDITION(aContainer, "Can't get at the new content if no container!");
709 /*
710 * If the state is LIST_DIRTY then we have no useful information in our list
711 * and we want to put off doing work as much as possible.
712 *
713 * Also, if aContainer is anonymous from our point of view, we know that we
714 * can't possibly be matching any of the kids.
715 *
716 * Optimize out also the common case when just one new node is appended and
717 * it doesn't match us.
718 */
719 if (mState == LIST_DIRTY ||
720 !nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer) ||
721 !MayContainRelevantNodes(aContainer) ||
722 (!aFirstNewContent->HasChildren() &&
723 !aFirstNewContent->GetNextSibling() &&
724 !MatchSelf(aFirstNewContent))) {
725 return;
726 }
728 /*
729 * We want to handle the case of ContentAppended by sometimes
730 * appending the content to our list, not just setting state to
731 * LIST_DIRTY, since most of our ContentAppended notifications
732 * should come during pageload and be at the end of the document.
733 * Do a bit of work to see whether we could just append to what we
734 * already have.
735 */
737 int32_t count = aContainer->GetChildCount();
739 if (count > 0) {
740 uint32_t ourCount = mElements.Length();
741 bool appendToList = false;
742 if (ourCount == 0) {
743 appendToList = true;
744 } else {
745 nsIContent* ourLastContent = mElements[ourCount - 1];
746 /*
747 * We want to append instead of invalidating if the first thing
748 * that got appended comes after ourLastContent.
749 */
750 if (nsContentUtils::PositionIsBefore(ourLastContent, aFirstNewContent)) {
751 appendToList = true;
752 }
753 }
756 if (!appendToList) {
757 // The new stuff is somewhere in the middle of our list; check
758 // whether we need to invalidate
759 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
760 if (MatchSelf(cur)) {
761 // Uh-oh. We're gonna have to add elements into the middle
762 // of our list. That's not worth the effort.
763 SetDirty();
764 break;
765 }
766 }
768 ASSERT_IN_SYNC;
769 return;
770 }
772 /*
773 * At this point we know we could append. If we're not up to
774 * date, however, that would be a bad idea -- it could miss some
775 * content that we never picked up due to being lazy. Further, we
776 * may never get asked for this content... so don't grab it yet.
777 */
778 if (mState == LIST_LAZY) // be lazy
779 return;
781 /*
782 * We're up to date. That means someone's actively using us; we
783 * may as well grab this content....
784 */
785 if (mDeep) {
786 for (nsIContent* cur = aFirstNewContent;
787 cur;
788 cur = cur->GetNextNode(aContainer)) {
789 if (cur->IsElement() && Match(cur->AsElement())) {
790 mElements.AppendElement(cur);
791 }
792 }
793 } else {
794 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
795 if (cur->IsElement() && Match(cur->AsElement())) {
796 mElements.AppendElement(cur);
797 }
798 }
799 }
801 ASSERT_IN_SYNC;
802 }
803 }
805 void
806 nsContentList::ContentInserted(nsIDocument *aDocument,
807 nsIContent* aContainer,
808 nsIContent* aChild,
809 int32_t aIndexInContainer)
810 {
811 // Note that aContainer can be null here if we are inserting into
812 // the document itself; any attempted optimizations to this method
813 // should deal with that.
814 if (mState != LIST_DIRTY &&
815 MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
816 nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
817 MatchSelf(aChild)) {
818 SetDirty();
819 }
821 ASSERT_IN_SYNC;
822 }
824 void
825 nsContentList::ContentRemoved(nsIDocument *aDocument,
826 nsIContent* aContainer,
827 nsIContent* aChild,
828 int32_t aIndexInContainer,
829 nsIContent* aPreviousSibling)
830 {
831 // Note that aContainer can be null here if we are removing from
832 // the document itself; any attempted optimizations to this method
833 // should deal with that.
834 if (mState != LIST_DIRTY &&
835 MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
836 nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
837 MatchSelf(aChild)) {
838 SetDirty();
839 }
841 ASSERT_IN_SYNC;
842 }
844 bool
845 nsContentList::Match(Element *aElement)
846 {
847 if (mFunc) {
848 return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData);
849 }
851 if (!mXMLMatchAtom)
852 return false;
854 nsINodeInfo *ni = aElement->NodeInfo();
856 bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown;
857 bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard;
858 bool toReturn = mMatchAll;
859 if (!unknown && !wildcard)
860 toReturn &= ni->NamespaceEquals(mMatchNameSpaceId);
862 if (toReturn)
863 return toReturn;
865 bool matchHTML = aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
866 aElement->OwnerDoc()->IsHTML();
868 if (unknown) {
869 return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) :
870 ni->QualifiedNameEquals(mXMLMatchAtom);
871 }
873 if (wildcard) {
874 return matchHTML ? ni->Equals(mHTMLMatchAtom) :
875 ni->Equals(mXMLMatchAtom);
876 }
878 return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId) :
879 ni->Equals(mXMLMatchAtom, mMatchNameSpaceId);
880 }
882 bool
883 nsContentList::MatchSelf(nsIContent *aContent)
884 {
885 NS_PRECONDITION(aContent, "Can't match null stuff, you know");
886 NS_PRECONDITION(mDeep || aContent->GetParentNode() == mRootNode,
887 "MatchSelf called on a node that we can't possibly match");
889 if (!aContent->IsElement()) {
890 return false;
891 }
893 if (Match(aContent->AsElement()))
894 return true;
896 if (!mDeep)
897 return false;
899 for (nsIContent* cur = aContent->GetFirstChild();
900 cur;
901 cur = cur->GetNextNode(aContent)) {
902 if (cur->IsElement() && Match(cur->AsElement())) {
903 return true;
904 }
905 }
907 return false;
908 }
910 void
911 nsContentList::PopulateSelf(uint32_t aNeededLength)
912 {
913 if (!mRootNode) {
914 return;
915 }
917 ASSERT_IN_SYNC;
919 uint32_t count = mElements.Length();
920 NS_ASSERTION(mState != LIST_DIRTY || count == 0,
921 "Reset() not called when setting state to LIST_DIRTY?");
923 if (count >= aNeededLength) // We're all set
924 return;
926 uint32_t elementsToAppend = aNeededLength - count;
927 #ifdef DEBUG
928 uint32_t invariant = elementsToAppend + mElements.Length();
929 #endif
931 if (mDeep) {
932 // If we already have nodes start searching at the last one, otherwise
933 // start searching at the root.
934 nsINode* cur = count ? mElements[count - 1] : mRootNode;
935 do {
936 cur = cur->GetNextNode(mRootNode);
937 if (!cur) {
938 break;
939 }
940 if (cur->IsElement() && Match(cur->AsElement())) {
941 // Append AsElement() to get nsIContent instead of nsINode
942 mElements.AppendElement(cur->AsElement());
943 --elementsToAppend;
944 }
945 } while (elementsToAppend);
946 } else {
947 nsIContent* cur =
948 count ? mElements[count-1]->GetNextSibling() : mRootNode->GetFirstChild();
949 for ( ; cur && elementsToAppend; cur = cur->GetNextSibling()) {
950 if (cur->IsElement() && Match(cur->AsElement())) {
951 mElements.AppendElement(cur);
952 --elementsToAppend;
953 }
954 }
955 }
957 NS_ASSERTION(elementsToAppend + mElements.Length() == invariant,
958 "Something is awry!");
960 if (elementsToAppend != 0)
961 mState = LIST_UP_TO_DATE;
962 else
963 mState = LIST_LAZY;
965 ASSERT_IN_SYNC;
966 }
968 void
969 nsContentList::RemoveFromHashtable()
970 {
971 if (mFunc) {
972 // This can't be in the table anyway
973 return;
974 }
976 nsDependentAtomString str(mXMLMatchAtom);
977 nsContentListKey key(mRootNode, mMatchNameSpaceId, str);
978 uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(key);
979 if (sRecentlyUsedContentLists[recentlyUsedCacheIndex] == this) {
980 sRecentlyUsedContentLists[recentlyUsedCacheIndex] = nullptr;
981 }
983 if (!gContentListHashTable.ops)
984 return;
986 PL_DHashTableOperate(&gContentListHashTable,
987 &key,
988 PL_DHASH_REMOVE);
990 if (gContentListHashTable.entryCount == 0) {
991 PL_DHashTableFinish(&gContentListHashTable);
992 gContentListHashTable.ops = nullptr;
993 }
994 }
996 void
997 nsContentList::BringSelfUpToDate(bool aDoFlush)
998 {
999 if (mRootNode && aDoFlush && mFlushesNeeded) {
1000 // XXX sXBL/XBL2 issue
1001 nsIDocument* doc = mRootNode->GetCurrentDoc();
1002 if (doc) {
1003 // Flush pending content changes Bug 4891.
1004 doc->FlushPendingNotifications(Flush_ContentAndNotify);
1005 }
1006 }
1008 if (mState != LIST_UP_TO_DATE)
1009 PopulateSelf(uint32_t(-1));
1011 ASSERT_IN_SYNC;
1012 NS_ASSERTION(!mRootNode || mState == LIST_UP_TO_DATE,
1013 "PopulateSelf dod not bring content list up to date!");
1014 }
1016 nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList()
1017 {
1018 RemoveFromFuncStringHashtable();
1019 }
1021 void
1022 nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable()
1023 {
1024 if (!gFuncStringContentListHashTable.ops) {
1025 return;
1026 }
1028 nsFuncStringCacheKey key(mRootNode, mFunc, mString);
1029 PL_DHashTableOperate(&gFuncStringContentListHashTable,
1030 &key,
1031 PL_DHASH_REMOVE);
1033 if (gFuncStringContentListHashTable.entryCount == 0) {
1034 PL_DHashTableFinish(&gFuncStringContentListHashTable);
1035 gFuncStringContentListHashTable.ops = nullptr;
1036 }
1037 }
1039 #ifdef DEBUG_CONTENT_LIST
1040 void
1041 nsContentList::AssertInSync()
1042 {
1043 if (mState == LIST_DIRTY) {
1044 return;
1045 }
1047 if (!mRootNode) {
1048 NS_ASSERTION(mElements.Length() == 0 && mState == LIST_DIRTY,
1049 "Empty iterator isn't quite empty?");
1050 return;
1051 }
1053 // XXX This code will need to change if nsContentLists can ever match
1054 // elements that are outside of the document element.
1055 nsIContent *root;
1056 if (mRootNode->IsNodeOfType(nsINode::eDOCUMENT)) {
1057 root = static_cast<nsIDocument*>(mRootNode)->GetRootElement();
1058 }
1059 else {
1060 root = static_cast<nsIContent*>(mRootNode);
1061 }
1063 nsCOMPtr<nsIContentIterator> iter;
1064 if (mDeep) {
1065 iter = NS_NewPreContentIterator();
1066 iter->Init(root);
1067 iter->First();
1068 }
1070 uint32_t cnt = 0, index = 0;
1071 while (true) {
1072 if (cnt == mElements.Length() && mState == LIST_LAZY) {
1073 break;
1074 }
1076 nsIContent *cur = mDeep ? iter->GetCurrentNode() :
1077 mRootNode->GetChildAt(index++);
1078 if (!cur) {
1079 break;
1080 }
1082 if (cur->IsElement() && Match(cur->AsElement())) {
1083 NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur,
1084 "Elements is out of sync");
1085 ++cnt;
1086 }
1088 if (mDeep) {
1089 iter->Next();
1090 }
1091 }
1093 NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
1094 }
1095 #endif