summaryrefslogtreecommitdiffstats
path: root/dom/base/nsDOMMutationObserver.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/base/nsDOMMutationObserver.h869
1 files changed, 869 insertions, 0 deletions
diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h
new file mode 100644
index 0000000000..3426cf4689
--- /dev/null
+++ b/dom/base/nsDOMMutationObserver.h
@@ -0,0 +1,869 @@
+/* -*- 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 <utility>
+
+#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<RefPtr<mozilla::dom::Animation>>;
+
+ 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<JSObject*> 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<nsINode> mTarget;
+ RefPtr<nsAtom> mType;
+ RefPtr<nsAtom> mAttrName;
+ nsString mAttrNamespace;
+ nsString mPrevValue;
+ RefPtr<nsSimpleContentList> mAddedNodes;
+ RefPtr<nsSimpleContentList> mRemovedNodes;
+ nsCOMPtr<nsINode> mPreviousSibling;
+ nsCOMPtr<nsINode> mNextSibling;
+ AnimationArray mAddedAnimations;
+ AnimationArray mRemovedAnimations;
+ AnimationArray mChangedAnimations;
+
+ RefPtr<nsDOMMutationRecord> mNext;
+ nsCOMPtr<nsISupports> 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<RefPtr<nsAtom>>& AttributeFilter() { return mAttributeFilter; }
+ void SetAttributeFilter(nsTArray<RefPtr<nsAtom>>&& 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<nsMutationReceiverBase> 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<nsMutationReceiverBase> 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<nsINode> mKungFuDeathGrip;
+
+ private:
+ nsTArray<RefPtr<nsAtom>> 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<nsMutationReceiver*>(mParent.get());
+ }
+
+ void RemoveClones() {
+ for (int32_t i = 0; i < mTransientReceivers.Count(); ++i) {
+ nsMutationReceiver* r =
+ static_cast<nsMutationReceiver*>(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<nsMutationReceiver*>(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<nsPIDOMWindowInner>&& 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<nsDOMMutationObserver> Constructor(
+ const mozilla::dom::GlobalObject&, mozilla::dom::MutationCallback&,
+ mozilla::ErrorResult&);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> 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<RefPtr<nsDOMMutationRecord>>& aRetVal);
+
+ MOZ_CAN_RUN_SCRIPT void HandleMutation();
+
+ void GetObservingInfo(
+ nsTArray<mozilla::dom::Nullable<MutationObservingInfo>>& 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<nsDOMMutationRecord> aRecord) {
+ RefPtr<nsDOMMutationRecord> 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<nsDOMMutationRecord> 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<nsMutationReceiver*>& 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<nsPIDOMWindowInner> mOwner;
+
+ nsCOMArray<nsMutationReceiver> mReceivers;
+ nsClassHashtable<nsISupportsHashKey, nsCOMArray<nsMutationReceiver>>
+ mTransientReceivers;
+ // MutationRecords which are being constructed.
+ AutoTArray<nsDOMMutationRecord*, 4> mCurrentMutations;
+ // MutationRecords which will be handed to the callback at the end of
+ // the microtask.
+ RefPtr<nsDOMMutationRecord> mFirstPendingMutation;
+ nsDOMMutationRecord* mLastPendingMutation;
+ uint32_t mPendingMutationCount;
+
+ RefPtr<mozilla::dom::MutationCallback> mCallback;
+
+ bool mWaitingForRun;
+ bool mMergeAttributeRecords;
+
+ uint64_t mId;
+
+ static uint64_t sCount;
+ static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
+ sScheduledMutationObservers;
+
+ static uint32_t sMutationLevel;
+ static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 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<BatchObserver, 2> mObservers;
+ nsTArray<nsCOMPtr<nsIContent>> mRemovedNodes;
+ nsTArray<nsCOMPtr<nsIContent>> mAddedNodes;
+ nsINode* mBatchTarget;
+ bool mRemovalDone;
+ bool mFromFirstToLast;
+ bool mAllowNestedBatches;
+ nsCOMPtr<nsINode> mPrevSibling;
+ nsCOMPtr<nsINode> 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<mozilla::dom::Animation> mAnimation;
+ State mState;
+ bool mChanged;
+ };
+
+ static nsAutoAnimationMutationBatch* sCurrentBatch;
+ AutoTArray<nsDOMMutationObserver*, 2> mObservers;
+ using EntryArray = nsTArray<Entry>;
+ nsClassHashtable<nsPtrHashKey<nsINode>, 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<nsINode*> mBatchTargets;
+};
+
+inline nsDOMMutationObserver* nsMutationReceiverBase::Observer() {
+ return mParent ? mParent->Observer()
+ : static_cast<nsDOMMutationObserver*>(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