/* -*- 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/. */ #ifndef nsDOMMutationObserver_h #define nsDOMMutationObserver_h #include #include "mozilla/Attributes.h" #include "mozilla/dom/Animation.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/MutationEventBinding.h" #include "mozilla/dom/MutationObserverBinding.h" #include "mozilla/dom/Nullable.h" #include "nsCOMArray.h" #include "nsClassHashtable.h" #include "nsContentList.h" #include "nsCycleCollectionParticipant.h" #include "nsGlobalWindow.h" #include "nsIAnimationObserver.h" #include "nsIScriptContext.h" #include "nsIVariant.h" #include "nsPIDOMWindow.h" #include "nsStubAnimationObserver.h" #include "nsTArray.h" #include "nsWrapperCache.h" class nsIPrincipal; class nsDOMMutationObserver; using mozilla::dom::MutationObservingInfo; namespace mozilla::dom { class Element; } class nsDOMMutationRecord final : public nsISupports, public nsWrapperCache { virtual ~nsDOMMutationRecord() = default; public: using AnimationArray = nsTArray>; nsDOMMutationRecord(nsAtom* aType, nsISupports* aOwner) : mType(aType), mAttrNamespace(VoidString()), mPrevValue(VoidString()), mOwner(aOwner) {} nsISupports* GetParentObject() const { return mOwner; } virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override { return mozilla::dom::MutationRecord_Binding::Wrap(aCx, this, aGivenProto); } NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMMutationRecord) void GetType(mozilla::dom::DOMString& aRetVal) const { aRetVal.SetKnownLiveAtom(mType, mozilla::dom::DOMString::eNullNotExpected); } nsINode* GetTarget() const { return mTarget; } nsINodeList* AddedNodes(); nsINodeList* RemovedNodes(); nsINode* GetPreviousSibling() const { return mPreviousSibling; } nsINode* GetNextSibling() const { return mNextSibling; } void GetAttributeName(mozilla::dom::DOMString& aRetVal) const { aRetVal.SetKnownLiveAtom(mAttrName, mozilla::dom::DOMString::eTreatNullAsNull); } void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const { aRetVal.SetKnownLiveString(mAttrNamespace); } void GetOldValue(mozilla::dom::DOMString& aRetVal) const { aRetVal.SetKnownLiveString(mPrevValue); } void GetAddedAnimations(AnimationArray& aRetVal) const { aRetVal = mAddedAnimations.Clone(); } void GetRemovedAnimations(AnimationArray& aRetVal) const { aRetVal = mRemovedAnimations.Clone(); } void GetChangedAnimations(AnimationArray& aRetVal) const { aRetVal = mChangedAnimations.Clone(); } nsCOMPtr mTarget; RefPtr mType; RefPtr mAttrName; nsString mAttrNamespace; nsString mPrevValue; RefPtr mAddedNodes; RefPtr mRemovedNodes; nsCOMPtr mPreviousSibling; nsCOMPtr mNextSibling; AnimationArray mAddedAnimations; AnimationArray mRemovedAnimations; AnimationArray mChangedAnimations; RefPtr mNext; nsCOMPtr mOwner; }; // Base class just prevents direct access to // members to make sure we go through getters/setters. class nsMutationReceiverBase : public nsStubAnimationObserver { public: virtual ~nsMutationReceiverBase() = default; nsDOMMutationObserver* Observer(); nsINode* Target() { return mParent ? mParent->Target() : mTarget; } nsINode* RegisterTarget() { return mRegisterTarget; } bool Subtree() { return mParent ? mParent->Subtree() : mSubtree; } void SetSubtree(bool aSubtree) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mSubtree = aSubtree; } bool ChildList() { return mParent ? mParent->ChildList() : mChildList; } void SetChildList(bool aChildList) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mChildList = aChildList; } bool CharacterData() { return mParent ? mParent->CharacterData() : mCharacterData; } void SetCharacterData(bool aCharacterData) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mCharacterData = aCharacterData; } bool CharacterDataOldValue() const { return mParent ? mParent->CharacterDataOldValue() : mCharacterDataOldValue; } void SetCharacterDataOldValue(bool aOldValue) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mCharacterDataOldValue = aOldValue; } bool Attributes() const { return mParent ? mParent->Attributes() : mAttributes; } void SetAttributes(bool aAttributes) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mAttributes = aAttributes; } bool AllAttributes() const { return mParent ? mParent->AllAttributes() : mAllAttributes; } void SetAllAttributes(bool aAll) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mAllAttributes = aAll; } bool Animations() const { return mParent ? mParent->Animations() : mAnimations; } void SetAnimations(bool aAnimations) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mAnimations = aAnimations; } bool AttributeOldValue() const { return mParent ? mParent->AttributeOldValue() : mAttributeOldValue; } void SetAttributeOldValue(bool aOldValue) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mAttributeOldValue = aOldValue; } bool ChromeOnlyNodes() const { return mParent ? mParent->ChromeOnlyNodes() : mChromeOnlyNodes; } void SetChromeOnlyNodes(bool aChromeOnlyNodes) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mChromeOnlyNodes = aChromeOnlyNodes; } nsTArray>& AttributeFilter() { return mAttributeFilter; } void SetAttributeFilter(nsTArray>&& aFilter) { NS_ASSERTION(!mParent, "Shouldn't have parent"); mAttributeFilter.Clear(); mAttributeFilter = std::move(aFilter); } void AddClone(nsMutationReceiverBase* aClone) { mTransientReceivers.AppendObject(aClone); } void RemoveClone(nsMutationReceiverBase* aClone) { mTransientReceivers.RemoveObject(aClone); } protected: nsMutationReceiverBase(nsINode* aTarget, nsDOMMutationObserver* aObserver) : mTarget(aTarget), mObserver(aObserver), mRegisterTarget(aTarget), mSubtree(false), mChildList(false), mCharacterData(false), mCharacterDataOldValue(false), mAttributes(false), mAllAttributes(false), mAttributeOldValue(false), mAnimations(false) {} nsMutationReceiverBase(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent) : mTarget(nullptr), mObserver(nullptr), mParent(aParent), mRegisterTarget(aRegisterTarget), mKungFuDeathGrip(aParent->Target()), mSubtree(false), mChildList(false), mCharacterData(false), mCharacterDataOldValue(false), mAttributes(false), mAllAttributes(false), mAttributeOldValue(false), mAnimations(false), mChromeOnlyNodes(false) { NS_ASSERTION(mParent->Subtree(), "Should clone a non-subtree observer!"); } virtual void AddMutationObserver() = 0; void AddObserver() { AddMutationObserver(); mRegisterTarget->SetMayHaveDOMMutationObserver(); mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers(); } bool IsObservable(nsIContent* aContent); bool ObservesAttr(nsINode* aRegisterTarget, mozilla::dom::Element* aElement, int32_t aNameSpaceID, nsAtom* aAttr); // The target for the MutationObserver.observe() method. nsINode* mTarget; nsDOMMutationObserver* mObserver; RefPtr mParent; // Cleared after microtask. // The node to which Gecko-internal nsIMutationObserver was registered to. // This is different than mTarget when dealing with transient observers. nsINode* mRegisterTarget; nsCOMArray mTransientReceivers; // While we have transient receivers, keep the original mutation receiver // alive so it doesn't go away and disconnect all its transient receivers. nsCOMPtr mKungFuDeathGrip; private: nsTArray> mAttributeFilter; bool mSubtree : 1; bool mChildList : 1; bool mCharacterData : 1; bool mCharacterDataOldValue : 1; bool mAttributes : 1; bool mAllAttributes : 1; bool mAttributeOldValue : 1; bool mAnimations : 1; bool mChromeOnlyNodes : 1; }; class nsMutationReceiver : public nsMutationReceiverBase { protected: virtual ~nsMutationReceiver() { Disconnect(false); } public: static nsMutationReceiver* Create(nsINode* aTarget, nsDOMMutationObserver* aObserver) { nsMutationReceiver* r = new nsMutationReceiver(aTarget, aObserver); r->AddObserver(); return r; } static nsMutationReceiver* Create(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent) { nsMutationReceiver* r = new nsMutationReceiver(aRegisterTarget, aParent); aParent->AddClone(r); r->AddObserver(); return r; } nsMutationReceiver* GetParent() { return static_cast(mParent.get()); } void RemoveClones() { for (int32_t i = 0; i < mTransientReceivers.Count(); ++i) { nsMutationReceiver* r = static_cast(mTransientReceivers[i]); r->DisconnectTransientReceiver(); } mTransientReceivers.Clear(); } void DisconnectTransientReceiver() { if (mRegisterTarget) { mRegisterTarget->RemoveMutationObserver(this); mRegisterTarget = nullptr; } mParent = nullptr; NS_ASSERTION(!mTarget, "Should not have mTarget"); NS_ASSERTION(!mObserver, "Should not have mObserver"); } void Disconnect(bool aRemoveFromObserver); NS_DECL_ISUPPORTS NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED virtual void AttributeSetToCurrentValue(mozilla::dom::Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute) override { // We can reuse AttributeWillChange implementation. AttributeWillChange(aElement, aNameSpaceID, aAttribute, mozilla::dom::MutationEvent_Binding::MODIFICATION); } protected: nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver); nsMutationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent) : nsMutationReceiverBase(aRegisterTarget, aParent) { NS_ASSERTION(!static_cast(aParent)->GetParent(), "Shouldn't create deep observer hierarchies!"); } virtual void AddMutationObserver() override { mRegisterTarget->AddMutationObserver(this); } }; class nsAnimationReceiver : public nsMutationReceiver { public: static nsAnimationReceiver* Create(nsINode* aTarget, nsDOMMutationObserver* aObserver) { nsAnimationReceiver* r = new nsAnimationReceiver(aTarget, aObserver); r->AddObserver(); return r; } static nsAnimationReceiver* Create(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent) { nsAnimationReceiver* r = new nsAnimationReceiver(aRegisterTarget, aParent); aParent->AddClone(r); r->AddObserver(); return r; } NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED protected: virtual ~nsAnimationReceiver() = default; nsAnimationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver) : nsMutationReceiver(aTarget, aObserver) {} nsAnimationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent) : nsMutationReceiver(aRegisterTarget, aParent) {} virtual void AddMutationObserver() override { mRegisterTarget->AddAnimationObserver(this); } private: enum AnimationMutation { eAnimationMutation_Added, eAnimationMutation_Changed, eAnimationMutation_Removed }; void RecordAnimationMutation(mozilla::dom::Animation* aAnimation, AnimationMutation aMutationType); }; #define NS_DOM_MUTATION_OBSERVER_IID \ { \ 0x0c3b91f8, 0xcc3b, 0x4b08, { \ 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 \ } \ } class nsDOMMutationObserver final : public nsISupports, public nsWrapperCache { public: nsDOMMutationObserver(nsCOMPtr&& aOwner, mozilla::dom::MutationCallback& aCb) : mOwner(std::move(aOwner)), mLastPendingMutation(nullptr), mPendingMutationCount(0), mCallback(&aCb), mWaitingForRun(false), mMergeAttributeRecords(false), mId(++sCount) {} NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver) NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID) static already_AddRefed Constructor( const mozilla::dom::GlobalObject&, mozilla::dom::MutationCallback&, mozilla::ErrorResult&); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override { return mozilla::dom::MutationObserver_Binding::Wrap(aCx, this, aGivenProto); } nsISupports* GetParentObject() const { return mOwner; } void Observe(nsINode& aTarget, const mozilla::dom::MutationObserverInit& aOptions, nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aRv); void Disconnect(); void TakeRecords(nsTArray>& aRetVal); MOZ_CAN_RUN_SCRIPT void HandleMutation(); void GetObservingInfo( nsTArray>& aResult, mozilla::ErrorResult& aRv); mozilla::dom::MutationCallback* MutationCallback() { return mCallback; } bool MergeAttributeRecords() { return mMergeAttributeRecords; } void SetMergeAttributeRecords(bool aVal) { mMergeAttributeRecords = aVal; } // If both records are for 'attributes' type and for the same target and // attribute name and namespace are the same, we can skip the newer record. // aOldRecord->mPrevValue holds the original value, if observed. bool MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord, nsDOMMutationRecord* aRecord); void AppendMutationRecord(already_AddRefed aRecord) { RefPtr record = aRecord; MOZ_ASSERT(record); if (!mLastPendingMutation) { MOZ_ASSERT(!mFirstPendingMutation); mFirstPendingMutation = std::move(record); mLastPendingMutation = mFirstPendingMutation; } else { MOZ_ASSERT(mFirstPendingMutation); mLastPendingMutation->mNext = std::move(record); mLastPendingMutation = mLastPendingMutation->mNext; } ++mPendingMutationCount; } void ClearPendingRecords() { // Break down the pending mutation record list so that cycle collector // can delete the objects sooner. RefPtr current = std::move(mFirstPendingMutation); mLastPendingMutation = nullptr; mPendingMutationCount = 0; while (current) { current = std::move(current->mNext); } } // static methods static void QueueMutationObserverMicroTask(); MOZ_CAN_RUN_SCRIPT static void HandleMutations(mozilla::AutoSlowOperation& aAso); static bool AllScheduledMutationObserversAreSuppressed() { if (sScheduledMutationObservers) { uint32_t len = sScheduledMutationObservers->Length(); if (len > 0) { for (uint32_t i = 0; i < len; ++i) { if (!(*sScheduledMutationObservers)[i]->Suppressed()) { return false; } } return true; } } return false; } static void EnterMutationHandling(); static void LeaveMutationHandling(); static void Shutdown(); protected: virtual ~nsDOMMutationObserver(); friend class nsMutationReceiver; friend class nsAnimationReceiver; friend class nsAutoMutationBatch; friend class nsAutoAnimationMutationBatch; nsMutationReceiver* GetReceiverFor(nsINode* aNode, bool aMayCreate, bool aWantsAnimations); void RemoveReceiver(nsMutationReceiver* aReceiver); void GetAllSubtreeObserversFor(nsINode* aNode, nsTArray& aObservers); void ScheduleForRun(); void RescheduleForRun(); nsDOMMutationRecord* CurrentRecord(nsAtom* aType); bool HasCurrentRecord(const nsAString& aType); bool Suppressed() { return mOwner && nsGlobalWindowInner::Cast(mOwner)->IsInSyncOperation(); } MOZ_CAN_RUN_SCRIPT static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso); static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver, uint32_t aMutationLevel); nsCOMPtr mOwner; nsCOMArray mReceivers; nsClassHashtable> mTransientReceivers; // MutationRecords which are being constructed. AutoTArray mCurrentMutations; // MutationRecords which will be handed to the callback at the end of // the microtask. RefPtr mFirstPendingMutation; nsDOMMutationRecord* mLastPendingMutation; uint32_t mPendingMutationCount; RefPtr mCallback; bool mWaitingForRun; bool mMergeAttributeRecords; uint64_t mId; static uint64_t sCount; static AutoTArray, 4>* sScheduledMutationObservers; static uint32_t sMutationLevel; static AutoTArray, 4>, 4>* sCurrentlyHandlingObservers; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver, NS_DOM_MUTATION_OBSERVER_IID) class nsAutoMutationBatch { public: nsAutoMutationBatch() : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false), mFromFirstToLast(false), mAllowNestedBatches(false) {} nsAutoMutationBatch(nsINode* aTarget, bool aFromFirstToLast, bool aAllowNestedBatches) : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false), mFromFirstToLast(false), mAllowNestedBatches(false) { Init(aTarget, aFromFirstToLast, aAllowNestedBatches); } void Init(nsINode* aTarget, bool aFromFirstToLast, bool aAllowNestedBatches) { if (aTarget && aTarget->OwnerDoc()->MayHaveDOMMutationObservers()) { if (sCurrentBatch && !sCurrentBatch->mAllowNestedBatches) { return; } mBatchTarget = aTarget; mFromFirstToLast = aFromFirstToLast; mAllowNestedBatches = aAllowNestedBatches; mPreviousBatch = sCurrentBatch; sCurrentBatch = this; nsDOMMutationObserver::EnterMutationHandling(); } } void RemovalDone() { mRemovalDone = true; } static bool IsRemovalDone() { return sCurrentBatch->mRemovalDone; } void SetPrevSibling(nsINode* aNode) { mPrevSibling = aNode; } void SetNextSibling(nsINode* aNode) { mNextSibling = aNode; } void Done(); ~nsAutoMutationBatch() { NodesAdded(); } static bool IsBatching() { return !!sCurrentBatch; } static nsAutoMutationBatch* GetCurrentBatch() { return sCurrentBatch; } static void UpdateObserver(nsDOMMutationObserver* aObserver, bool aWantsChildList) { uint32_t l = sCurrentBatch->mObservers.Length(); for (uint32_t i = 0; i < l; ++i) { if (sCurrentBatch->mObservers[i].mObserver == aObserver) { if (aWantsChildList) { sCurrentBatch->mObservers[i].mWantsChildList = aWantsChildList; } return; } } BatchObserver* bo = sCurrentBatch->mObservers.AppendElement(); bo->mObserver = aObserver; bo->mWantsChildList = aWantsChildList; } static nsINode* GetBatchTarget() { return sCurrentBatch->mBatchTarget; } // Mutation receivers notify the batch about removed child nodes. static void NodeRemoved(nsIContent* aChild) { if (IsBatching() && !sCurrentBatch->mRemovalDone) { uint32_t len = sCurrentBatch->mRemovedNodes.Length(); if (!len || sCurrentBatch->mRemovedNodes[len - 1] != aChild) { sCurrentBatch->mRemovedNodes.AppendElement(aChild); } } } // Called after new child nodes have been added to the batch target. void NodesAdded() { if (sCurrentBatch != this) { return; } nsIContent* c = mPrevSibling ? mPrevSibling->GetNextSibling() : mBatchTarget->GetFirstChild(); for (; c != mNextSibling; c = c->GetNextSibling()) { mAddedNodes.AppendElement(c); } Done(); } private: struct BatchObserver { nsDOMMutationObserver* mObserver; bool mWantsChildList; }; static nsAutoMutationBatch* sCurrentBatch; nsAutoMutationBatch* mPreviousBatch; AutoTArray mObservers; nsTArray> mRemovedNodes; nsTArray> mAddedNodes; nsINode* mBatchTarget; bool mRemovalDone; bool mFromFirstToLast; bool mAllowNestedBatches; nsCOMPtr mPrevSibling; nsCOMPtr mNextSibling; }; class nsAutoAnimationMutationBatch { struct Entry; public: explicit nsAutoAnimationMutationBatch(mozilla::dom::Document* aDocument) { Init(aDocument); } void Init(mozilla::dom::Document* aDocument) { if (!aDocument || !aDocument->MayHaveDOMMutationObservers() || sCurrentBatch) { return; } sCurrentBatch = this; nsDOMMutationObserver::EnterMutationHandling(); } ~nsAutoAnimationMutationBatch() { Done(); } void Done(); static bool IsBatching() { return !!sCurrentBatch; } static nsAutoAnimationMutationBatch* GetCurrentBatch() { return sCurrentBatch; } static void AddObserver(nsDOMMutationObserver* aObserver) { if (sCurrentBatch->mObservers.Contains(aObserver)) { return; } sCurrentBatch->mObservers.AppendElement(aObserver); } static void AnimationAdded(mozilla::dom::Animation* aAnimation, nsINode* aTarget) { if (!IsBatching()) { return; } Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget); if (entry) { switch (entry->mState) { case eState_RemainedAbsent: entry->mState = eState_Added; break; case eState_Removed: entry->mState = eState_RemainedPresent; break; case eState_Added: // FIXME bug 1189015 NS_ERROR("shouldn't have observed an animation being added twice"); break; case eState_RemainedPresent: MOZ_ASSERT_UNREACHABLE( "shouldn't have observed an animation " "remaining present"); break; } } else { entry = sCurrentBatch->AddEntry(aAnimation, aTarget); entry->mState = eState_Added; entry->mChanged = false; } } static void AnimationChanged(mozilla::dom::Animation* aAnimation, nsINode* aTarget) { Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget); if (entry) { NS_ASSERTION(entry->mState == eState_RemainedPresent || entry->mState == eState_Added, "shouldn't have observed an animation being changed after " "being removed"); entry->mChanged = true; } else { entry = sCurrentBatch->AddEntry(aAnimation, aTarget); entry->mState = eState_RemainedPresent; entry->mChanged = true; } } static void AnimationRemoved(mozilla::dom::Animation* aAnimation, nsINode* aTarget) { Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget); if (entry) { switch (entry->mState) { case eState_RemainedPresent: entry->mState = eState_Removed; break; case eState_Added: entry->mState = eState_RemainedAbsent; break; case eState_RemainedAbsent: MOZ_ASSERT_UNREACHABLE( "shouldn't have observed an animation " "remaining absent"); break; case eState_Removed: // FIXME bug 1189015 NS_ERROR("shouldn't have observed an animation being removed twice"); break; } } else { entry = sCurrentBatch->AddEntry(aAnimation, aTarget); entry->mState = eState_Removed; entry->mChanged = false; } } private: Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) { EntryArray* entries = mEntryTable.Get(aTarget); if (!entries) { return nullptr; } for (Entry& e : *entries) { if (e.mAnimation == aAnimation) { return &e; } } return nullptr; } Entry* AddEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) { EntryArray* entries = sCurrentBatch->mEntryTable.GetOrInsertNew(aTarget); if (entries->IsEmpty()) { sCurrentBatch->mBatchTargets.AppendElement(aTarget); } Entry* entry = entries->AppendElement(); entry->mAnimation = aAnimation; return entry; } enum State { eState_RemainedPresent, eState_RemainedAbsent, eState_Added, eState_Removed }; struct Entry { RefPtr mAnimation; State mState; bool mChanged; }; static nsAutoAnimationMutationBatch* sCurrentBatch; AutoTArray mObservers; using EntryArray = nsTArray; nsClassHashtable, EntryArray> mEntryTable; // List of nodes referred to by mEntryTable so we can sort them // For a specific pseudo element, we use its parent element as the // batch target, so they will be put in the same EntryArray. nsTArray mBatchTargets; }; inline nsDOMMutationObserver* nsMutationReceiverBase::Observer() { return mParent ? mParent->Observer() : static_cast(mObserver); } class MOZ_RAII nsDOMMutationEnterLeave { public: explicit nsDOMMutationEnterLeave(mozilla::dom::Document* aDoc) : mNeeded(aDoc->MayHaveDOMMutationObservers()) { if (mNeeded) { nsDOMMutationObserver::EnterMutationHandling(); } } ~nsDOMMutationEnterLeave() { if (mNeeded) { nsDOMMutationObserver::LeaveMutationHandling(); } } private: const bool mNeeded; }; #endif