tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsDOMMutationObserver.h (27006B)


      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef nsDOMMutationObserver_h
      8 #define nsDOMMutationObserver_h
      9 
     10 #include <utility>
     11 
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/dom/Animation.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "mozilla/dom/MutationObserverBinding.h"
     16 #include "mozilla/dom/Nullable.h"
     17 #include "nsCOMArray.h"
     18 #include "nsClassHashtable.h"
     19 #include "nsContentList.h"
     20 #include "nsCycleCollectionParticipant.h"
     21 #include "nsGlobalWindowInner.h"
     22 #include "nsIAnimationObserver.h"
     23 #include "nsIMutationObserver.h"
     24 #include "nsPIDOMWindow.h"
     25 #include "nsStubAnimationObserver.h"
     26 #include "nsTArray.h"
     27 #include "nsWrapperCache.h"
     28 
     29 class nsIPrincipal;
     30 
     31 class nsDOMMutationObserver;
     32 using mozilla::dom::MutationObservingInfo;
     33 
     34 namespace mozilla::dom {
     35 class Element;
     36 }
     37 
     38 class nsDOMMutationRecord final : public nsISupports, public nsWrapperCache {
     39  virtual ~nsDOMMutationRecord() = default;
     40 
     41 public:
     42  using AnimationArray = nsTArray<RefPtr<mozilla::dom::Animation>>;
     43 
     44  nsDOMMutationRecord(nsAtom* aType, nsISupports* aOwner)
     45      : mType(aType),
     46        mAttrNamespace(VoidString()),
     47        mPrevValue(VoidString()),
     48        mOwner(aOwner) {}
     49 
     50  nsISupports* GetParentObject() const { return mOwner; }
     51 
     52  virtual JSObject* WrapObject(JSContext* aCx,
     53                               JS::Handle<JSObject*> aGivenProto) override {
     54    return mozilla::dom::MutationRecord_Binding::Wrap(aCx, this, aGivenProto);
     55  }
     56 
     57  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     58  NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMMutationRecord)
     59 
     60  void GetType(mozilla::dom::DOMString& aRetVal) const {
     61    aRetVal.SetKnownLiveAtom(mType, mozilla::dom::DOMString::eNullNotExpected);
     62  }
     63 
     64  nsINode* GetTarget() const { return mTarget; }
     65 
     66  nsINodeList* AddedNodes();
     67 
     68  nsINodeList* RemovedNodes();
     69 
     70  nsINode* GetPreviousSibling() const { return mPreviousSibling; }
     71 
     72  nsINode* GetNextSibling() const { return mNextSibling; }
     73 
     74  void GetAttributeName(mozilla::dom::DOMString& aRetVal) const {
     75    aRetVal.SetKnownLiveAtom(mAttrName,
     76                             mozilla::dom::DOMString::eTreatNullAsNull);
     77  }
     78 
     79  void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const {
     80    aRetVal.SetKnownLiveString(mAttrNamespace);
     81  }
     82 
     83  void GetOldValue(mozilla::dom::DOMString& aRetVal) const {
     84    aRetVal.SetKnownLiveString(mPrevValue);
     85  }
     86 
     87  void GetAddedAnimations(AnimationArray& aRetVal) const {
     88    aRetVal = mAddedAnimations.Clone();
     89  }
     90 
     91  void GetRemovedAnimations(AnimationArray& aRetVal) const {
     92    aRetVal = mRemovedAnimations.Clone();
     93  }
     94 
     95  void GetChangedAnimations(AnimationArray& aRetVal) const {
     96    aRetVal = mChangedAnimations.Clone();
     97  }
     98 
     99  nsCOMPtr<nsINode> mTarget;
    100  RefPtr<nsAtom> mType;
    101  RefPtr<nsAtom> mAttrName;
    102  nsString mAttrNamespace;
    103  nsString mPrevValue;
    104  RefPtr<nsSimpleContentList> mAddedNodes;
    105  RefPtr<nsSimpleContentList> mRemovedNodes;
    106  nsCOMPtr<nsINode> mPreviousSibling;
    107  nsCOMPtr<nsINode> mNextSibling;
    108  AnimationArray mAddedAnimations;
    109  AnimationArray mRemovedAnimations;
    110  AnimationArray mChangedAnimations;
    111 
    112  RefPtr<nsDOMMutationRecord> mNext;
    113  nsCOMPtr<nsISupports> mOwner;
    114 };
    115 
    116 // Base class just prevents direct access to
    117 // members to make sure we go through getters/setters.
    118 class nsMutationReceiverBase : public nsStubAnimationObserver {
    119 public:
    120  virtual ~nsMutationReceiverBase() = default;
    121 
    122  nsDOMMutationObserver* Observer();
    123  nsINode* Target() { return mParent ? mParent->Target() : mTarget; }
    124  nsINode* RegisterTarget() { return mRegisterTarget; }
    125 
    126  bool Subtree() { return mParent ? mParent->Subtree() : mSubtree; }
    127  void SetSubtree(bool aSubtree) {
    128    NS_ASSERTION(!mParent, "Shouldn't have parent");
    129    mSubtree = aSubtree;
    130  }
    131 
    132  bool ChildList() { return mParent ? mParent->ChildList() : mChildList; }
    133  void SetChildList(bool aChildList) {
    134    NS_ASSERTION(!mParent, "Shouldn't have parent");
    135    mChildList = aChildList;
    136  }
    137 
    138  bool CharacterData() {
    139    return mParent ? mParent->CharacterData() : mCharacterData;
    140  }
    141  void SetCharacterData(bool aCharacterData) {
    142    NS_ASSERTION(!mParent, "Shouldn't have parent");
    143    mCharacterData = aCharacterData;
    144  }
    145 
    146  bool CharacterDataOldValue() const {
    147    return mParent ? mParent->CharacterDataOldValue() : mCharacterDataOldValue;
    148  }
    149  void SetCharacterDataOldValue(bool aOldValue) {
    150    NS_ASSERTION(!mParent, "Shouldn't have parent");
    151    mCharacterDataOldValue = aOldValue;
    152  }
    153 
    154  bool Attributes() const {
    155    return mParent ? mParent->Attributes() : mAttributes;
    156  }
    157  void SetAttributes(bool aAttributes) {
    158    NS_ASSERTION(!mParent, "Shouldn't have parent");
    159    mAttributes = aAttributes;
    160  }
    161 
    162  bool AllAttributes() const {
    163    return mParent ? mParent->AllAttributes() : mAllAttributes;
    164  }
    165  void SetAllAttributes(bool aAll) {
    166    NS_ASSERTION(!mParent, "Shouldn't have parent");
    167    mAllAttributes = aAll;
    168  }
    169 
    170  bool Animations() const {
    171    return mParent ? mParent->Animations() : mAnimations;
    172  }
    173  void SetAnimations(bool aAnimations) {
    174    NS_ASSERTION(!mParent, "Shouldn't have parent");
    175    mAnimations = aAnimations;
    176  }
    177 
    178  bool AttributeOldValue() const {
    179    return mParent ? mParent->AttributeOldValue() : mAttributeOldValue;
    180  }
    181  void SetAttributeOldValue(bool aOldValue) {
    182    NS_ASSERTION(!mParent, "Shouldn't have parent");
    183    mAttributeOldValue = aOldValue;
    184  }
    185 
    186  bool ChromeOnlyNodes() const {
    187    return mParent ? mParent->ChromeOnlyNodes() : mChromeOnlyNodes;
    188  }
    189 
    190  void SetChromeOnlyNodes(bool aChromeOnlyNodes) {
    191    NS_ASSERTION(!mParent, "Shouldn't have parent");
    192    mChromeOnlyNodes = aChromeOnlyNodes;
    193  }
    194 
    195  nsTArray<RefPtr<nsAtom>>& AttributeFilter() { return mAttributeFilter; }
    196  void SetAttributeFilter(nsTArray<RefPtr<nsAtom>>&& aFilter) {
    197    NS_ASSERTION(!mParent, "Shouldn't have parent");
    198    mAttributeFilter.Clear();
    199    mAttributeFilter = std::move(aFilter);
    200  }
    201 
    202  void AddClone(nsMutationReceiverBase* aClone) {
    203    mTransientReceivers.AppendObject(aClone);
    204  }
    205 
    206  void RemoveClone(nsMutationReceiverBase* aClone) {
    207    mTransientReceivers.RemoveObject(aClone);
    208  }
    209 
    210 protected:
    211  nsMutationReceiverBase(nsINode* aTarget, nsDOMMutationObserver* aObserver)
    212      : mTarget(aTarget),
    213        mObserver(aObserver),
    214        mRegisterTarget(aTarget),
    215        mSubtree(false),
    216        mChildList(false),
    217        mCharacterData(false),
    218        mCharacterDataOldValue(false),
    219        mAttributes(false),
    220        mAllAttributes(false),
    221        mAttributeOldValue(false),
    222        mAnimations(false) {}
    223 
    224  nsMutationReceiverBase(nsINode* aRegisterTarget,
    225                         nsMutationReceiverBase* aParent)
    226      : mTarget(nullptr),
    227        mObserver(nullptr),
    228        mParent(aParent),
    229        mRegisterTarget(aRegisterTarget),
    230        mKungFuDeathGrip(aParent->Target()),
    231        mSubtree(false),
    232        mChildList(false),
    233        mCharacterData(false),
    234        mCharacterDataOldValue(false),
    235        mAttributes(false),
    236        mAllAttributes(false),
    237        mAttributeOldValue(false),
    238        mAnimations(false),
    239        mChromeOnlyNodes(false) {
    240    NS_ASSERTION(mParent->Subtree(), "Should clone a non-subtree observer!");
    241  }
    242 
    243  virtual void AddMutationObserver() = 0;
    244 
    245  void AddObserver() {
    246    AddMutationObserver();
    247    mRegisterTarget->SetMayHaveDOMMutationObserver();
    248    mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers();
    249  }
    250 
    251  bool IsObservable(nsIContent* aContent);
    252 
    253  bool ObservesAttr(nsINode* aRegisterTarget, mozilla::dom::Element* aElement,
    254                    int32_t aNameSpaceID, nsAtom* aAttr);
    255 
    256  // The target for the MutationObserver.observe() method.
    257  nsINode* mTarget;
    258  nsDOMMutationObserver* mObserver;
    259  RefPtr<nsMutationReceiverBase> mParent;  // Cleared after microtask.
    260  // The node to which Gecko-internal nsIMutationObserver was registered to.
    261  // This is different than mTarget when dealing with transient observers.
    262  nsINode* mRegisterTarget;
    263  nsCOMArray<nsMutationReceiverBase> mTransientReceivers;
    264  // While we have transient receivers, keep the original mutation receiver
    265  // alive so it doesn't go away and disconnect all its transient receivers.
    266  nsCOMPtr<nsINode> mKungFuDeathGrip;
    267 
    268 private:
    269  nsTArray<RefPtr<nsAtom>> mAttributeFilter;
    270  bool mSubtree : 1;
    271  bool mChildList : 1;
    272  bool mCharacterData : 1;
    273  bool mCharacterDataOldValue : 1;
    274  bool mAttributes : 1;
    275  bool mAllAttributes : 1;
    276  bool mAttributeOldValue : 1;
    277  bool mAnimations : 1;
    278  bool mChromeOnlyNodes : 1;
    279 };
    280 
    281 class nsMutationReceiver : public nsMutationReceiverBase {
    282 protected:
    283  virtual ~nsMutationReceiver() { Disconnect(false); }
    284 
    285 public:
    286  static nsMutationReceiver* Create(nsINode* aTarget,
    287                                    nsDOMMutationObserver* aObserver) {
    288    nsMutationReceiver* r = new nsMutationReceiver(aTarget, aObserver);
    289    r->AddObserver();
    290    return r;
    291  }
    292 
    293  static nsMutationReceiver* Create(nsINode* aRegisterTarget,
    294                                    nsMutationReceiverBase* aParent) {
    295    nsMutationReceiver* r = new nsMutationReceiver(aRegisterTarget, aParent);
    296    aParent->AddClone(r);
    297    r->AddObserver();
    298    return r;
    299  }
    300 
    301  nsMutationReceiver* GetParent() {
    302    return static_cast<nsMutationReceiver*>(mParent.get());
    303  }
    304 
    305  void RemoveClones() {
    306    for (int32_t i = 0; i < mTransientReceivers.Count(); ++i) {
    307      nsMutationReceiver* r =
    308          static_cast<nsMutationReceiver*>(mTransientReceivers[i]);
    309      r->DisconnectTransientReceiver();
    310    }
    311    mTransientReceivers.Clear();
    312  }
    313 
    314  void DisconnectTransientReceiver() {
    315    if (mRegisterTarget) {
    316      mRegisterTarget->RemoveMutationObserver(this);
    317      mRegisterTarget = nullptr;
    318    }
    319 
    320    mParent = nullptr;
    321    NS_ASSERTION(!mTarget, "Should not have mTarget");
    322    NS_ASSERTION(!mObserver, "Should not have mObserver");
    323  }
    324 
    325  void Disconnect(bool aRemoveFromObserver);
    326 
    327  NS_DECL_ISUPPORTS
    328 
    329  NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
    330  NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
    331  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
    332  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
    333  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
    334  NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
    335 
    336  virtual void AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
    337                                          int32_t aNameSpaceID,
    338                                          nsAtom* aAttribute) override {
    339    // We can reuse AttributeWillChange implementation.
    340    AttributeWillChange(aElement, aNameSpaceID, aAttribute,
    341                        AttrModType::Modification);
    342  }
    343 
    344 protected:
    345  nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver);
    346 
    347  nsMutationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
    348      : nsMutationReceiverBase(aRegisterTarget, aParent) {
    349    NS_ASSERTION(!static_cast<nsMutationReceiver*>(aParent)->GetParent(),
    350                 "Shouldn't create deep observer hierarchies!");
    351  }
    352 
    353  virtual void AddMutationObserver() override {
    354    mRegisterTarget->AddMutationObserver(this);
    355  }
    356 };
    357 
    358 class nsAnimationReceiver : public nsMutationReceiver {
    359 public:
    360  static nsAnimationReceiver* Create(nsINode* aTarget,
    361                                     nsDOMMutationObserver* aObserver) {
    362    nsAnimationReceiver* r = new nsAnimationReceiver(aTarget, aObserver);
    363    r->AddObserver();
    364    return r;
    365  }
    366 
    367  static nsAnimationReceiver* Create(nsINode* aRegisterTarget,
    368                                     nsMutationReceiverBase* aParent) {
    369    nsAnimationReceiver* r = new nsAnimationReceiver(aRegisterTarget, aParent);
    370    aParent->AddClone(r);
    371    r->AddObserver();
    372    return r;
    373  }
    374 
    375  NS_DECL_ISUPPORTS_INHERITED
    376 
    377  NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED
    378  NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED
    379  NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED
    380 
    381 protected:
    382  virtual ~nsAnimationReceiver() = default;
    383 
    384  nsAnimationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver)
    385      : nsMutationReceiver(aTarget, aObserver) {}
    386 
    387  nsAnimationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
    388      : nsMutationReceiver(aRegisterTarget, aParent) {}
    389 
    390  virtual void AddMutationObserver() override {
    391    mRegisterTarget->AddAnimationObserver(this);
    392  }
    393 
    394 private:
    395  enum AnimationMutation {
    396    eAnimationMutation_Added,
    397    eAnimationMutation_Changed,
    398    eAnimationMutation_Removed
    399  };
    400 
    401  void RecordAnimationMutation(mozilla::dom::Animation* aAnimation,
    402                               AnimationMutation aMutationType);
    403 };
    404 
    405 #define NS_DOM_MUTATION_OBSERVER_IID \
    406  {0x0c3b91f8, 0xcc3b, 0x4b08, {0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4}}
    407 
    408 class nsDOMMutationObserver final : public nsISupports, public nsWrapperCache {
    409 public:
    410  nsDOMMutationObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner,
    411                        mozilla::dom::MutationCallback& aCb)
    412      : mOwner(std::move(aOwner)),
    413        mLastPendingMutation(nullptr),
    414        mPendingMutationCount(0),
    415        mCallback(&aCb),
    416        mWaitingForRun(false),
    417        mMergeAttributeRecords(false),
    418        mId(++sCount) {}
    419  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    420  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
    421  NS_INLINE_DECL_STATIC_IID(NS_DOM_MUTATION_OBSERVER_IID)
    422 
    423  static already_AddRefed<nsDOMMutationObserver> Constructor(
    424      const mozilla::dom::GlobalObject&, mozilla::dom::MutationCallback&,
    425      mozilla::ErrorResult&);
    426 
    427  JSObject* WrapObject(JSContext* aCx,
    428                       JS::Handle<JSObject*> aGivenProto) override {
    429    return mozilla::dom::MutationObserver_Binding::Wrap(aCx, this, aGivenProto);
    430  }
    431 
    432  nsISupports* GetParentObject() const { return mOwner; }
    433 
    434  void Observe(nsINode& aTarget,
    435               const mozilla::dom::MutationObserverInit& aOptions,
    436               nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aRv);
    437 
    438  void Disconnect();
    439 
    440  void TakeRecords(nsTArray<RefPtr<nsDOMMutationRecord>>& aRetVal);
    441 
    442  MOZ_CAN_RUN_SCRIPT void HandleMutation();
    443 
    444  void GetObservingInfo(
    445      nsTArray<mozilla::dom::Nullable<MutationObservingInfo>>& aResult,
    446      mozilla::ErrorResult& aRv);
    447 
    448  mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }
    449 
    450  bool MergeAttributeRecords() { return mMergeAttributeRecords; }
    451 
    452  void SetMergeAttributeRecords(bool aVal) { mMergeAttributeRecords = aVal; }
    453 
    454  // If both records are for 'attributes' type and for the same target and
    455  // attribute name and namespace are the same, we can skip the newer record.
    456  // aOldRecord->mPrevValue holds the original value, if observed.
    457  bool MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord,
    458                                nsDOMMutationRecord* aRecord);
    459 
    460  void AppendMutationRecord(already_AddRefed<nsDOMMutationRecord> aRecord) {
    461    RefPtr<nsDOMMutationRecord> record = aRecord;
    462    MOZ_ASSERT(record);
    463    if (!mLastPendingMutation) {
    464      MOZ_ASSERT(!mFirstPendingMutation);
    465      mFirstPendingMutation = std::move(record);
    466      mLastPendingMutation = mFirstPendingMutation;
    467    } else {
    468      MOZ_ASSERT(mFirstPendingMutation);
    469      mLastPendingMutation->mNext = std::move(record);
    470      mLastPendingMutation = mLastPendingMutation->mNext;
    471    }
    472    ++mPendingMutationCount;
    473  }
    474 
    475  void ClearPendingRecords() {
    476    // Break down the pending mutation record list so that cycle collector
    477    // can delete the objects sooner.
    478    RefPtr<nsDOMMutationRecord> current = std::move(mFirstPendingMutation);
    479    mLastPendingMutation = nullptr;
    480    mPendingMutationCount = 0;
    481    while (current) {
    482      current = std::move(current->mNext);
    483    }
    484  }
    485 
    486  // static methods
    487  static void QueueMutationObserverMicroTask();
    488 
    489  MOZ_CAN_RUN_SCRIPT
    490  static void HandleMutations(mozilla::AutoSlowOperation& aAso);
    491 
    492  static bool AllScheduledMutationObserversAreSuppressed() {
    493    if (sScheduledMutationObservers) {
    494      uint32_t len = sScheduledMutationObservers->Length();
    495      if (len > 0) {
    496        for (uint32_t i = 0; i < len; ++i) {
    497          if (!(*sScheduledMutationObservers)[i]->Suppressed()) {
    498            return false;
    499          }
    500        }
    501        return true;
    502      }
    503    }
    504    return false;
    505  }
    506 
    507  static void EnterMutationHandling();
    508  static void LeaveMutationHandling();
    509 
    510  static void Shutdown();
    511 
    512 protected:
    513  virtual ~nsDOMMutationObserver();
    514 
    515  friend class nsMutationReceiver;
    516  friend class nsAnimationReceiver;
    517  friend class nsAutoMutationBatch;
    518  friend class nsAutoAnimationMutationBatch;
    519  nsMutationReceiver* GetReceiverFor(nsINode* aNode, bool aMayCreate,
    520                                     bool aWantsAnimations);
    521  void RemoveReceiver(nsMutationReceiver* aReceiver);
    522 
    523  void GetAllSubtreeObserversFor(nsINode* aNode,
    524                                 nsTArray<nsMutationReceiver*>& aObservers);
    525  void ScheduleForRun();
    526  void RescheduleForRun();
    527 
    528  nsDOMMutationRecord* CurrentRecord(nsAtom* aType);
    529  bool HasCurrentRecord(const nsAString& aType);
    530 
    531  bool Suppressed() {
    532    return mOwner && nsGlobalWindowInner::Cast(mOwner)->IsInSyncOperation();
    533  }
    534 
    535  MOZ_CAN_RUN_SCRIPT
    536  static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso);
    537 
    538  static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver,
    539                                           uint32_t aMutationLevel);
    540 
    541  nsCOMPtr<nsPIDOMWindowInner> mOwner;
    542 
    543  nsCOMArray<nsMutationReceiver> mReceivers;
    544  nsClassHashtable<nsISupportsHashKey, nsCOMArray<nsMutationReceiver>>
    545      mTransientReceivers;
    546  // MutationRecords which are being constructed.
    547  AutoTArray<nsDOMMutationRecord*, 4> mCurrentMutations;
    548  // MutationRecords which will be handed to the callback at the end of
    549  // the microtask.
    550  RefPtr<nsDOMMutationRecord> mFirstPendingMutation;
    551  nsDOMMutationRecord* mLastPendingMutation;
    552  uint32_t mPendingMutationCount;
    553 
    554  RefPtr<mozilla::dom::MutationCallback> mCallback;
    555 
    556  bool mWaitingForRun;
    557  bool mMergeAttributeRecords;
    558 
    559  uint64_t mId;
    560 
    561  static uint64_t sCount;
    562  static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
    563      sScheduledMutationObservers;
    564 
    565  static uint32_t sMutationLevel;
    566  static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>*
    567      sCurrentlyHandlingObservers;
    568 };
    569 
    570 class nsAutoMutationBatch {
    571 public:
    572  nsAutoMutationBatch()
    573      : mPreviousBatch(nullptr),
    574        mBatchTarget(nullptr),
    575        mRemovalDone(false),
    576        mFromFirstToLast(false),
    577        mAllowNestedBatches(false) {}
    578 
    579  nsAutoMutationBatch(nsINode* aTarget, bool aFromFirstToLast,
    580                      bool aAllowNestedBatches)
    581      : mPreviousBatch(nullptr),
    582        mBatchTarget(nullptr),
    583        mRemovalDone(false),
    584        mFromFirstToLast(false),
    585        mAllowNestedBatches(false) {
    586    Init(aTarget, aFromFirstToLast, aAllowNestedBatches);
    587  }
    588 
    589  void Init(nsINode* aTarget, bool aFromFirstToLast, bool aAllowNestedBatches) {
    590    if (aTarget && aTarget->OwnerDoc()->MayHaveDOMMutationObservers()) {
    591      if (sCurrentBatch && !sCurrentBatch->mAllowNestedBatches) {
    592        return;
    593      }
    594      mBatchTarget = aTarget;
    595      mFromFirstToLast = aFromFirstToLast;
    596      mAllowNestedBatches = aAllowNestedBatches;
    597      mPreviousBatch = sCurrentBatch;
    598      sCurrentBatch = this;
    599      nsDOMMutationObserver::EnterMutationHandling();
    600    }
    601  }
    602 
    603  void RemovalDone() { mRemovalDone = true; }
    604  static bool IsRemovalDone() { return sCurrentBatch->mRemovalDone; }
    605 
    606  void SetPrevSibling(nsINode* aNode) { mPrevSibling = aNode; }
    607  void SetNextSibling(nsINode* aNode) { mNextSibling = aNode; }
    608 
    609  void Done();
    610 
    611  ~nsAutoMutationBatch() { NodesAdded(); }
    612 
    613  static bool IsBatching() { return !!sCurrentBatch; }
    614 
    615  static nsAutoMutationBatch* GetCurrentBatch() { return sCurrentBatch; }
    616 
    617  static void UpdateObserver(nsDOMMutationObserver* aObserver,
    618                             bool aWantsChildList) {
    619    uint32_t l = sCurrentBatch->mObservers.Length();
    620    for (uint32_t i = 0; i < l; ++i) {
    621      if (sCurrentBatch->mObservers[i].mObserver == aObserver) {
    622        if (aWantsChildList) {
    623          sCurrentBatch->mObservers[i].mWantsChildList = aWantsChildList;
    624        }
    625        return;
    626      }
    627    }
    628    BatchObserver* bo = sCurrentBatch->mObservers.AppendElement();
    629    bo->mObserver = aObserver;
    630    bo->mWantsChildList = aWantsChildList;
    631  }
    632 
    633  static nsINode* GetBatchTarget() { return sCurrentBatch->mBatchTarget; }
    634 
    635  // Mutation receivers notify the batch about removed child nodes.
    636  static void NodeRemoved(nsIContent* aChild) {
    637    if (IsBatching() && !sCurrentBatch->mRemovalDone) {
    638      uint32_t len = sCurrentBatch->mRemovedNodes.Length();
    639      if (!len || sCurrentBatch->mRemovedNodes[len - 1] != aChild) {
    640        sCurrentBatch->mRemovedNodes.AppendElement(aChild);
    641      }
    642    }
    643  }
    644 
    645  // Called after new child nodes have been added to the batch target.
    646  void NodesAdded() {
    647    if (sCurrentBatch != this) {
    648      return;
    649    }
    650 
    651    nsIContent* c = mPrevSibling ? mPrevSibling->GetNextSibling()
    652                                 : mBatchTarget->GetFirstChild();
    653    for (; c != mNextSibling; c = c->GetNextSibling()) {
    654      mAddedNodes.AppendElement(c);
    655    }
    656    Done();
    657  }
    658 
    659 private:
    660  struct BatchObserver {
    661    nsDOMMutationObserver* mObserver;
    662    bool mWantsChildList;
    663  };
    664 
    665  static nsAutoMutationBatch* sCurrentBatch;
    666  nsAutoMutationBatch* mPreviousBatch;
    667  AutoTArray<BatchObserver, 2> mObservers;
    668  nsTArray<nsCOMPtr<nsIContent>> mRemovedNodes;
    669  nsTArray<nsCOMPtr<nsIContent>> mAddedNodes;
    670  nsINode* mBatchTarget;
    671  bool mRemovalDone;
    672  bool mFromFirstToLast;
    673  bool mAllowNestedBatches;
    674  nsCOMPtr<nsINode> mPrevSibling;
    675  nsCOMPtr<nsINode> mNextSibling;
    676 };
    677 
    678 class nsAutoAnimationMutationBatch {
    679  struct Entry;
    680 
    681 public:
    682  explicit nsAutoAnimationMutationBatch(mozilla::dom::Document* aDocument) {
    683    Init(aDocument);
    684  }
    685 
    686  void Init(mozilla::dom::Document* aDocument) {
    687    if (!aDocument || !aDocument->MayHaveDOMMutationObservers() ||
    688        sCurrentBatch) {
    689      return;
    690    }
    691 
    692    sCurrentBatch = this;
    693    nsDOMMutationObserver::EnterMutationHandling();
    694  }
    695 
    696  ~nsAutoAnimationMutationBatch() { Done(); }
    697 
    698  void Done();
    699 
    700  static bool IsBatching() { return !!sCurrentBatch; }
    701 
    702  static nsAutoAnimationMutationBatch* GetCurrentBatch() {
    703    return sCurrentBatch;
    704  }
    705 
    706  static void AddObserver(nsDOMMutationObserver* aObserver) {
    707    if (sCurrentBatch->mObservers.Contains(aObserver)) {
    708      return;
    709    }
    710    sCurrentBatch->mObservers.AppendElement(aObserver);
    711  }
    712 
    713  static void AnimationAdded(mozilla::dom::Animation* aAnimation,
    714                             nsINode* aTarget) {
    715    if (!IsBatching()) {
    716      return;
    717    }
    718 
    719    Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
    720    if (entry) {
    721      switch (entry->mState) {
    722        case eState_RemainedAbsent:
    723          entry->mState = eState_Added;
    724          break;
    725        case eState_Removed:
    726          entry->mState = eState_RemainedPresent;
    727          break;
    728        case eState_Added:
    729          // FIXME bug 1189015
    730          NS_ERROR("shouldn't have observed an animation being added twice");
    731          break;
    732        case eState_RemainedPresent:
    733          MOZ_ASSERT_UNREACHABLE(
    734              "shouldn't have observed an animation "
    735              "remaining present");
    736          break;
    737      }
    738    } else {
    739      entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
    740      entry->mState = eState_Added;
    741      entry->mChanged = false;
    742    }
    743  }
    744 
    745  static void AnimationChanged(mozilla::dom::Animation* aAnimation,
    746                               nsINode* aTarget) {
    747    Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
    748    if (entry) {
    749      NS_ASSERTION(entry->mState == eState_RemainedPresent ||
    750                       entry->mState == eState_Added,
    751                   "shouldn't have observed an animation being changed after "
    752                   "being removed");
    753      entry->mChanged = true;
    754    } else {
    755      entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
    756      entry->mState = eState_RemainedPresent;
    757      entry->mChanged = true;
    758    }
    759  }
    760 
    761  static void AnimationRemoved(mozilla::dom::Animation* aAnimation,
    762                               nsINode* aTarget) {
    763    Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
    764    if (entry) {
    765      switch (entry->mState) {
    766        case eState_RemainedPresent:
    767          entry->mState = eState_Removed;
    768          break;
    769        case eState_Added:
    770          entry->mState = eState_RemainedAbsent;
    771          break;
    772        case eState_RemainedAbsent:
    773          MOZ_ASSERT_UNREACHABLE(
    774              "shouldn't have observed an animation "
    775              "remaining absent");
    776          break;
    777        case eState_Removed:
    778          // FIXME bug 1189015
    779          NS_ERROR("shouldn't have observed an animation being removed twice");
    780          break;
    781      }
    782    } else {
    783      entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
    784      entry->mState = eState_Removed;
    785      entry->mChanged = false;
    786    }
    787  }
    788 
    789 private:
    790  Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
    791    EntryArray* entries = mEntryTable.Get(aTarget);
    792    if (!entries) {
    793      return nullptr;
    794    }
    795 
    796    for (Entry& e : *entries) {
    797      if (e.mAnimation == aAnimation) {
    798        return &e;
    799      }
    800    }
    801    return nullptr;
    802  }
    803 
    804  Entry* AddEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
    805    EntryArray* entries = sCurrentBatch->mEntryTable.GetOrInsertNew(aTarget);
    806    if (entries->IsEmpty()) {
    807      sCurrentBatch->mBatchTargets.AppendElement(aTarget);
    808    }
    809    Entry* entry = entries->AppendElement();
    810    entry->mAnimation = aAnimation;
    811    return entry;
    812  }
    813 
    814  enum State {
    815    eState_RemainedPresent,
    816    eState_RemainedAbsent,
    817    eState_Added,
    818    eState_Removed
    819  };
    820 
    821  struct Entry {
    822    RefPtr<mozilla::dom::Animation> mAnimation;
    823    State mState;
    824    bool mChanged;
    825  };
    826 
    827  static nsAutoAnimationMutationBatch* sCurrentBatch;
    828  AutoTArray<nsDOMMutationObserver*, 2> mObservers;
    829  using EntryArray = nsTArray<Entry>;
    830  nsClassHashtable<nsPtrHashKey<nsINode>, EntryArray> mEntryTable;
    831  // List of nodes referred to by mEntryTable so we can sort them
    832  // For a specific pseudo element, we use its parent element as the
    833  // batch target, so they will be put in the same EntryArray.
    834  nsTArray<nsINode*> mBatchTargets;
    835 };
    836 
    837 inline nsDOMMutationObserver* nsMutationReceiverBase::Observer() {
    838  return mParent ? mParent->Observer()
    839                 : static_cast<nsDOMMutationObserver*>(mObserver);
    840 }
    841 
    842 class MOZ_RAII nsDOMMutationEnterLeave {
    843 public:
    844  explicit nsDOMMutationEnterLeave(mozilla::dom::Document* aDoc)
    845      : mNeeded(aDoc->MayHaveDOMMutationObservers()) {
    846    if (mNeeded) {
    847      nsDOMMutationObserver::EnterMutationHandling();
    848    }
    849  }
    850  ~nsDOMMutationEnterLeave() {
    851    if (mNeeded) {
    852      nsDOMMutationObserver::LeaveMutationHandling();
    853    }
    854  }
    855 
    856 private:
    857  const bool mNeeded;
    858 };
    859 
    860 #endif