From e459a22bdc463df30c2864dac0611a335913f911 Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Tue, 21 Apr 2015 17:58:29 -0700 Subject: [PATCH] Bug 1013743, MutationObserver should observe only the subtree it is attached to, r=wchen --HG-- extra : rebase_source : c66bb5a6c5bc81163c084ddee732becd471db40e --- dom/base/nsDOMMutationObserver.cpp | 44 ++++++++++---- dom/base/nsDOMMutationObserver.h | 23 ++++++-- dom/base/test/test_mutationobservers.html | 72 ++++++++++++++++++++++- 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp index 19c4c953bcd3..7bd9bbf086f2 100644 --- a/dom/base/nsDOMMutationObserver.cpp +++ b/dom/base/nsDOMMutationObserver.cpp @@ -64,6 +64,13 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMMutationRecord, // Observer +bool +nsMutationReceiverBase::IsObservable(nsIContent* aContent) +{ + return !aContent->ChromeOnlyAccess() && + (Observer()->IsChrome() || !aContent->IsInAnonymousSubtree()); +} + NS_IMPL_ADDREF(nsMutationReceiver) NS_IMPL_RELEASE(nsMutationReceiver) @@ -111,8 +118,7 @@ nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument, int32_t aModType) { if (nsAutoMutationBatch::IsBatching() || - !ObservesAttr(aElement, aNameSpaceID, aAttribute) || - aElement->ChromeOnlyAccess()) { + !ObservesAttr(RegisterTarget(), aElement, aNameSpaceID, aAttribute)) { return; } @@ -147,14 +153,16 @@ nsMutationReceiver::CharacterDataWillChange(nsIDocument *aDocument, CharacterDataChangeInfo* aInfo) { if (nsAutoMutationBatch::IsBatching() || - !CharacterData() || !(Subtree() || aContent == Target()) || - aContent->ChromeOnlyAccess()) { + !CharacterData() || + (!Subtree() && aContent != Target()) || + (Subtree() && RegisterTarget()->SubtreeRoot() != aContent->SubtreeRoot()) || + !IsObservable(aContent)) { return; } - + nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::characterData); - + NS_ASSERTION(!m->mTarget || m->mTarget == aContent, "Wrong target!"); @@ -173,8 +181,11 @@ nsMutationReceiver::ContentAppended(nsIDocument* aDocument, int32_t aNewIndexInContainer) { nsINode* parent = NODE_FROM(aContainer, aDocument); - bool wantsChildList = ChildList() && (Subtree() || parent == Target()); - if (!wantsChildList || aFirstNewContent->ChromeOnlyAccess()) { + bool wantsChildList = + ChildList() && + ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) || + parent == Target()); + if (!wantsChildList || !IsObservable(aFirstNewContent)) { return; } @@ -211,8 +222,11 @@ nsMutationReceiver::ContentInserted(nsIDocument* aDocument, int32_t aIndexInContainer) { nsINode* parent = NODE_FROM(aContainer, aDocument); - bool wantsChildList = ChildList() && (Subtree() || parent == Target()); - if (!wantsChildList || aChild->ChromeOnlyAccess()) { + bool wantsChildList = + ChildList() && + ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) || + parent == Target()); + if (!wantsChildList || !IsObservable(aChild)) { return; } @@ -243,11 +257,14 @@ nsMutationReceiver::ContentRemoved(nsIDocument* aDocument, int32_t aIndexInContainer, nsIContent* aPreviousSibling) { - if (aChild->ChromeOnlyAccess()) { + if (!IsObservable(aChild)) { return; } nsINode* parent = NODE_FROM(aContainer, aDocument); + if (Subtree() && parent->SubtreeRoot() != RegisterTarget()->SubtreeRoot()) { + return; + } if (nsAutoMutationBatch::IsBatching()) { if (nsAutoMutationBatch::IsRemovalDone()) { // This can happen for example if HTML parser parses to @@ -265,7 +282,7 @@ nsMutationReceiver::ContentRemoved(nsIDocument* aDocument, } return; - } + } if (Subtree()) { // Try to avoid creating transient observer if the node @@ -714,8 +731,9 @@ nsDOMMutationObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal, return nullptr; } MOZ_ASSERT(window->IsInnerWindow()); + bool isChrome = nsContentUtils::IsChromeDoc(window->GetExtantDoc()); nsRefPtr observer = - new nsDOMMutationObserver(window.forget(), aCb); + new nsDOMMutationObserver(window.forget(), aCb, isChrome); return observer.forget(); } diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h index 7917027fc5bd..fda8e03afb09 100644 --- a/dom/base/nsDOMMutationObserver.h +++ b/dom/base/nsDOMMutationObserver.h @@ -248,14 +248,20 @@ protected: mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers(); } - bool ObservesAttr(mozilla::dom::Element* aElement, + bool IsObservable(nsIContent* aContent); + + bool ObservesAttr(nsINode* aRegisterTarget, + mozilla::dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttr) { if (mParent) { - return mParent->ObservesAttr(aElement, aNameSpaceID, aAttr); + return mParent->ObservesAttr(aRegisterTarget, aElement, aNameSpaceID, aAttr); } - if (!Attributes() || (!Subtree() && aElement != Target())) { + if (!Attributes() || + (!Subtree() && aElement != Target()) || + (Subtree() && aRegisterTarget->SubtreeRoot() != aElement->SubtreeRoot()) || + !IsObservable(aElement)) { return false; } if (AllAttributes()) { @@ -447,9 +453,10 @@ class nsDOMMutationObserver final : public nsISupports, { public: nsDOMMutationObserver(already_AddRefed&& aOwner, - mozilla::dom::MutationCallback& aCb) + mozilla::dom::MutationCallback& aCb, + bool aChrome) : mOwner(aOwner), mLastPendingMutation(nullptr), mPendingMutationCount(0), - mCallback(&aCb), mWaitingForRun(false), mId(++sCount) + mCallback(&aCb), mWaitingForRun(false), mIsChrome(aChrome), mId(++sCount) { } NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -471,6 +478,11 @@ public: return mOwner; } + bool IsChrome() + { + return mIsChrome; + } + void Observe(nsINode& aTarget, const mozilla::dom::MutationObserverInit& aOptions, mozilla::ErrorResult& aRv); @@ -571,6 +583,7 @@ protected: nsRefPtr mCallback; bool mWaitingForRun; + bool mIsChrome; uint64_t mId; diff --git a/dom/base/test/test_mutationobservers.html b/dom/base/test/test_mutationobservers.html index 3f0647900943..076da19d8cee 100644 --- a/dom/base/test/test_mutationobservers.html +++ b/dom/base/test/test_mutationobservers.html @@ -20,6 +20,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=641821 /** Test for Bug 641821 **/ +SimpleTest.requestFlakyTimeout("requestFlakyTimeout is silly. (But make sure marquee has time to initialize itself.)"); + var div = document.createElement("div"); var M; @@ -580,7 +582,7 @@ function testExpandos() { var m2 = new M(function(records, observer) { is(observer.expandoProperty, true); observer.disconnect(); - then(testStyleCreate); + then(testOutsideShadowDOM); }); m2.expandoProperty = true; m2.observe(div, { attributes: true }); @@ -596,6 +598,74 @@ function testExpandos() { div.setAttribute("foo", "bar2"); } +function testOutsideShadowDOM() { + var m = new M(function(records, observer) { + is(records.length, 1); + is(records[0].type, "attributes", "Should have got attributes"); + observer.disconnect(); + then(testInsideShadowDOM); + }); + m.observe(div, { + attributes: true, + childList: true, + characterData: true, + subtree: true + }) + var sr = div.createShadowRoot(); + sr.innerHTML = "text"; + sr.firstChild.setAttribute("foo", "bar"); + sr.firstChild.firstChild.data = "text2"; + sr.firstChild.appendChild(document.createElement("div")); + div.setAttribute("foo", "bar"); +} + +function testInsideShadowDOM() { + var m = new M(function(records, observer) { + is(records.length, 4); + is(records[0].type, "childList"); + is(records[1].type, "attributes"); + is(records[2].type, "characterData"); + is(records[3].type, "childList"); + observer.disconnect(); + then(testMarquee); + }); + var sr = div.createShadowRoot(); + m.observe(sr, { + attributes: true, + childList: true, + characterData: true, + subtree: true + }); + + sr.innerHTML = "text"; + sr.firstChild.setAttribute("foo", "bar"); + sr.firstChild.firstChild.data = "text2"; + sr.firstChild.appendChild(document.createElement("div")); + div.setAttribute("foo", "bar2"); + +} + +function testMarquee() { + var m = new M(function(records, observer) { + is(records.length, 1); + is(records[0].type, "attributes"); + is(records[0].attributeName, "ok"); + is(records[0].oldValue, null); + observer.disconnect(); + then(testStyleCreate); + }); + var marquee = document.createElement("marquee"); + m.observe(marquee, { + attributes: true, + attributeOldValue: true, + childList: true, + characterData: true, + subtree: true + }); + document.body.appendChild(marquee); + setTimeout(function() {marquee.setAttribute("ok", "ok")}, 500); +} + function testStyleCreate() { m = new M(function(records, observer) { is(records.length, 1, "number of records");