Line data Source code
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/. */
6 :
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 : */
12 :
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>
28 : #include "mozilla/dom/NodeInfoInlines.h"
29 :
30 : // Form related includes
31 : #include "nsIDOMHTMLFormElement.h"
32 :
33 : #include "PLDHashTable.h"
34 :
35 : #ifdef DEBUG_CONTENT_LIST
36 : #include "nsIContentIterator.h"
37 : #define ASSERT_IN_SYNC AssertInSync()
38 : #else
39 : #define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
40 : #endif
41 :
42 : using namespace mozilla;
43 : using namespace mozilla::dom;
44 :
45 1 : nsBaseContentList::~nsBaseContentList()
46 : {
47 1 : }
48 :
49 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList)
50 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList)
51 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mElements)
52 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
53 0 : tmp->RemoveFromCaches();
54 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
55 8 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList)
56 8 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElements)
57 8 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
58 17 : NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsBaseContentList)
59 :
60 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList)
61 0 : if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) {
62 0 : for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) {
63 0 : nsIContent* c = tmp->mElements[i];
64 0 : if (c->IsPurple()) {
65 0 : c->RemovePurple();
66 : }
67 0 : Element::MarkNodeChildren(c);
68 : }
69 0 : return true;
70 : }
71 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
72 :
73 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList)
74 0 : return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
75 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
76 :
77 0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList)
78 0 : return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper();
79 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
80 :
81 : #define NS_CONTENT_LIST_INTERFACES(_class) \
82 : NS_INTERFACE_TABLE_ENTRY(_class, nsINodeList) \
83 : NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMNodeList)
84 :
85 : // QueryInterface implementation for nsBaseContentList
86 70 : NS_INTERFACE_TABLE_HEAD(nsBaseContentList)
87 70 : NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
88 62 : NS_INTERFACE_TABLE(nsBaseContentList, nsINodeList, nsIDOMNodeList)
89 62 : NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList)
90 0 : NS_INTERFACE_MAP_END
91 :
92 :
93 31 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList)
94 12 : NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsBaseContentList,
95 : LastRelease())
96 :
97 :
98 : NS_IMETHODIMP
99 14 : nsBaseContentList::GetLength(uint32_t* aLength)
100 : {
101 14 : *aLength = mElements.Length();
102 :
103 14 : return NS_OK;
104 : }
105 :
106 : NS_IMETHODIMP
107 0 : nsBaseContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
108 : {
109 0 : nsISupports *tmp = Item(aIndex);
110 :
111 0 : if (!tmp) {
112 0 : *aReturn = nullptr;
113 :
114 0 : return NS_OK;
115 : }
116 :
117 0 : return CallQueryInterface(tmp, aReturn);
118 : }
119 :
120 : nsIContent*
121 8 : nsBaseContentList::Item(uint32_t aIndex)
122 : {
123 8 : return mElements.SafeElementAt(aIndex);
124 : }
125 :
126 :
127 : int32_t
128 0 : nsBaseContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
129 : {
130 0 : return mElements.IndexOf(aContent);
131 : }
132 :
133 : int32_t
134 0 : nsBaseContentList::IndexOf(nsIContent* aContent)
135 : {
136 0 : return IndexOf(aContent, true);
137 : }
138 :
139 6 : NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList,
140 : mRoot)
141 :
142 72 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsSimpleContentList)
143 36 : NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList)
144 :
145 :
146 12 : NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList)
147 6 : NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
148 :
149 : JSObject*
150 6 : nsSimpleContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
151 : {
152 6 : return NodeListBinding::Wrap(cx, this, aGivenProto);
153 : }
154 :
155 : // Hashtable for storing nsContentLists
156 : static PLDHashTable* gContentListHashTable;
157 :
158 : #define RECENTLY_USED_CONTENT_LIST_CACHE_SIZE 31
159 : static nsContentList*
160 : sRecentlyUsedContentLists[RECENTLY_USED_CONTENT_LIST_CACHE_SIZE] = {};
161 :
162 : static MOZ_ALWAYS_INLINE uint32_t
163 5 : RecentlyUsedCacheIndex(const nsContentListKey& aKey)
164 : {
165 5 : return aKey.GetHash() % RECENTLY_USED_CONTENT_LIST_CACHE_SIZE;
166 : }
167 :
168 : struct ContentListHashEntry : public PLDHashEntryHdr
169 : {
170 : nsContentList* mContentList;
171 : };
172 :
173 : static PLDHashNumber
174 5 : ContentListHashtableHashKey(const void *key)
175 : {
176 5 : const nsContentListKey* list = static_cast<const nsContentListKey *>(key);
177 5 : return list->GetHash();
178 : }
179 :
180 : static bool
181 1 : ContentListHashtableMatchEntry(const PLDHashEntryHdr *entry, const void *key)
182 : {
183 : const ContentListHashEntry *e =
184 1 : static_cast<const ContentListHashEntry *>(entry);
185 1 : const nsContentList* list = e->mContentList;
186 1 : const nsContentListKey* ourKey = static_cast<const nsContentListKey *>(key);
187 :
188 1 : return list->MatchesKey(*ourKey);
189 : }
190 :
191 : already_AddRefed<nsContentList>
192 3 : NS_GetContentList(nsINode* aRootNode,
193 : int32_t aMatchNameSpaceId,
194 : const nsAString& aTagname)
195 : {
196 3 : NS_ASSERTION(aRootNode, "content list has to have a root");
197 :
198 6 : RefPtr<nsContentList> list;
199 : nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname,
200 3 : aRootNode->OwnerDoc()->IsHTMLDocument());
201 3 : uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(hashKey);
202 3 : nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex];
203 3 : if (cachedList && cachedList->MatchesKey(hashKey)) {
204 0 : list = cachedList;
205 0 : return list.forget();
206 : }
207 :
208 : static const PLDHashTableOps hash_table_ops =
209 : {
210 : ContentListHashtableHashKey,
211 : ContentListHashtableMatchEntry,
212 : PLDHashTable::MoveEntryStub,
213 : PLDHashTable::ClearEntryStub
214 : };
215 :
216 : // Initialize the hashtable if needed.
217 3 : if (!gContentListHashTable) {
218 2 : gContentListHashTable =
219 2 : new PLDHashTable(&hash_table_ops, sizeof(ContentListHashEntry));
220 : }
221 :
222 : // First we look in our hashtable. Then we create a content list if needed
223 : auto entry = static_cast<ContentListHashEntry*>
224 3 : (gContentListHashTable->Add(&hashKey, fallible));
225 3 : if (entry)
226 3 : list = entry->mContentList;
227 :
228 3 : if (!list) {
229 : // We need to create a ContentList and add it to our new entry, if
230 : // we have an entry
231 6 : nsCOMPtr<nsIAtom> xmlAtom = NS_Atomize(aTagname);
232 6 : nsCOMPtr<nsIAtom> htmlAtom;
233 3 : if (aMatchNameSpaceId == kNameSpaceID_Unknown) {
234 2 : nsAutoString lowercaseName;
235 1 : nsContentUtils::ASCIIToLower(aTagname, lowercaseName);
236 1 : htmlAtom = NS_Atomize(lowercaseName);
237 : } else {
238 2 : htmlAtom = xmlAtom;
239 : }
240 6 : list = new nsContentList(aRootNode, aMatchNameSpaceId, htmlAtom, xmlAtom);
241 3 : if (entry) {
242 3 : entry->mContentList = list;
243 : }
244 : }
245 :
246 3 : sRecentlyUsedContentLists[recentlyUsedCacheIndex] = list;
247 3 : return list.forget();
248 : }
249 :
250 : #ifdef DEBUG
251 : const nsCacheableFuncStringContentList::ContentListType
252 : nsCacheableFuncStringNodeList::sType = nsCacheableFuncStringContentList::eNodeList;
253 : const nsCacheableFuncStringContentList::ContentListType
254 : nsCacheableFuncStringHTMLCollection::sType = nsCacheableFuncStringContentList::eHTMLCollection;
255 : #endif
256 :
257 : // Hashtable for storing nsCacheableFuncStringContentList
258 : static PLDHashTable* gFuncStringContentListHashTable;
259 :
260 : struct FuncStringContentListHashEntry : public PLDHashEntryHdr
261 : {
262 : nsCacheableFuncStringContentList* mContentList;
263 : };
264 :
265 : static PLDHashNumber
266 0 : FuncStringContentListHashtableHashKey(const void *key)
267 : {
268 : const nsFuncStringCacheKey* funcStringKey =
269 0 : static_cast<const nsFuncStringCacheKey *>(key);
270 0 : return funcStringKey->GetHash();
271 : }
272 :
273 : static bool
274 0 : FuncStringContentListHashtableMatchEntry(const PLDHashEntryHdr *entry,
275 : const void *key)
276 : {
277 : const FuncStringContentListHashEntry *e =
278 0 : static_cast<const FuncStringContentListHashEntry *>(entry);
279 : const nsFuncStringCacheKey* ourKey =
280 0 : static_cast<const nsFuncStringCacheKey *>(key);
281 :
282 0 : return e->mContentList->Equals(ourKey);
283 : }
284 :
285 : template<class ListType>
286 : already_AddRefed<nsContentList>
287 0 : GetFuncStringContentList(nsINode* aRootNode,
288 : nsContentListMatchFunc aFunc,
289 : nsContentListDestroyFunc aDestroyFunc,
290 : nsFuncStringContentListDataAllocator aDataAllocator,
291 : const nsAString& aString)
292 : {
293 0 : NS_ASSERTION(aRootNode, "content list has to have a root");
294 :
295 0 : RefPtr<nsCacheableFuncStringContentList> list;
296 :
297 : static const PLDHashTableOps hash_table_ops =
298 : {
299 : FuncStringContentListHashtableHashKey,
300 : FuncStringContentListHashtableMatchEntry,
301 : PLDHashTable::MoveEntryStub,
302 : PLDHashTable::ClearEntryStub
303 : };
304 :
305 : // Initialize the hashtable if needed.
306 0 : if (!gFuncStringContentListHashTable) {
307 0 : gFuncStringContentListHashTable =
308 0 : new PLDHashTable(&hash_table_ops, sizeof(FuncStringContentListHashEntry));
309 : }
310 :
311 0 : FuncStringContentListHashEntry *entry = nullptr;
312 : // First we look in our hashtable. Then we create a content list if needed
313 0 : if (gFuncStringContentListHashTable) {
314 0 : nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
315 :
316 0 : entry = static_cast<FuncStringContentListHashEntry*>
317 0 : (gFuncStringContentListHashTable->Add(&hashKey, fallible));
318 0 : if (entry) {
319 0 : list = entry->mContentList;
320 : #ifdef DEBUG
321 0 : MOZ_ASSERT_IF(list, list->mType == ListType::sType);
322 : #endif
323 : }
324 : }
325 :
326 0 : if (!list) {
327 : // We need to create a ContentList and add it to our new entry, if
328 : // we have an entry
329 0 : list = new ListType(aRootNode, aFunc, aDestroyFunc, aDataAllocator,
330 : aString);
331 0 : if (entry) {
332 0 : entry->mContentList = list;
333 : }
334 : }
335 :
336 : // Don't cache these lists globally
337 :
338 0 : return list.forget();
339 : }
340 :
341 : already_AddRefed<nsContentList>
342 0 : NS_GetFuncStringNodeList(nsINode* aRootNode,
343 : nsContentListMatchFunc aFunc,
344 : nsContentListDestroyFunc aDestroyFunc,
345 : nsFuncStringContentListDataAllocator aDataAllocator,
346 : const nsAString& aString)
347 : {
348 : return GetFuncStringContentList<nsCacheableFuncStringNodeList>(aRootNode,
349 : aFunc,
350 : aDestroyFunc,
351 : aDataAllocator,
352 0 : aString);
353 : }
354 :
355 : already_AddRefed<nsContentList>
356 0 : NS_GetFuncStringHTMLCollection(nsINode* aRootNode,
357 : nsContentListMatchFunc aFunc,
358 : nsContentListDestroyFunc aDestroyFunc,
359 : nsFuncStringContentListDataAllocator aDataAllocator,
360 : const nsAString& aString)
361 : {
362 : return GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>(aRootNode,
363 : aFunc,
364 : aDestroyFunc,
365 : aDataAllocator,
366 0 : aString);
367 : }
368 :
369 : //-----------------------------------------------------
370 : // nsContentList implementation
371 :
372 8 : nsContentList::nsContentList(nsINode* aRootNode,
373 : int32_t aMatchNameSpaceId,
374 : nsIAtom* aHTMLMatchAtom,
375 : nsIAtom* aXMLMatchAtom,
376 8 : bool aDeep)
377 : : nsBaseContentList(),
378 : mRootNode(aRootNode),
379 : mMatchNameSpaceId(aMatchNameSpaceId),
380 : mHTMLMatchAtom(aHTMLMatchAtom),
381 : mXMLMatchAtom(aXMLMatchAtom),
382 : mFunc(nullptr),
383 : mDestroyFunc(nullptr),
384 : mData(nullptr),
385 : mState(LIST_DIRTY),
386 : mDeep(aDeep),
387 : mFuncMayDependOnAttr(false),
388 8 : mIsHTMLDocument(aRootNode->OwnerDoc()->IsHTMLDocument())
389 : {
390 8 : NS_ASSERTION(mRootNode, "Must have root");
391 8 : if (nsGkAtoms::_asterisk == mHTMLMatchAtom) {
392 5 : NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterisk, "HTML atom and XML atom are not both asterisk?");
393 5 : mMatchAll = true;
394 : }
395 : else {
396 3 : mMatchAll = false;
397 : }
398 8 : mRootNode->AddMutationObserver(this);
399 :
400 : // We only need to flush if we're in an non-HTML document, since the
401 : // HTML5 parser doesn't need flushing. Further, if we're not in a
402 : // document at all right now (in the GetUncomposedDoc() sense), we're
403 : // not parser-created and don't need to be flushing stuff under us
404 : // to get our kids right.
405 8 : nsIDocument* doc = mRootNode->GetUncomposedDoc();
406 8 : mFlushesNeeded = doc && !doc->IsHTMLDocument();
407 8 : }
408 :
409 2 : nsContentList::nsContentList(nsINode* aRootNode,
410 : nsContentListMatchFunc aFunc,
411 : nsContentListDestroyFunc aDestroyFunc,
412 : void* aData,
413 : bool aDeep,
414 : nsIAtom* aMatchAtom,
415 : int32_t aMatchNameSpaceId,
416 2 : bool aFuncMayDependOnAttr)
417 : : nsBaseContentList(),
418 : mRootNode(aRootNode),
419 : mMatchNameSpaceId(aMatchNameSpaceId),
420 : mHTMLMatchAtom(aMatchAtom),
421 : mXMLMatchAtom(aMatchAtom),
422 : mFunc(aFunc),
423 : mDestroyFunc(aDestroyFunc),
424 : mData(aData),
425 : mState(LIST_DIRTY),
426 : mMatchAll(false),
427 : mDeep(aDeep),
428 : mFuncMayDependOnAttr(aFuncMayDependOnAttr),
429 2 : mIsHTMLDocument(false)
430 : {
431 2 : NS_ASSERTION(mRootNode, "Must have root");
432 2 : mRootNode->AddMutationObserver(this);
433 :
434 : // We only need to flush if we're in an non-HTML document, since the
435 : // HTML5 parser doesn't need flushing. Further, if we're not in a
436 : // document at all right now (in the GetUncomposedDoc() sense), we're
437 : // not parser-created and don't need to be flushing stuff under us
438 : // to get our kids right.
439 2 : nsIDocument* doc = mRootNode->GetUncomposedDoc();
440 2 : mFlushesNeeded = doc && !doc->IsHTMLDocument();
441 2 : }
442 :
443 3 : nsContentList::~nsContentList()
444 : {
445 1 : RemoveFromHashtable();
446 1 : if (mRootNode) {
447 0 : mRootNode->RemoveMutationObserver(this);
448 : }
449 :
450 1 : if (mDestroyFunc) {
451 : // Clean up mData
452 0 : (*mDestroyFunc)(mData);
453 : }
454 3 : }
455 :
456 : JSObject*
457 9 : nsContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
458 : {
459 9 : return HTMLCollectionBinding::Wrap(cx, this, aGivenProto);
460 : }
461 :
462 58 : NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList,
463 : nsIHTMLCollection, nsIDOMHTMLCollection,
464 : nsIMutationObserver)
465 :
466 : uint32_t
467 42 : nsContentList::Length(bool aDoFlush)
468 : {
469 42 : BringSelfUpToDate(aDoFlush);
470 :
471 42 : return mElements.Length();
472 : }
473 :
474 : nsIContent *
475 41 : nsContentList::Item(uint32_t aIndex, bool aDoFlush)
476 : {
477 41 : if (mRootNode && aDoFlush && mFlushesNeeded) {
478 : // XXX sXBL/XBL2 issue
479 17 : nsIDocument* doc = mRootNode->GetUncomposedDoc();
480 17 : if (doc) {
481 : // Flush pending content changes Bug 4891.
482 17 : doc->FlushPendingNotifications(FlushType::ContentAndNotify);
483 : }
484 : }
485 :
486 41 : if (mState != LIST_UP_TO_DATE)
487 4 : PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1);
488 :
489 : ASSERT_IN_SYNC;
490 41 : NS_ASSERTION(!mRootNode || mState != LIST_DIRTY,
491 : "PopulateSelf left the list in a dirty (useless) state!");
492 :
493 41 : return mElements.SafeElementAt(aIndex);
494 : }
495 :
496 : Element*
497 0 : nsContentList::NamedItem(const nsAString& aName, bool aDoFlush)
498 : {
499 0 : if (aName.IsEmpty()) {
500 0 : return nullptr;
501 : }
502 :
503 0 : BringSelfUpToDate(aDoFlush);
504 :
505 0 : uint32_t i, count = mElements.Length();
506 :
507 : // Typically IDs and names are atomized
508 0 : nsCOMPtr<nsIAtom> name = NS_Atomize(aName);
509 0 : NS_ENSURE_TRUE(name, nullptr);
510 :
511 0 : for (i = 0; i < count; i++) {
512 0 : nsIContent *content = mElements[i];
513 : // XXX Should this pass eIgnoreCase?
514 0 : if (content &&
515 0 : ((content->IsHTMLElement() &&
516 0 : content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
517 0 : name, eCaseMatters)) ||
518 0 : content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
519 : name, eCaseMatters))) {
520 0 : return content->AsElement();
521 : }
522 : }
523 :
524 0 : return nullptr;
525 : }
526 :
527 : void
528 0 : nsContentList::GetSupportedNames(nsTArray<nsString>& aNames)
529 : {
530 0 : BringSelfUpToDate(true);
531 :
532 0 : AutoTArray<nsIAtom*, 8> atoms;
533 0 : for (uint32_t i = 0; i < mElements.Length(); ++i) {
534 0 : nsIContent *content = mElements.ElementAt(i);
535 0 : if (content->HasID()) {
536 0 : nsIAtom* id = content->GetID();
537 0 : MOZ_ASSERT(id != nsGkAtoms::_empty,
538 : "Empty ids don't get atomized");
539 0 : if (!atoms.Contains(id)) {
540 0 : atoms.AppendElement(id);
541 : }
542 : }
543 :
544 0 : nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(content);
545 0 : if (el) {
546 : // XXXbz should we be checking for particular tags here? How
547 : // stable is this part of the spec?
548 : // Note: nsINode::HasName means the name is exposed on the document,
549 : // which is false for options, so we don't check it here.
550 0 : const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
551 0 : if (val && val->Type() == nsAttrValue::eAtom) {
552 0 : nsIAtom* name = val->GetAtomValue();
553 0 : MOZ_ASSERT(name != nsGkAtoms::_empty,
554 : "Empty names don't get atomized");
555 0 : if (!atoms.Contains(name)) {
556 0 : atoms.AppendElement(name);
557 : }
558 : }
559 : }
560 : }
561 :
562 0 : uint32_t atomsLen = atoms.Length();
563 0 : nsString* names = aNames.AppendElements(atomsLen);
564 0 : for (uint32_t i = 0; i < atomsLen; ++i) {
565 0 : atoms[i]->ToString(names[i]);
566 : }
567 0 : }
568 :
569 : int32_t
570 0 : nsContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
571 : {
572 0 : BringSelfUpToDate(aDoFlush);
573 :
574 0 : return mElements.IndexOf(aContent);
575 : }
576 :
577 : int32_t
578 0 : nsContentList::IndexOf(nsIContent* aContent)
579 : {
580 0 : return IndexOf(aContent, true);
581 : }
582 :
583 : void
584 0 : nsContentList::NodeWillBeDestroyed(const nsINode* aNode)
585 : {
586 : // We shouldn't do anything useful from now on
587 :
588 0 : RemoveFromCaches();
589 0 : mRootNode = nullptr;
590 :
591 : // We will get no more updates, so we can never know we're up to
592 : // date
593 0 : SetDirty();
594 0 : }
595 :
596 : void
597 1 : nsContentList::LastRelease()
598 : {
599 1 : RemoveFromCaches();
600 1 : if (mRootNode) {
601 1 : mRootNode->RemoveMutationObserver(this);
602 1 : mRootNode = nullptr;
603 : }
604 1 : SetDirty();
605 1 : }
606 :
607 : NS_IMETHODIMP
608 42 : nsContentList::GetLength(uint32_t* aLength)
609 : {
610 42 : *aLength = Length(true);
611 :
612 42 : return NS_OK;
613 : }
614 :
615 : NS_IMETHODIMP
616 0 : nsContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
617 : {
618 0 : nsINode* node = Item(aIndex);
619 :
620 0 : if (node) {
621 0 : return CallQueryInterface(node, aReturn);
622 : }
623 :
624 0 : *aReturn = nullptr;
625 :
626 0 : return NS_OK;
627 : }
628 :
629 : NS_IMETHODIMP
630 0 : nsContentList::NamedItem(const nsAString& aName, nsIDOMNode** aReturn)
631 : {
632 0 : nsIContent *content = NamedItem(aName, true);
633 :
634 0 : if (content) {
635 0 : return CallQueryInterface(content, aReturn);
636 : }
637 :
638 0 : *aReturn = nullptr;
639 :
640 0 : return NS_OK;
641 : }
642 :
643 : Element*
644 40 : nsContentList::GetElementAt(uint32_t aIndex)
645 : {
646 40 : return static_cast<Element*>(Item(aIndex, true));
647 : }
648 :
649 : nsIContent*
650 0 : nsContentList::Item(uint32_t aIndex)
651 : {
652 0 : return GetElementAt(aIndex);
653 : }
654 :
655 : void
656 104 : nsContentList::AttributeChanged(nsIDocument *aDocument, Element* aElement,
657 : int32_t aNameSpaceID, nsIAtom* aAttribute,
658 : int32_t aModType,
659 : const nsAttrValue* aOldValue)
660 : {
661 104 : NS_PRECONDITION(aElement, "Must have a content node to work with");
662 :
663 208 : if (!mFunc || !mFuncMayDependOnAttr || mState == LIST_DIRTY ||
664 104 : !MayContainRelevantNodes(aElement->GetParentNode()) ||
665 0 : !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
666 : // Either we're already dirty or this notification doesn't affect
667 : // whether we might match aElement.
668 104 : return;
669 : }
670 :
671 0 : if (Match(aElement)) {
672 0 : if (mElements.IndexOf(aElement) == mElements.NoIndex) {
673 : // We match aElement now, and it's not in our list already. Just dirty
674 : // ourselves; this is simpler than trying to figure out where to insert
675 : // aElement.
676 0 : SetDirty();
677 : }
678 : } else {
679 : // We no longer match aElement. Remove it from our list. If it's
680 : // already not there, this is a no-op (though a potentially
681 : // expensive one). Either way, no change of mState is required
682 : // here.
683 0 : mElements.RemoveElement(aElement);
684 : }
685 : }
686 :
687 : void
688 10 : nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
689 : nsIContent* aFirstNewContent,
690 : int32_t aNewIndexInContainer)
691 : {
692 10 : NS_PRECONDITION(aContainer, "Can't get at the new content if no container!");
693 :
694 : /*
695 : * If the state is LIST_DIRTY then we have no useful information in our list
696 : * and we want to put off doing work as much as possible.
697 : *
698 : * Also, if aContainer is anonymous from our point of view, we know that we
699 : * can't possibly be matching any of the kids.
700 : *
701 : * Optimize out also the common case when just one new node is appended and
702 : * it doesn't match us.
703 : */
704 30 : if (mState == LIST_DIRTY ||
705 17 : !nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer) ||
706 29 : !MayContainRelevantNodes(aContainer) ||
707 4 : (!aFirstNewContent->HasChildren() &&
708 4 : !aFirstNewContent->GetNextSibling() &&
709 2 : !MatchSelf(aFirstNewContent))) {
710 10 : return;
711 : }
712 :
713 : /*
714 : * We want to handle the case of ContentAppended by sometimes
715 : * appending the content to our list, not just setting state to
716 : * LIST_DIRTY, since most of our ContentAppended notifications
717 : * should come during pageload and be at the end of the document.
718 : * Do a bit of work to see whether we could just append to what we
719 : * already have.
720 : */
721 :
722 0 : int32_t count = aContainer->GetChildCount();
723 :
724 0 : if (count > 0) {
725 0 : uint32_t ourCount = mElements.Length();
726 0 : bool appendToList = false;
727 0 : if (ourCount == 0) {
728 0 : appendToList = true;
729 : } else {
730 0 : nsIContent* ourLastContent = mElements[ourCount - 1];
731 : /*
732 : * We want to append instead of invalidating if the first thing
733 : * that got appended comes after ourLastContent.
734 : */
735 0 : if (nsContentUtils::PositionIsBefore(ourLastContent, aFirstNewContent)) {
736 0 : appendToList = true;
737 : }
738 : }
739 :
740 :
741 0 : if (!appendToList) {
742 : // The new stuff is somewhere in the middle of our list; check
743 : // whether we need to invalidate
744 0 : for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
745 0 : if (MatchSelf(cur)) {
746 : // Uh-oh. We're gonna have to add elements into the middle
747 : // of our list. That's not worth the effort.
748 0 : SetDirty();
749 0 : break;
750 : }
751 : }
752 :
753 : ASSERT_IN_SYNC;
754 0 : return;
755 : }
756 :
757 : /*
758 : * At this point we know we could append. If we're not up to
759 : * date, however, that would be a bad idea -- it could miss some
760 : * content that we never picked up due to being lazy. Further, we
761 : * may never get asked for this content... so don't grab it yet.
762 : */
763 0 : if (mState == LIST_LAZY) // be lazy
764 0 : return;
765 :
766 : /*
767 : * We're up to date. That means someone's actively using us; we
768 : * may as well grab this content....
769 : */
770 0 : if (mDeep) {
771 0 : for (nsIContent* cur = aFirstNewContent;
772 0 : cur;
773 0 : cur = cur->GetNextNode(aContainer)) {
774 0 : if (cur->IsElement() && Match(cur->AsElement())) {
775 0 : mElements.AppendElement(cur);
776 : }
777 : }
778 : } else {
779 0 : for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
780 0 : if (cur->IsElement() && Match(cur->AsElement())) {
781 0 : mElements.AppendElement(cur);
782 : }
783 : }
784 : }
785 :
786 : ASSERT_IN_SYNC;
787 : }
788 : }
789 :
790 : void
791 13 : nsContentList::ContentInserted(nsIDocument *aDocument,
792 : nsIContent* aContainer,
793 : nsIContent* aChild,
794 : int32_t aIndexInContainer)
795 : {
796 : // Note that aContainer can be null here if we are inserting into
797 : // the document itself; any attempted optimizations to this method
798 : // should deal with that.
799 39 : if (mState != LIST_DIRTY &&
800 13 : MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
801 13 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
802 0 : MatchSelf(aChild)) {
803 0 : SetDirty();
804 : }
805 :
806 : ASSERT_IN_SYNC;
807 13 : }
808 :
809 : void
810 28 : nsContentList::ContentRemoved(nsIDocument *aDocument,
811 : nsIContent* aContainer,
812 : nsIContent* aChild,
813 : int32_t aIndexInContainer,
814 : nsIContent* aPreviousSibling)
815 : {
816 : // Note that aContainer can be null here if we are removing from
817 : // the document itself; any attempted optimizations to this method
818 : // should deal with that.
819 64 : if (mState != LIST_DIRTY &&
820 12 : MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
821 36 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
822 4 : MatchSelf(aChild)) {
823 3 : SetDirty();
824 : }
825 :
826 : ASSERT_IN_SYNC;
827 28 : }
828 :
829 : bool
830 62 : nsContentList::Match(Element *aElement)
831 : {
832 62 : if (mFunc) {
833 4 : return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData);
834 : }
835 :
836 58 : if (!mXMLMatchAtom)
837 0 : return false;
838 :
839 58 : NodeInfo *ni = aElement->NodeInfo();
840 :
841 58 : bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown;
842 58 : bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard;
843 58 : bool toReturn = mMatchAll;
844 58 : if (!unknown && !wildcard)
845 8 : toReturn &= ni->NamespaceEquals(mMatchNameSpaceId);
846 :
847 58 : if (toReturn)
848 45 : return toReturn;
849 :
850 : bool matchHTML =
851 13 : mIsHTMLDocument && aElement->GetNameSpaceID() == kNameSpaceID_XHTML;
852 :
853 13 : if (unknown) {
854 10 : return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) :
855 10 : ni->QualifiedNameEquals(mXMLMatchAtom);
856 : }
857 :
858 8 : if (wildcard) {
859 0 : return matchHTML ? ni->Equals(mHTMLMatchAtom) :
860 0 : ni->Equals(mXMLMatchAtom);
861 : }
862 :
863 12 : return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId) :
864 12 : ni->Equals(mXMLMatchAtom, mMatchNameSpaceId);
865 : }
866 :
867 : bool
868 6 : nsContentList::MatchSelf(nsIContent *aContent)
869 : {
870 6 : NS_PRECONDITION(aContent, "Can't match null stuff, you know");
871 6 : NS_PRECONDITION(mDeep || aContent->GetParentNode() == mRootNode,
872 : "MatchSelf called on a node that we can't possibly match");
873 :
874 6 : if (!aContent->IsElement()) {
875 0 : return false;
876 : }
877 :
878 6 : if (Match(aContent->AsElement()))
879 3 : return true;
880 :
881 3 : if (!mDeep)
882 0 : return false;
883 :
884 3 : for (nsIContent* cur = aContent->GetFirstChild();
885 3 : cur;
886 0 : cur = cur->GetNextNode(aContent)) {
887 0 : if (cur->IsElement() && Match(cur->AsElement())) {
888 0 : return true;
889 : }
890 : }
891 :
892 3 : return false;
893 : }
894 :
895 : void
896 11 : nsContentList::PopulateSelf(uint32_t aNeededLength)
897 : {
898 11 : if (!mRootNode) {
899 0 : return;
900 : }
901 :
902 : ASSERT_IN_SYNC;
903 :
904 11 : uint32_t count = mElements.Length();
905 11 : NS_ASSERTION(mState != LIST_DIRTY || count == 0,
906 : "Reset() not called when setting state to LIST_DIRTY?");
907 :
908 11 : if (count >= aNeededLength) // We're all set
909 0 : return;
910 :
911 11 : uint32_t elementsToAppend = aNeededLength - count;
912 : #ifdef DEBUG
913 11 : uint32_t invariant = elementsToAppend + mElements.Length();
914 : #endif
915 :
916 11 : if (mDeep) {
917 : // If we already have nodes start searching at the last one, otherwise
918 : // start searching at the root.
919 5 : nsINode* cur = count ? mElements[count - 1] : mRootNode;
920 14 : do {
921 19 : cur = cur->GetNextNode(mRootNode);
922 19 : if (!cur) {
923 3 : break;
924 : }
925 16 : if (cur->IsElement() && Match(cur->AsElement())) {
926 : // Append AsElement() to get nsIContent instead of nsINode
927 2 : mElements.AppendElement(cur->AsElement());
928 2 : --elementsToAppend;
929 : }
930 16 : } while (elementsToAppend);
931 : } else {
932 : nsIContent* cur =
933 6 : count ? mElements[count-1]->GetNextSibling() : mRootNode->GetFirstChild();
934 90 : for ( ; cur && elementsToAppend; cur = cur->GetNextSibling()) {
935 42 : if (cur->IsElement() && Match(cur->AsElement())) {
936 42 : mElements.AppendElement(cur);
937 42 : --elementsToAppend;
938 : }
939 : }
940 : }
941 :
942 11 : NS_ASSERTION(elementsToAppend + mElements.Length() == invariant,
943 : "Something is awry!");
944 :
945 11 : if (elementsToAppend != 0)
946 9 : mState = LIST_UP_TO_DATE;
947 : else
948 2 : mState = LIST_LAZY;
949 :
950 : ASSERT_IN_SYNC;
951 : }
952 :
953 : void
954 2 : nsContentList::RemoveFromHashtable()
955 : {
956 2 : if (mFunc) {
957 : // This can't be in the table anyway
958 0 : return;
959 : }
960 :
961 4 : nsDependentAtomString str(mXMLMatchAtom);
962 2 : nsContentListKey key(mRootNode, mMatchNameSpaceId, str, mIsHTMLDocument);
963 2 : uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(key);
964 2 : if (sRecentlyUsedContentLists[recentlyUsedCacheIndex] == this) {
965 1 : sRecentlyUsedContentLists[recentlyUsedCacheIndex] = nullptr;
966 : }
967 :
968 2 : if (!gContentListHashTable)
969 0 : return;
970 :
971 2 : gContentListHashTable->Remove(&key);
972 :
973 2 : if (gContentListHashTable->EntryCount() == 0) {
974 1 : delete gContentListHashTable;
975 1 : gContentListHashTable = nullptr;
976 : }
977 : }
978 :
979 : void
980 42 : nsContentList::BringSelfUpToDate(bool aDoFlush)
981 : {
982 42 : if (mRootNode && aDoFlush && mFlushesNeeded) {
983 : // XXX sXBL/XBL2 issue
984 18 : nsIDocument* doc = mRootNode->GetUncomposedDoc();
985 18 : if (doc) {
986 : // Flush pending content changes Bug 4891.
987 18 : doc->FlushPendingNotifications(FlushType::ContentAndNotify);
988 : }
989 : }
990 :
991 42 : if (mState != LIST_UP_TO_DATE)
992 7 : PopulateSelf(uint32_t(-1));
993 :
994 : ASSERT_IN_SYNC;
995 42 : NS_ASSERTION(!mRootNode || mState == LIST_UP_TO_DATE,
996 : "PopulateSelf dod not bring content list up to date!");
997 42 : }
998 :
999 0 : nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList()
1000 : {
1001 0 : RemoveFromFuncStringHashtable();
1002 0 : }
1003 :
1004 : void
1005 0 : nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable()
1006 : {
1007 0 : if (!gFuncStringContentListHashTable) {
1008 0 : return;
1009 : }
1010 :
1011 0 : nsFuncStringCacheKey key(mRootNode, mFunc, mString);
1012 0 : gFuncStringContentListHashTable->Remove(&key);
1013 :
1014 0 : if (gFuncStringContentListHashTable->EntryCount() == 0) {
1015 0 : delete gFuncStringContentListHashTable;
1016 0 : gFuncStringContentListHashTable = nullptr;
1017 : }
1018 : }
1019 :
1020 : #ifdef DEBUG_CONTENT_LIST
1021 : void
1022 : nsContentList::AssertInSync()
1023 : {
1024 : if (mState == LIST_DIRTY) {
1025 : return;
1026 : }
1027 :
1028 : if (!mRootNode) {
1029 : NS_ASSERTION(mElements.Length() == 0 && mState == LIST_DIRTY,
1030 : "Empty iterator isn't quite empty?");
1031 : return;
1032 : }
1033 :
1034 : // XXX This code will need to change if nsContentLists can ever match
1035 : // elements that are outside of the document element.
1036 : nsIContent *root;
1037 : if (mRootNode->IsNodeOfType(nsINode::eDOCUMENT)) {
1038 : root = static_cast<nsIDocument*>(mRootNode)->GetRootElement();
1039 : }
1040 : else {
1041 : root = static_cast<nsIContent*>(mRootNode);
1042 : }
1043 :
1044 : nsCOMPtr<nsIContentIterator> iter;
1045 : if (mDeep) {
1046 : iter = NS_NewPreContentIterator();
1047 : iter->Init(root);
1048 : iter->First();
1049 : }
1050 :
1051 : uint32_t cnt = 0, index = 0;
1052 : while (true) {
1053 : if (cnt == mElements.Length() && mState == LIST_LAZY) {
1054 : break;
1055 : }
1056 :
1057 : nsIContent *cur = mDeep ? iter->GetCurrentNode() :
1058 : mRootNode->GetChildAt(index++);
1059 : if (!cur) {
1060 : break;
1061 : }
1062 :
1063 : if (cur->IsElement() && Match(cur->AsElement())) {
1064 : NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur,
1065 : "Elements is out of sync");
1066 : ++cnt;
1067 : }
1068 :
1069 : if (mDeep) {
1070 : iter->Next();
1071 : }
1072 : }
1073 :
1074 : NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
1075 : }
1076 : #endif
1077 :
1078 : //-----------------------------------------------------
1079 : // nsCacheableFuncStringNodeList
1080 :
1081 : JSObject*
1082 0 : nsCacheableFuncStringNodeList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
1083 : {
1084 0 : return NodeListBinding::Wrap(cx, this, aGivenProto);
1085 : }
1086 :
1087 : //-----------------------------------------------------
1088 : // nsCacheableFuncStringHTMLCollection
1089 :
1090 : JSObject*
1091 0 : nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
1092 : {
1093 0 : return HTMLCollectionBinding::Wrap(cx, this, aGivenProto);
1094 : }
1095 :
1096 : //-----------------------------------------------------
1097 : // nsLabelsNodeList
1098 :
1099 : JSObject*
1100 0 : nsLabelsNodeList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
1101 : {
1102 0 : return NodeListBinding::Wrap(cx, this, aGivenProto);
1103 : }
1104 :
1105 : void
1106 0 : nsLabelsNodeList::AttributeChanged(nsIDocument* aDocument, Element* aElement,
1107 : int32_t aNameSpaceID, nsIAtom* aAttribute,
1108 : int32_t aModType,
1109 : const nsAttrValue* aOldValue)
1110 : {
1111 0 : MOZ_ASSERT(aElement, "Must have a content node to work with");
1112 0 : if (mState == LIST_DIRTY ||
1113 0 : !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
1114 0 : return;
1115 : }
1116 :
1117 : // We need to handle input type changes to or from "hidden".
1118 0 : if (aElement->IsHTMLElement(nsGkAtoms::input) &&
1119 0 : aAttribute == nsGkAtoms::type && aNameSpaceID == kNameSpaceID_None) {
1120 0 : SetDirty();
1121 0 : return;
1122 : }
1123 : }
1124 :
1125 : void
1126 0 : nsLabelsNodeList::ContentAppended(nsIDocument* aDocument,
1127 : nsIContent* aContainer,
1128 : nsIContent* aFirstNewContent,
1129 : int32_t aNewIndexInContainer)
1130 : {
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 0 : if (mState != LIST_DIRTY ||
1135 0 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer)) {
1136 0 : SetDirty();
1137 0 : return;
1138 : }
1139 : }
1140 :
1141 : void
1142 0 : nsLabelsNodeList::ContentInserted(nsIDocument* aDocument,
1143 : nsIContent* aContainer,
1144 : nsIContent* aChild,
1145 : int32_t aIndexInContainer)
1146 : {
1147 : // If a labelable element is moved to outside or inside of
1148 : // nested associated labels, we're gonna have to modify
1149 : // the content list.
1150 0 : if (mState != LIST_DIRTY ||
1151 0 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
1152 0 : SetDirty();
1153 0 : return;
1154 : }
1155 : }
1156 :
1157 : void
1158 0 : nsLabelsNodeList::ContentRemoved(nsIDocument* aDocument,
1159 : nsIContent* aContainer,
1160 : nsIContent* aChild,
1161 : int32_t aIndexInContainer,
1162 : nsIContent* aPreviousSibling)
1163 : {
1164 : // If a labelable element is removed, we're gonna have to clean
1165 : // the content list.
1166 0 : if (mState != LIST_DIRTY ||
1167 0 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
1168 0 : SetDirty();
1169 0 : return;
1170 : }
1171 : }
1172 :
1173 : void
1174 0 : nsLabelsNodeList::MaybeResetRoot(nsINode* aRootNode)
1175 : {
1176 0 : MOZ_ASSERT(aRootNode, "Must have root");
1177 0 : if (mRootNode == aRootNode) {
1178 0 : return;
1179 : }
1180 :
1181 0 : if (mRootNode) {
1182 0 : mRootNode->RemoveMutationObserver(this);
1183 : }
1184 0 : mRootNode = aRootNode;
1185 0 : mRootNode->AddMutationObserver(this);
1186 0 : SetDirty();
1187 : }
1188 :
1189 : void
1190 0 : nsLabelsNodeList::PopulateSelf(uint32_t aNeededLength)
1191 : {
1192 0 : MOZ_ASSERT(mRootNode, "Must have root");
1193 :
1194 : // Start searching at the root.
1195 0 : nsINode* cur = mRootNode;
1196 0 : if (mElements.IsEmpty() && cur->IsElement() && Match(cur->AsElement())) {
1197 0 : mElements.AppendElement(cur->AsElement());
1198 : }
1199 :
1200 0 : nsContentList::PopulateSelf(aNeededLength);
1201 0 : }
|