1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : /*
39 : * nsBaseContentList is a basic list of content nodes; nsContentList
40 : * is a commonly used NodeList implementation (used for
41 : * getElementsByTagName, some properties on nsIDOMHTMLDocument, etc).
42 : */
43 :
44 : #ifndef nsContentList_h___
45 : #define nsContentList_h___
46 :
47 : #include "nsISupports.h"
48 : #include "nsTArray.h"
49 : #include "nsString.h"
50 : #include "nsIHTMLCollection.h"
51 : #include "nsIDOMNodeList.h"
52 : #include "nsINodeList.h"
53 : #include "nsStubMutationObserver.h"
54 : #include "nsIAtom.h"
55 : #include "nsINameSpaceManager.h"
56 : #include "nsCycleCollectionParticipant.h"
57 : #include "nsWrapperCache.h"
58 : #include "nsHashKeys.h"
59 : #include "mozilla/HashFunctions.h"
60 :
61 : // Magic namespace id that means "match all namespaces". This is
62 : // negative so it won't collide with actual namespace constants.
63 : #define kNameSpaceID_Wildcard PR_INT32_MIN
64 :
65 : // This is a callback function type that can be used to implement an
66 : // arbitrary matching algorithm. aContent is the content that may
67 : // match the list, while aNamespaceID, aAtom, and aData are whatever
68 : // was passed to the list's constructor.
69 : typedef bool (*nsContentListMatchFunc)(nsIContent* aContent,
70 : PRInt32 aNamespaceID,
71 : nsIAtom* aAtom,
72 : void* aData);
73 :
74 : typedef void (*nsContentListDestroyFunc)(void* aData);
75 :
76 : class nsIDocument;
77 : namespace mozilla {
78 : namespace dom {
79 : class Element;
80 : }
81 : }
82 :
83 :
84 : class nsBaseContentList : public nsINodeList
85 : {
86 : public:
87 : nsBaseContentList()
88 : {
89 : // Mark ourselves as a proxy
90 : SetIsProxy();
91 : }
92 : virtual ~nsBaseContentList();
93 :
94 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
95 :
96 : // nsIDOMNodeList
97 : NS_DECL_NSIDOMNODELIST
98 :
99 : // nsINodeList
100 : virtual PRInt32 IndexOf(nsIContent* aContent);
101 :
102 : PRUint32 Length() const {
103 : return mElements.Length();
104 : }
105 :
106 : NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsBaseContentList)
107 :
108 164 : void AppendElement(nsIContent *aContent)
109 : {
110 164 : mElements.AppendElement(aContent);
111 164 : }
112 0 : void MaybeAppendElement(nsIContent* aContent)
113 : {
114 0 : if (aContent)
115 0 : AppendElement(aContent);
116 0 : }
117 :
118 : /**
119 : * Insert the element at a given index, shifting the objects at
120 : * the given index and later to make space.
121 : * @param aContent Element to insert, must not be null
122 : * @param aIndex Index to insert the element at.
123 : */
124 : void InsertElementAt(nsIContent* aContent, PRInt32 aIndex)
125 : {
126 : NS_ASSERTION(aContent, "Element to insert must not be null");
127 : mElements.InsertElementAt(aIndex, aContent);
128 : }
129 :
130 : void RemoveElement(nsIContent *aContent)
131 : {
132 : mElements.RemoveElement(aContent);
133 : }
134 :
135 : void Reset() {
136 : mElements.Clear();
137 : }
138 :
139 : virtual PRInt32 IndexOf(nsIContent *aContent, bool aDoFlush);
140 :
141 : virtual JSObject* WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
142 : bool *triedToWrap) = 0;
143 :
144 : protected:
145 : nsTArray< nsCOMPtr<nsIContent> > mElements;
146 : };
147 :
148 :
149 : class nsSimpleContentList : public nsBaseContentList
150 : {
151 : public:
152 : nsSimpleContentList(nsINode *aRoot) : nsBaseContentList(),
153 : mRoot(aRoot)
154 : {
155 : }
156 :
157 : NS_DECL_ISUPPORTS_INHERITED
158 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSimpleContentList,
159 : nsBaseContentList)
160 :
161 : virtual nsINode* GetParentObject()
162 : {
163 : return mRoot;
164 : }
165 : virtual JSObject* WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
166 : bool *triedToWrap);
167 :
168 : private:
169 : // This has to be a strong reference, the root might go away before the list.
170 : nsCOMPtr<nsINode> mRoot;
171 : };
172 :
173 : // This class is used only by form element code and this is a static
174 : // list of elements. NOTE! This list holds strong references to
175 : // the elements in the list.
176 : class nsFormContentList : public nsSimpleContentList
177 : {
178 : public:
179 : nsFormContentList(nsIContent *aForm,
180 : nsBaseContentList& aContentList);
181 : };
182 :
183 : /**
184 : * Class that's used as the key to hash nsContentList implementations
185 : * for fast retrieval
186 : */
187 : struct nsContentListKey
188 : {
189 : nsContentListKey(nsINode* aRootNode,
190 : PRInt32 aMatchNameSpaceId,
191 : const nsAString& aTagname)
192 : : mRootNode(aRootNode),
193 : mMatchNameSpaceId(aMatchNameSpaceId),
194 : mTagname(aTagname)
195 : {
196 : }
197 :
198 : nsContentListKey(const nsContentListKey& aContentListKey)
199 : : mRootNode(aContentListKey.mRootNode),
200 : mMatchNameSpaceId(aContentListKey.mMatchNameSpaceId),
201 : mTagname(aContentListKey.mTagname)
202 : {
203 : }
204 :
205 : inline PRUint32 GetHash(void) const
206 : {
207 : PRUint32 hash = mozilla::HashString(mTagname);
208 : return mozilla::AddToHash(hash, mRootNode, mMatchNameSpaceId);
209 : }
210 :
211 : nsINode* const mRootNode; // Weak ref
212 : const PRInt32 mMatchNameSpaceId;
213 : const nsAString& mTagname;
214 : };
215 :
216 : /**
217 : * LIST_UP_TO_DATE means that the list is up to date and need not do
218 : * any walking to be able to answer any questions anyone may have.
219 : */
220 : #define LIST_UP_TO_DATE 0
221 : /**
222 : * LIST_DIRTY means that the list contains no useful information and
223 : * if anyone asks it anything it will have to populate itself before
224 : * answering.
225 : */
226 : #define LIST_DIRTY 1
227 : /**
228 : * LIST_LAZY means that the list has populated itself to a certain
229 : * extent and that that part of the list is still valid. Requests for
230 : * things outside that part of the list will require walking the tree
231 : * some more. When a list is in this state, the last thing in
232 : * mElements is the last node in the tree that the list looked at.
233 : */
234 : #define LIST_LAZY 2
235 :
236 : /**
237 : * Class that implements a live NodeList that matches Elements in the
238 : * tree based on some criterion.
239 : */
240 : class nsContentList : public nsBaseContentList,
241 : public nsIHTMLCollection,
242 : public nsStubMutationObserver
243 : {
244 : public:
245 : NS_DECL_ISUPPORTS_INHERITED
246 :
247 : /**
248 : * @param aRootNode The node under which to limit our search.
249 : * @param aMatchAtom An atom whose meaning depends on aMatchNameSpaceId.
250 : * The special value "*" always matches whatever aMatchAtom
251 : * is matched against.
252 : * @param aMatchNameSpaceId If kNameSpaceID_Unknown, then aMatchAtom is the
253 : * tagName to match.
254 : * If kNameSpaceID_Wildcard, then aMatchAtom is the
255 : * localName to match.
256 : * Otherwise we match nodes whose namespace is
257 : * aMatchNameSpaceId and localName matches
258 : * aMatchAtom.
259 : * @param aDeep If false, then look only at children of the root, nothing
260 : * deeper. If true, then look at the whole subtree rooted at
261 : * our root.
262 : */
263 : nsContentList(nsINode* aRootNode,
264 : PRInt32 aMatchNameSpaceId,
265 : nsIAtom* aHTMLMatchAtom,
266 : nsIAtom* aXMLMatchAtom,
267 : bool aDeep = true);
268 :
269 : /**
270 : * @param aRootNode The node under which to limit our search.
271 : * @param aFunc the function to be called to determine whether we match.
272 : * This function MUST NOT ever cause mutation of the DOM.
273 : * The nsContentList implementation guarantees that everything
274 : * passed to the function will be IsElement().
275 : * @param aDestroyFunc the function that will be called to destroy aData
276 : * @param aData closure data that will need to be passed back to aFunc
277 : * @param aDeep If false, then look only at children of the root, nothing
278 : * deeper. If true, then look at the whole subtree rooted at
279 : * our root.
280 : * @param aMatchAtom an atom to be passed back to aFunc
281 : * @param aMatchNameSpaceId a namespace id to be passed back to aFunc
282 : * @param aFuncMayDependOnAttr a boolean that indicates whether this list is
283 : * sensitive to attribute changes.
284 : */
285 : nsContentList(nsINode* aRootNode,
286 : nsContentListMatchFunc aFunc,
287 : nsContentListDestroyFunc aDestroyFunc,
288 : void* aData,
289 : bool aDeep = true,
290 : nsIAtom* aMatchAtom = nsnull,
291 : PRInt32 aMatchNameSpaceId = kNameSpaceID_None,
292 : bool aFuncMayDependOnAttr = true);
293 : virtual ~nsContentList();
294 :
295 : // nsWrapperCache
296 : virtual JSObject* WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
297 : bool *triedToWrap);
298 :
299 : // nsIDOMHTMLCollection
300 : NS_DECL_NSIDOMHTMLCOLLECTION
301 :
302 : // nsBaseContentList overrides
303 : virtual PRInt32 IndexOf(nsIContent *aContent, bool aDoFlush);
304 : virtual PRInt32 IndexOf(nsIContent* aContent);
305 : virtual nsINode* GetParentObject()
306 : {
307 : return mRootNode;
308 : }
309 :
310 : // nsContentList public methods
311 : NS_HIDDEN_(PRUint32) Length(bool aDoFlush);
312 : NS_HIDDEN_(nsIContent*) Item(PRUint32 aIndex, bool aDoFlush);
313 : NS_HIDDEN_(nsIContent*) NamedItem(const nsAString& aName, bool aDoFlush);
314 :
315 : // nsIMutationObserver
316 : NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
317 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
318 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
319 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
320 : NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
321 :
322 0 : static nsContentList* FromSupports(nsISupports* aSupports)
323 : {
324 0 : nsINodeList* list = static_cast<nsINodeList*>(aSupports);
325 : #ifdef DEBUG
326 : {
327 0 : nsCOMPtr<nsINodeList> list_qi = do_QueryInterface(aSupports);
328 :
329 : // If this assertion fires the QI implementation for the object in
330 : // question doesn't use the nsINodeList pointer as the nsISupports
331 : // pointer. That must be fixed, or we'll crash...
332 0 : NS_ASSERTION(list_qi == list, "Uh, fix QI!");
333 : }
334 : #endif
335 0 : return static_cast<nsContentList*>(list);
336 : }
337 :
338 : bool MatchesKey(const nsContentListKey& aKey) const
339 : {
340 : // The root node is most commonly the same: the document. And the
341 : // most common namespace id is kNameSpaceID_Unknown. So check the
342 : // string first.
343 : NS_PRECONDITION(mXMLMatchAtom,
344 : "How did we get here with a null match atom on our list?");
345 : return
346 : mXMLMatchAtom->Equals(aKey.mTagname) &&
347 : mRootNode == aKey.mRootNode &&
348 : mMatchNameSpaceId == aKey.mMatchNameSpaceId;
349 : }
350 :
351 : protected:
352 : /**
353 : * Returns whether the element matches our criterion
354 : *
355 : * @param aElement the element to attempt to match
356 : * @return whether we match
357 : */
358 : bool Match(mozilla::dom::Element *aElement);
359 : /**
360 : * See if anything in the subtree rooted at aContent, including
361 : * aContent itself, matches our criterion.
362 : *
363 : * @param aContent the root of the subtree to match against
364 : * @return whether we match something in the tree rooted at aContent
365 : */
366 : bool MatchSelf(nsIContent *aContent);
367 :
368 : /**
369 : * Populate our list. Stop once we have at least aNeededLength
370 : * elements. At the end of PopulateSelf running, either the last
371 : * node we examined is the last node in our array or we have
372 : * traversed the whole document (or both).
373 : *
374 : * @param aNeededLength the length the list should have when we are
375 : * done (unless it exhausts the document)
376 : */
377 : void PopulateSelf(PRUint32 aNeededLength);
378 :
379 : /**
380 : * @param aContainer a content node which must be a descendant of
381 : * mRootNode
382 : * @return true if children or descendants of aContainer could match our
383 : * criterion.
384 : * false otherwise.
385 : */
386 : bool MayContainRelevantNodes(nsINode* aContainer)
387 : {
388 : return mDeep || aContainer == mRootNode;
389 : }
390 :
391 : /**
392 : * Remove ourselves from the hashtable that caches commonly accessed
393 : * content lists. Generally done on destruction.
394 : */
395 : void RemoveFromHashtable();
396 : /**
397 : * If state is not LIST_UP_TO_DATE, fully populate ourselves with
398 : * all the nodes we can find.
399 : */
400 : inline void BringSelfUpToDate(bool aDoFlush);
401 :
402 : /**
403 : * Sets the state to LIST_DIRTY and clears mElements array.
404 : * @note This is the only acceptable way to set state to LIST_DIRTY.
405 : */
406 : void SetDirty()
407 : {
408 : mState = LIST_DIRTY;
409 : Reset();
410 : }
411 :
412 : /**
413 : * To be called from non-destructor locations that want to remove from caches.
414 : * Needed because if subclasses want to have cache behavior they can't just
415 : * override RemoveFromHashtable(), since we call that in our destructor.
416 : */
417 : virtual void RemoveFromCaches() {
418 : RemoveFromHashtable();
419 : }
420 :
421 : nsINode* mRootNode; // Weak ref
422 : PRInt32 mMatchNameSpaceId;
423 : nsCOMPtr<nsIAtom> mHTMLMatchAtom;
424 : nsCOMPtr<nsIAtom> mXMLMatchAtom;
425 :
426 : /**
427 : * Function to use to determine whether a piece of content matches
428 : * our criterion
429 : */
430 : nsContentListMatchFunc mFunc;
431 : /**
432 : * Cleanup closure data with this.
433 : */
434 : nsContentListDestroyFunc mDestroyFunc;
435 : /**
436 : * Closure data to pass to mFunc when we call it
437 : */
438 : void* mData;
439 : /**
440 : * The current state of the list (possible values are:
441 : * LIST_UP_TO_DATE, LIST_LAZY, LIST_DIRTY
442 : */
443 : PRUint8 mState;
444 :
445 : // The booleans have to use PRUint8 to pack with mState, because MSVC won't
446 : // pack different typedefs together. Once we no longer have to worry about
447 : // flushes in XML documents, we can go back to using bool for the
448 : // booleans.
449 :
450 : /**
451 : * True if we are looking for elements named "*"
452 : */
453 : PRUint8 mMatchAll : 1;
454 : /**
455 : * Whether to actually descend the tree. If this is false, we won't
456 : * consider grandkids of mRootNode.
457 : */
458 : PRUint8 mDeep : 1;
459 : /**
460 : * Whether the return value of mFunc could depend on the values of
461 : * attributes.
462 : */
463 : PRUint8 mFuncMayDependOnAttr : 1;
464 : /**
465 : * Whether we actually need to flush to get our state correct.
466 : */
467 : PRUint8 mFlushesNeeded : 1;
468 :
469 : #ifdef DEBUG_CONTENT_LIST
470 : void AssertInSync();
471 : #endif
472 : };
473 :
474 : /**
475 : * A class of cacheable content list; cached on the combination of aRootNode + aFunc + aDataString
476 : */
477 : class nsCacheableFuncStringContentList;
478 :
479 : class NS_STACK_CLASS nsFuncStringCacheKey {
480 : public:
481 : nsFuncStringCacheKey(nsINode* aRootNode,
482 : nsContentListMatchFunc aFunc,
483 : const nsAString& aString) :
484 : mRootNode(aRootNode),
485 : mFunc(aFunc),
486 : mString(aString)
487 : {}
488 :
489 : PRUint32 GetHash(void) const
490 : {
491 : PRUint32 hash = mozilla::HashString(mString);
492 : return mozilla::AddToHash(hash, mRootNode, mFunc);
493 : }
494 :
495 : private:
496 : friend class nsCacheableFuncStringContentList;
497 :
498 : nsINode* const mRootNode;
499 : const nsContentListMatchFunc mFunc;
500 : const nsAString& mString;
501 : };
502 :
503 : /**
504 : * A function that allocates the matching data for this
505 : * FuncStringContentList. Returning aString is perfectly fine; in
506 : * that case the destructor function should be a no-op.
507 : */
508 : typedef void* (*nsFuncStringContentListDataAllocator)(nsINode* aRootNode,
509 : const nsString* aString);
510 :
511 : // aDestroyFunc is allowed to be null
512 : class nsCacheableFuncStringContentList : public nsContentList {
513 : public:
514 : nsCacheableFuncStringContentList(nsINode* aRootNode,
515 : nsContentListMatchFunc aFunc,
516 : nsContentListDestroyFunc aDestroyFunc,
517 : nsFuncStringContentListDataAllocator aDataAllocator,
518 : const nsAString& aString) :
519 : nsContentList(aRootNode, aFunc, aDestroyFunc, nsnull),
520 : mString(aString)
521 : {
522 : mData = (*aDataAllocator)(aRootNode, &mString);
523 : }
524 :
525 : virtual ~nsCacheableFuncStringContentList();
526 :
527 : bool Equals(const nsFuncStringCacheKey* aKey) {
528 : return mRootNode == aKey->mRootNode && mFunc == aKey->mFunc &&
529 : mString == aKey->mString;
530 : }
531 :
532 : bool AllocatedData() const { return !!mData; }
533 : protected:
534 : virtual void RemoveFromCaches() {
535 : RemoveFromFuncStringHashtable();
536 : }
537 : void RemoveFromFuncStringHashtable();
538 :
539 : nsString mString;
540 : };
541 :
542 : // If aMatchNameSpaceId is kNameSpaceID_Unknown, this will return a
543 : // content list which matches ASCIIToLower(aTagname) against HTML
544 : // elements in HTML documents and aTagname against everything else.
545 : // For any other value of aMatchNameSpaceId, the list will match
546 : // aTagname against all elements.
547 : already_AddRefed<nsContentList>
548 : NS_GetContentList(nsINode* aRootNode,
549 : PRInt32 aMatchNameSpaceId,
550 : const nsAString& aTagname);
551 :
552 : already_AddRefed<nsContentList>
553 : NS_GetFuncStringContentList(nsINode* aRootNode,
554 : nsContentListMatchFunc aFunc,
555 : nsContentListDestroyFunc aDestroyFunc,
556 : nsFuncStringContentListDataAllocator aDataAllocator,
557 : const nsAString& aString);
558 : #endif // nsContentList_h___
|