From 80bb4b4f786cfacd4b5c768d8e8f50f88be28e75 Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Fri, 19 May 2023 21:34:44 +0000 Subject: [PATCH] Bug 1834103 - Optimize out some nsIMutationObserver calls, r=emilio Differential Revision: https://phabricator.services.mozilla.com/D178558 --- dom/base/Document.h | 6 +++- dom/base/MutationObservers.cpp | 63 +++++++++++++++++++++------------ dom/base/MutationObservers.h | 6 ++-- dom/base/nsContentList.cpp | 7 ++++ dom/base/nsContentList.h | 10 ++++++ dom/base/nsIMutationObserver.h | 39 ++++++++++++++++++++ dom/base/nsRange.cpp | 2 +- dom/events/IMEContentObserver.h | 5 ++- 8 files changed, 110 insertions(+), 28 deletions(-) diff --git a/dom/base/Document.h b/dom/base/Document.h index 1c518ccf6d24..e0676cc1acda 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -574,7 +574,11 @@ class Document : public nsINode, #define NS_DOCUMENT_NOTIFY_OBSERVERS(func_, params_) \ do { \ - NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, func_, params_); \ + for (RefPtr obs : mObservers.ForwardRange()) { \ + if (obs->IsCallbackEnabled(nsIMutationObserver::k##func_)) { \ + obs->func_ params_; \ + } \ + } \ /* FIXME(emilio): Apparently we can keep observing from the BFCache? That \ looks bogus. */ \ if (PresShell* presShell = GetObservingPresShell()) { \ diff --git a/dom/base/MutationObservers.cpp b/dom/base/MutationObservers.cpp index cf2d48c14b4f..e3e7c8b02ef9 100644 --- a/dom/base/MutationObservers.cpp +++ b/dom/base/MutationObservers.cpp @@ -47,22 +47,25 @@ using namespace mozilla::dom; template static inline nsINode* ForEachAncestorObserver(nsINode* aNode, - NotifyObserver& aFunc) { + NotifyObserver& aFunc, + uint32_t aCallback) { nsINode* last; nsINode* node = aNode; do { mozilla::SafeDoublyLinkedList* observers = node->GetMutationObservers(); - if (observers && !observers->isEmpty()) { + if (observers) { for (auto iter = observers->begin(); iter != observers->end(); ++iter) { - aFunc(&*iter); + if (iter->IsCallbackEnabled(aCallback)) { + aFunc(&*iter); + } } } last = node; - if (ShadowRoot* shadow = ShadowRoot::FromNode(node)) { - node = shadow->GetHost(); - } else { - node = node->GetParentNode(); + if (!(node = node->GetParentNode())) { + if (ShadowRoot* shadow = ShadowRoot::FromNode(last)) { + node = shadow->GetHost(); + } } } while (node); return last; @@ -75,7 +78,8 @@ enum class NotifyPresShell { No, Before, After }; template -static inline void Notify(nsINode* aNode, NotifyObserver&& aNotify) { +static inline void Notify(nsINode* aNode, NotifyObserver&& aNotify, + uint32_t aCallback) { Document* doc = aNode->OwnerDoc(); nsDOMMutationEnterLeave enterLeave(doc); @@ -87,7 +91,7 @@ static inline void Notify(nsINode* aNode, NotifyObserver&& aNotify) { NOTIFY_PRESSHELL(aNotify); } } - nsINode* last = ForEachAncestorObserver(aNode, aNotify); + nsINode* last = ForEachAncestorObserver(aNode, aNotify, aCallback); // For non-removals, the pres shell gets notified last, since it needs to // operate on the "final" DOM shape. if constexpr (aNotifyPresShell == NotifyPresShell::After) { @@ -108,27 +112,31 @@ static inline void Notify(nsINode* aNode, NotifyObserver&& aNotify) { obs->func_ params_; \ } \ }; \ - ForEachAncestorObserver(content_, forEach); \ + ForEachAncestorObserver(content_, forEach, nsIMutationObserver::k##func_); \ PR_END_MACRO namespace mozilla { void MutationObservers::NotifyCharacterDataWillChange( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { - Notify(aContent, NOTIFIER(CharacterDataWillChange, aContent, aInfo)); + Notify(aContent, NOTIFIER(CharacterDataWillChange, aContent, aInfo), + nsIMutationObserver::kCharacterDataWillChange); } void MutationObservers::NotifyCharacterDataChanged( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { aContent->OwnerDoc()->Changed(); - Notify(aContent, NOTIFIER(CharacterDataChanged, aContent, aInfo)); + Notify(aContent, NOTIFIER(CharacterDataChanged, aContent, aInfo), + nsIMutationObserver::kCharacterDataChanged); } void MutationObservers::NotifyAttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { - Notify(aElement, NOTIFIER(AttributeWillChange, aElement, aNameSpaceID, - aAttribute, aModType)); + Notify(aElement, + NOTIFIER(AttributeWillChange, aElement, aNameSpaceID, aAttribute, + aModType), + nsIMutationObserver::kAttributeWillChange); } void MutationObservers::NotifyAttributeChanged(Element* aElement, @@ -137,21 +145,26 @@ void MutationObservers::NotifyAttributeChanged(Element* aElement, int32_t aModType, const nsAttrValue* aOldValue) { aElement->OwnerDoc()->Changed(); - Notify(aElement, NOTIFIER(AttributeChanged, aElement, aNameSpaceID, - aAttribute, aModType, aOldValue)); + Notify(aElement, + NOTIFIER(AttributeChanged, aElement, aNameSpaceID, aAttribute, + aModType, aOldValue), + nsIMutationObserver::kAttributeChanged); } void MutationObservers::NotifyAttributeSetToCurrentValue(Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute) { - Notify(aElement, NOTIFIER(AttributeSetToCurrentValue, aElement, aNameSpaceID, - aAttribute)); + Notify( + aElement, + NOTIFIER(AttributeSetToCurrentValue, aElement, aNameSpaceID, aAttribute), + nsIMutationObserver::kAttributeSetToCurrentValue); } void MutationObservers::NotifyContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent) { aContainer->OwnerDoc()->Changed(); - Notify(aContainer, NOTIFIER(ContentAppended, aFirstNewContent)); + Notify(aContainer, NOTIFIER(ContentAppended, aFirstNewContent), + nsIMutationObserver::kContentAppended); } void MutationObservers::NotifyContentInserted(nsINode* aContainer, @@ -159,7 +172,8 @@ void MutationObservers::NotifyContentInserted(nsINode* aContainer, MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(), "container must be an nsIContent or an Document"); aContainer->OwnerDoc()->Changed(); - Notify(aContainer, NOTIFIER(ContentInserted, aChild)); + Notify(aContainer, NOTIFIER(ContentInserted, aChild), + nsIMutationObserver::kContentInserted); } void MutationObservers::NotifyContentRemoved(nsINode* aContainer, @@ -171,21 +185,24 @@ void MutationObservers::NotifyContentRemoved(nsINode* aContainer, MOZ_ASSERT(aChild->GetParentNode() == aContainer, "We expect the parent link to be still around at this point"); Notify( - aContainer, NOTIFIER(ContentRemoved, aChild, aPreviousSibling)); + aContainer, NOTIFIER(ContentRemoved, aChild, aPreviousSibling), + nsIMutationObserver::kContentRemoved); } void MutationObservers::NotifyARIAAttributeDefaultWillChange( mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) { Notify( aElement, - NOTIFIER(ARIAAttributeDefaultWillChange, aElement, aAttribute, aModType)); + NOTIFIER(ARIAAttributeDefaultWillChange, aElement, aAttribute, aModType), + nsIMutationObserver::kARIAAttributeDefaultWillChange); } void MutationObservers::NotifyARIAAttributeDefaultChanged( mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) { Notify( aElement, - NOTIFIER(ARIAAttributeDefaultChanged, aElement, aAttribute, aModType)); + NOTIFIER(ARIAAttributeDefaultChanged, aElement, aAttribute, aModType), + nsIMutationObserver::kARIAAttributeDefaultChanged); } } // namespace mozilla diff --git a/dom/base/MutationObservers.h b/dom/base/MutationObservers.h index 119d5d1d425f..1ca86bdf4329 100644 --- a/dom/base/MutationObservers.h +++ b/dom/base/MutationObservers.h @@ -111,9 +111,11 @@ class MutationObservers { static inline void NotifyParentChainChanged(nsIContent* aContent) { mozilla::SafeDoublyLinkedList* observers = aContent->GetMutationObservers(); - if (observers && !observers->isEmpty()) { + if (observers) { for (auto iter = observers->begin(); iter != observers->end(); ++iter) { - iter->ParentChainChanged(aContent); + if (iter->IsCallbackEnabled(nsIMutationObserver::kParentChainChanged)) { + iter->ParentChainChanged(aContent); + } } } } diff --git a/dom/base/nsContentList.cpp b/dom/base/nsContentList.cpp index 7c296a4645e0..de8fb46d891f 100644 --- a/dom/base/nsContentList.cpp +++ b/dom/base/nsContentList.cpp @@ -398,6 +398,7 @@ nsContentList::nsContentList(nsINode* aRootNode, int32_t aMatchNameSpaceId, } // This is aLiveList instead of mIsLiveList to avoid Valgrind errors. if (aLiveList) { + SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed); mRootNode->AddMutationObserver(this); } @@ -434,6 +435,7 @@ nsContentList::nsContentList(nsINode* aRootNode, nsContentListMatchFunc aFunc, NS_ASSERTION(mRootNode, "Must have root"); // This is aLiveList instead of mIsLiveList to avoid Valgrind errors. if (aLiveList) { + SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed); mRootNode->AddMutationObserver(this); } @@ -723,6 +725,7 @@ void nsContentList::ContentAppended(nsIContent* aFirstNewContent) { !MayContainRelevantNodes(container) || (!aFirstNewContent->HasChildren() && !aFirstNewContent->GetNextSibling() && !MatchSelf(aFirstNewContent))) { + MaybeMarkDirty(); return; } @@ -933,6 +936,8 @@ void nsContentList::PopulateSelf(uint32_t aNeededLength, mState = State::Lazy; } + SetEnabledCallbacks(nsIMutationObserver::kAll); + ASSERT_IN_SYNC; } @@ -975,6 +980,8 @@ void nsContentList::BringSelfUpToDate(bool aDoFlush) { PopulateSelf(uint32_t(-1)); } + mMissedUpdates = 0; + ASSERT_IN_SYNC; NS_ASSERTION(!mRootNode || mState == State::UpToDate, "PopulateSelf dod not bring content list up to date!"); diff --git a/dom/base/nsContentList.h b/dom/base/nsContentList.h index 47241137c4a5..8e2bd606ada7 100644 --- a/dom/base/nsContentList.h +++ b/dom/base/nsContentList.h @@ -346,6 +346,7 @@ class nsContentList : public nsBaseContentList, mState = State::Dirty; InvalidateNamedItemsCache(); Reset(); + SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed); } void LastRelease() override; @@ -430,6 +431,13 @@ class nsContentList : public nsBaseContentList, */ void RemoveFromCaches() override { RemoveFromHashtable(); } + void MaybeMarkDirty() { + if (mState != State::Dirty && ++mMissedUpdates > 128) { + mMissedUpdates = 0; + SetDirty(); + } + } + nsINode* mRootNode; // Weak ref int32_t mMatchNameSpaceId; RefPtr mHTMLMatchAtom; @@ -451,6 +459,8 @@ class nsContentList : public nsBaseContentList, mozilla::UniquePtr mNamedItemsCache; + uint8_t mMissedUpdates = 0; + // The current state of the list. State mState; diff --git a/dom/base/nsIMutationObserver.h b/dom/base/nsIMutationObserver.h index de960681efd9..71abf05925fe 100644 --- a/dom/base/nsIMutationObserver.h +++ b/dom/base/nsIMutationObserver.h @@ -302,6 +302,45 @@ class nsIMutationObserver virtual void ARIAAttributeDefaultChanged(mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) = 0; + + enum : uint32_t { + kNone = 0, + kCharacterDataWillChange = 1 << 0, + kCharacterDataChanged = 1 << 1, + kAttributeWillChange = 1 << 2, + kAttributeChanged = 1 << 3, + kAttributeSetToCurrentValue = 1 << 4, + kContentAppended = 1 << 5, + kContentInserted = 1 << 6, + kContentRemoved = 1 << 7, + kNodeWillBeDestroyed = 1 << 8, + kParentChainChanged = 1 << 9, + kARIAAttributeDefaultWillChange = 1 << 10, + kARIAAttributeDefaultChanged = 1 << 11, + + kBeginUpdate = 1 << 12, + kEndUpdate = 1 << 13, + kBeginLoad = 1 << 14, + kEndLoad = 1 << 15, + kElementStateChanged = 1 << 16, + + kAnimationAdded = 1 << 17, + kAnimationChanged = 1 << 18, + kAnimationRemoved = 1 << 19, + + kAll = 0xFFFFFFFF + }; + + void SetEnabledCallbacks(uint32_t aCallbacks) { + mEnabledCallbacks = aCallbacks; + } + + bool IsCallbackEnabled(uint32_t aCallback) const { + return mEnabledCallbacks & aCallback; + } + + private: + uint32_t mEnabledCallbacks = kAll; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID) diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index 1acb51c7c9e0..b29af7925a2d 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -170,7 +170,7 @@ nsRange::nsRange(nsINode* aNode) mNextEndRef(nullptr) { // printf("Size of nsRange: %zu\n", sizeof(nsRange)); - static_assert(sizeof(nsRange) <= 216, + static_assert(sizeof(nsRange) <= 224, "nsRange size shouldn't be increased as far as possible"); } diff --git a/dom/events/IMEContentObserver.h b/dom/events/IMEContentObserver.h index 7de01b84adad..ab62e9ff92d7 100644 --- a/dom/events/IMEContentObserver.h +++ b/dom/events/IMEContentObserver.h @@ -395,7 +395,10 @@ class IMEContentObserver final : public nsStubMutationObserver, class DocumentObserver final : public nsStubDocumentObserver { public: explicit DocumentObserver(IMEContentObserver& aIMEContentObserver) - : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) {} + : mIMEContentObserver(&aIMEContentObserver), mDocumentUpdating(0) { + SetEnabledCallbacks(nsIMutationObserver::kBeginUpdate | + nsIMutationObserver::kEndUpdate); + } NS_DECL_CYCLE_COLLECTION_CLASS(DocumentObserver) NS_DECL_CYCLE_COLLECTING_ISUPPORTS