Bug 1013743, MutationObserver should observe only the subtree it is attached to, r=wchen

--HG--
extra : rebase_source : c66bb5a6c5bc81163c084ddee732becd471db40e
This commit is contained in:
Olli Pettay 2015-04-21 17:58:29 -07:00
parent b90b61a958
commit e459a22bdc
3 changed files with 120 additions and 19 deletions

View File

@ -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<nsDOMMutationObserver> observer =
new nsDOMMutationObserver(window.forget(), aCb);
new nsDOMMutationObserver(window.forget(), aCb, isChrome);
return observer.forget();
}

View File

@ -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<nsPIDOMWindow>&& 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<mozilla::dom::MutationCallback> mCallback;
bool mWaitingForRun;
bool mIsChrome;
uint64_t mId;

View File

@ -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 = "<div" + ">text</" + "div>";
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 = "<div" + ">text</" + "div>";
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");