Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / base / nsDOMMutationObserver.h
blob71b8ed54d459007d82ccc648a2cde0128c837f80
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 "nsIScriptContext.h"
25 #include "nsIVariant.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsStubAnimationObserver.h"
28 #include "nsTArray.h"
29 #include "nsWrapperCache.h"
31 class nsIPrincipal;
33 class nsDOMMutationObserver;
34 using mozilla::dom::MutationObservingInfo;
36 namespace mozilla::dom {
37 class Element;
40 class nsDOMMutationRecord final : public nsISupports, public nsWrapperCache {
41 virtual ~nsDOMMutationRecord() = default;
43 public:
44 using AnimationArray = nsTArray<RefPtr<mozilla::dom::Animation>>;
46 nsDOMMutationRecord(nsAtom* aType, nsISupports* aOwner)
47 : mType(aType),
48 mAttrNamespace(VoidString()),
49 mPrevValue(VoidString()),
50 mOwner(aOwner) {}
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;
105 nsString mPrevValue;
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 {
121 public:
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");
131 mSubtree = aSubtree;
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);
212 protected:
213 nsMutationReceiverBase(nsINode* aTarget, nsDOMMutationObserver* aObserver)
214 : mTarget(aTarget),
215 mObserver(aObserver),
216 mRegisterTarget(aTarget),
217 mSubtree(false),
218 mChildList(false),
219 mCharacterData(false),
220 mCharacterDataOldValue(false),
221 mAttributes(false),
222 mAllAttributes(false),
223 mAttributeOldValue(false),
224 mAnimations(false) {}
226 nsMutationReceiverBase(nsINode* aRegisterTarget,
227 nsMutationReceiverBase* aParent)
228 : mTarget(nullptr),
229 mObserver(nullptr),
230 mParent(aParent),
231 mRegisterTarget(aRegisterTarget),
232 mKungFuDeathGrip(aParent->Target()),
233 mSubtree(false),
234 mChildList(false),
235 mCharacterData(false),
236 mCharacterDataOldValue(false),
237 mAttributes(false),
238 mAllAttributes(false),
239 mAttributeOldValue(false),
240 mAnimations(false),
241 mChromeOnlyNodes(false) {
242 NS_ASSERTION(mParent->Subtree(), "Should clone a non-subtree observer!");
245 virtual void AddMutationObserver() = 0;
247 void AddObserver() {
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.
259 nsINode* mTarget;
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;
270 private:
271 nsTArray<RefPtr<nsAtom>> mAttributeFilter;
272 bool mSubtree : 1;
273 bool mChildList : 1;
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 {
284 protected:
285 virtual ~nsMutationReceiver() { Disconnect(false); }
287 public:
288 static nsMutationReceiver* Create(nsINode* aTarget,
289 nsDOMMutationObserver* aObserver) {
290 nsMutationReceiver* r = new nsMutationReceiver(aTarget, aObserver);
291 r->AddObserver();
292 return r;
295 static nsMutationReceiver* Create(nsINode* aRegisterTarget,
296 nsMutationReceiverBase* aParent) {
297 nsMutationReceiver* r = new nsMutationReceiver(aRegisterTarget, aParent);
298 aParent->AddClone(r);
299 r->AddObserver();
300 return 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;
322 mParent = nullptr;
323 NS_ASSERTION(!mTarget, "Should not have mTarget");
324 NS_ASSERTION(!mObserver, "Should not have mObserver");
327 void Disconnect(bool aRemoveFromObserver);
329 NS_DECL_ISUPPORTS
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);
346 protected:
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 {
361 public:
362 static nsAnimationReceiver* Create(nsINode* aTarget,
363 nsDOMMutationObserver* aObserver) {
364 nsAnimationReceiver* r = new nsAnimationReceiver(aTarget, aObserver);
365 r->AddObserver();
366 return r;
369 static nsAnimationReceiver* Create(nsINode* aRegisterTarget,
370 nsMutationReceiverBase* aParent) {
371 nsAnimationReceiver* r = new nsAnimationReceiver(aRegisterTarget, aParent);
372 aParent->AddClone(r);
373 r->AddObserver();
374 return r;
377 NS_DECL_ISUPPORTS_INHERITED
379 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED
380 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED
381 NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED
383 protected:
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);
396 private:
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 {
415 public:
416 nsDOMMutationObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner,
417 mozilla::dom::MutationCallback& aCb)
418 : mOwner(std::move(aOwner)),
419 mLastPendingMutation(nullptr),
420 mPendingMutationCount(0),
421 mCallback(&aCb),
422 mWaitingForRun(false),
423 mMergeAttributeRecords(false),
424 mId(++sCount) {}
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);
444 void Disconnect();
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;
468 MOZ_ASSERT(record);
469 if (!mLastPendingMutation) {
470 MOZ_ASSERT(!mFirstPendingMutation);
471 mFirstPendingMutation = std::move(record);
472 mLastPendingMutation = mFirstPendingMutation;
473 } else {
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;
487 while (current) {
488 current = std::move(current->mNext);
492 // static methods
493 static void QueueMutationObserverMicroTask();
495 MOZ_CAN_RUN_SCRIPT
496 static void HandleMutations(mozilla::AutoSlowOperation& aAso);
498 static bool AllScheduledMutationObserversAreSuppressed() {
499 if (sScheduledMutationObservers) {
500 uint32_t len = sScheduledMutationObservers->Length();
501 if (len > 0) {
502 for (uint32_t i = 0; i < len; ++i) {
503 if (!(*sScheduledMutationObservers)[i]->Suppressed()) {
504 return false;
507 return true;
510 return false;
513 static void EnterMutationHandling();
514 static void LeaveMutationHandling();
516 static void Shutdown();
518 protected:
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);
537 bool Suppressed() {
538 return mOwner && nsGlobalWindowInner::Cast(mOwner)->IsInSyncOperation();
541 MOZ_CAN_RUN_SCRIPT
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>>
551 mTransientReceivers;
552 // MutationRecords which are being constructed.
553 AutoTArray<nsDOMMutationRecord*, 4> mCurrentMutations;
554 // MutationRecords which will be handed to the callback at the end of
555 // the microtask.
556 RefPtr<nsDOMMutationRecord> mFirstPendingMutation;
557 nsDOMMutationRecord* mLastPendingMutation;
558 uint32_t mPendingMutationCount;
560 RefPtr<mozilla::dom::MutationCallback> mCallback;
562 bool mWaitingForRun;
563 bool mMergeAttributeRecords;
565 uint64_t mId;
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 {
580 public:
581 nsAutoMutationBatch()
582 : mPreviousBatch(nullptr),
583 mBatchTarget(nullptr),
584 mRemovalDone(false),
585 mFromFirstToLast(false),
586 mAllowNestedBatches(false) {}
588 nsAutoMutationBatch(nsINode* aTarget, bool aFromFirstToLast,
589 bool aAllowNestedBatches)
590 : mPreviousBatch(nullptr),
591 mBatchTarget(nullptr),
592 mRemovalDone(false),
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) {
601 return;
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; }
618 void Done();
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;
634 return;
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.
655 void NodesAdded() {
656 if (sCurrentBatch != this) {
657 return;
660 nsIContent* c = mPrevSibling ? mPrevSibling->GetNextSibling()
661 : mBatchTarget->GetFirstChild();
662 for (; c != mNextSibling; c = c->GetNextSibling()) {
663 mAddedNodes.AppendElement(c);
665 Done();
668 private:
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;
680 bool mRemovalDone;
681 bool mFromFirstToLast;
682 bool mAllowNestedBatches;
683 nsCOMPtr<nsINode> mPrevSibling;
684 nsCOMPtr<nsINode> mNextSibling;
687 class nsAutoAnimationMutationBatch {
688 struct Entry;
690 public:
691 explicit nsAutoAnimationMutationBatch(mozilla::dom::Document* aDocument) {
692 Init(aDocument);
695 void Init(mozilla::dom::Document* aDocument) {
696 if (!aDocument || !aDocument->MayHaveDOMMutationObservers() ||
697 sCurrentBatch) {
698 return;
701 sCurrentBatch = this;
702 nsDOMMutationObserver::EnterMutationHandling();
705 ~nsAutoAnimationMutationBatch() { Done(); }
707 void 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)) {
717 return;
719 sCurrentBatch->mObservers.AppendElement(aObserver);
722 static void AnimationAdded(mozilla::dom::Animation* aAnimation,
723 nsINode* aTarget) {
724 if (!IsBatching()) {
725 return;
728 Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
729 if (entry) {
730 switch (entry->mState) {
731 case eState_RemainedAbsent:
732 entry->mState = eState_Added;
733 break;
734 case eState_Removed:
735 entry->mState = eState_RemainedPresent;
736 break;
737 case eState_Added:
738 // FIXME bug 1189015
739 NS_ERROR("shouldn't have observed an animation being added twice");
740 break;
741 case eState_RemainedPresent:
742 MOZ_ASSERT_UNREACHABLE(
743 "shouldn't have observed an animation "
744 "remaining present");
745 break;
747 } else {
748 entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
749 entry->mState = eState_Added;
750 entry->mChanged = false;
754 static void AnimationChanged(mozilla::dom::Animation* aAnimation,
755 nsINode* aTarget) {
756 Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
757 if (entry) {
758 NS_ASSERTION(entry->mState == eState_RemainedPresent ||
759 entry->mState == eState_Added,
760 "shouldn't have observed an animation being changed after "
761 "being removed");
762 entry->mChanged = true;
763 } else {
764 entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
765 entry->mState = eState_RemainedPresent;
766 entry->mChanged = true;
770 static void AnimationRemoved(mozilla::dom::Animation* aAnimation,
771 nsINode* aTarget) {
772 Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
773 if (entry) {
774 switch (entry->mState) {
775 case eState_RemainedPresent:
776 entry->mState = eState_Removed;
777 break;
778 case eState_Added:
779 entry->mState = eState_RemainedAbsent;
780 break;
781 case eState_RemainedAbsent:
782 MOZ_ASSERT_UNREACHABLE(
783 "shouldn't have observed an animation "
784 "remaining absent");
785 break;
786 case eState_Removed:
787 // FIXME bug 1189015
788 NS_ERROR("shouldn't have observed an animation being removed twice");
789 break;
791 } else {
792 entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
793 entry->mState = eState_Removed;
794 entry->mChanged = false;
798 private:
799 Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
800 EntryArray* entries = mEntryTable.Get(aTarget);
801 if (!entries) {
802 return nullptr;
805 for (Entry& e : *entries) {
806 if (e.mAnimation == aAnimation) {
807 return &e;
810 return nullptr;
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;
820 return entry;
823 enum State {
824 eState_RemainedPresent,
825 eState_RemainedAbsent,
826 eState_Added,
827 eState_Removed
830 struct Entry {
831 RefPtr<mozilla::dom::Animation> mAnimation;
832 State mState;
833 bool mChanged;
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 {
852 public:
853 explicit nsDOMMutationEnterLeave(mozilla::dom::Document* aDoc)
854 : mNeeded(aDoc->MayHaveDOMMutationObservers()) {
855 if (mNeeded) {
856 nsDOMMutationObserver::EnterMutationHandling();
859 ~nsDOMMutationEnterLeave() {
860 if (mNeeded) {
861 nsDOMMutationObserver::LeaveMutationHandling();
865 private:
866 const bool mNeeded;
869 #endif