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
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 "nsIScriptContext.h"
25 #include "nsIVariant.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsStubAnimationObserver.h"
29 #include "nsWrapperCache.h"
33 class nsDOMMutationObserver
;
34 using mozilla::dom::MutationObservingInfo
;
36 namespace mozilla::dom
{
40 class nsDOMMutationRecord final
: public nsISupports
, public nsWrapperCache
{
41 virtual ~nsDOMMutationRecord() = default;
44 using AnimationArray
= nsTArray
<RefPtr
<mozilla::dom::Animation
>>;
46 nsDOMMutationRecord(nsAtom
* aType
, nsISupports
* aOwner
)
48 mAttrNamespace(VoidString()),
49 mPrevValue(VoidString()),
52 nsISupports
* GetParentObject() const { return mOwner
; }
54 virtual JSObject
* WrapObject(JSContext
* aCx
,
55 JS::Handle
<JSObject
*> aGivenProto
) override
{
56 return mozilla::dom::MutationRecord_Binding::Wrap(aCx
, this, aGivenProto
);
59 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
60 NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsDOMMutationRecord
)
62 void GetType(mozilla::dom::DOMString
& aRetVal
) const {
63 aRetVal
.SetKnownLiveAtom(mType
, mozilla::dom::DOMString::eNullNotExpected
);
66 nsINode
* GetTarget() const { return mTarget
; }
68 nsINodeList
* AddedNodes();
70 nsINodeList
* RemovedNodes();
72 nsINode
* GetPreviousSibling() const { return mPreviousSibling
; }
74 nsINode
* GetNextSibling() const { return mNextSibling
; }
76 void GetAttributeName(mozilla::dom::DOMString
& aRetVal
) const {
77 aRetVal
.SetKnownLiveAtom(mAttrName
,
78 mozilla::dom::DOMString::eTreatNullAsNull
);
81 void GetAttributeNamespace(mozilla::dom::DOMString
& aRetVal
) const {
82 aRetVal
.SetKnownLiveString(mAttrNamespace
);
85 void GetOldValue(mozilla::dom::DOMString
& aRetVal
) const {
86 aRetVal
.SetKnownLiveString(mPrevValue
);
89 void GetAddedAnimations(AnimationArray
& aRetVal
) const {
90 aRetVal
= mAddedAnimations
.Clone();
93 void GetRemovedAnimations(AnimationArray
& aRetVal
) const {
94 aRetVal
= mRemovedAnimations
.Clone();
97 void GetChangedAnimations(AnimationArray
& aRetVal
) const {
98 aRetVal
= mChangedAnimations
.Clone();
101 nsCOMPtr
<nsINode
> mTarget
;
102 RefPtr
<nsAtom
> mType
;
103 RefPtr
<nsAtom
> mAttrName
;
104 nsString mAttrNamespace
;
106 RefPtr
<nsSimpleContentList
> mAddedNodes
;
107 RefPtr
<nsSimpleContentList
> mRemovedNodes
;
108 nsCOMPtr
<nsINode
> mPreviousSibling
;
109 nsCOMPtr
<nsINode
> mNextSibling
;
110 AnimationArray mAddedAnimations
;
111 AnimationArray mRemovedAnimations
;
112 AnimationArray mChangedAnimations
;
114 RefPtr
<nsDOMMutationRecord
> mNext
;
115 nsCOMPtr
<nsISupports
> mOwner
;
118 // Base class just prevents direct access to
119 // members to make sure we go through getters/setters.
120 class nsMutationReceiverBase
: public nsStubAnimationObserver
{
122 virtual ~nsMutationReceiverBase() = default;
124 nsDOMMutationObserver
* Observer();
125 nsINode
* Target() { return mParent
? mParent
->Target() : mTarget
; }
126 nsINode
* RegisterTarget() { return mRegisterTarget
; }
128 bool Subtree() { return mParent
? mParent
->Subtree() : mSubtree
; }
129 void SetSubtree(bool aSubtree
) {
130 NS_ASSERTION(!mParent
, "Shouldn't have parent");
134 bool ChildList() { return mParent
? mParent
->ChildList() : mChildList
; }
135 void SetChildList(bool aChildList
) {
136 NS_ASSERTION(!mParent
, "Shouldn't have parent");
137 mChildList
= aChildList
;
140 bool CharacterData() {
141 return mParent
? mParent
->CharacterData() : mCharacterData
;
143 void SetCharacterData(bool aCharacterData
) {
144 NS_ASSERTION(!mParent
, "Shouldn't have parent");
145 mCharacterData
= aCharacterData
;
148 bool CharacterDataOldValue() const {
149 return mParent
? mParent
->CharacterDataOldValue() : mCharacterDataOldValue
;
151 void SetCharacterDataOldValue(bool aOldValue
) {
152 NS_ASSERTION(!mParent
, "Shouldn't have parent");
153 mCharacterDataOldValue
= aOldValue
;
156 bool Attributes() const {
157 return mParent
? mParent
->Attributes() : mAttributes
;
159 void SetAttributes(bool aAttributes
) {
160 NS_ASSERTION(!mParent
, "Shouldn't have parent");
161 mAttributes
= aAttributes
;
164 bool AllAttributes() const {
165 return mParent
? mParent
->AllAttributes() : mAllAttributes
;
167 void SetAllAttributes(bool aAll
) {
168 NS_ASSERTION(!mParent
, "Shouldn't have parent");
169 mAllAttributes
= aAll
;
172 bool Animations() const {
173 return mParent
? mParent
->Animations() : mAnimations
;
175 void SetAnimations(bool aAnimations
) {
176 NS_ASSERTION(!mParent
, "Shouldn't have parent");
177 mAnimations
= aAnimations
;
180 bool AttributeOldValue() const {
181 return mParent
? mParent
->AttributeOldValue() : mAttributeOldValue
;
183 void SetAttributeOldValue(bool aOldValue
) {
184 NS_ASSERTION(!mParent
, "Shouldn't have parent");
185 mAttributeOldValue
= aOldValue
;
188 bool ChromeOnlyNodes() const {
189 return mParent
? mParent
->ChromeOnlyNodes() : mChromeOnlyNodes
;
192 void SetChromeOnlyNodes(bool aChromeOnlyNodes
) {
193 NS_ASSERTION(!mParent
, "Shouldn't have parent");
194 mChromeOnlyNodes
= aChromeOnlyNodes
;
197 nsTArray
<RefPtr
<nsAtom
>>& AttributeFilter() { return mAttributeFilter
; }
198 void SetAttributeFilter(nsTArray
<RefPtr
<nsAtom
>>&& aFilter
) {
199 NS_ASSERTION(!mParent
, "Shouldn't have parent");
200 mAttributeFilter
.Clear();
201 mAttributeFilter
= std::move(aFilter
);
204 void AddClone(nsMutationReceiverBase
* aClone
) {
205 mTransientReceivers
.AppendObject(aClone
);
208 void RemoveClone(nsMutationReceiverBase
* aClone
) {
209 mTransientReceivers
.RemoveObject(aClone
);
213 nsMutationReceiverBase(nsINode
* aTarget
, nsDOMMutationObserver
* aObserver
)
215 mObserver(aObserver
),
216 mRegisterTarget(aTarget
),
219 mCharacterData(false),
220 mCharacterDataOldValue(false),
222 mAllAttributes(false),
223 mAttributeOldValue(false),
224 mAnimations(false) {}
226 nsMutationReceiverBase(nsINode
* aRegisterTarget
,
227 nsMutationReceiverBase
* aParent
)
231 mRegisterTarget(aRegisterTarget
),
232 mKungFuDeathGrip(aParent
->Target()),
235 mCharacterData(false),
236 mCharacterDataOldValue(false),
238 mAllAttributes(false),
239 mAttributeOldValue(false),
241 mChromeOnlyNodes(false) {
242 NS_ASSERTION(mParent
->Subtree(), "Should clone a non-subtree observer!");
245 virtual void AddMutationObserver() = 0;
248 AddMutationObserver();
249 mRegisterTarget
->SetMayHaveDOMMutationObserver();
250 mRegisterTarget
->OwnerDoc()->SetMayHaveDOMMutationObservers();
253 bool IsObservable(nsIContent
* aContent
);
255 bool ObservesAttr(nsINode
* aRegisterTarget
, mozilla::dom::Element
* aElement
,
256 int32_t aNameSpaceID
, nsAtom
* aAttr
);
258 // The target for the MutationObserver.observe() method.
260 nsDOMMutationObserver
* mObserver
;
261 RefPtr
<nsMutationReceiverBase
> mParent
; // Cleared after microtask.
262 // The node to which Gecko-internal nsIMutationObserver was registered to.
263 // This is different than mTarget when dealing with transient observers.
264 nsINode
* mRegisterTarget
;
265 nsCOMArray
<nsMutationReceiverBase
> mTransientReceivers
;
266 // While we have transient receivers, keep the original mutation receiver
267 // alive so it doesn't go away and disconnect all its transient receivers.
268 nsCOMPtr
<nsINode
> mKungFuDeathGrip
;
271 nsTArray
<RefPtr
<nsAtom
>> mAttributeFilter
;
274 bool mCharacterData
: 1;
275 bool mCharacterDataOldValue
: 1;
276 bool mAttributes
: 1;
277 bool mAllAttributes
: 1;
278 bool mAttributeOldValue
: 1;
279 bool mAnimations
: 1;
280 bool mChromeOnlyNodes
: 1;
283 class nsMutationReceiver
: public nsMutationReceiverBase
{
285 virtual ~nsMutationReceiver() { Disconnect(false); }
288 static nsMutationReceiver
* Create(nsINode
* aTarget
,
289 nsDOMMutationObserver
* aObserver
) {
290 nsMutationReceiver
* r
= new nsMutationReceiver(aTarget
, aObserver
);
295 static nsMutationReceiver
* Create(nsINode
* aRegisterTarget
,
296 nsMutationReceiverBase
* aParent
) {
297 nsMutationReceiver
* r
= new nsMutationReceiver(aRegisterTarget
, aParent
);
298 aParent
->AddClone(r
);
303 nsMutationReceiver
* GetParent() {
304 return static_cast<nsMutationReceiver
*>(mParent
.get());
307 void RemoveClones() {
308 for (int32_t i
= 0; i
< mTransientReceivers
.Count(); ++i
) {
309 nsMutationReceiver
* r
=
310 static_cast<nsMutationReceiver
*>(mTransientReceivers
[i
]);
311 r
->DisconnectTransientReceiver();
313 mTransientReceivers
.Clear();
316 void DisconnectTransientReceiver() {
317 if (mRegisterTarget
) {
318 mRegisterTarget
->RemoveMutationObserver(this);
319 mRegisterTarget
= nullptr;
323 NS_ASSERTION(!mTarget
, "Should not have mTarget");
324 NS_ASSERTION(!mObserver
, "Should not have mObserver");
327 void Disconnect(bool aRemoveFromObserver
);
331 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
332 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
333 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
334 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
335 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
336 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
338 virtual void AttributeSetToCurrentValue(mozilla::dom::Element
* aElement
,
339 int32_t aNameSpaceID
,
340 nsAtom
* aAttribute
) override
{
341 // We can reuse AttributeWillChange implementation.
342 AttributeWillChange(aElement
, aNameSpaceID
, aAttribute
,
343 mozilla::dom::MutationEvent_Binding::MODIFICATION
);
347 nsMutationReceiver(nsINode
* aTarget
, nsDOMMutationObserver
* aObserver
);
349 nsMutationReceiver(nsINode
* aRegisterTarget
, nsMutationReceiverBase
* aParent
)
350 : nsMutationReceiverBase(aRegisterTarget
, aParent
) {
351 NS_ASSERTION(!static_cast<nsMutationReceiver
*>(aParent
)->GetParent(),
352 "Shouldn't create deep observer hierarchies!");
355 virtual void AddMutationObserver() override
{
356 mRegisterTarget
->AddMutationObserver(this);
360 class nsAnimationReceiver
: public nsMutationReceiver
{
362 static nsAnimationReceiver
* Create(nsINode
* aTarget
,
363 nsDOMMutationObserver
* aObserver
) {
364 nsAnimationReceiver
* r
= new nsAnimationReceiver(aTarget
, aObserver
);
369 static nsAnimationReceiver
* Create(nsINode
* aRegisterTarget
,
370 nsMutationReceiverBase
* aParent
) {
371 nsAnimationReceiver
* r
= new nsAnimationReceiver(aRegisterTarget
, aParent
);
372 aParent
->AddClone(r
);
377 NS_DECL_ISUPPORTS_INHERITED
379 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED
380 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED
381 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED
384 virtual ~nsAnimationReceiver() = default;
386 nsAnimationReceiver(nsINode
* aTarget
, nsDOMMutationObserver
* aObserver
)
387 : nsMutationReceiver(aTarget
, aObserver
) {}
389 nsAnimationReceiver(nsINode
* aRegisterTarget
, nsMutationReceiverBase
* aParent
)
390 : nsMutationReceiver(aRegisterTarget
, aParent
) {}
392 virtual void AddMutationObserver() override
{
393 mRegisterTarget
->AddAnimationObserver(this);
397 enum AnimationMutation
{
398 eAnimationMutation_Added
,
399 eAnimationMutation_Changed
,
400 eAnimationMutation_Removed
403 void RecordAnimationMutation(mozilla::dom::Animation
* aAnimation
,
404 AnimationMutation aMutationType
);
407 #define NS_DOM_MUTATION_OBSERVER_IID \
409 0x0c3b91f8, 0xcc3b, 0x4b08, { \
410 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 \
414 class nsDOMMutationObserver final
: public nsISupports
, public nsWrapperCache
{
416 nsDOMMutationObserver(nsCOMPtr
<nsPIDOMWindowInner
>&& aOwner
,
417 mozilla::dom::MutationCallback
& aCb
)
418 : mOwner(std::move(aOwner
)),
419 mLastPendingMutation(nullptr),
420 mPendingMutationCount(0),
422 mWaitingForRun(false),
423 mMergeAttributeRecords(false),
425 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
426 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver
)
427 NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID
)
429 static already_AddRefed
<nsDOMMutationObserver
> Constructor(
430 const mozilla::dom::GlobalObject
&, mozilla::dom::MutationCallback
&,
431 mozilla::ErrorResult
&);
433 JSObject
* WrapObject(JSContext
* aCx
,
434 JS::Handle
<JSObject
*> aGivenProto
) override
{
435 return mozilla::dom::MutationObserver_Binding::Wrap(aCx
, this, aGivenProto
);
438 nsISupports
* GetParentObject() const { return mOwner
; }
440 void Observe(nsINode
& aTarget
,
441 const mozilla::dom::MutationObserverInit
& aOptions
,
442 nsIPrincipal
& aSubjectPrincipal
, mozilla::ErrorResult
& aRv
);
446 void TakeRecords(nsTArray
<RefPtr
<nsDOMMutationRecord
>>& aRetVal
);
448 MOZ_CAN_RUN_SCRIPT
void HandleMutation();
450 void GetObservingInfo(
451 nsTArray
<mozilla::dom::Nullable
<MutationObservingInfo
>>& aResult
,
452 mozilla::ErrorResult
& aRv
);
454 mozilla::dom::MutationCallback
* MutationCallback() { return mCallback
; }
456 bool MergeAttributeRecords() { return mMergeAttributeRecords
; }
458 void SetMergeAttributeRecords(bool aVal
) { mMergeAttributeRecords
= aVal
; }
460 // If both records are for 'attributes' type and for the same target and
461 // attribute name and namespace are the same, we can skip the newer record.
462 // aOldRecord->mPrevValue holds the original value, if observed.
463 bool MergeableAttributeRecord(nsDOMMutationRecord
* aOldRecord
,
464 nsDOMMutationRecord
* aRecord
);
466 void AppendMutationRecord(already_AddRefed
<nsDOMMutationRecord
> aRecord
) {
467 RefPtr
<nsDOMMutationRecord
> record
= aRecord
;
469 if (!mLastPendingMutation
) {
470 MOZ_ASSERT(!mFirstPendingMutation
);
471 mFirstPendingMutation
= std::move(record
);
472 mLastPendingMutation
= mFirstPendingMutation
;
474 MOZ_ASSERT(mFirstPendingMutation
);
475 mLastPendingMutation
->mNext
= std::move(record
);
476 mLastPendingMutation
= mLastPendingMutation
->mNext
;
478 ++mPendingMutationCount
;
481 void ClearPendingRecords() {
482 // Break down the pending mutation record list so that cycle collector
483 // can delete the objects sooner.
484 RefPtr
<nsDOMMutationRecord
> current
= std::move(mFirstPendingMutation
);
485 mLastPendingMutation
= nullptr;
486 mPendingMutationCount
= 0;
488 current
= std::move(current
->mNext
);
493 static void QueueMutationObserverMicroTask();
496 static void HandleMutations(mozilla::AutoSlowOperation
& aAso
);
498 static bool AllScheduledMutationObserversAreSuppressed() {
499 if (sScheduledMutationObservers
) {
500 uint32_t len
= sScheduledMutationObservers
->Length();
502 for (uint32_t i
= 0; i
< len
; ++i
) {
503 if (!(*sScheduledMutationObservers
)[i
]->Suppressed()) {
513 static void EnterMutationHandling();
514 static void LeaveMutationHandling();
516 static void Shutdown();
519 virtual ~nsDOMMutationObserver();
521 friend class nsMutationReceiver
;
522 friend class nsAnimationReceiver
;
523 friend class nsAutoMutationBatch
;
524 friend class nsAutoAnimationMutationBatch
;
525 nsMutationReceiver
* GetReceiverFor(nsINode
* aNode
, bool aMayCreate
,
526 bool aWantsAnimations
);
527 void RemoveReceiver(nsMutationReceiver
* aReceiver
);
529 void GetAllSubtreeObserversFor(nsINode
* aNode
,
530 nsTArray
<nsMutationReceiver
*>& aObservers
);
531 void ScheduleForRun();
532 void RescheduleForRun();
534 nsDOMMutationRecord
* CurrentRecord(nsAtom
* aType
);
535 bool HasCurrentRecord(const nsAString
& aType
);
538 return mOwner
&& nsGlobalWindowInner::Cast(mOwner
)->IsInSyncOperation();
542 static void HandleMutationsInternal(mozilla::AutoSlowOperation
& aAso
);
544 static void AddCurrentlyHandlingObserver(nsDOMMutationObserver
* aObserver
,
545 uint32_t aMutationLevel
);
547 nsCOMPtr
<nsPIDOMWindowInner
> mOwner
;
549 nsCOMArray
<nsMutationReceiver
> mReceivers
;
550 nsClassHashtable
<nsISupportsHashKey
, nsCOMArray
<nsMutationReceiver
>>
552 // MutationRecords which are being constructed.
553 AutoTArray
<nsDOMMutationRecord
*, 4> mCurrentMutations
;
554 // MutationRecords which will be handed to the callback at the end of
556 RefPtr
<nsDOMMutationRecord
> mFirstPendingMutation
;
557 nsDOMMutationRecord
* mLastPendingMutation
;
558 uint32_t mPendingMutationCount
;
560 RefPtr
<mozilla::dom::MutationCallback
> mCallback
;
563 bool mMergeAttributeRecords
;
567 static uint64_t sCount
;
568 static AutoTArray
<RefPtr
<nsDOMMutationObserver
>, 4>*
569 sScheduledMutationObservers
;
571 static uint32_t sMutationLevel
;
572 static AutoTArray
<AutoTArray
<RefPtr
<nsDOMMutationObserver
>, 4>, 4>*
573 sCurrentlyHandlingObservers
;
576 NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver
,
577 NS_DOM_MUTATION_OBSERVER_IID
)
579 class nsAutoMutationBatch
{
581 nsAutoMutationBatch()
582 : mPreviousBatch(nullptr),
583 mBatchTarget(nullptr),
585 mFromFirstToLast(false),
586 mAllowNestedBatches(false) {}
588 nsAutoMutationBatch(nsINode
* aTarget
, bool aFromFirstToLast
,
589 bool aAllowNestedBatches
)
590 : mPreviousBatch(nullptr),
591 mBatchTarget(nullptr),
593 mFromFirstToLast(false),
594 mAllowNestedBatches(false) {
595 Init(aTarget
, aFromFirstToLast
, aAllowNestedBatches
);
598 void Init(nsINode
* aTarget
, bool aFromFirstToLast
, bool aAllowNestedBatches
) {
599 if (aTarget
&& aTarget
->OwnerDoc()->MayHaveDOMMutationObservers()) {
600 if (sCurrentBatch
&& !sCurrentBatch
->mAllowNestedBatches
) {
603 mBatchTarget
= aTarget
;
604 mFromFirstToLast
= aFromFirstToLast
;
605 mAllowNestedBatches
= aAllowNestedBatches
;
606 mPreviousBatch
= sCurrentBatch
;
607 sCurrentBatch
= this;
608 nsDOMMutationObserver::EnterMutationHandling();
612 void RemovalDone() { mRemovalDone
= true; }
613 static bool IsRemovalDone() { return sCurrentBatch
->mRemovalDone
; }
615 void SetPrevSibling(nsINode
* aNode
) { mPrevSibling
= aNode
; }
616 void SetNextSibling(nsINode
* aNode
) { mNextSibling
= aNode
; }
620 ~nsAutoMutationBatch() { NodesAdded(); }
622 static bool IsBatching() { return !!sCurrentBatch
; }
624 static nsAutoMutationBatch
* GetCurrentBatch() { return sCurrentBatch
; }
626 static void UpdateObserver(nsDOMMutationObserver
* aObserver
,
627 bool aWantsChildList
) {
628 uint32_t l
= sCurrentBatch
->mObservers
.Length();
629 for (uint32_t i
= 0; i
< l
; ++i
) {
630 if (sCurrentBatch
->mObservers
[i
].mObserver
== aObserver
) {
631 if (aWantsChildList
) {
632 sCurrentBatch
->mObservers
[i
].mWantsChildList
= aWantsChildList
;
637 BatchObserver
* bo
= sCurrentBatch
->mObservers
.AppendElement();
638 bo
->mObserver
= aObserver
;
639 bo
->mWantsChildList
= aWantsChildList
;
642 static nsINode
* GetBatchTarget() { return sCurrentBatch
->mBatchTarget
; }
644 // Mutation receivers notify the batch about removed child nodes.
645 static void NodeRemoved(nsIContent
* aChild
) {
646 if (IsBatching() && !sCurrentBatch
->mRemovalDone
) {
647 uint32_t len
= sCurrentBatch
->mRemovedNodes
.Length();
648 if (!len
|| sCurrentBatch
->mRemovedNodes
[len
- 1] != aChild
) {
649 sCurrentBatch
->mRemovedNodes
.AppendElement(aChild
);
654 // Called after new child nodes have been added to the batch target.
656 if (sCurrentBatch
!= this) {
660 nsIContent
* c
= mPrevSibling
? mPrevSibling
->GetNextSibling()
661 : mBatchTarget
->GetFirstChild();
662 for (; c
!= mNextSibling
; c
= c
->GetNextSibling()) {
663 mAddedNodes
.AppendElement(c
);
669 struct BatchObserver
{
670 nsDOMMutationObserver
* mObserver
;
671 bool mWantsChildList
;
674 static nsAutoMutationBatch
* sCurrentBatch
;
675 nsAutoMutationBatch
* mPreviousBatch
;
676 AutoTArray
<BatchObserver
, 2> mObservers
;
677 nsTArray
<nsCOMPtr
<nsIContent
>> mRemovedNodes
;
678 nsTArray
<nsCOMPtr
<nsIContent
>> mAddedNodes
;
679 nsINode
* mBatchTarget
;
681 bool mFromFirstToLast
;
682 bool mAllowNestedBatches
;
683 nsCOMPtr
<nsINode
> mPrevSibling
;
684 nsCOMPtr
<nsINode
> mNextSibling
;
687 class nsAutoAnimationMutationBatch
{
691 explicit nsAutoAnimationMutationBatch(mozilla::dom::Document
* aDocument
) {
695 void Init(mozilla::dom::Document
* aDocument
) {
696 if (!aDocument
|| !aDocument
->MayHaveDOMMutationObservers() ||
701 sCurrentBatch
= this;
702 nsDOMMutationObserver::EnterMutationHandling();
705 ~nsAutoAnimationMutationBatch() { Done(); }
709 static bool IsBatching() { return !!sCurrentBatch
; }
711 static nsAutoAnimationMutationBatch
* GetCurrentBatch() {
712 return sCurrentBatch
;
715 static void AddObserver(nsDOMMutationObserver
* aObserver
) {
716 if (sCurrentBatch
->mObservers
.Contains(aObserver
)) {
719 sCurrentBatch
->mObservers
.AppendElement(aObserver
);
722 static void AnimationAdded(mozilla::dom::Animation
* aAnimation
,
728 Entry
* entry
= sCurrentBatch
->FindEntry(aAnimation
, aTarget
);
730 switch (entry
->mState
) {
731 case eState_RemainedAbsent
:
732 entry
->mState
= eState_Added
;
735 entry
->mState
= eState_RemainedPresent
;
739 NS_ERROR("shouldn't have observed an animation being added twice");
741 case eState_RemainedPresent
:
742 MOZ_ASSERT_UNREACHABLE(
743 "shouldn't have observed an animation "
744 "remaining present");
748 entry
= sCurrentBatch
->AddEntry(aAnimation
, aTarget
);
749 entry
->mState
= eState_Added
;
750 entry
->mChanged
= false;
754 static void AnimationChanged(mozilla::dom::Animation
* aAnimation
,
756 Entry
* entry
= sCurrentBatch
->FindEntry(aAnimation
, aTarget
);
758 NS_ASSERTION(entry
->mState
== eState_RemainedPresent
||
759 entry
->mState
== eState_Added
,
760 "shouldn't have observed an animation being changed after "
762 entry
->mChanged
= true;
764 entry
= sCurrentBatch
->AddEntry(aAnimation
, aTarget
);
765 entry
->mState
= eState_RemainedPresent
;
766 entry
->mChanged
= true;
770 static void AnimationRemoved(mozilla::dom::Animation
* aAnimation
,
772 Entry
* entry
= sCurrentBatch
->FindEntry(aAnimation
, aTarget
);
774 switch (entry
->mState
) {
775 case eState_RemainedPresent
:
776 entry
->mState
= eState_Removed
;
779 entry
->mState
= eState_RemainedAbsent
;
781 case eState_RemainedAbsent
:
782 MOZ_ASSERT_UNREACHABLE(
783 "shouldn't have observed an animation "
788 NS_ERROR("shouldn't have observed an animation being removed twice");
792 entry
= sCurrentBatch
->AddEntry(aAnimation
, aTarget
);
793 entry
->mState
= eState_Removed
;
794 entry
->mChanged
= false;
799 Entry
* FindEntry(mozilla::dom::Animation
* aAnimation
, nsINode
* aTarget
) {
800 EntryArray
* entries
= mEntryTable
.Get(aTarget
);
805 for (Entry
& e
: *entries
) {
806 if (e
.mAnimation
== aAnimation
) {
813 Entry
* AddEntry(mozilla::dom::Animation
* aAnimation
, nsINode
* aTarget
) {
814 EntryArray
* entries
= sCurrentBatch
->mEntryTable
.GetOrInsertNew(aTarget
);
815 if (entries
->IsEmpty()) {
816 sCurrentBatch
->mBatchTargets
.AppendElement(aTarget
);
818 Entry
* entry
= entries
->AppendElement();
819 entry
->mAnimation
= aAnimation
;
824 eState_RemainedPresent
,
825 eState_RemainedAbsent
,
831 RefPtr
<mozilla::dom::Animation
> mAnimation
;
836 static nsAutoAnimationMutationBatch
* sCurrentBatch
;
837 AutoTArray
<nsDOMMutationObserver
*, 2> mObservers
;
838 using EntryArray
= nsTArray
<Entry
>;
839 nsClassHashtable
<nsPtrHashKey
<nsINode
>, EntryArray
> mEntryTable
;
840 // List of nodes referred to by mEntryTable so we can sort them
841 // For a specific pseudo element, we use its parent element as the
842 // batch target, so they will be put in the same EntryArray.
843 nsTArray
<nsINode
*> mBatchTargets
;
846 inline nsDOMMutationObserver
* nsMutationReceiverBase::Observer() {
847 return mParent
? mParent
->Observer()
848 : static_cast<nsDOMMutationObserver
*>(mObserver
);
851 class MOZ_RAII nsDOMMutationEnterLeave
{
853 explicit nsDOMMutationEnterLeave(mozilla::dom::Document
* aDoc
)
854 : mNeeded(aDoc
->MayHaveDOMMutationObservers()) {
856 nsDOMMutationObserver::EnterMutationHandling();
859 ~nsDOMMutationEnterLeave() {
861 nsDOMMutationObserver::LeaveMutationHandling();