Backed out changeset 1d9301697aa0 (bug 1887752) for causing failures on browser_all_f...
[gecko.git] / dom / base / nsDOMMutationObserver.h
blob92ab53d43a92b449a405c88893dfa33a6df23608
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/. */
7 #ifndef nsDOMMutationObserver_h
8 #define nsDOMMutationObserver_h
10 #include <utility>
12 #include "mozilla/Attributes.h"
13 #include "mozilla/dom/Animation.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/dom/MutationEventBinding.h"
16 #include "mozilla/dom/MutationObserverBinding.h"
17 #include "mozilla/dom/Nullable.h"
18 #include "nsCOMArray.h"
19 #include "nsClassHashtable.h"
20 #include "nsContentList.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsGlobalWindowInner.h"
23 #include "nsIAnimationObserver.h"
24 #include "nsPIDOMWindow.h"
25 #include "nsStubAnimationObserver.h"
26 #include "nsTArray.h"
27 #include "nsWrapperCache.h"
29 class nsIPrincipal;
31 class nsDOMMutationObserver;
32 using mozilla::dom::MutationObservingInfo;
34 namespace mozilla::dom {
35 class Element;
38 class nsDOMMutationRecord final : public nsISupports, public nsWrapperCache {
39 virtual ~nsDOMMutationRecord() = default;
41 public:
42 using AnimationArray = nsTArray<RefPtr<mozilla::dom::Animation>>;
44 nsDOMMutationRecord(nsAtom* aType, nsISupports* aOwner)
45 : mType(aType),
46 mAttrNamespace(VoidString()),
47 mPrevValue(VoidString()),
48 mOwner(aOwner) {}
50 nsISupports* GetParentObject() const { return mOwner; }
52 virtual JSObject* WrapObject(JSContext* aCx,
53 JS::Handle<JSObject*> aGivenProto) override {
54 return mozilla::dom::MutationRecord_Binding::Wrap(aCx, this, aGivenProto);
57 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
58 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMMutationRecord)
60 void GetType(mozilla::dom::DOMString& aRetVal) const {
61 aRetVal.SetKnownLiveAtom(mType, mozilla::dom::DOMString::eNullNotExpected);
64 nsINode* GetTarget() const { return mTarget; }
66 nsINodeList* AddedNodes();
68 nsINodeList* RemovedNodes();
70 nsINode* GetPreviousSibling() const { return mPreviousSibling; }
72 nsINode* GetNextSibling() const { return mNextSibling; }
74 void GetAttributeName(mozilla::dom::DOMString& aRetVal) const {
75 aRetVal.SetKnownLiveAtom(mAttrName,
76 mozilla::dom::DOMString::eTreatNullAsNull);
79 void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const {
80 aRetVal.SetKnownLiveString(mAttrNamespace);
83 void GetOldValue(mozilla::dom::DOMString& aRetVal) const {
84 aRetVal.SetKnownLiveString(mPrevValue);
87 void GetAddedAnimations(AnimationArray& aRetVal) const {
88 aRetVal = mAddedAnimations.Clone();
91 void GetRemovedAnimations(AnimationArray& aRetVal) const {
92 aRetVal = mRemovedAnimations.Clone();
95 void GetChangedAnimations(AnimationArray& aRetVal) const {
96 aRetVal = mChangedAnimations.Clone();
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;
112 RefPtr<nsDOMMutationRecord> mNext;
113 nsCOMPtr<nsISupports> mOwner;
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;
122 nsDOMMutationObserver* Observer();
123 nsINode* Target() { return mParent ? mParent->Target() : mTarget; }
124 nsINode* RegisterTarget() { return mRegisterTarget; }
126 bool Subtree() { return mParent ? mParent->Subtree() : mSubtree; }
127 void SetSubtree(bool aSubtree) {
128 NS_ASSERTION(!mParent, "Shouldn't have parent");
129 mSubtree = aSubtree;
132 bool ChildList() { return mParent ? mParent->ChildList() : mChildList; }
133 void SetChildList(bool aChildList) {
134 NS_ASSERTION(!mParent, "Shouldn't have parent");
135 mChildList = aChildList;
138 bool CharacterData() {
139 return mParent ? mParent->CharacterData() : mCharacterData;
141 void SetCharacterData(bool aCharacterData) {
142 NS_ASSERTION(!mParent, "Shouldn't have parent");
143 mCharacterData = aCharacterData;
146 bool CharacterDataOldValue() const {
147 return mParent ? mParent->CharacterDataOldValue() : mCharacterDataOldValue;
149 void SetCharacterDataOldValue(bool aOldValue) {
150 NS_ASSERTION(!mParent, "Shouldn't have parent");
151 mCharacterDataOldValue = aOldValue;
154 bool Attributes() const {
155 return mParent ? mParent->Attributes() : mAttributes;
157 void SetAttributes(bool aAttributes) {
158 NS_ASSERTION(!mParent, "Shouldn't have parent");
159 mAttributes = aAttributes;
162 bool AllAttributes() const {
163 return mParent ? mParent->AllAttributes() : mAllAttributes;
165 void SetAllAttributes(bool aAll) {
166 NS_ASSERTION(!mParent, "Shouldn't have parent");
167 mAllAttributes = aAll;
170 bool Animations() const {
171 return mParent ? mParent->Animations() : mAnimations;
173 void SetAnimations(bool aAnimations) {
174 NS_ASSERTION(!mParent, "Shouldn't have parent");
175 mAnimations = aAnimations;
178 bool AttributeOldValue() const {
179 return mParent ? mParent->AttributeOldValue() : mAttributeOldValue;
181 void SetAttributeOldValue(bool aOldValue) {
182 NS_ASSERTION(!mParent, "Shouldn't have parent");
183 mAttributeOldValue = aOldValue;
186 bool ChromeOnlyNodes() const {
187 return mParent ? mParent->ChromeOnlyNodes() : mChromeOnlyNodes;
190 void SetChromeOnlyNodes(bool aChromeOnlyNodes) {
191 NS_ASSERTION(!mParent, "Shouldn't have parent");
192 mChromeOnlyNodes = aChromeOnlyNodes;
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);
202 void AddClone(nsMutationReceiverBase* aClone) {
203 mTransientReceivers.AppendObject(aClone);
206 void RemoveClone(nsMutationReceiverBase* aClone) {
207 mTransientReceivers.RemoveObject(aClone);
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) {}
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!");
243 virtual void AddMutationObserver() = 0;
245 void AddObserver() {
246 AddMutationObserver();
247 mRegisterTarget->SetMayHaveDOMMutationObserver();
248 mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers();
251 bool IsObservable(nsIContent* aContent);
253 bool ObservesAttr(nsINode* aRegisterTarget, mozilla::dom::Element* aElement,
254 int32_t aNameSpaceID, nsAtom* aAttr);
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;
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;
281 class nsMutationReceiver : public nsMutationReceiverBase {
282 protected:
283 virtual ~nsMutationReceiver() { Disconnect(false); }
285 public:
286 static nsMutationReceiver* Create(nsINode* aTarget,
287 nsDOMMutationObserver* aObserver) {
288 nsMutationReceiver* r = new nsMutationReceiver(aTarget, aObserver);
289 r->AddObserver();
290 return r;
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;
301 nsMutationReceiver* GetParent() {
302 return static_cast<nsMutationReceiver*>(mParent.get());
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();
311 mTransientReceivers.Clear();
314 void DisconnectTransientReceiver() {
315 if (mRegisterTarget) {
316 mRegisterTarget->RemoveMutationObserver(this);
317 mRegisterTarget = nullptr;
320 mParent = nullptr;
321 NS_ASSERTION(!mTarget, "Should not have mTarget");
322 NS_ASSERTION(!mObserver, "Should not have mObserver");
325 void Disconnect(bool aRemoveFromObserver);
327 NS_DECL_ISUPPORTS
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
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 mozilla::dom::MutationEvent_Binding::MODIFICATION);
344 protected:
345 nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver);
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!");
353 virtual void AddMutationObserver() override {
354 mRegisterTarget->AddMutationObserver(this);
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;
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;
375 NS_DECL_ISUPPORTS_INHERITED
377 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED
378 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED
379 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED
381 protected:
382 virtual ~nsAnimationReceiver() = default;
384 nsAnimationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver)
385 : nsMutationReceiver(aTarget, aObserver) {}
387 nsAnimationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
388 : nsMutationReceiver(aRegisterTarget, aParent) {}
390 virtual void AddMutationObserver() override {
391 mRegisterTarget->AddAnimationObserver(this);
394 private:
395 enum AnimationMutation {
396 eAnimationMutation_Added,
397 eAnimationMutation_Changed,
398 eAnimationMutation_Removed
401 void RecordAnimationMutation(mozilla::dom::Animation* aAnimation,
402 AnimationMutation aMutationType);
405 #define NS_DOM_MUTATION_OBSERVER_IID \
407 0x0c3b91f8, 0xcc3b, 0x4b08, { \
408 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 \
412 class nsDOMMutationObserver final : public nsISupports, public nsWrapperCache {
413 public:
414 nsDOMMutationObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner,
415 mozilla::dom::MutationCallback& aCb)
416 : mOwner(std::move(aOwner)),
417 mLastPendingMutation(nullptr),
418 mPendingMutationCount(0),
419 mCallback(&aCb),
420 mWaitingForRun(false),
421 mMergeAttributeRecords(false),
422 mId(++sCount) {}
423 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
424 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
425 NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID)
427 static already_AddRefed<nsDOMMutationObserver> Constructor(
428 const mozilla::dom::GlobalObject&, mozilla::dom::MutationCallback&,
429 mozilla::ErrorResult&);
431 JSObject* WrapObject(JSContext* aCx,
432 JS::Handle<JSObject*> aGivenProto) override {
433 return mozilla::dom::MutationObserver_Binding::Wrap(aCx, this, aGivenProto);
436 nsISupports* GetParentObject() const { return mOwner; }
438 void Observe(nsINode& aTarget,
439 const mozilla::dom::MutationObserverInit& aOptions,
440 nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aRv);
442 void Disconnect();
444 void TakeRecords(nsTArray<RefPtr<nsDOMMutationRecord>>& aRetVal);
446 MOZ_CAN_RUN_SCRIPT void HandleMutation();
448 void GetObservingInfo(
449 nsTArray<mozilla::dom::Nullable<MutationObservingInfo>>& aResult,
450 mozilla::ErrorResult& aRv);
452 mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }
454 bool MergeAttributeRecords() { return mMergeAttributeRecords; }
456 void SetMergeAttributeRecords(bool aVal) { mMergeAttributeRecords = aVal; }
458 // If both records are for 'attributes' type and for the same target and
459 // attribute name and namespace are the same, we can skip the newer record.
460 // aOldRecord->mPrevValue holds the original value, if observed.
461 bool MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord,
462 nsDOMMutationRecord* aRecord);
464 void AppendMutationRecord(already_AddRefed<nsDOMMutationRecord> aRecord) {
465 RefPtr<nsDOMMutationRecord> record = aRecord;
466 MOZ_ASSERT(record);
467 if (!mLastPendingMutation) {
468 MOZ_ASSERT(!mFirstPendingMutation);
469 mFirstPendingMutation = std::move(record);
470 mLastPendingMutation = mFirstPendingMutation;
471 } else {
472 MOZ_ASSERT(mFirstPendingMutation);
473 mLastPendingMutation->mNext = std::move(record);
474 mLastPendingMutation = mLastPendingMutation->mNext;
476 ++mPendingMutationCount;
479 void ClearPendingRecords() {
480 // Break down the pending mutation record list so that cycle collector
481 // can delete the objects sooner.
482 RefPtr<nsDOMMutationRecord> current = std::move(mFirstPendingMutation);
483 mLastPendingMutation = nullptr;
484 mPendingMutationCount = 0;
485 while (current) {
486 current = std::move(current->mNext);
490 // static methods
491 static void QueueMutationObserverMicroTask();
493 MOZ_CAN_RUN_SCRIPT
494 static void HandleMutations(mozilla::AutoSlowOperation& aAso);
496 static bool AllScheduledMutationObserversAreSuppressed() {
497 if (sScheduledMutationObservers) {
498 uint32_t len = sScheduledMutationObservers->Length();
499 if (len > 0) {
500 for (uint32_t i = 0; i < len; ++i) {
501 if (!(*sScheduledMutationObservers)[i]->Suppressed()) {
502 return false;
505 return true;
508 return false;
511 static void EnterMutationHandling();
512 static void LeaveMutationHandling();
514 static void Shutdown();
516 protected:
517 virtual ~nsDOMMutationObserver();
519 friend class nsMutationReceiver;
520 friend class nsAnimationReceiver;
521 friend class nsAutoMutationBatch;
522 friend class nsAutoAnimationMutationBatch;
523 nsMutationReceiver* GetReceiverFor(nsINode* aNode, bool aMayCreate,
524 bool aWantsAnimations);
525 void RemoveReceiver(nsMutationReceiver* aReceiver);
527 void GetAllSubtreeObserversFor(nsINode* aNode,
528 nsTArray<nsMutationReceiver*>& aObservers);
529 void ScheduleForRun();
530 void RescheduleForRun();
532 nsDOMMutationRecord* CurrentRecord(nsAtom* aType);
533 bool HasCurrentRecord(const nsAString& aType);
535 bool Suppressed() {
536 return mOwner && nsGlobalWindowInner::Cast(mOwner)->IsInSyncOperation();
539 MOZ_CAN_RUN_SCRIPT
540 static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso);
542 static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver,
543 uint32_t aMutationLevel);
545 nsCOMPtr<nsPIDOMWindowInner> mOwner;
547 nsCOMArray<nsMutationReceiver> mReceivers;
548 nsClassHashtable<nsISupportsHashKey, nsCOMArray<nsMutationReceiver>>
549 mTransientReceivers;
550 // MutationRecords which are being constructed.
551 AutoTArray<nsDOMMutationRecord*, 4> mCurrentMutations;
552 // MutationRecords which will be handed to the callback at the end of
553 // the microtask.
554 RefPtr<nsDOMMutationRecord> mFirstPendingMutation;
555 nsDOMMutationRecord* mLastPendingMutation;
556 uint32_t mPendingMutationCount;
558 RefPtr<mozilla::dom::MutationCallback> mCallback;
560 bool mWaitingForRun;
561 bool mMergeAttributeRecords;
563 uint64_t mId;
565 static uint64_t sCount;
566 static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
567 sScheduledMutationObservers;
569 static uint32_t sMutationLevel;
570 static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>*
571 sCurrentlyHandlingObservers;
574 NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver,
575 NS_DOM_MUTATION_OBSERVER_IID)
577 class nsAutoMutationBatch {
578 public:
579 nsAutoMutationBatch()
580 : mPreviousBatch(nullptr),
581 mBatchTarget(nullptr),
582 mRemovalDone(false),
583 mFromFirstToLast(false),
584 mAllowNestedBatches(false) {}
586 nsAutoMutationBatch(nsINode* aTarget, bool aFromFirstToLast,
587 bool aAllowNestedBatches)
588 : mPreviousBatch(nullptr),
589 mBatchTarget(nullptr),
590 mRemovalDone(false),
591 mFromFirstToLast(false),
592 mAllowNestedBatches(false) {
593 Init(aTarget, aFromFirstToLast, aAllowNestedBatches);
596 void Init(nsINode* aTarget, bool aFromFirstToLast, bool aAllowNestedBatches) {
597 if (aTarget && aTarget->OwnerDoc()->MayHaveDOMMutationObservers()) {
598 if (sCurrentBatch && !sCurrentBatch->mAllowNestedBatches) {
599 return;
601 mBatchTarget = aTarget;
602 mFromFirstToLast = aFromFirstToLast;
603 mAllowNestedBatches = aAllowNestedBatches;
604 mPreviousBatch = sCurrentBatch;
605 sCurrentBatch = this;
606 nsDOMMutationObserver::EnterMutationHandling();
610 void RemovalDone() { mRemovalDone = true; }
611 static bool IsRemovalDone() { return sCurrentBatch->mRemovalDone; }
613 void SetPrevSibling(nsINode* aNode) { mPrevSibling = aNode; }
614 void SetNextSibling(nsINode* aNode) { mNextSibling = aNode; }
616 void Done();
618 ~nsAutoMutationBatch() { NodesAdded(); }
620 static bool IsBatching() { return !!sCurrentBatch; }
622 static nsAutoMutationBatch* GetCurrentBatch() { return sCurrentBatch; }
624 static void UpdateObserver(nsDOMMutationObserver* aObserver,
625 bool aWantsChildList) {
626 uint32_t l = sCurrentBatch->mObservers.Length();
627 for (uint32_t i = 0; i < l; ++i) {
628 if (sCurrentBatch->mObservers[i].mObserver == aObserver) {
629 if (aWantsChildList) {
630 sCurrentBatch->mObservers[i].mWantsChildList = aWantsChildList;
632 return;
635 BatchObserver* bo = sCurrentBatch->mObservers.AppendElement();
636 bo->mObserver = aObserver;
637 bo->mWantsChildList = aWantsChildList;
640 static nsINode* GetBatchTarget() { return sCurrentBatch->mBatchTarget; }
642 // Mutation receivers notify the batch about removed child nodes.
643 static void NodeRemoved(nsIContent* aChild) {
644 if (IsBatching() && !sCurrentBatch->mRemovalDone) {
645 uint32_t len = sCurrentBatch->mRemovedNodes.Length();
646 if (!len || sCurrentBatch->mRemovedNodes[len - 1] != aChild) {
647 sCurrentBatch->mRemovedNodes.AppendElement(aChild);
652 // Called after new child nodes have been added to the batch target.
653 void NodesAdded() {
654 if (sCurrentBatch != this) {
655 return;
658 nsIContent* c = mPrevSibling ? mPrevSibling->GetNextSibling()
659 : mBatchTarget->GetFirstChild();
660 for (; c != mNextSibling; c = c->GetNextSibling()) {
661 mAddedNodes.AppendElement(c);
663 Done();
666 private:
667 struct BatchObserver {
668 nsDOMMutationObserver* mObserver;
669 bool mWantsChildList;
672 static nsAutoMutationBatch* sCurrentBatch;
673 nsAutoMutationBatch* mPreviousBatch;
674 AutoTArray<BatchObserver, 2> mObservers;
675 nsTArray<nsCOMPtr<nsIContent>> mRemovedNodes;
676 nsTArray<nsCOMPtr<nsIContent>> mAddedNodes;
677 nsINode* mBatchTarget;
678 bool mRemovalDone;
679 bool mFromFirstToLast;
680 bool mAllowNestedBatches;
681 nsCOMPtr<nsINode> mPrevSibling;
682 nsCOMPtr<nsINode> mNextSibling;
685 class nsAutoAnimationMutationBatch {
686 struct Entry;
688 public:
689 explicit nsAutoAnimationMutationBatch(mozilla::dom::Document* aDocument) {
690 Init(aDocument);
693 void Init(mozilla::dom::Document* aDocument) {
694 if (!aDocument || !aDocument->MayHaveDOMMutationObservers() ||
695 sCurrentBatch) {
696 return;
699 sCurrentBatch = this;
700 nsDOMMutationObserver::EnterMutationHandling();
703 ~nsAutoAnimationMutationBatch() { Done(); }
705 void Done();
707 static bool IsBatching() { return !!sCurrentBatch; }
709 static nsAutoAnimationMutationBatch* GetCurrentBatch() {
710 return sCurrentBatch;
713 static void AddObserver(nsDOMMutationObserver* aObserver) {
714 if (sCurrentBatch->mObservers.Contains(aObserver)) {
715 return;
717 sCurrentBatch->mObservers.AppendElement(aObserver);
720 static void AnimationAdded(mozilla::dom::Animation* aAnimation,
721 nsINode* aTarget) {
722 if (!IsBatching()) {
723 return;
726 Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
727 if (entry) {
728 switch (entry->mState) {
729 case eState_RemainedAbsent:
730 entry->mState = eState_Added;
731 break;
732 case eState_Removed:
733 entry->mState = eState_RemainedPresent;
734 break;
735 case eState_Added:
736 // FIXME bug 1189015
737 NS_ERROR("shouldn't have observed an animation being added twice");
738 break;
739 case eState_RemainedPresent:
740 MOZ_ASSERT_UNREACHABLE(
741 "shouldn't have observed an animation "
742 "remaining present");
743 break;
745 } else {
746 entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
747 entry->mState = eState_Added;
748 entry->mChanged = false;
752 static void AnimationChanged(mozilla::dom::Animation* aAnimation,
753 nsINode* aTarget) {
754 Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
755 if (entry) {
756 NS_ASSERTION(entry->mState == eState_RemainedPresent ||
757 entry->mState == eState_Added,
758 "shouldn't have observed an animation being changed after "
759 "being removed");
760 entry->mChanged = true;
761 } else {
762 entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
763 entry->mState = eState_RemainedPresent;
764 entry->mChanged = true;
768 static void AnimationRemoved(mozilla::dom::Animation* aAnimation,
769 nsINode* aTarget) {
770 Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
771 if (entry) {
772 switch (entry->mState) {
773 case eState_RemainedPresent:
774 entry->mState = eState_Removed;
775 break;
776 case eState_Added:
777 entry->mState = eState_RemainedAbsent;
778 break;
779 case eState_RemainedAbsent:
780 MOZ_ASSERT_UNREACHABLE(
781 "shouldn't have observed an animation "
782 "remaining absent");
783 break;
784 case eState_Removed:
785 // FIXME bug 1189015
786 NS_ERROR("shouldn't have observed an animation being removed twice");
787 break;
789 } else {
790 entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
791 entry->mState = eState_Removed;
792 entry->mChanged = false;
796 private:
797 Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
798 EntryArray* entries = mEntryTable.Get(aTarget);
799 if (!entries) {
800 return nullptr;
803 for (Entry& e : *entries) {
804 if (e.mAnimation == aAnimation) {
805 return &e;
808 return nullptr;
811 Entry* AddEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
812 EntryArray* entries = sCurrentBatch->mEntryTable.GetOrInsertNew(aTarget);
813 if (entries->IsEmpty()) {
814 sCurrentBatch->mBatchTargets.AppendElement(aTarget);
816 Entry* entry = entries->AppendElement();
817 entry->mAnimation = aAnimation;
818 return entry;
821 enum State {
822 eState_RemainedPresent,
823 eState_RemainedAbsent,
824 eState_Added,
825 eState_Removed
828 struct Entry {
829 RefPtr<mozilla::dom::Animation> mAnimation;
830 State mState;
831 bool mChanged;
834 static nsAutoAnimationMutationBatch* sCurrentBatch;
835 AutoTArray<nsDOMMutationObserver*, 2> mObservers;
836 using EntryArray = nsTArray<Entry>;
837 nsClassHashtable<nsPtrHashKey<nsINode>, EntryArray> mEntryTable;
838 // List of nodes referred to by mEntryTable so we can sort them
839 // For a specific pseudo element, we use its parent element as the
840 // batch target, so they will be put in the same EntryArray.
841 nsTArray<nsINode*> mBatchTargets;
844 inline nsDOMMutationObserver* nsMutationReceiverBase::Observer() {
845 return mParent ? mParent->Observer()
846 : static_cast<nsDOMMutationObserver*>(mObserver);
849 class MOZ_RAII nsDOMMutationEnterLeave {
850 public:
851 explicit nsDOMMutationEnterLeave(mozilla::dom::Document* aDoc)
852 : mNeeded(aDoc->MayHaveDOMMutationObservers()) {
853 if (mNeeded) {
854 nsDOMMutationObserver::EnterMutationHandling();
857 ~nsDOMMutationEnterLeave() {
858 if (mNeeded) {
859 nsDOMMutationObserver::LeaveMutationHandling();
863 private:
864 const bool mNeeded;
867 #endif