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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : /*
40 : * nsBaseContentList is a basic list of content nodes; nsContentList
41 : * is a commonly used NodeList implementation (used for
42 : * getElementsByTagName, some properties on nsIDOMHTMLDocument, etc).
43 : */
44 :
45 : #include "nsContentList.h"
46 : #include "nsIContent.h"
47 : #include "nsIDOMNode.h"
48 : #include "nsIDocument.h"
49 : #include "nsGenericElement.h"
50 : #include "nsWrapperCacheInlines.h"
51 : #include "nsContentUtils.h"
52 : #include "nsCCUncollectableMarker.h"
53 : #include "nsGkAtoms.h"
54 :
55 : #include "dombindings.h"
56 :
57 : // Form related includes
58 : #include "nsIDOMHTMLFormElement.h"
59 :
60 : #include "pldhash.h"
61 :
62 : #ifdef DEBUG_CONTENT_LIST
63 : #include "nsIContentIterator.h"
64 : nsresult
65 : NS_NewPreContentIterator(nsIContentIterator** aInstancePtrResult);
66 : #define ASSERT_IN_SYNC AssertInSync()
67 : #else
68 : #define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
69 : #endif
70 :
71 : using namespace mozilla::dom;
72 :
73 3412 : nsBaseContentList::~nsBaseContentList()
74 : {
75 6824 : }
76 :
77 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList)
78 75 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList)
79 75 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mElements)
80 75 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
81 75 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
82 79 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList)
83 79 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
84 79 : if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) {
85 0 : return NS_SUCCESS_INTERRUPTED_TRAVERSE;
86 : }
87 79 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_OF_NSCOMPTR(mElements)
88 79 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
89 79 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsBaseContentList)
90 79 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
91 79 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
92 :
93 1798 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList)
94 1798 : if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) {
95 3661 : for (PRUint32 i = 0; i < tmp->mElements.Length(); ++i) {
96 1970 : nsIContent* c = tmp->mElements[i];
97 1970 : if (c->IsPurple()) {
98 287 : c->RemovePurple();
99 : }
100 1970 : nsGenericElement::MarkNodeChildren(c);
101 : }
102 1691 : return true;
103 : }
104 107 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
105 :
106 75 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsBaseContentList)
107 75 : return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
108 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
109 :
110 75 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList)
111 75 : return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
112 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
113 :
114 : #define NS_CONTENT_LIST_INTERFACES(_class) \
115 : NS_INTERFACE_TABLE_ENTRY(_class, nsINodeList) \
116 : NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMNodeList)
117 :
118 : DOMCI_DATA(NodeList, nsBaseContentList)
119 :
120 : // QueryInterface implementation for nsBaseContentList
121 17975 : NS_INTERFACE_TABLE_HEAD(nsBaseContentList)
122 17975 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
123 : NS_NODELIST_OFFSET_AND_INTERFACE_TABLE_BEGIN(nsBaseContentList)
124 : NS_CONTENT_LIST_INTERFACES(nsBaseContentList)
125 12327 : NS_OFFSET_AND_INTERFACE_TABLE_END
126 12327 : NS_OFFSET_AND_INTERFACE_TABLE_TO_MAP_SEGUE
127 12217 : NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsBaseContentList)
128 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NodeList)
129 0 : NS_INTERFACE_MAP_END
130 :
131 :
132 9954 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList)
133 9954 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBaseContentList)
134 :
135 :
136 : NS_IMETHODIMP
137 128 : nsBaseContentList::GetLength(PRUint32* aLength)
138 : {
139 128 : *aLength = mElements.Length();
140 :
141 128 : return NS_OK;
142 : }
143 :
144 : NS_IMETHODIMP
145 0 : nsBaseContentList::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
146 : {
147 0 : nsISupports *tmp = GetNodeAt(aIndex);
148 :
149 0 : if (!tmp) {
150 0 : *aReturn = nsnull;
151 :
152 0 : return NS_OK;
153 : }
154 :
155 0 : return CallQueryInterface(tmp, aReturn);
156 : }
157 :
158 : nsIContent*
159 162 : nsBaseContentList::GetNodeAt(PRUint32 aIndex)
160 : {
161 162 : return mElements.SafeElementAt(aIndex);
162 : }
163 :
164 :
165 : PRInt32
166 0 : nsBaseContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
167 : {
168 0 : return mElements.IndexOf(aContent);
169 : }
170 :
171 : PRInt32
172 0 : nsBaseContentList::IndexOf(nsIContent* aContent)
173 : {
174 0 : return IndexOf(aContent, true);
175 : }
176 :
177 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsSimpleContentList)
178 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsSimpleContentList,
179 : nsBaseContentList)
180 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRoot)
181 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
182 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsSimpleContentList,
183 : nsBaseContentList)
184 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot)
185 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
186 :
187 694 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsSimpleContentList)
188 426 : NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList)
189 :
190 :
191 330 : NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList)
192 330 : NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
193 :
194 : JSObject*
195 110 : nsSimpleContentList::WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
196 : bool *triedToWrap)
197 : {
198 110 : return mozilla::dom::binding::NodeList::create(cx, scope, this, triedToWrap);
199 : }
200 :
201 : // nsFormContentList
202 :
203 0 : nsFormContentList::nsFormContentList(nsIContent *aForm,
204 : nsBaseContentList& aContentList)
205 0 : : nsSimpleContentList(aForm)
206 : {
207 :
208 : // move elements that belong to mForm into this content list
209 :
210 0 : PRUint32 i, length = 0;
211 0 : aContentList.GetLength(&length);
212 :
213 0 : for (i = 0; i < length; i++) {
214 0 : nsIContent *c = aContentList.GetNodeAt(i);
215 0 : if (c && nsContentUtils::BelongsInForm(aForm, c)) {
216 0 : AppendElement(c);
217 : }
218 : }
219 0 : }
220 :
221 : // Hashtable for storing nsContentLists
222 : static PLDHashTable gContentListHashTable;
223 :
224 : struct ContentListHashEntry : public PLDHashEntryHdr
225 : {
226 : nsContentList* mContentList;
227 : };
228 :
229 : static PLDHashNumber
230 6324 : ContentListHashtableHashKey(PLDHashTable *table, const void *key)
231 : {
232 6324 : const nsContentListKey* list = static_cast<const nsContentListKey *>(key);
233 6324 : return list->GetHash();
234 : }
235 :
236 : static bool
237 3179 : ContentListHashtableMatchEntry(PLDHashTable *table,
238 : const PLDHashEntryHdr *entry,
239 : const void *key)
240 : {
241 : const ContentListHashEntry *e =
242 3179 : static_cast<const ContentListHashEntry *>(entry);
243 3179 : const nsContentList* list = e->mContentList;
244 3179 : const nsContentListKey* ourKey = static_cast<const nsContentListKey *>(key);
245 :
246 3179 : return list->MatchesKey(*ourKey);
247 : }
248 :
249 : already_AddRefed<nsContentList>
250 3179 : NS_GetContentList(nsINode* aRootNode,
251 : PRInt32 aMatchNameSpaceId,
252 : const nsAString& aTagname)
253 :
254 : {
255 3179 : NS_ASSERTION(aRootNode, "content list has to have a root");
256 :
257 3179 : nsContentList* list = nsnull;
258 :
259 : static PLDHashTableOps hash_table_ops =
260 : {
261 : PL_DHashAllocTable,
262 : PL_DHashFreeTable,
263 : ContentListHashtableHashKey,
264 : ContentListHashtableMatchEntry,
265 : PL_DHashMoveEntryStub,
266 : PL_DHashClearEntryStub,
267 : PL_DHashFinalizeStub
268 : };
269 :
270 : // Initialize the hashtable if needed.
271 3179 : if (!gContentListHashTable.ops) {
272 : bool success = PL_DHashTableInit(&gContentListHashTable,
273 : &hash_table_ops, nsnull,
274 : sizeof(ContentListHashEntry),
275 59 : 16);
276 :
277 59 : if (!success) {
278 0 : gContentListHashTable.ops = nsnull;
279 : }
280 : }
281 :
282 3179 : ContentListHashEntry *entry = nsnull;
283 : // First we look in our hashtable. Then we create a content list if needed
284 3179 : if (gContentListHashTable.ops) {
285 3179 : nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname);
286 :
287 : // A PL_DHASH_ADD is equivalent to a PL_DHASH_LOOKUP for cases
288 : // when the entry is already in the hashtable.
289 : entry = static_cast<ContentListHashEntry *>
290 : (PL_DHashTableOperate(&gContentListHashTable,
291 : &hashKey,
292 3179 : PL_DHASH_ADD));
293 3179 : if (entry)
294 3179 : list = entry->mContentList;
295 : }
296 :
297 3179 : if (!list) {
298 : // We need to create a ContentList and add it to our new entry, if
299 : // we have an entry
300 6290 : nsCOMPtr<nsIAtom> xmlAtom = do_GetAtom(aTagname);
301 6290 : nsCOMPtr<nsIAtom> htmlAtom;
302 3145 : if (aMatchNameSpaceId == kNameSpaceID_Unknown) {
303 6236 : nsAutoString lowercaseName;
304 3118 : nsContentUtils::ASCIIToLower(aTagname, lowercaseName);
305 3118 : htmlAtom = do_GetAtom(lowercaseName);
306 : } else {
307 27 : htmlAtom = xmlAtom;
308 : }
309 : list = new nsContentList(aRootNode, aMatchNameSpaceId,
310 6290 : htmlAtom, xmlAtom);
311 3145 : if (entry) {
312 3145 : entry->mContentList = list;
313 : }
314 : }
315 :
316 3179 : NS_ADDREF(list);
317 :
318 3179 : return list;
319 : }
320 :
321 : // Hashtable for storing nsCacheableFuncStringContentList
322 : static PLDHashTable gFuncStringContentListHashTable;
323 :
324 : struct FuncStringContentListHashEntry : public PLDHashEntryHdr
325 : {
326 : nsCacheableFuncStringContentList* mContentList;
327 : };
328 :
329 : static PLDHashNumber
330 0 : FuncStringContentListHashtableHashKey(PLDHashTable *table, const void *key)
331 : {
332 : const nsFuncStringCacheKey* funcStringKey =
333 0 : static_cast<const nsFuncStringCacheKey *>(key);
334 0 : return funcStringKey->GetHash();
335 : }
336 :
337 : static bool
338 0 : FuncStringContentListHashtableMatchEntry(PLDHashTable *table,
339 : const PLDHashEntryHdr *entry,
340 : const void *key)
341 : {
342 : const FuncStringContentListHashEntry *e =
343 0 : static_cast<const FuncStringContentListHashEntry *>(entry);
344 : const nsFuncStringCacheKey* ourKey =
345 0 : static_cast<const nsFuncStringCacheKey *>(key);
346 :
347 0 : return e->mContentList->Equals(ourKey);
348 : }
349 :
350 : already_AddRefed<nsContentList>
351 0 : NS_GetFuncStringContentList(nsINode* aRootNode,
352 : nsContentListMatchFunc aFunc,
353 : nsContentListDestroyFunc aDestroyFunc,
354 : nsFuncStringContentListDataAllocator aDataAllocator,
355 : const nsAString& aString)
356 : {
357 0 : NS_ASSERTION(aRootNode, "content list has to have a root");
358 :
359 0 : nsCacheableFuncStringContentList* list = nsnull;
360 :
361 : static PLDHashTableOps hash_table_ops =
362 : {
363 : PL_DHashAllocTable,
364 : PL_DHashFreeTable,
365 : FuncStringContentListHashtableHashKey,
366 : FuncStringContentListHashtableMatchEntry,
367 : PL_DHashMoveEntryStub,
368 : PL_DHashClearEntryStub,
369 : PL_DHashFinalizeStub
370 : };
371 :
372 : // Initialize the hashtable if needed.
373 0 : if (!gFuncStringContentListHashTable.ops) {
374 : bool success = PL_DHashTableInit(&gFuncStringContentListHashTable,
375 : &hash_table_ops, nsnull,
376 : sizeof(FuncStringContentListHashEntry),
377 0 : 16);
378 :
379 0 : if (!success) {
380 0 : gFuncStringContentListHashTable.ops = nsnull;
381 : }
382 : }
383 :
384 0 : FuncStringContentListHashEntry *entry = nsnull;
385 : // First we look in our hashtable. Then we create a content list if needed
386 0 : if (gFuncStringContentListHashTable.ops) {
387 0 : nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
388 :
389 : // A PL_DHASH_ADD is equivalent to a PL_DHASH_LOOKUP for cases
390 : // when the entry is already in the hashtable.
391 : entry = static_cast<FuncStringContentListHashEntry *>
392 : (PL_DHashTableOperate(&gFuncStringContentListHashTable,
393 : &hashKey,
394 0 : PL_DHASH_ADD));
395 0 : if (entry)
396 0 : list = entry->mContentList;
397 : }
398 :
399 0 : if (!list) {
400 : // We need to create a ContentList and add it to our new entry, if
401 : // we have an entry
402 : list = new nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc,
403 0 : aDataAllocator, aString);
404 0 : if (list && !list->AllocatedData()) {
405 : // Failed to allocate the data
406 0 : delete list;
407 0 : list = nsnull;
408 : }
409 :
410 0 : if (entry) {
411 0 : if (list)
412 0 : entry->mContentList = list;
413 : else
414 0 : PL_DHashTableRawRemove(&gContentListHashTable, entry);
415 : }
416 :
417 0 : NS_ENSURE_TRUE(list, nsnull);
418 : }
419 :
420 0 : NS_ADDREF(list);
421 :
422 : // Don't cache these lists globally
423 :
424 0 : return list;
425 : }
426 :
427 : // nsContentList implementation
428 :
429 3220 : nsContentList::nsContentList(nsINode* aRootNode,
430 : PRInt32 aMatchNameSpaceId,
431 : nsIAtom* aHTMLMatchAtom,
432 : nsIAtom* aXMLMatchAtom,
433 : bool aDeep)
434 : : nsBaseContentList(),
435 : mRootNode(aRootNode),
436 : mMatchNameSpaceId(aMatchNameSpaceId),
437 : mHTMLMatchAtom(aHTMLMatchAtom),
438 : mXMLMatchAtom(aXMLMatchAtom),
439 : mFunc(nsnull),
440 : mDestroyFunc(nsnull),
441 : mData(nsnull),
442 : mState(LIST_DIRTY),
443 : mDeep(aDeep),
444 3220 : mFuncMayDependOnAttr(false)
445 : {
446 3220 : NS_ASSERTION(mRootNode, "Must have root");
447 3220 : if (nsGkAtoms::_asterix == mHTMLMatchAtom) {
448 83 : NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterix, "HTML atom and XML atom are not both asterix?");
449 83 : mMatchAll = true;
450 : }
451 : else {
452 3137 : mMatchAll = false;
453 : }
454 3220 : mRootNode->AddMutationObserver(this);
455 :
456 : // We only need to flush if we're in an non-HTML document, since the
457 : // HTML5 parser doesn't need flushing. Further, if we're not in a
458 : // document at all right now (in the GetCurrentDoc() sense), we're
459 : // not parser-created and don't need to be flushing stuff under us
460 : // to get our kids right.
461 3220 : nsIDocument* doc = mRootNode->GetCurrentDoc();
462 3220 : mFlushesNeeded = doc && !doc->IsHTML();
463 3220 : }
464 :
465 82 : nsContentList::nsContentList(nsINode* aRootNode,
466 : nsContentListMatchFunc aFunc,
467 : nsContentListDestroyFunc aDestroyFunc,
468 : void* aData,
469 : bool aDeep,
470 : nsIAtom* aMatchAtom,
471 : PRInt32 aMatchNameSpaceId,
472 : bool aFuncMayDependOnAttr)
473 : : nsBaseContentList(),
474 : mRootNode(aRootNode),
475 : mMatchNameSpaceId(aMatchNameSpaceId),
476 : mHTMLMatchAtom(aMatchAtom),
477 : mXMLMatchAtom(aMatchAtom),
478 : mFunc(aFunc),
479 : mDestroyFunc(aDestroyFunc),
480 : mData(aData),
481 : mState(LIST_DIRTY),
482 : mMatchAll(false),
483 : mDeep(aDeep),
484 82 : mFuncMayDependOnAttr(aFuncMayDependOnAttr)
485 : {
486 82 : NS_ASSERTION(mRootNode, "Must have root");
487 82 : mRootNode->AddMutationObserver(this);
488 :
489 : // We only need to flush if we're in an non-HTML document, since the
490 : // HTML5 parser doesn't need flushing. Further, if we're not in a
491 : // document at all right now (in the GetCurrentDoc() sense), we're
492 : // not parser-created and don't need to be flushing stuff under us
493 : // to get our kids right.
494 82 : nsIDocument* doc = mRootNode->GetCurrentDoc();
495 82 : mFlushesNeeded = doc && !doc->IsHTML();
496 82 : }
497 :
498 9906 : nsContentList::~nsContentList()
499 : {
500 3302 : RemoveFromHashtable();
501 3302 : if (mRootNode) {
502 3281 : mRootNode->RemoveMutationObserver(this);
503 : }
504 :
505 3302 : if (mDestroyFunc) {
506 : // Clean up mData
507 82 : (*mDestroyFunc)(mData);
508 : }
509 13208 : }
510 :
511 : JSObject*
512 2684 : nsContentList::WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
513 : bool *triedToWrap)
514 : {
515 : return mozilla::dom::binding::HTMLCollection::create(cx, scope, this,
516 2684 : triedToWrap);
517 : }
518 :
519 : DOMCI_DATA(ContentList, nsContentList)
520 :
521 : // QueryInterface implementation for nsContentList
522 20923 : NS_INTERFACE_TABLE_HEAD(nsContentList)
523 : NS_NODELIST_OFFSET_AND_INTERFACE_TABLE_BEGIN(nsContentList)
524 : NS_CONTENT_LIST_INTERFACES(nsContentList)
525 : NS_INTERFACE_TABLE_ENTRY(nsContentList, nsIHTMLCollection)
526 : NS_INTERFACE_TABLE_ENTRY(nsContentList, nsIDOMHTMLCollection)
527 : NS_INTERFACE_TABLE_ENTRY(nsContentList, nsIMutationObserver)
528 20923 : NS_OFFSET_AND_INTERFACE_TABLE_END
529 20923 : NS_OFFSET_AND_INTERFACE_TABLE_TO_MAP_SEGUE
530 17561 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ContentList)
531 17549 : NS_INTERFACE_MAP_END_INHERITING(nsBaseContentList)
532 :
533 :
534 9624 : NS_IMPL_ADDREF_INHERITED(nsContentList, nsBaseContentList)
535 9624 : NS_IMPL_RELEASE_INHERITED(nsContentList, nsBaseContentList)
536 :
537 : PRUint32
538 3253 : nsContentList::Length(bool aDoFlush)
539 : {
540 3253 : BringSelfUpToDate(aDoFlush);
541 :
542 3253 : return mElements.Length();
543 : }
544 :
545 : nsIContent *
546 4078 : nsContentList::Item(PRUint32 aIndex, bool aDoFlush)
547 : {
548 4078 : if (mRootNode && aDoFlush && mFlushesNeeded) {
549 : // XXX sXBL/XBL2 issue
550 4078 : nsIDocument* doc = mRootNode->GetCurrentDoc();
551 4078 : if (doc) {
552 : // Flush pending content changes Bug 4891.
553 4078 : doc->FlushPendingNotifications(Flush_ContentAndNotify);
554 : }
555 : }
556 :
557 4078 : if (mState != LIST_UP_TO_DATE)
558 538 : PopulateSelf(NS_MIN(aIndex, PR_UINT32_MAX - 1) + 1);
559 :
560 : ASSERT_IN_SYNC;
561 4078 : NS_ASSERTION(!mRootNode || mState != LIST_DIRTY,
562 : "PopulateSelf left the list in a dirty (useless) state!");
563 :
564 4078 : return mElements.SafeElementAt(aIndex);
565 : }
566 :
567 : nsIContent *
568 0 : nsContentList::NamedItem(const nsAString& aName, bool aDoFlush)
569 : {
570 0 : BringSelfUpToDate(aDoFlush);
571 :
572 0 : PRUint32 i, count = mElements.Length();
573 :
574 : // Typically IDs and names are atomized
575 0 : nsCOMPtr<nsIAtom> name = do_GetAtom(aName);
576 0 : NS_ENSURE_TRUE(name, nsnull);
577 :
578 0 : for (i = 0; i < count; i++) {
579 0 : nsIContent *content = mElements[i];
580 : // XXX Should this pass eIgnoreCase?
581 0 : if (content &&
582 : (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
583 0 : name, eCaseMatters) ||
584 : content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
585 0 : name, eCaseMatters))) {
586 0 : return content;
587 : }
588 : }
589 :
590 0 : return nsnull;
591 : }
592 :
593 : PRInt32
594 0 : nsContentList::IndexOf(nsIContent *aContent, bool aDoFlush)
595 : {
596 0 : BringSelfUpToDate(aDoFlush);
597 :
598 0 : return mElements.IndexOf(aContent);
599 : }
600 :
601 : PRInt32
602 0 : nsContentList::IndexOf(nsIContent* aContent)
603 : {
604 0 : return IndexOf(aContent, true);
605 : }
606 :
607 : void
608 21 : nsContentList::NodeWillBeDestroyed(const nsINode* aNode)
609 : {
610 : // We shouldn't do anything useful from now on
611 :
612 21 : RemoveFromCaches();
613 21 : mRootNode = nsnull;
614 :
615 : // We will get no more updates, so we can never know we're up to
616 : // date
617 21 : SetDirty();
618 21 : }
619 :
620 : NS_IMETHODIMP
621 3253 : nsContentList::GetLength(PRUint32* aLength)
622 : {
623 3253 : *aLength = Length(true);
624 :
625 3253 : return NS_OK;
626 : }
627 :
628 : NS_IMETHODIMP
629 826 : nsContentList::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
630 : {
631 826 : nsINode* node = GetNodeAt(aIndex);
632 :
633 826 : if (node) {
634 794 : return CallQueryInterface(node, aReturn);
635 : }
636 :
637 32 : *aReturn = nsnull;
638 :
639 32 : return NS_OK;
640 : }
641 :
642 : NS_IMETHODIMP
643 0 : nsContentList::NamedItem(const nsAString& aName, nsIDOMNode** aReturn)
644 : {
645 0 : nsIContent *content = NamedItem(aName, true);
646 :
647 0 : if (content) {
648 0 : return CallQueryInterface(content, aReturn);
649 : }
650 :
651 0 : *aReturn = nsnull;
652 :
653 0 : return NS_OK;
654 : }
655 :
656 : nsIContent*
657 4078 : nsContentList::GetNodeAt(PRUint32 aIndex)
658 : {
659 4078 : return Item(aIndex, true);
660 : }
661 :
662 : nsISupports*
663 0 : nsContentList::GetNamedItem(const nsAString& aName, nsWrapperCache **aCache)
664 : {
665 : nsIContent *item;
666 0 : *aCache = item = NamedItem(aName, true);
667 0 : return item;
668 : }
669 :
670 : void
671 2 : nsContentList::AttributeChanged(nsIDocument *aDocument, Element* aElement,
672 : PRInt32 aNameSpaceID, nsIAtom* aAttribute,
673 : PRInt32 aModType)
674 : {
675 2 : NS_PRECONDITION(aElement, "Must have a content node to work with");
676 :
677 2 : if (!mFunc || !mFuncMayDependOnAttr || mState == LIST_DIRTY ||
678 0 : !MayContainRelevantNodes(aElement->GetNodeParent()) ||
679 0 : !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
680 : // Either we're already dirty or this notification doesn't affect
681 : // whether we might match aElement.
682 2 : return;
683 : }
684 :
685 0 : if (Match(aElement)) {
686 0 : if (mElements.IndexOf(aElement) == mElements.NoIndex) {
687 : // We match aElement now, and it's not in our list already. Just dirty
688 : // ourselves; this is simpler than trying to figure out where to insert
689 : // aElement.
690 0 : SetDirty();
691 : }
692 : } else {
693 : // We no longer match aElement. Remove it from our list. If it's
694 : // already not there, this is a no-op (though a potentially
695 : // expensive one). Either way, no change of mState is required
696 : // here.
697 0 : mElements.RemoveElement(aElement);
698 : }
699 : }
700 :
701 : void
702 56 : nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
703 : nsIContent* aFirstNewContent,
704 : PRInt32 aNewIndexInContainer)
705 : {
706 56 : NS_PRECONDITION(aContainer, "Can't get at the new content if no container!");
707 :
708 : /*
709 : * If the state is LIST_DIRTY then we have no useful information in our list
710 : * and we want to put off doing work as much as possible. Also, if
711 : * aContainer is anonymous from our point of view, we know that we can't
712 : * possibly be matching any of the kids.
713 : */
714 152 : if (mState == LIST_DIRTY ||
715 48 : !nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer) ||
716 48 : !MayContainRelevantNodes(aContainer))
717 8 : return;
718 :
719 : /*
720 : * We want to handle the case of ContentAppended by sometimes
721 : * appending the content to our list, not just setting state to
722 : * LIST_DIRTY, since most of our ContentAppended notifications
723 : * should come during pageload and be at the end of the document.
724 : * Do a bit of work to see whether we could just append to what we
725 : * already have.
726 : */
727 :
728 48 : PRInt32 count = aContainer->GetChildCount();
729 :
730 48 : if (count > 0) {
731 48 : PRUint32 ourCount = mElements.Length();
732 48 : bool appendToList = false;
733 48 : if (ourCount == 0) {
734 27 : appendToList = true;
735 : } else {
736 21 : nsIContent* ourLastContent = mElements[ourCount - 1];
737 : /*
738 : * We want to append instead of invalidating if the first thing
739 : * that got appended comes after ourLastContent.
740 : */
741 21 : if (nsContentUtils::PositionIsBefore(ourLastContent, aFirstNewContent)) {
742 15 : appendToList = true;
743 : }
744 : }
745 :
746 :
747 48 : if (!appendToList) {
748 : // The new stuff is somewhere in the middle of our list; check
749 : // whether we need to invalidate
750 11 : for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
751 6 : if (MatchSelf(cur)) {
752 : // Uh-oh. We're gonna have to add elements into the middle
753 : // of our list. That's not worth the effort.
754 1 : SetDirty();
755 1 : break;
756 : }
757 : }
758 :
759 : ASSERT_IN_SYNC;
760 6 : return;
761 : }
762 :
763 : /*
764 : * At this point we know we could append. If we're not up to
765 : * date, however, that would be a bad idea -- it could miss some
766 : * content that we never picked up due to being lazy. Further, we
767 : * may never get asked for this content... so don't grab it yet.
768 : */
769 42 : if (mState == LIST_LAZY) // be lazy
770 2 : return;
771 :
772 : /*
773 : * We're up to date. That means someone's actively using us; we
774 : * may as well grab this content....
775 : */
776 40 : if (mDeep) {
777 80 : for (nsIContent* cur = aFirstNewContent;
778 : cur;
779 40 : cur = cur->GetNextNode(aContainer)) {
780 40 : if (cur->IsElement() && Match(cur->AsElement())) {
781 6 : mElements.AppendElement(cur);
782 : }
783 : }
784 : } else {
785 0 : for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
786 0 : if (cur->IsElement() && Match(cur->AsElement())) {
787 0 : mElements.AppendElement(cur);
788 : }
789 : }
790 : }
791 :
792 : ASSERT_IN_SYNC;
793 : }
794 : }
795 :
796 : void
797 4 : nsContentList::ContentInserted(nsIDocument *aDocument,
798 : nsIContent* aContainer,
799 : nsIContent* aChild,
800 : PRInt32 aIndexInContainer)
801 : {
802 : // Note that aContainer can be null here if we are inserting into
803 : // the document itself; any attempted optimizations to this method
804 : // should deal with that.
805 4 : if (mState != LIST_DIRTY &&
806 0 : MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
807 0 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
808 0 : MatchSelf(aChild)) {
809 0 : SetDirty();
810 : }
811 :
812 : ASSERT_IN_SYNC;
813 4 : }
814 :
815 : void
816 10 : nsContentList::ContentRemoved(nsIDocument *aDocument,
817 : nsIContent* aContainer,
818 : nsIContent* aChild,
819 : PRInt32 aIndexInContainer,
820 : nsIContent* aPreviousSibling)
821 : {
822 : // Note that aContainer can be null here if we are removing from
823 : // the document itself; any attempted optimizations to this method
824 : // should deal with that.
825 22 : if (mState != LIST_DIRTY &&
826 4 : MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
827 4 : nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
828 4 : MatchSelf(aChild)) {
829 0 : SetDirty();
830 : }
831 :
832 : ASSERT_IN_SYNC;
833 10 : }
834 :
835 : bool
836 36486 : nsContentList::Match(Element *aElement)
837 : {
838 36486 : if (mFunc) {
839 2604 : return (*mFunc)(aElement, mMatchNameSpaceId, mXMLMatchAtom, mData);
840 : }
841 :
842 33882 : if (!mXMLMatchAtom)
843 0 : return false;
844 :
845 33882 : nsINodeInfo *ni = aElement->NodeInfo();
846 :
847 33882 : bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown;
848 33882 : bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard;
849 33882 : bool toReturn = mMatchAll;
850 33882 : if (!unknown && !wildcard)
851 284 : toReturn &= ni->NamespaceEquals(mMatchNameSpaceId);
852 :
853 33882 : if (toReturn)
854 377 : return toReturn;
855 :
856 33505 : bool matchHTML = aElement->GetNameSpaceID() == kNameSpaceID_XHTML &&
857 33505 : aElement->OwnerDoc()->IsHTML();
858 :
859 33505 : if (unknown) {
860 0 : return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) :
861 33142 : ni->QualifiedNameEquals(mXMLMatchAtom);
862 : }
863 :
864 363 : if (wildcard) {
865 0 : return matchHTML ? ni->Equals(mHTMLMatchAtom) :
866 80 : ni->Equals(mXMLMatchAtom);
867 : }
868 :
869 0 : return matchHTML ? ni->Equals(mHTMLMatchAtom, mMatchNameSpaceId) :
870 283 : ni->Equals(mXMLMatchAtom, mMatchNameSpaceId);
871 : }
872 :
873 : bool
874 10 : nsContentList::MatchSelf(nsIContent *aContent)
875 : {
876 10 : NS_PRECONDITION(aContent, "Can't match null stuff, you know");
877 10 : NS_PRECONDITION(mDeep || aContent->GetNodeParent() == mRootNode,
878 : "MatchSelf called on a node that we can't possibly match");
879 :
880 10 : if (!aContent->IsElement()) {
881 9 : return false;
882 : }
883 :
884 1 : if (Match(aContent->AsElement()))
885 1 : return true;
886 :
887 0 : if (!mDeep)
888 0 : return false;
889 :
890 0 : for (nsIContent* cur = aContent->GetFirstChild();
891 : cur;
892 0 : cur = cur->GetNextNode(aContent)) {
893 0 : if (cur->IsElement() && Match(cur->AsElement())) {
894 0 : return true;
895 : }
896 : }
897 :
898 0 : return false;
899 : }
900 :
901 : void
902 3300 : nsContentList::PopulateSelf(PRUint32 aNeededLength)
903 : {
904 3300 : if (!mRootNode) {
905 0 : return;
906 : }
907 :
908 : ASSERT_IN_SYNC;
909 :
910 3300 : PRUint32 count = mElements.Length();
911 3300 : NS_ASSERTION(mState != LIST_DIRTY || count == 0,
912 : "Reset() not called when setting state to LIST_DIRTY?");
913 :
914 3300 : if (count >= aNeededLength) // We're all set
915 0 : return;
916 :
917 3300 : PRUint32 elementsToAppend = aNeededLength - count;
918 : #ifdef DEBUG
919 3300 : PRUint32 invariant = elementsToAppend + mElements.Length();
920 : #endif
921 :
922 3300 : if (mDeep) {
923 : // If we already have nodes start searching at the last one, otherwise
924 : // start searching at the root.
925 3225 : nsINode* cur = count ? mElements[count - 1] : mRootNode;
926 107509 : do {
927 110228 : cur = cur->GetNextNode(mRootNode);
928 110228 : if (!cur) {
929 2719 : break;
930 : }
931 107509 : if (cur->IsElement() && Match(cur->AsElement())) {
932 : // Append AsElement() to get nsIContent instead of nsINode
933 4366 : mElements.AppendElement(cur->AsElement());
934 4366 : --elementsToAppend;
935 : }
936 : } while (elementsToAppend);
937 : } else {
938 : nsIContent* cur =
939 75 : count ? mElements[count-1]->GetNextSibling() : mRootNode->GetFirstChild();
940 622 : for ( ; cur && elementsToAppend; cur = cur->GetNextSibling()) {
941 547 : if (cur->IsElement() && Match(cur->AsElement())) {
942 236 : mElements.AppendElement(cur);
943 236 : --elementsToAppend;
944 : }
945 : }
946 : }
947 :
948 3300 : NS_ASSERTION(elementsToAppend + mElements.Length() == invariant,
949 : "Something is awry!");
950 :
951 3300 : if (elementsToAppend != 0)
952 2794 : mState = LIST_UP_TO_DATE;
953 : else
954 506 : mState = LIST_LAZY;
955 :
956 : ASSERT_IN_SYNC;
957 : }
958 :
959 : void
960 3323 : nsContentList::RemoveFromHashtable()
961 : {
962 3323 : if (mFunc) {
963 : // This can't be in the table anyway
964 82 : return;
965 : }
966 :
967 3241 : if (!gContentListHashTable.ops)
968 96 : return;
969 :
970 6290 : nsDependentAtomString str(mXMLMatchAtom);
971 3145 : nsContentListKey key(mRootNode, mMatchNameSpaceId, str);
972 : PL_DHashTableOperate(&gContentListHashTable,
973 : &key,
974 3145 : PL_DHASH_REMOVE);
975 :
976 3145 : if (gContentListHashTable.entryCount == 0) {
977 59 : PL_DHashTableFinish(&gContentListHashTable);
978 59 : gContentListHashTable.ops = nsnull;
979 : }
980 : }
981 :
982 : void
983 3253 : nsContentList::BringSelfUpToDate(bool aDoFlush)
984 : {
985 3253 : if (mRootNode && aDoFlush && mFlushesNeeded) {
986 : // XXX sXBL/XBL2 issue
987 3253 : nsIDocument* doc = mRootNode->GetCurrentDoc();
988 3253 : if (doc) {
989 : // Flush pending content changes Bug 4891.
990 3253 : doc->FlushPendingNotifications(Flush_ContentAndNotify);
991 : }
992 : }
993 :
994 3253 : if (mState != LIST_UP_TO_DATE)
995 2762 : PopulateSelf(PRUint32(-1));
996 :
997 : ASSERT_IN_SYNC;
998 3253 : NS_ASSERTION(!mRootNode || mState == LIST_UP_TO_DATE,
999 : "PopulateSelf dod not bring content list up to date!");
1000 3253 : }
1001 :
1002 0 : nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList()
1003 : {
1004 0 : RemoveFromFuncStringHashtable();
1005 0 : }
1006 :
1007 : void
1008 0 : nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable()
1009 : {
1010 0 : if (!gFuncStringContentListHashTable.ops) {
1011 0 : return;
1012 : }
1013 :
1014 0 : nsFuncStringCacheKey key(mRootNode, mFunc, mString);
1015 : PL_DHashTableOperate(&gFuncStringContentListHashTable,
1016 : &key,
1017 0 : PL_DHASH_REMOVE);
1018 :
1019 0 : if (gFuncStringContentListHashTable.entryCount == 0) {
1020 0 : PL_DHashTableFinish(&gFuncStringContentListHashTable);
1021 0 : gFuncStringContentListHashTable.ops = nsnull;
1022 : }
1023 4392 : }
1024 :
1025 : #ifdef DEBUG_CONTENT_LIST
1026 : void
1027 : nsContentList::AssertInSync()
1028 : {
1029 : if (mState == LIST_DIRTY) {
1030 : return;
1031 : }
1032 :
1033 : if (!mRootNode) {
1034 : NS_ASSERTION(mElements.Length() == 0 && mState == LIST_DIRTY,
1035 : "Empty iterator isn't quite empty?");
1036 : return;
1037 : }
1038 :
1039 : // XXX This code will need to change if nsContentLists can ever match
1040 : // elements that are outside of the document element.
1041 : nsIContent *root;
1042 : if (mRootNode->IsNodeOfType(nsINode::eDOCUMENT)) {
1043 : root = static_cast<nsIDocument*>(mRootNode)->GetRootElement();
1044 : }
1045 : else {
1046 : root = static_cast<nsIContent*>(mRootNode);
1047 : }
1048 :
1049 : nsCOMPtr<nsIContentIterator> iter;
1050 : if (mDeep) {
1051 : NS_NewPreContentIterator(getter_AddRefs(iter));
1052 : iter->Init(root);
1053 : iter->First();
1054 : }
1055 :
1056 : PRUint32 cnt = 0, index = 0;
1057 : while (true) {
1058 : if (cnt == mElements.Length() && mState == LIST_LAZY) {
1059 : break;
1060 : }
1061 :
1062 : nsIContent *cur = mDeep ? iter->GetCurrentNode() :
1063 : mRootNode->GetChildAt(index++);
1064 : if (!cur) {
1065 : break;
1066 : }
1067 :
1068 : if (cur->IsElement() && Match(cur->AsElement())) {
1069 : NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur,
1070 : "Elements is out of sync");
1071 : ++cnt;
1072 : }
1073 :
1074 : if (mDeep) {
1075 : iter->Next();
1076 : }
1077 : }
1078 :
1079 : NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
1080 : }
1081 : #endif
|