|
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/. */ |
|
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 |
|
29 // Form related includes |
|
30 #include "nsIDOMHTMLFormElement.h" |
|
31 |
|
32 #include "pldhash.h" |
|
33 |
|
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 |
|
40 |
|
41 using namespace mozilla; |
|
42 using namespace mozilla::dom; |
|
43 |
|
44 nsBaseContentList::~nsBaseContentList() |
|
45 { |
|
46 } |
|
47 |
|
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) |
|
59 |
|
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 |
|
72 |
|
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 |
|
76 |
|
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 |
|
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 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 |
|
91 |
|
92 |
|
93 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList) |
|
94 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBaseContentList) |
|
95 |
|
96 |
|
97 NS_IMETHODIMP |
|
98 nsBaseContentList::GetLength(uint32_t* aLength) |
|
99 { |
|
100 *aLength = mElements.Length(); |
|
101 |
|
102 return NS_OK; |
|
103 } |
|
104 |
|
105 NS_IMETHODIMP |
|
106 nsBaseContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn) |
|
107 { |
|
108 nsISupports *tmp = Item(aIndex); |
|
109 |
|
110 if (!tmp) { |
|
111 *aReturn = nullptr; |
|
112 |
|
113 return NS_OK; |
|
114 } |
|
115 |
|
116 return CallQueryInterface(tmp, aReturn); |
|
117 } |
|
118 |
|
119 nsIContent* |
|
120 nsBaseContentList::Item(uint32_t aIndex) |
|
121 { |
|
122 return mElements.SafeElementAt(aIndex); |
|
123 } |
|
124 |
|
125 |
|
126 int32_t |
|
127 nsBaseContentList::IndexOf(nsIContent *aContent, bool aDoFlush) |
|
128 { |
|
129 return mElements.IndexOf(aContent); |
|
130 } |
|
131 |
|
132 int32_t |
|
133 nsBaseContentList::IndexOf(nsIContent* aContent) |
|
134 { |
|
135 return IndexOf(aContent, true); |
|
136 } |
|
137 |
|
138 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList, |
|
139 mRoot) |
|
140 |
|
141 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsSimpleContentList) |
|
142 NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList) |
|
143 |
|
144 |
|
145 NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList) |
|
146 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList) |
|
147 |
|
148 JSObject* |
|
149 nsSimpleContentList::WrapObject(JSContext *cx) |
|
150 { |
|
151 return NodeListBinding::Wrap(cx, this); |
|
152 } |
|
153 |
|
154 // Hashtable for storing nsContentLists |
|
155 static PLDHashTable gContentListHashTable; |
|
156 |
|
157 #define RECENTLY_USED_CONTENT_LIST_CACHE_SIZE 31 |
|
158 static nsContentList* |
|
159 sRecentlyUsedContentLists[RECENTLY_USED_CONTENT_LIST_CACHE_SIZE] = {}; |
|
160 |
|
161 static MOZ_ALWAYS_INLINE uint32_t |
|
162 RecentlyUsedCacheIndex(const nsContentListKey& aKey) |
|
163 { |
|
164 return aKey.GetHash() % RECENTLY_USED_CONTENT_LIST_CACHE_SIZE; |
|
165 } |
|
166 |
|
167 struct ContentListHashEntry : public PLDHashEntryHdr |
|
168 { |
|
169 nsContentList* mContentList; |
|
170 }; |
|
171 |
|
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 } |
|
178 |
|
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); |
|
188 |
|
189 return list->MatchesKey(*ourKey); |
|
190 } |
|
191 |
|
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"); |
|
198 |
|
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 } |
|
207 |
|
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 }; |
|
218 |
|
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 } |
|
226 |
|
227 ContentListHashEntry *entry = nullptr; |
|
228 // First we look in our hashtable. Then we create a content list if needed |
|
229 if (gContentListHashTable.ops) { |
|
230 |
|
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 } |
|
240 |
|
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 } |
|
259 |
|
260 sRecentlyUsedContentLists[recentlyUsedCacheIndex] = list; |
|
261 return list.forget(); |
|
262 } |
|
263 |
|
264 #ifdef DEBUG |
|
265 const nsCacheableFuncStringContentList::ContentListType |
|
266 nsCacheableFuncStringNodeList::sType = nsCacheableFuncStringContentList::eNodeList; |
|
267 const nsCacheableFuncStringContentList::ContentListType |
|
268 nsCacheableFuncStringHTMLCollection::sType = nsCacheableFuncStringContentList::eHTMLCollection; |
|
269 #endif |
|
270 |
|
271 JSObject* |
|
272 nsCacheableFuncStringNodeList::WrapObject(JSContext *cx) |
|
273 { |
|
274 return NodeListBinding::Wrap(cx, this); |
|
275 } |
|
276 |
|
277 |
|
278 JSObject* |
|
279 nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx) |
|
280 { |
|
281 return HTMLCollectionBinding::Wrap(cx, this); |
|
282 } |
|
283 |
|
284 // Hashtable for storing nsCacheableFuncStringContentList |
|
285 static PLDHashTable gFuncStringContentListHashTable; |
|
286 |
|
287 struct FuncStringContentListHashEntry : public PLDHashEntryHdr |
|
288 { |
|
289 nsCacheableFuncStringContentList* mContentList; |
|
290 }; |
|
291 |
|
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 } |
|
299 |
|
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); |
|
309 |
|
310 return e->mContentList->Equals(ourKey); |
|
311 } |
|
312 |
|
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"); |
|
322 |
|
323 nsRefPtr<nsCacheableFuncStringContentList> list; |
|
324 |
|
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 }; |
|
335 |
|
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 } |
|
343 |
|
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); |
|
348 |
|
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 } |
|
362 |
|
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 } |
|
372 |
|
373 // Don't cache these lists globally |
|
374 |
|
375 return list.forget(); |
|
376 } |
|
377 |
|
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 } |
|
391 |
|
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 } |
|
405 |
|
406 // nsContentList implementation |
|
407 |
|
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); |
|
434 |
|
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 } |
|
443 |
|
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); |
|
467 |
|
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 } |
|
476 |
|
477 nsContentList::~nsContentList() |
|
478 { |
|
479 RemoveFromHashtable(); |
|
480 if (mRootNode) { |
|
481 mRootNode->RemoveMutationObserver(this); |
|
482 } |
|
483 |
|
484 if (mDestroyFunc) { |
|
485 // Clean up mData |
|
486 (*mDestroyFunc)(mData); |
|
487 } |
|
488 } |
|
489 |
|
490 JSObject* |
|
491 nsContentList::WrapObject(JSContext *cx) |
|
492 { |
|
493 return HTMLCollectionBinding::Wrap(cx, this); |
|
494 } |
|
495 |
|
496 NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList, |
|
497 nsIHTMLCollection, nsIDOMHTMLCollection, |
|
498 nsIMutationObserver) |
|
499 |
|
500 uint32_t |
|
501 nsContentList::Length(bool aDoFlush) |
|
502 { |
|
503 BringSelfUpToDate(aDoFlush); |
|
504 |
|
505 return mElements.Length(); |
|
506 } |
|
507 |
|
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 } |
|
519 |
|
520 if (mState != LIST_UP_TO_DATE) |
|
521 PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1); |
|
522 |
|
523 ASSERT_IN_SYNC; |
|
524 NS_ASSERTION(!mRootNode || mState != LIST_DIRTY, |
|
525 "PopulateSelf left the list in a dirty (useless) state!"); |
|
526 |
|
527 return mElements.SafeElementAt(aIndex); |
|
528 } |
|
529 |
|
530 Element* |
|
531 nsContentList::NamedItem(const nsAString& aName, bool aDoFlush) |
|
532 { |
|
533 BringSelfUpToDate(aDoFlush); |
|
534 |
|
535 uint32_t i, count = mElements.Length(); |
|
536 |
|
537 // Typically IDs and names are atomized |
|
538 nsCOMPtr<nsIAtom> name = do_GetAtom(aName); |
|
539 NS_ENSURE_TRUE(name, nullptr); |
|
540 |
|
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 } |
|
552 |
|
553 return nullptr; |
|
554 } |
|
555 |
|
556 void |
|
557 nsContentList::GetSupportedNames(unsigned aFlags, nsTArray<nsString>& aNames) |
|
558 { |
|
559 if (!(aFlags & JSITER_HIDDEN)) { |
|
560 return; |
|
561 } |
|
562 |
|
563 BringSelfUpToDate(true); |
|
564 |
|
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 } |
|
589 |
|
590 aNames.SetCapacity(atoms.Length()); |
|
591 for (uint32_t i = 0; i < atoms.Length(); ++i) { |
|
592 aNames.AppendElement(nsDependentAtomString(atoms[i])); |
|
593 } |
|
594 } |
|
595 |
|
596 int32_t |
|
597 nsContentList::IndexOf(nsIContent *aContent, bool aDoFlush) |
|
598 { |
|
599 BringSelfUpToDate(aDoFlush); |
|
600 |
|
601 return mElements.IndexOf(aContent); |
|
602 } |
|
603 |
|
604 int32_t |
|
605 nsContentList::IndexOf(nsIContent* aContent) |
|
606 { |
|
607 return IndexOf(aContent, true); |
|
608 } |
|
609 |
|
610 void |
|
611 nsContentList::NodeWillBeDestroyed(const nsINode* aNode) |
|
612 { |
|
613 // We shouldn't do anything useful from now on |
|
614 |
|
615 RemoveFromCaches(); |
|
616 mRootNode = nullptr; |
|
617 |
|
618 // We will get no more updates, so we can never know we're up to |
|
619 // date |
|
620 SetDirty(); |
|
621 } |
|
622 |
|
623 NS_IMETHODIMP |
|
624 nsContentList::GetLength(uint32_t* aLength) |
|
625 { |
|
626 *aLength = Length(true); |
|
627 |
|
628 return NS_OK; |
|
629 } |
|
630 |
|
631 NS_IMETHODIMP |
|
632 nsContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn) |
|
633 { |
|
634 nsINode* node = Item(aIndex); |
|
635 |
|
636 if (node) { |
|
637 return CallQueryInterface(node, aReturn); |
|
638 } |
|
639 |
|
640 *aReturn = nullptr; |
|
641 |
|
642 return NS_OK; |
|
643 } |
|
644 |
|
645 NS_IMETHODIMP |
|
646 nsContentList::NamedItem(const nsAString& aName, nsIDOMNode** aReturn) |
|
647 { |
|
648 nsIContent *content = NamedItem(aName, true); |
|
649 |
|
650 if (content) { |
|
651 return CallQueryInterface(content, aReturn); |
|
652 } |
|
653 |
|
654 *aReturn = nullptr; |
|
655 |
|
656 return NS_OK; |
|
657 } |
|
658 |
|
659 Element* |
|
660 nsContentList::GetElementAt(uint32_t aIndex) |
|
661 { |
|
662 return static_cast<Element*>(Item(aIndex, true)); |
|
663 } |
|
664 |
|
665 nsIContent* |
|
666 nsContentList::Item(uint32_t aIndex) |
|
667 { |
|
668 return GetElementAt(aIndex); |
|
669 } |
|
670 |
|
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"); |
|
677 |
|
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 } |
|
685 |
|
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 } |
|
701 |
|
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!"); |
|
708 |
|
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 } |
|
727 |
|
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 */ |
|
736 |
|
737 int32_t count = aContainer->GetChildCount(); |
|
738 |
|
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 } |
|
754 |
|
755 |
|
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 } |
|
767 |
|
768 ASSERT_IN_SYNC; |
|
769 return; |
|
770 } |
|
771 |
|
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; |
|
780 |
|
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 } |
|
800 |
|
801 ASSERT_IN_SYNC; |
|
802 } |
|
803 } |
|
804 |
|
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 } |
|
820 |
|
821 ASSERT_IN_SYNC; |
|
822 } |
|
823 |
|
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 } |
|
840 |
|
841 ASSERT_IN_SYNC; |
|
842 } |
|
843 |
|
844 bool |
|
845 nsContentList::Match(Element *aElement) |
|
846 { |
|
847 if (mFunc) { |
|
848 return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData); |
|
849 } |
|
850 |
|
851 if (!mXMLMatchAtom) |
|
852 return false; |
|
853 |
|
854 nsINodeInfo *ni = aElement->NodeInfo(); |
|
855 |
|
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); |
|
861 |
|
862 if (toReturn) |
|
863 return toReturn; |
|
864 |
|
865 bool matchHTML = aElement->GetNameSpaceID() == kNameSpaceID_XHTML && |
|
866 aElement->OwnerDoc()->IsHTML(); |
|
867 |
|
868 if (unknown) { |
|
869 return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) : |
|
870 ni->QualifiedNameEquals(mXMLMatchAtom); |
|
871 } |
|
872 |
|
873 if (wildcard) { |
|
874 return matchHTML ? ni->Equals(mHTMLMatchAtom) : |
|
875 ni->Equals(mXMLMatchAtom); |
|
876 } |
|
877 |
|
878 return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId) : |
|
879 ni->Equals(mXMLMatchAtom, mMatchNameSpaceId); |
|
880 } |
|
881 |
|
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"); |
|
888 |
|
889 if (!aContent->IsElement()) { |
|
890 return false; |
|
891 } |
|
892 |
|
893 if (Match(aContent->AsElement())) |
|
894 return true; |
|
895 |
|
896 if (!mDeep) |
|
897 return false; |
|
898 |
|
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 } |
|
906 |
|
907 return false; |
|
908 } |
|
909 |
|
910 void |
|
911 nsContentList::PopulateSelf(uint32_t aNeededLength) |
|
912 { |
|
913 if (!mRootNode) { |
|
914 return; |
|
915 } |
|
916 |
|
917 ASSERT_IN_SYNC; |
|
918 |
|
919 uint32_t count = mElements.Length(); |
|
920 NS_ASSERTION(mState != LIST_DIRTY || count == 0, |
|
921 "Reset() not called when setting state to LIST_DIRTY?"); |
|
922 |
|
923 if (count >= aNeededLength) // We're all set |
|
924 return; |
|
925 |
|
926 uint32_t elementsToAppend = aNeededLength - count; |
|
927 #ifdef DEBUG |
|
928 uint32_t invariant = elementsToAppend + mElements.Length(); |
|
929 #endif |
|
930 |
|
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 } |
|
956 |
|
957 NS_ASSERTION(elementsToAppend + mElements.Length() == invariant, |
|
958 "Something is awry!"); |
|
959 |
|
960 if (elementsToAppend != 0) |
|
961 mState = LIST_UP_TO_DATE; |
|
962 else |
|
963 mState = LIST_LAZY; |
|
964 |
|
965 ASSERT_IN_SYNC; |
|
966 } |
|
967 |
|
968 void |
|
969 nsContentList::RemoveFromHashtable() |
|
970 { |
|
971 if (mFunc) { |
|
972 // This can't be in the table anyway |
|
973 return; |
|
974 } |
|
975 |
|
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 } |
|
982 |
|
983 if (!gContentListHashTable.ops) |
|
984 return; |
|
985 |
|
986 PL_DHashTableOperate(&gContentListHashTable, |
|
987 &key, |
|
988 PL_DHASH_REMOVE); |
|
989 |
|
990 if (gContentListHashTable.entryCount == 0) { |
|
991 PL_DHashTableFinish(&gContentListHashTable); |
|
992 gContentListHashTable.ops = nullptr; |
|
993 } |
|
994 } |
|
995 |
|
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 } |
|
1007 |
|
1008 if (mState != LIST_UP_TO_DATE) |
|
1009 PopulateSelf(uint32_t(-1)); |
|
1010 |
|
1011 ASSERT_IN_SYNC; |
|
1012 NS_ASSERTION(!mRootNode || mState == LIST_UP_TO_DATE, |
|
1013 "PopulateSelf dod not bring content list up to date!"); |
|
1014 } |
|
1015 |
|
1016 nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList() |
|
1017 { |
|
1018 RemoveFromFuncStringHashtable(); |
|
1019 } |
|
1020 |
|
1021 void |
|
1022 nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable() |
|
1023 { |
|
1024 if (!gFuncStringContentListHashTable.ops) { |
|
1025 return; |
|
1026 } |
|
1027 |
|
1028 nsFuncStringCacheKey key(mRootNode, mFunc, mString); |
|
1029 PL_DHashTableOperate(&gFuncStringContentListHashTable, |
|
1030 &key, |
|
1031 PL_DHASH_REMOVE); |
|
1032 |
|
1033 if (gFuncStringContentListHashTable.entryCount == 0) { |
|
1034 PL_DHashTableFinish(&gFuncStringContentListHashTable); |
|
1035 gFuncStringContentListHashTable.ops = nullptr; |
|
1036 } |
|
1037 } |
|
1038 |
|
1039 #ifdef DEBUG_CONTENT_LIST |
|
1040 void |
|
1041 nsContentList::AssertInSync() |
|
1042 { |
|
1043 if (mState == LIST_DIRTY) { |
|
1044 return; |
|
1045 } |
|
1046 |
|
1047 if (!mRootNode) { |
|
1048 NS_ASSERTION(mElements.Length() == 0 && mState == LIST_DIRTY, |
|
1049 "Empty iterator isn't quite empty?"); |
|
1050 return; |
|
1051 } |
|
1052 |
|
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 } |
|
1062 |
|
1063 nsCOMPtr<nsIContentIterator> iter; |
|
1064 if (mDeep) { |
|
1065 iter = NS_NewPreContentIterator(); |
|
1066 iter->Init(root); |
|
1067 iter->First(); |
|
1068 } |
|
1069 |
|
1070 uint32_t cnt = 0, index = 0; |
|
1071 while (true) { |
|
1072 if (cnt == mElements.Length() && mState == LIST_LAZY) { |
|
1073 break; |
|
1074 } |
|
1075 |
|
1076 nsIContent *cur = mDeep ? iter->GetCurrentNode() : |
|
1077 mRootNode->GetChildAt(index++); |
|
1078 if (!cur) { |
|
1079 break; |
|
1080 } |
|
1081 |
|
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 } |
|
1087 |
|
1088 if (mDeep) { |
|
1089 iter->Next(); |
|
1090 } |
|
1091 } |
|
1092 |
|
1093 NS_ASSERTION(cnt == mElements.Length(), "Too few elements"); |
|
1094 } |
|
1095 #endif |