diff options
Diffstat (limited to 'dom/base/nsContentList.h')
-rw-r--r-- | dom/base/nsContentList.h | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/dom/base/nsContentList.h b/dom/base/nsContentList.h new file mode 100644 index 0000000000..a70c5281b2 --- /dev/null +++ b/dom/base/nsContentList.h @@ -0,0 +1,655 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsBaseContentList is a basic list of content nodes; nsContentList + * is a commonly used NodeList implementation (used for + * getElementsByTagName, some properties on HTMLDocument/Document, etc). + */ + +#ifndef nsContentList_h___ +#define nsContentList_h___ + +#include "mozilla/Attributes.h" +#include "nsContentListDeclarations.h" +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsIHTMLCollection.h" +#include "nsINodeList.h" +#include "nsStubMutationObserver.h" +#include "nsAtomHashKeys.h" +#include "nsCycleCollectionParticipant.h" +#include "nsNameSpaceManager.h" +#include "nsWrapperCache.h" +#include "nsHashKeys.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/NameSpaceConstants.h" + +// XXX Avoid including this here by moving function bodies to the cpp file. +#include "nsIContent.h" + +namespace mozilla::dom { +class Element; +} // namespace mozilla::dom + +class nsBaseContentList : public nsINodeList { + protected: + using Element = mozilla::dom::Element; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + + // nsINodeList + int32_t IndexOf(nsIContent* aContent) override; + nsIContent* Item(uint32_t aIndex) override; + + uint32_t Length() override { return mElements.Length(); } + + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_WRAPPERCACHE_CLASS(nsBaseContentList) + + void AppendElement(nsIContent* aContent) { + MOZ_ASSERT(aContent); + mElements.AppendElement(aContent); + } + void MaybeAppendElement(nsIContent* aContent) { + if (aContent) { + AppendElement(aContent); + } + } + + /** + * Insert the element at a given index, shifting the objects at + * the given index and later to make space. + * @param aContent Element to insert, must not be null + * @param aIndex Index to insert the element at. + */ + void InsertElementAt(nsIContent* aContent, int32_t aIndex) { + NS_ASSERTION(aContent, "Element to insert must not be null"); + mElements.InsertElementAt(aIndex, aContent); + } + + void RemoveElement(nsIContent* aContent) { + mElements.RemoveElement(aContent); + } + + void Reset() { mElements.Clear(); } + + virtual int32_t IndexOf(nsIContent* aContent, bool aDoFlush); + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override = 0; + + void SetCapacity(uint32_t aCapacity) { mElements.SetCapacity(aCapacity); } + + virtual void LastRelease() {} + + // Memory reporting. For now, subclasses of nsBaseContentList don't really + // need to report any members that are not part of the object itself, so we + // don't need to make this virtual. + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + protected: + virtual ~nsBaseContentList(); + + /** + * To be called from non-destructor locations (e.g. unlink) that want to + * remove from caches. Cacheable subclasses should override. + */ + virtual void RemoveFromCaches() {} + + AutoTArray<nsCOMPtr<nsIContent>, 10> mElements; +}; + +class nsSimpleContentList : public nsBaseContentList { + public: + explicit nsSimpleContentList(nsINode* aRoot) + : nsBaseContentList(), mRoot(aRoot) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSimpleContentList, + nsBaseContentList) + + nsINode* GetParentObject() override { return mRoot; } + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + protected: + virtual ~nsSimpleContentList() = default; + + private: + // This has to be a strong reference, the root might go away before the list. + nsCOMPtr<nsINode> mRoot; +}; + +// Used for returning lists that will always be empty, such as the applets list +// in HTML Documents +class nsEmptyContentList final : public nsBaseContentList, + public nsIHTMLCollection { + public: + explicit nsEmptyContentList(nsINode* aRoot) + : nsBaseContentList(), mRoot(aRoot) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsEmptyContentList, + nsBaseContentList) + + nsINode* GetParentObject() override { return mRoot; } + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + JSObject* GetWrapperPreserveColorInternal() override { + return nsWrapperCache::GetWrapperPreserveColor(); + } + void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override { + nsWrapperCache::PreserveWrapper(aScriptObjectHolder); + } + + uint32_t Length() final { return 0; } + nsIContent* Item(uint32_t aIndex) override; + Element* GetElementAt(uint32_t index) override; + Element* GetFirstNamedElement(const nsAString& aName, bool& aFound) override; + void GetSupportedNames(nsTArray<nsString>& aNames) override; + + protected: + virtual ~nsEmptyContentList() = default; + + private: + // This has to be a strong reference, the root might go away before the list. + nsCOMPtr<nsINode> mRoot; +}; + +/** + * Class that's used as the key to hash nsContentList implementations + * for fast retrieval + */ +struct nsContentListKey { + // We have to take an aIsHTMLDocument arg for two reasons: + // 1) We don't want to include Document.h in this header. + // 2) We need to do that to make nsContentList::RemoveFromHashtable + // work, because by the time it's called the document of the + // list's root node might have changed. + nsContentListKey(nsINode* aRootNode, int32_t aMatchNameSpaceId, + const nsAString& aTagname, bool aIsHTMLDocument) + : mRootNode(aRootNode), + mMatchNameSpaceId(aMatchNameSpaceId), + mTagname(aTagname), + mIsHTMLDocument(aIsHTMLDocument), + mHash(mozilla::AddToHash(mozilla::HashString(aTagname), mRootNode, + mMatchNameSpaceId, mIsHTMLDocument)) {} + + nsContentListKey(const nsContentListKey& aContentListKey) = default; + + inline uint32_t GetHash(void) const { return mHash; } + + nsINode* const mRootNode; // Weak ref + const int32_t mMatchNameSpaceId; + const nsAString& mTagname; + bool mIsHTMLDocument; + const uint32_t mHash; +}; + +/** + * Class that implements a possibly live NodeList that matches Elements + * in the tree based on some criterion. + */ +class nsContentList : public nsBaseContentList, + public nsIHTMLCollection, + public nsStubMutationObserver { + protected: + enum class State : uint8_t { + // The list is up to date and need not do any walking to be able to answer + // any questions anyone may have. + UpToDate = 0, + // The list contains no useful information and if anyone asks it anything it + // will have to populate itself before answering. + Dirty, + // The list has populated itself to a certain extent and that that part of + // the list is still valid. Requests for things outside that part of the + // list will require walking the tree some more. When a list is in this + // state, the last thing in mElements is the last node in the tree that the + // list looked at. + Lazy, + }; + + public: + NS_DECL_ISUPPORTS_INHERITED + + /** + * @param aRootNode The node under which to limit our search. + * @param aMatchAtom An atom whose meaning depends on aMatchNameSpaceId. + * The special value "*" always matches whatever aMatchAtom + * is matched against. + * @param aMatchNameSpaceId If kNameSpaceID_Unknown, then aMatchAtom is the + * tagName to match. + * If kNameSpaceID_Wildcard, then aMatchAtom is the + * localName to match. + * Otherwise we match nodes whose namespace is + * aMatchNameSpaceId and localName matches + * aMatchAtom. + * @param aDeep If false, then look only at children of the root, nothing + * deeper. If true, then look at the whole subtree rooted at + * our root. + * @param aLiveList Whether the created list should be a live list observing + * mutations to the DOM tree. + */ + nsContentList(nsINode* aRootNode, int32_t aMatchNameSpaceId, + nsAtom* aHTMLMatchAtom, nsAtom* aXMLMatchAtom, + bool aDeep = true, bool aLiveList = true); + + /** + * @param aRootNode The node under which to limit our search. + * @param aFunc the function to be called to determine whether we match. + * This function MUST NOT ever cause mutation of the DOM. + * The nsContentList implementation guarantees that everything + * passed to the function will be IsElement(). + * @param aDestroyFunc the function that will be called to destroy aData + * @param aData closure data that will need to be passed back to aFunc + * @param aDeep If false, then look only at children of the root, nothing + * deeper. If true, then look at the whole subtree rooted at + * our root. + * @param aMatchAtom an atom to be passed back to aFunc + * @param aMatchNameSpaceId a namespace id to be passed back to aFunc + * @param aFuncMayDependOnAttr a boolean that indicates whether this list is + * sensitive to attribute changes. + * @param aLiveList Whether the created list should be a live list observing + * mutations to the DOM tree. + */ + nsContentList(nsINode* aRootNode, nsContentListMatchFunc aFunc, + nsContentListDestroyFunc aDestroyFunc, void* aData, + bool aDeep = true, nsAtom* aMatchAtom = nullptr, + int32_t aMatchNameSpaceId = kNameSpaceID_None, + bool aFuncMayDependOnAttr = true, bool aLiveList = true); + + // nsWrapperCache + using nsWrapperCache::GetWrapperPreserveColor; + using nsWrapperCache::PreserveWrapper; + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + protected: + virtual ~nsContentList(); + + JSObject* GetWrapperPreserveColorInternal() override { + return nsWrapperCache::GetWrapperPreserveColor(); + } + void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override { + nsWrapperCache::PreserveWrapper(aScriptObjectHolder); + } + + public: + // nsBaseContentList overrides + int32_t IndexOf(nsIContent* aContent, bool aDoFlush) override; + int32_t IndexOf(nsIContent* aContent) override; + nsINode* GetParentObject() override { return mRootNode; } + + uint32_t Length() final { return Length(true); } + nsIContent* Item(uint32_t aIndex) final; + Element* GetElementAt(uint32_t index) override; + Element* GetFirstNamedElement(const nsAString& aName, bool& aFound) override { + Element* item = NamedItem(aName, true); + aFound = !!item; + return item; + } + void GetSupportedNames(nsTArray<nsString>& aNames) override; + + // nsContentList public methods + uint32_t Length(bool aDoFlush); + nsIContent* Item(uint32_t aIndex, bool aDoFlush); + Element* NamedItem(const nsAString& aName, bool aDoFlush); + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + + static nsContentList* FromSupports(nsISupports* aSupports) { + nsINodeList* list = static_cast<nsINodeList*>(aSupports); +#ifdef DEBUG + { + nsCOMPtr<nsINodeList> list_qi = do_QueryInterface(aSupports); + + // If this assertion fires the QI implementation for the object in + // question doesn't use the nsINodeList pointer as the nsISupports + // pointer. That must be fixed, or we'll crash... + NS_ASSERTION(list_qi == list, "Uh, fix QI!"); + } +#endif + return static_cast<nsContentList*>(list); + } + + bool MatchesKey(const nsContentListKey& aKey) const { + // The root node is most commonly the same: the document. And the + // most common namespace id is kNameSpaceID_Unknown. So check the + // string first. Cases in which whether our root's ownerDocument + // is HTML changes are extremely rare, so check those last. + MOZ_ASSERT(mXMLMatchAtom, + "How did we get here with a null match atom on our list?"); + return mXMLMatchAtom->Equals(aKey.mTagname) && + mRootNode == aKey.mRootNode && + mMatchNameSpaceId == aKey.mMatchNameSpaceId && + mIsHTMLDocument == aKey.mIsHTMLDocument; + } + + /** + * Sets the state to LIST_DIRTY and clears mElements array. + * @note This is the only acceptable way to set state to LIST_DIRTY. + */ + void SetDirty() { + mState = State::Dirty; + InvalidateNamedItemsCache(); + Reset(); + SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed); + } + + void LastRelease() override; + + class HashEntry; + + protected: + // A cache from name to the first named item in mElements. Only possibly + // non-null when mState is State::UpToDate. Elements are kept alive by our + // mElements array. + using NamedItemsCache = nsTHashMap<nsAtomHashKey, Element*>; + + void InvalidateNamedItemsCache() { + mNamedItemsCache = nullptr; + mNamedItemsCacheValid = false; + } + + inline void InsertElementInNamedItemsCache(nsIContent&); + inline void InvalidateNamedItemsCacheForAttributeChange(int32_t aNameSpaceID, + nsAtom* aAttribute); + inline void InvalidateNamedItemsCacheForInsertion(Element&); + inline void InvalidateNamedItemsCacheForDeletion(Element&); + + void EnsureNamedItemsCacheValid(bool aDoFlush); + + /** + * Returns whether the element matches our criterion + * + * @param aElement the element to attempt to match + * @return whether we match + */ + bool Match(Element* aElement); + /** + * See if anything in the subtree rooted at aContent, including + * aContent itself, matches our criterion. + * + * @param aContent the root of the subtree to match against + * @return whether we match something in the tree rooted at aContent + */ + bool MatchSelf(nsIContent* aContent); + + /** + * Populate our list. Stop once we have at least aNeededLength + * elements. At the end of PopulateSelf running, either the last + * node we examined is the last node in our array or we have + * traversed the whole document (or both). + * + * @param aNeededLength the length the list should have when we are + * done (unless it exhausts the document) + * @param aExpectedElementsIfDirty is for debugging only to + * assert that mElements has expected number of entries. + */ + virtual void PopulateSelf(uint32_t aNeededLength, + uint32_t aExpectedElementsIfDirty = 0); + + /** + * @param aContainer a content node which must be a descendant of + * mRootNode + * @return true if children or descendants of aContainer could match our + * criterion. + * false otherwise. + */ + bool MayContainRelevantNodes(nsINode* aContainer) { + return mDeep || aContainer == mRootNode; + } + + /** + * Remove ourselves from the hashtable that caches commonly accessed + * content lists. Generally done on destruction. + */ + void RemoveFromHashtable(); + /** + * If state is not LIST_UP_TO_DATE, fully populate ourselves with + * all the nodes we can find. + */ + inline void BringSelfUpToDate(bool aDoFlush); + + /** + * To be called from non-destructor locations that want to remove from caches. + * Needed because if subclasses want to have cache behavior they can't just + * override RemoveFromHashtable(), since we call that in our destructor. + */ + void RemoveFromCaches() override { RemoveFromHashtable(); } + + void MaybeMarkDirty() { + if (mState != State::Dirty && ++mMissedUpdates > 128) { + mMissedUpdates = 0; + SetDirty(); + } + } + + nsINode* mRootNode; // Weak ref + int32_t mMatchNameSpaceId; + RefPtr<nsAtom> mHTMLMatchAtom; + RefPtr<nsAtom> mXMLMatchAtom; + + /** + * Function to use to determine whether a piece of content matches + * our criterion + */ + nsContentListMatchFunc mFunc = nullptr; + /** + * Cleanup closure data with this. + */ + nsContentListDestroyFunc mDestroyFunc = nullptr; + /** + * Closure data to pass to mFunc when we call it + */ + void* mData = nullptr; + + mozilla::UniquePtr<NamedItemsCache> mNamedItemsCache; + + uint8_t mMissedUpdates = 0; + + // The current state of the list. + State mState; + + /** + * True if we are looking for elements named "*" + */ + bool mMatchAll : 1; + /** + * Whether to actually descend the tree. If this is false, we won't + * consider grandkids of mRootNode. + */ + bool mDeep : 1; + /** + * Whether the return value of mFunc could depend on the values of + * attributes. + */ + bool mFuncMayDependOnAttr : 1; + /** + * Whether we actually need to flush to get our state correct. + */ + bool mFlushesNeeded : 1; + /** + * Whether the ownerDocument of our root node at list creation time was an + * HTML document. Only needed when we're doing a namespace/atom match, not + * when doing function matching, always false otherwise. + */ + bool mIsHTMLDocument : 1; + /** + * True mNamedItemsCache is valid. Note mNamedItemsCache might still be null + * if there's no named items at all. + */ + bool mNamedItemsCacheValid : 1; + /** + * Whether the list observes mutations to the DOM tree. + */ + const bool mIsLiveList : 1; + /* + * True if this content list is cached in a hash table. + * For nsContentList (but not its subclasses), the hash table is + * gContentListHashTable. + * For nsCacheableFuncStringContentList, the hash table is + * gFuncStringContentListHashTable. + * Other subclasses of nsContentList can't be in hash tables. + */ + bool mInHashtable : 1; + +#ifdef DEBUG_CONTENT_LIST + void AssertInSync(); +#endif +}; + +/** + * A class of cacheable content list; cached on the combination of aRootNode + + * aFunc + aDataString + */ +class nsCacheableFuncStringContentList; + +class MOZ_STACK_CLASS nsFuncStringCacheKey { + public: + nsFuncStringCacheKey(nsINode* aRootNode, nsContentListMatchFunc aFunc, + const nsAString& aString) + : mRootNode(aRootNode), mFunc(aFunc), mString(aString) {} + + uint32_t GetHash(void) const { + uint32_t hash = mozilla::HashString(mString); + return mozilla::AddToHash(hash, mRootNode, mFunc); + } + + private: + friend class nsCacheableFuncStringContentList; + + nsINode* const mRootNode; + const nsContentListMatchFunc mFunc; + const nsAString& mString; +}; + +// aDestroyFunc is allowed to be null +// aDataAllocator must always return a non-null pointer +class nsCacheableFuncStringContentList : public nsContentList { + public: + virtual ~nsCacheableFuncStringContentList(); + + bool Equals(const nsFuncStringCacheKey* aKey) { + return mRootNode == aKey->mRootNode && mFunc == aKey->mFunc && + mString == aKey->mString; + } + + enum ContentListType { eNodeList, eHTMLCollection }; +#ifdef DEBUG + ContentListType mType; +#endif + + class HashEntry; + + protected: + nsCacheableFuncStringContentList( + nsINode* aRootNode, nsContentListMatchFunc aFunc, + nsContentListDestroyFunc aDestroyFunc, + nsFuncStringContentListDataAllocator aDataAllocator, + const nsAString& aString, mozilla::DebugOnly<ContentListType> aType) + : nsContentList(aRootNode, aFunc, aDestroyFunc, nullptr), +#ifdef DEBUG + mType(aType), +#endif + mString(aString) { + mData = (*aDataAllocator)(aRootNode, &mString); + MOZ_ASSERT(mData); + } + + void RemoveFromCaches() override { RemoveFromFuncStringHashtable(); } + void RemoveFromFuncStringHashtable(); + + nsString mString; +}; + +class nsCachableElementsByNameNodeList + : public nsCacheableFuncStringContentList { + public: + nsCachableElementsByNameNodeList( + nsINode* aRootNode, nsContentListMatchFunc aFunc, + nsContentListDestroyFunc aDestroyFunc, + nsFuncStringContentListDataAllocator aDataAllocator, + const nsAString& aString) + : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc, + aDataAllocator, aString, eNodeList) {} + + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + +#ifdef DEBUG + static const ContentListType sType; +#endif +}; + +class nsCacheableFuncStringHTMLCollection + : public nsCacheableFuncStringContentList { + public: + nsCacheableFuncStringHTMLCollection( + nsINode* aRootNode, nsContentListMatchFunc aFunc, + nsContentListDestroyFunc aDestroyFunc, + nsFuncStringContentListDataAllocator aDataAllocator, + const nsAString& aString) + : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc, + aDataAllocator, aString, + eHTMLCollection) {} + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + +#ifdef DEBUG + static const ContentListType sType; +#endif +}; + +class nsLabelsNodeList final : public nsContentList { + public: + nsLabelsNodeList(nsINode* aRootNode, nsContentListMatchFunc aFunc, + nsContentListDestroyFunc aDestroyFunc, void* aData) + : nsContentList(aRootNode, aFunc, aDestroyFunc, aData) {} + + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override; + + /** + * Reset root, mutation observer, and clear content list + * if the root has been changed. + * + * @param aRootNode The node under which to limit our search. + */ + void MaybeResetRoot(nsINode* aRootNode); + + private: + /** + * Start searching at the last one if we already have nodes, otherwise + * start searching at the root. + * + * @param aNeededLength The list of length should have when we are + * done (unless it exhausts the document). + * @param aExpectedElementsIfDirty is for debugging only to + * assert that mElements has expected number of entries. + */ + void PopulateSelf(uint32_t aNeededLength, + uint32_t aExpectedElementsIfDirty = 0) override; +}; +#endif // nsContentList_h___ |