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 : #ifndef nsContentList_h___
14 : #define nsContentList_h___
15 :
16 : #include "mozilla/Attributes.h"
17 : #include "nsContentListDeclarations.h"
18 : #include "nsISupports.h"
19 : #include "nsTArray.h"
20 : #include "nsString.h"
21 : #include "nsIHTMLCollection.h"
22 : #include "nsIDOMNodeList.h"
23 : #include "nsINodeList.h"
24 : #include "nsStubMutationObserver.h"
25 : #include "nsIAtom.h"
26 : #include "nsCycleCollectionParticipant.h"
27 : #include "nsNameSpaceManager.h"
28 : #include "nsWrapperCache.h"
29 : #include "nsHashKeys.h"
30 : #include "mozilla/HashFunctions.h"
31 : #include "mozilla/dom/NameSpaceConstants.h"
32 :
33 : namespace mozilla {
34 : namespace dom {
35 : class Element;
36 : } // namespace dom
37 : } // namespace mozilla
38 :
39 :
40 16 : class nsBaseContentList : public nsINodeList
41 : {
42 : public:
43 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
44 :
45 : // nsIDOMNodeList
46 : NS_DECL_NSIDOMNODELIST
47 :
48 : // nsINodeList
49 : virtual int32_t IndexOf(nsIContent* aContent) override;
50 : virtual nsIContent* Item(uint32_t aIndex) override;
51 :
52 0 : uint32_t Length() const {
53 0 : return mElements.Length();
54 : }
55 :
56 272 : NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsBaseContentList)
57 :
58 8 : void AppendElement(nsIContent *aContent)
59 : {
60 8 : mElements.AppendElement(aContent);
61 8 : }
62 : void MaybeAppendElement(nsIContent* aContent)
63 : {
64 : if (aContent)
65 : AppendElement(aContent);
66 : }
67 :
68 : /**
69 : * Insert the element at a given index, shifting the objects at
70 : * the given index and later to make space.
71 : * @param aContent Element to insert, must not be null
72 : * @param aIndex Index to insert the element at.
73 : */
74 0 : void InsertElementAt(nsIContent* aContent, int32_t aIndex)
75 : {
76 0 : NS_ASSERTION(aContent, "Element to insert must not be null");
77 0 : mElements.InsertElementAt(aIndex, aContent);
78 0 : }
79 :
80 0 : void RemoveElement(nsIContent *aContent)
81 : {
82 0 : mElements.RemoveElement(aContent);
83 0 : }
84 :
85 4 : void Reset() {
86 4 : mElements.Clear();
87 4 : }
88 :
89 : virtual int32_t IndexOf(nsIContent *aContent, bool aDoFlush);
90 :
91 : virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
92 : override = 0;
93 :
94 4 : void SetCapacity(uint32_t aCapacity)
95 : {
96 4 : mElements.SetCapacity(aCapacity);
97 4 : }
98 :
99 0 : virtual void LastRelease() {}
100 :
101 : protected:
102 : virtual ~nsBaseContentList();
103 :
104 : /**
105 : * To be called from non-destructor locations (e.g. unlink) that want to
106 : * remove from caches. Cacheable subclasses should override.
107 : */
108 0 : virtual void RemoveFromCaches()
109 : {
110 0 : }
111 :
112 : AutoTArray<nsCOMPtr<nsIContent>, 10> mElements;
113 : };
114 :
115 :
116 : class nsSimpleContentList : public nsBaseContentList
117 : {
118 : public:
119 6 : explicit nsSimpleContentList(nsINode* aRoot) : nsBaseContentList(),
120 6 : mRoot(aRoot)
121 : {
122 6 : }
123 :
124 : NS_DECL_ISUPPORTS_INHERITED
125 66 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSimpleContentList,
126 : nsBaseContentList)
127 :
128 6 : virtual nsINode* GetParentObject() override
129 : {
130 6 : return mRoot;
131 : }
132 : virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
133 :
134 : protected:
135 0 : virtual ~nsSimpleContentList() {}
136 :
137 : private:
138 : // This has to be a strong reference, the root might go away before the list.
139 : nsCOMPtr<nsINode> mRoot;
140 : };
141 :
142 : /**
143 : * Class that's used as the key to hash nsContentList implementations
144 : * for fast retrieval
145 : */
146 : struct nsContentListKey
147 : {
148 : // We have to take an aIsHTMLDocument arg for two reasons:
149 : // 1) We don't want to include nsIDocument.h in this header.
150 : // 2) We need to do that to make nsContentList::RemoveFromHashtable
151 : // work, because by the time it's called the document of the
152 : // list's root node might have changed.
153 5 : nsContentListKey(nsINode* aRootNode,
154 : int32_t aMatchNameSpaceId,
155 : const nsAString& aTagname,
156 : bool aIsHTMLDocument)
157 5 : : mRootNode(aRootNode),
158 : mMatchNameSpaceId(aMatchNameSpaceId),
159 : mTagname(aTagname),
160 : mIsHTMLDocument(aIsHTMLDocument),
161 10 : mHash(mozilla::AddToHash(mozilla::HashString(aTagname), mRootNode,
162 15 : mMatchNameSpaceId, mIsHTMLDocument))
163 : {
164 5 : }
165 :
166 : nsContentListKey(const nsContentListKey& aContentListKey)
167 : : mRootNode(aContentListKey.mRootNode),
168 : mMatchNameSpaceId(aContentListKey.mMatchNameSpaceId),
169 : mTagname(aContentListKey.mTagname),
170 : mIsHTMLDocument(aContentListKey.mIsHTMLDocument),
171 : mHash(aContentListKey.mHash)
172 : {
173 : }
174 :
175 10 : inline uint32_t GetHash(void) const
176 : {
177 10 : return mHash;
178 : }
179 :
180 : nsINode* const mRootNode; // Weak ref
181 : const int32_t mMatchNameSpaceId;
182 : const nsAString& mTagname;
183 : bool mIsHTMLDocument;
184 : const uint32_t mHash;
185 : };
186 :
187 : /**
188 : * LIST_UP_TO_DATE means that the list is up to date and need not do
189 : * any walking to be able to answer any questions anyone may have.
190 : */
191 : #define LIST_UP_TO_DATE 0
192 : /**
193 : * LIST_DIRTY means that the list contains no useful information and
194 : * if anyone asks it anything it will have to populate itself before
195 : * answering.
196 : */
197 : #define LIST_DIRTY 1
198 : /**
199 : * LIST_LAZY means that the list has populated itself to a certain
200 : * extent and that that part of the list is still valid. Requests for
201 : * things outside that part of the list will require walking the tree
202 : * some more. When a list is in this state, the last thing in
203 : * mElements is the last node in the tree that the list looked at.
204 : */
205 : #define LIST_LAZY 2
206 :
207 : /**
208 : * Class that implements a live NodeList that matches Elements in the
209 : * tree based on some criterion.
210 : */
211 : class nsContentList : public nsBaseContentList,
212 : public nsIHTMLCollection,
213 : public nsStubMutationObserver
214 : {
215 : public:
216 : NS_DECL_ISUPPORTS_INHERITED
217 :
218 : /**
219 : * @param aRootNode The node under which to limit our search.
220 : * @param aMatchAtom An atom whose meaning depends on aMatchNameSpaceId.
221 : * The special value "*" always matches whatever aMatchAtom
222 : * is matched against.
223 : * @param aMatchNameSpaceId If kNameSpaceID_Unknown, then aMatchAtom is the
224 : * tagName to match.
225 : * If kNameSpaceID_Wildcard, then aMatchAtom is the
226 : * localName to match.
227 : * Otherwise we match nodes whose namespace is
228 : * aMatchNameSpaceId and localName matches
229 : * aMatchAtom.
230 : * @param aDeep If false, then look only at children of the root, nothing
231 : * deeper. If true, then look at the whole subtree rooted at
232 : * our root.
233 : */
234 : nsContentList(nsINode* aRootNode,
235 : int32_t aMatchNameSpaceId,
236 : nsIAtom* aHTMLMatchAtom,
237 : nsIAtom* aXMLMatchAtom,
238 : bool aDeep = true);
239 :
240 : /**
241 : * @param aRootNode The node under which to limit our search.
242 : * @param aFunc the function to be called to determine whether we match.
243 : * This function MUST NOT ever cause mutation of the DOM.
244 : * The nsContentList implementation guarantees that everything
245 : * passed to the function will be IsElement().
246 : * @param aDestroyFunc the function that will be called to destroy aData
247 : * @param aData closure data that will need to be passed back to aFunc
248 : * @param aDeep If false, then look only at children of the root, nothing
249 : * deeper. If true, then look at the whole subtree rooted at
250 : * our root.
251 : * @param aMatchAtom an atom to be passed back to aFunc
252 : * @param aMatchNameSpaceId a namespace id to be passed back to aFunc
253 : * @param aFuncMayDependOnAttr a boolean that indicates whether this list is
254 : * sensitive to attribute changes.
255 : */
256 : nsContentList(nsINode* aRootNode,
257 : nsContentListMatchFunc aFunc,
258 : nsContentListDestroyFunc aDestroyFunc,
259 : void* aData,
260 : bool aDeep = true,
261 : nsIAtom* aMatchAtom = nullptr,
262 : int32_t aMatchNameSpaceId = kNameSpaceID_None,
263 : bool aFuncMayDependOnAttr = true);
264 :
265 : // nsWrapperCache
266 : using nsWrapperCache::GetWrapperPreserveColor;
267 : using nsWrapperCache::PreserveWrapper;
268 : virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
269 : protected:
270 : virtual ~nsContentList();
271 :
272 7 : virtual JSObject* GetWrapperPreserveColorInternal() override
273 : {
274 7 : return nsWrapperCache::GetWrapperPreserveColor();
275 : }
276 0 : virtual void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override
277 : {
278 0 : nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
279 0 : }
280 : public:
281 :
282 : // nsIDOMHTMLCollection
283 : NS_DECL_NSIDOMHTMLCOLLECTION
284 :
285 : // nsBaseContentList overrides
286 : virtual int32_t IndexOf(nsIContent *aContent, bool aDoFlush) override;
287 : virtual int32_t IndexOf(nsIContent* aContent) override;
288 9 : virtual nsINode* GetParentObject() override
289 : {
290 9 : return mRootNode;
291 : }
292 :
293 : virtual nsIContent* Item(uint32_t aIndex) override;
294 : virtual mozilla::dom::Element* GetElementAt(uint32_t index) override;
295 : virtual mozilla::dom::Element*
296 0 : GetFirstNamedElement(const nsAString& aName, bool& aFound) override
297 : {
298 0 : mozilla::dom::Element* item = NamedItem(aName, true);
299 0 : aFound = !!item;
300 0 : return item;
301 : }
302 : virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
303 :
304 : // nsContentList public methods
305 : uint32_t Length(bool aDoFlush);
306 : nsIContent* Item(uint32_t aIndex, bool aDoFlush);
307 : mozilla::dom::Element*
308 : NamedItem(const nsAString& aName, bool aDoFlush);
309 :
310 : // nsIMutationObserver
311 : NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
312 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
313 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
314 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
315 : NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
316 :
317 : static nsContentList* FromSupports(nsISupports* aSupports)
318 : {
319 : nsINodeList* list = static_cast<nsINodeList*>(aSupports);
320 : #ifdef DEBUG
321 : {
322 : nsCOMPtr<nsINodeList> list_qi = do_QueryInterface(aSupports);
323 :
324 : // If this assertion fires the QI implementation for the object in
325 : // question doesn't use the nsINodeList pointer as the nsISupports
326 : // pointer. That must be fixed, or we'll crash...
327 : NS_ASSERTION(list_qi == list, "Uh, fix QI!");
328 : }
329 : #endif
330 : return static_cast<nsContentList*>(list);
331 : }
332 :
333 1 : bool MatchesKey(const nsContentListKey& aKey) const
334 : {
335 : // The root node is most commonly the same: the document. And the
336 : // most common namespace id is kNameSpaceID_Unknown. So check the
337 : // string first. Cases in which whether our root's ownerDocument
338 : // is HTML changes are extremely rare, so check those last.
339 1 : NS_PRECONDITION(mXMLMatchAtom,
340 : "How did we get here with a null match atom on our list?");
341 : return
342 2 : mXMLMatchAtom->Equals(aKey.mTagname) &&
343 2 : mRootNode == aKey.mRootNode &&
344 3 : mMatchNameSpaceId == aKey.mMatchNameSpaceId &&
345 2 : mIsHTMLDocument == aKey.mIsHTMLDocument;
346 : }
347 :
348 : /**
349 : * Sets the state to LIST_DIRTY and clears mElements array.
350 : * @note This is the only acceptable way to set state to LIST_DIRTY.
351 : */
352 4 : void SetDirty()
353 : {
354 4 : mState = LIST_DIRTY;
355 4 : Reset();
356 4 : }
357 :
358 : virtual void LastRelease() override;
359 :
360 : protected:
361 : /**
362 : * Returns whether the element matches our criterion
363 : *
364 : * @param aElement the element to attempt to match
365 : * @return whether we match
366 : */
367 : bool Match(mozilla::dom::Element *aElement);
368 : /**
369 : * See if anything in the subtree rooted at aContent, including
370 : * aContent itself, matches our criterion.
371 : *
372 : * @param aContent the root of the subtree to match against
373 : * @return whether we match something in the tree rooted at aContent
374 : */
375 : bool MatchSelf(nsIContent *aContent);
376 :
377 : /**
378 : * Populate our list. Stop once we have at least aNeededLength
379 : * elements. At the end of PopulateSelf running, either the last
380 : * node we examined is the last node in our array or we have
381 : * traversed the whole document (or both).
382 : *
383 : * @param aNeededLength the length the list should have when we are
384 : * done (unless it exhausts the document)
385 : */
386 : virtual void PopulateSelf(uint32_t aNeededLength);
387 :
388 : /**
389 : * @param aContainer a content node which must be a descendant of
390 : * mRootNode
391 : * @return true if children or descendants of aContainer could match our
392 : * criterion.
393 : * false otherwise.
394 : */
395 28 : bool MayContainRelevantNodes(nsINode* aContainer)
396 : {
397 28 : return mDeep || aContainer == mRootNode;
398 : }
399 :
400 : /**
401 : * Remove ourselves from the hashtable that caches commonly accessed
402 : * content lists. Generally done on destruction.
403 : */
404 : void RemoveFromHashtable();
405 : /**
406 : * If state is not LIST_UP_TO_DATE, fully populate ourselves with
407 : * all the nodes we can find.
408 : */
409 : inline void BringSelfUpToDate(bool aDoFlush);
410 :
411 : /**
412 : * To be called from non-destructor locations that want to remove from caches.
413 : * Needed because if subclasses want to have cache behavior they can't just
414 : * override RemoveFromHashtable(), since we call that in our destructor.
415 : */
416 1 : virtual void RemoveFromCaches() override
417 : {
418 1 : RemoveFromHashtable();
419 1 : }
420 :
421 : nsINode* mRootNode; // Weak ref
422 : int32_t 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 : uint8_t mState;
444 :
445 : // The booleans have to use uint8_t 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 : uint8_t mMatchAll : 1;
454 : /**
455 : * Whether to actually descend the tree. If this is false, we won't
456 : * consider grandkids of mRootNode.
457 : */
458 : uint8_t mDeep : 1;
459 : /**
460 : * Whether the return value of mFunc could depend on the values of
461 : * attributes.
462 : */
463 : uint8_t mFuncMayDependOnAttr : 1;
464 : /**
465 : * Whether we actually need to flush to get our state correct.
466 : */
467 : uint8_t mFlushesNeeded : 1;
468 : /**
469 : * Whether the ownerDocument of our root node at list creation time was an
470 : * HTML document. Only needed when we're doing a namespace/atom match, not
471 : * when doing function matching, always false otherwise.
472 : */
473 : uint8_t mIsHTMLDocument : 1;
474 :
475 : #ifdef DEBUG_CONTENT_LIST
476 : void AssertInSync();
477 : #endif
478 : };
479 :
480 : /**
481 : * A class of cacheable content list; cached on the combination of aRootNode + aFunc + aDataString
482 : */
483 : class nsCacheableFuncStringContentList;
484 :
485 : class MOZ_STACK_CLASS nsFuncStringCacheKey {
486 : public:
487 0 : nsFuncStringCacheKey(nsINode* aRootNode,
488 : nsContentListMatchFunc aFunc,
489 0 : const nsAString& aString) :
490 : mRootNode(aRootNode),
491 : mFunc(aFunc),
492 0 : mString(aString)
493 0 : {}
494 :
495 0 : uint32_t GetHash(void) const
496 : {
497 0 : uint32_t hash = mozilla::HashString(mString);
498 0 : return mozilla::AddToHash(hash, mRootNode, mFunc);
499 : }
500 :
501 : private:
502 : friend class nsCacheableFuncStringContentList;
503 :
504 : nsINode* const mRootNode;
505 : const nsContentListMatchFunc mFunc;
506 : const nsAString& mString;
507 : };
508 :
509 : // aDestroyFunc is allowed to be null
510 : // aDataAllocator must always return a non-null pointer
511 : class nsCacheableFuncStringContentList : public nsContentList {
512 : public:
513 : virtual ~nsCacheableFuncStringContentList();
514 :
515 0 : bool Equals(const nsFuncStringCacheKey* aKey) {
516 0 : return mRootNode == aKey->mRootNode && mFunc == aKey->mFunc &&
517 0 : mString == aKey->mString;
518 : }
519 :
520 : #ifdef DEBUG
521 : enum ContentListType {
522 : eNodeList,
523 : eHTMLCollection
524 : };
525 : ContentListType mType;
526 : #endif
527 :
528 : protected:
529 0 : nsCacheableFuncStringContentList(nsINode* aRootNode,
530 : nsContentListMatchFunc aFunc,
531 : nsContentListDestroyFunc aDestroyFunc,
532 : nsFuncStringContentListDataAllocator aDataAllocator,
533 0 : const nsAString& aString) :
534 : nsContentList(aRootNode, aFunc, aDestroyFunc, nullptr),
535 0 : mString(aString)
536 : {
537 0 : mData = (*aDataAllocator)(aRootNode, &mString);
538 0 : MOZ_ASSERT(mData);
539 0 : }
540 :
541 0 : virtual void RemoveFromCaches() override {
542 0 : RemoveFromFuncStringHashtable();
543 0 : }
544 : void RemoveFromFuncStringHashtable();
545 :
546 : nsString mString;
547 : };
548 :
549 0 : class nsCacheableFuncStringNodeList
550 : : public nsCacheableFuncStringContentList
551 : {
552 : public:
553 0 : nsCacheableFuncStringNodeList(nsINode* aRootNode,
554 : nsContentListMatchFunc aFunc,
555 : nsContentListDestroyFunc aDestroyFunc,
556 : nsFuncStringContentListDataAllocator aDataAllocator,
557 : const nsAString& aString)
558 0 : : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc,
559 0 : aDataAllocator, aString)
560 : {
561 : #ifdef DEBUG
562 0 : mType = eNodeList;
563 : #endif
564 0 : }
565 :
566 : virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
567 :
568 : #ifdef DEBUG
569 : static const ContentListType sType;
570 : #endif
571 : };
572 :
573 0 : class nsCacheableFuncStringHTMLCollection
574 : : public nsCacheableFuncStringContentList
575 : {
576 : public:
577 0 : nsCacheableFuncStringHTMLCollection(nsINode* aRootNode,
578 : nsContentListMatchFunc aFunc,
579 : nsContentListDestroyFunc aDestroyFunc,
580 : nsFuncStringContentListDataAllocator aDataAllocator,
581 : const nsAString& aString)
582 0 : : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc,
583 0 : aDataAllocator, aString)
584 : {
585 : #ifdef DEBUG
586 0 : mType = eHTMLCollection;
587 : #endif
588 0 : }
589 :
590 : virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
591 :
592 : #ifdef DEBUG
593 : static const ContentListType sType;
594 : #endif
595 : };
596 :
597 0 : class nsLabelsNodeList final : public nsContentList
598 : {
599 : public:
600 0 : nsLabelsNodeList(nsINode* aRootNode,
601 : nsContentListMatchFunc aFunc,
602 : nsContentListDestroyFunc aDestroyFunc,
603 : void* aData)
604 0 : : nsContentList(aRootNode, aFunc, aDestroyFunc, aData)
605 : {
606 0 : }
607 :
608 : NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
609 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
610 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
611 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
612 :
613 : virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
614 :
615 : /**
616 : * Reset root, mutation observer, and clear content list
617 : * if the root has been changed.
618 : *
619 : * @param aRootNode The node under which to limit our search.
620 : */
621 : void MaybeResetRoot(nsINode* aRootNode);
622 :
623 : private:
624 : /**
625 : * Start searching at the last one if we already have nodes, otherwise
626 : * start searching at the root.
627 : *
628 : * @param aNeededLength The list of length should have when we are
629 : * done (unless it exhausts the document).
630 : */
631 : void PopulateSelf(uint32_t aNeededLength) override;
632 : };
633 : #endif // nsContentList_h___
|