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