mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
c6e35a38ee
Adds a new chrome-only MutationObserverInit option called nativeAnonymousChildList that will cause a mutation to fire when a native anonymous root is bound or unbound
936 lines
33 KiB
HTML
936 lines
33 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<!--
|
|
https://bugzilla.mozilla.org/show_bug.cgi?id=641821
|
|
-->
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Test for Bug 641821</title>
|
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
|
</head>
|
|
<body onload="runTest()">
|
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641821">Mozilla Bug 641821</a>
|
|
<p id="display"></p>
|
|
<div id="content" style="display: none">
|
|
|
|
</div>
|
|
<pre id="test">
|
|
<script type="application/javascript">
|
|
|
|
/** 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;
|
|
if ("MozMutationObserver" in window) {
|
|
M = window.MozMutationObserver;
|
|
} else if ("WebKitMutationObserver" in window) {
|
|
M = window.WebKitMutationObserver;
|
|
} else {
|
|
M = window.MutationObserver;
|
|
}
|
|
|
|
function log(str) {
|
|
var d = document.createElement("div");
|
|
d.textContent = str;
|
|
if (str.indexOf("PASSED") >= 0) {
|
|
d.setAttribute("style", "color: green;");
|
|
} else {
|
|
d.setAttribute("style", "color: red;");
|
|
}
|
|
document.getElementById("log").appendChild(d);
|
|
}
|
|
|
|
// Some helper functions so that this test runs also outside mochitest.
|
|
if (!("ok" in window)) {
|
|
window.ok = function(val, str) {
|
|
log(str + (val ? " PASSED\n" : " FAILED\n"));
|
|
}
|
|
}
|
|
|
|
if (!("is" in window)) {
|
|
window.is = function(val, refVal, str) {
|
|
log(str + (val == refVal? " PASSED " : " FAILED ") +
|
|
(val != refVal ? "expected " + refVal + " got " + val + "\n" : "\n"));
|
|
}
|
|
}
|
|
|
|
if (!("isnot" in window)) {
|
|
window.isnot = function(val, refVal, str) {
|
|
log(str + (val != refVal? " PASSED " : " FAILED ") +
|
|
(val == refVal ? "Didn't expect " + refVal + "\n" : "\n"));
|
|
}
|
|
}
|
|
|
|
if (!("SimpleTest" in window)) {
|
|
window.SimpleTest =
|
|
{
|
|
finish: function() {
|
|
document.getElementById("log").appendChild(document.createTextNode("DONE"));
|
|
},
|
|
waitForExplicitFinish: function() {}
|
|
}
|
|
}
|
|
|
|
function then(thenFn) {
|
|
setTimeout(function() {
|
|
if (thenFn) {
|
|
setTimeout(thenFn, 0);
|
|
} else {
|
|
SimpleTest.finish();
|
|
}
|
|
}, 0);
|
|
}
|
|
|
|
var m;
|
|
var m2;
|
|
var m3;
|
|
var m4;
|
|
|
|
// Checks basic parameter validation and normal 'this' handling.
|
|
// Tests also basic attribute handling.
|
|
function runTest() {
|
|
m = new M(function(){});
|
|
ok(m, "MutationObserver supported");
|
|
|
|
var e = null;
|
|
try {
|
|
m.observe(document, {});
|
|
} catch (ex) {
|
|
e = ex;
|
|
}
|
|
ok(e, "Should have thrown an exception");
|
|
is(e.name, "TypeError", "Should have thrown TypeError");
|
|
|
|
e = null;
|
|
try {
|
|
m.observe(document, { childList: true, attributeOldValue: true });
|
|
} catch (ex) {
|
|
e = ex;
|
|
}
|
|
ok(!e, "Shouldn't have thrown an exception");
|
|
|
|
e = null;
|
|
try {
|
|
m.observe(document, { childList: true, attributeFilter: ["foo"] });
|
|
} catch (ex) {
|
|
e = ex;
|
|
}
|
|
ok(!e, "Shouldn't have thrown an exception");
|
|
|
|
e = null;
|
|
try {
|
|
m.observe(document, { childList: true, characterDataOldValue: true });
|
|
} catch (ex) {
|
|
e = ex;
|
|
}
|
|
ok(!e, "Shouldn't have thrown an exception");
|
|
|
|
e = null;
|
|
try {
|
|
m.observe(document);
|
|
} catch (ex) {
|
|
e = ex;
|
|
}
|
|
ok(e, "Should have thrown an exception");
|
|
|
|
m = new M(function(records, observer) {
|
|
is(observer, m, "2nd parameter should be the mutation observer");
|
|
is(observer, this, "2nd parameter should be 'this'");
|
|
is(records.length, 1, "Should have one record.");
|
|
is(records[0].type, "attributes", "Should have got attributes record");
|
|
is(records[0].target, div, "Should have got div as target");
|
|
is(records[0].attributeName, "foo", "Should have got record about foo attribute");
|
|
observer.disconnect();
|
|
then(testThisBind);
|
|
m = null;
|
|
});
|
|
m.observe(div, { attributes: true, attributeFilter: ["foo"] });
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true);
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter.length, 1)
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter[0], "foo")
|
|
div.setAttribute("foo", "bar");
|
|
}
|
|
|
|
// 'this' handling when fn.bind() is used.
|
|
function testThisBind() {
|
|
var child = div.appendChild(document.createElement("div"));
|
|
var gchild = child.appendChild(document.createElement("div"));
|
|
m = new M((function(records, observer) {
|
|
is(observer, m, "2nd parameter should be the mutation observer");
|
|
isnot(observer, this, "2nd parameter should be 'this'");
|
|
is(records.length, 3, "Should have one record.");
|
|
is(records[0].type, "attributes", "Should have got attributes record");
|
|
is(records[0].target, div, "Should have got div as target");
|
|
is(records[0].attributeName, "foo", "Should have got record about foo attribute");
|
|
is(records[0].oldValue, "bar", "oldValue should be bar");
|
|
is(records[1].type, "attributes", "Should have got attributes record");
|
|
is(records[1].target, div, "Should have got div as target");
|
|
is(records[1].attributeName, "foo", "Should have got record about foo attribute");
|
|
is(records[1].oldValue, "bar2", "oldValue should be bar2");
|
|
is(records[2].type, "attributes", "Should have got attributes record");
|
|
is(records[2].target, gchild, "Should have got div as target");
|
|
is(records[2].attributeName, "foo", "Should have got record about foo attribute");
|
|
is(records[2].oldValue, null, "oldValue should be bar2");
|
|
observer.disconnect();
|
|
then(testCharacterData);
|
|
m = null;
|
|
}).bind(window));
|
|
m.observe(div, { attributes: true, attributeOldValue: true, subtree: true });
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true)
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeOldValue, true)
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
|
|
div.setAttribute("foo", "bar2");
|
|
div.removeAttribute("foo");
|
|
div.removeChild(child);
|
|
child.removeChild(gchild);
|
|
div.appendChild(gchild);
|
|
div.removeChild(gchild);
|
|
gchild.setAttribute("foo", "bar");
|
|
}
|
|
|
|
function testCharacterData() {
|
|
m = new M(function(records, observer) {
|
|
is(records[0].type, "characterData", "Should have got characterData");
|
|
is(records[0].oldValue, null, "Shouldn't have got oldData");
|
|
observer.disconnect();
|
|
m = null;
|
|
});
|
|
m2 = new M(function(records, observer) {
|
|
is(records[0].type, "characterData", "Should have got characterData");
|
|
is(records[0].oldValue, "foo", "Should have got oldData");
|
|
observer.disconnect();
|
|
m2 = null;
|
|
});
|
|
m3 = new M(function(records, observer) {
|
|
ok(false, "This should not be called!");
|
|
observer.disconnect();
|
|
m3 = null;
|
|
});
|
|
m4 = new M(function(records, observer) {
|
|
is(records[0].oldValue, null, "Shouldn't have got oldData");
|
|
observer.disconnect();
|
|
m3.disconnect();
|
|
m3 = null;
|
|
then(testChildList);
|
|
m4 = null;
|
|
});
|
|
|
|
div.appendChild(document.createTextNode("foo"));
|
|
m.observe(div, { characterData: true, subtree: true });
|
|
m2.observe(div, { characterData: true, characterDataOldValue: true, subtree: true});
|
|
// If observing the same node twice, only the latter option should apply.
|
|
m3.observe(div, { characterData: true, subtree: true });
|
|
m3.observe(div, { characterData: true, subtree: false });
|
|
m4.observe(div.firstChild, { characterData: true, subtree: false });
|
|
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers().length, 3)
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].characterData, true)
|
|
is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].subtree, false)
|
|
|
|
div.firstChild.data = "bar";
|
|
}
|
|
|
|
function testChildList() {
|
|
var fc = div.firstChild;
|
|
m = new M(function(records, observer) {
|
|
is(records[0].type, "childList", "Should have got childList");
|
|
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
|
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
|
is(records[0].removedNodes[0], fc, "Should have removed a text node");
|
|
observer.disconnect();
|
|
then(testChildList2);
|
|
m = null;
|
|
});
|
|
m.observe(div, { childList: true});
|
|
div.removeChild(div.firstChild);
|
|
}
|
|
|
|
function testChildList2() {
|
|
div.innerHTML = "<span>1</span><span>2</span>";
|
|
m = new M(function(records, observer) {
|
|
is(records[0].type, "childList", "Should have got childList");
|
|
is(records[0].removedNodes.length, 2, "Should have got removedNodes");
|
|
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
|
observer.disconnect();
|
|
then(testChildList3);
|
|
m = null;
|
|
});
|
|
m.observe(div, { childList: true });
|
|
div.innerHTML = "<span><span>foo</span></span>";
|
|
}
|
|
|
|
function testChildList3() {
|
|
m = new M(function(records, observer) {
|
|
is(records[0].type, "childList", "Should have got childList");
|
|
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
|
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
|
observer.disconnect();
|
|
then(testChildList4);
|
|
m = null;
|
|
});
|
|
m.observe(div, { childList: true });
|
|
div.textContent = "hello";
|
|
}
|
|
|
|
function testChildList4() {
|
|
div.textContent = null;
|
|
var df = document.createDocumentFragment();
|
|
var t1 = df.appendChild(document.createTextNode("Hello "));
|
|
var t2 = df.appendChild(document.createTextNode("world!"));
|
|
var s1 = div.appendChild(document.createElement("span"));
|
|
s1.textContent = "foo";
|
|
var s2 = div.appendChild(document.createElement("span"));
|
|
function callback(records, observer) {
|
|
is(records.length, 3, "Should have got one record for removing nodes from document fragment and one record for adding them to div");
|
|
is(records[0].removedNodes.length, 2, "Should have got removedNodes");
|
|
is(records[0].removedNodes[0], t1, "Should be the 1st textnode");
|
|
is(records[0].removedNodes[1], t2, "Should be the 2nd textnode");
|
|
is(records[1].addedNodes.length, 2, "Should have got addedNodes");
|
|
is(records[1].addedNodes[0], t1, "Should be the 1st textnode");
|
|
is(records[1].addedNodes[1], t2, "Should be the 2nd textnode");
|
|
is(records[1].previousSibling, s1, "Should have previousSibling");
|
|
is(records[1].nextSibling, s2, "Should have nextSibling");
|
|
is(records[2].type, "characterData", "3rd record should be characterData");
|
|
is(records[2].target, t1, "target should be the textnode");
|
|
is(records[2].oldValue, "Hello ", "oldValue was 'Hello '");
|
|
observer.disconnect();
|
|
then(testChildList5);
|
|
m = null;
|
|
};
|
|
m = new M(callback);
|
|
m.observe(df, { childList: true, characterData: true, characterDataOldValue: true, subtree: true });
|
|
is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].childList, true)
|
|
is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterData, true)
|
|
is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterDataOldValue, true)
|
|
is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
|
|
ok(SpecialPowers.compare(SpecialPowers.wrap(df).getBoundMutationObservers()[0].mutationCallback, callback))
|
|
m.observe(div, { childList: true });
|
|
is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo().length, 2)
|
|
|
|
// Make sure transient observers aren't leaked.
|
|
var leakTest = new M(function(){});
|
|
leakTest.observe(div, { characterData: true, subtree: true });
|
|
|
|
div.insertBefore(df, s2);
|
|
s1.firstChild.data = "bar"; // This should *not* create a record.
|
|
t1.data = "Hello the whole "; // This should create a record.
|
|
}
|
|
|
|
function testChildList5() {
|
|
div.textContent = null;
|
|
var c1 = div.appendChild(document.createElement("div"));
|
|
var c2 = document.createElement("div");
|
|
var div2 = document.createElement("div");
|
|
var c3 = div2.appendChild(document.createElement("div"));
|
|
var c4 = document.createElement("div");
|
|
var c5 = document.createElement("div");
|
|
var df = document.createDocumentFragment();
|
|
var emptyDF = document.createDocumentFragment();
|
|
var dfc1 = df.appendChild(document.createElement("div"));
|
|
var dfc2 = df.appendChild(document.createElement("div"));
|
|
var dfc3 = df.appendChild(document.createElement("div"));
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 6 , "");
|
|
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
|
is(records[0].removedNodes[0], c1, "");
|
|
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
|
is(records[0].addedNodes[0], c2, "");
|
|
is(records[0].previousSibling, null, "");
|
|
is(records[0].nextSibling, null, "");
|
|
is(records[1].removedNodes.length, 1, "Should have got removedNodes");
|
|
is(records[1].removedNodes[0], c3, "");
|
|
is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
|
is(records[1].previousSibling, null, "");
|
|
is(records[1].nextSibling, null, "");
|
|
is(records[2].removedNodes.length, 1, "Should have got removedNodes");
|
|
is(records[2].removedNodes[0], c2, "");
|
|
is(records[2].addedNodes.length, 1, "Should have got addedNodes");
|
|
is(records[2].addedNodes[0], c3, "");
|
|
is(records[2].previousSibling, null, "");
|
|
is(records[2].nextSibling, null, "");
|
|
// Check document fragment handling
|
|
is(records[5].removedNodes.length, 1, "");
|
|
is(records[5].removedNodes[0], c4, "");
|
|
is(records[5].addedNodes.length, 3, "");
|
|
is(records[5].addedNodes[0], dfc1, "");
|
|
is(records[5].addedNodes[1], dfc2, "");
|
|
is(records[5].addedNodes[2], dfc3, "");
|
|
is(records[5].previousSibling, c3, "");
|
|
is(records[5].nextSibling, c5, "");
|
|
observer.disconnect();
|
|
then(testAdoptNode);
|
|
m = null;
|
|
});
|
|
m.observe(div, { childList: true, subtree: true });
|
|
m.observe(div2, { childList: true, subtree: true });
|
|
div.replaceChild(c2, c1);
|
|
div.replaceChild(c3, c2);
|
|
div.appendChild(c4);
|
|
div.appendChild(c5);
|
|
div.replaceChild(df, c4);
|
|
div.appendChild(emptyDF); // empty document shouldn't cause mutation records
|
|
}
|
|
|
|
function testAdoptNode() {
|
|
var d1 = document.implementation.createHTMLDocument(null);
|
|
var d2 = document.implementation.createHTMLDocument(null);
|
|
var addedNode;
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 3, "Should have 2 records");
|
|
is(records[0].target.ownerDocument, d1, "ownerDocument should be the initial document")
|
|
is(records[1].target.ownerDocument, d2, "ownerDocument should be the new document");
|
|
is(records[2].type, "attributes", "Should have got attribute mutation")
|
|
is(records[2].attributeName, "foo", "Should have got foo attribute mutation")
|
|
observer.disconnect();
|
|
then(testOuterHTML);
|
|
m = null;
|
|
});
|
|
m.observe(d1, { childList: true, subtree: true, attributes: true });
|
|
d2.body.appendChild(d1.body);
|
|
addedNode = d2.body.lastChild.appendChild(d2.createElement("div"));
|
|
addedNode.setAttribute("foo", "bar");
|
|
}
|
|
|
|
function testOuterHTML() {
|
|
var doc = document.implementation.createHTMLDocument(null);
|
|
var d1 = doc.body.appendChild(document.createElement("div"));
|
|
var d2 = doc.body.appendChild(document.createElement("div"));
|
|
var d3 = doc.body.appendChild(document.createElement("div"));
|
|
var d4 = doc.body.appendChild(document.createElement("div"));
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 4, "Should have 1 record");
|
|
is(records[0].removedNodes.length, 1, "Should have 1 removed nodes");
|
|
is(records[0].addedNodes.length, 2, "Should have 2 added nodes");
|
|
is(records[0].previousSibling, null, "");
|
|
is(records[0].nextSibling, d2, "");
|
|
is(records[1].removedNodes.length, 1, "Should have 1 removed nodes");
|
|
is(records[1].addedNodes.length, 2, "Should have 2 added nodes");
|
|
is(records[1].previousSibling, records[0].addedNodes[1], "");
|
|
is(records[1].nextSibling, d3, "");
|
|
is(records[2].removedNodes.length, 1, "Should have 1 removed nodes");
|
|
is(records[2].addedNodes.length, 2, "Should have 2 added nodes");
|
|
is(records[2].previousSibling, records[1].addedNodes[1], "");
|
|
is(records[2].nextSibling, d4, "");
|
|
is(records[3].removedNodes.length, 1, "Should have 1 removed nodes");
|
|
is(records[3].addedNodes.length, 0);
|
|
is(records[3].previousSibling, records[2].addedNodes[1], "");
|
|
is(records[3].nextSibling, null, "");
|
|
observer.disconnect();
|
|
then(testInsertAdjacentHTML);
|
|
m = null;
|
|
});
|
|
m.observe(doc, { childList: true, subtree: true });
|
|
d1.outerHTML = "<div>1</div><div>1</div>";
|
|
d2.outerHTML = "<div>2</div><div>2</div>";
|
|
d3.outerHTML = "<div>3</div><div>3</div>";
|
|
d4.outerHTML = "";
|
|
}
|
|
|
|
function testInsertAdjacentHTML() {
|
|
var doc = document.implementation.createHTMLDocument(null);
|
|
var d1 = doc.body.appendChild(document.createElement("div"));
|
|
var d2 = doc.body.appendChild(document.createElement("div"));
|
|
var d3 = doc.body.appendChild(document.createElement("div"));
|
|
var d4 = doc.body.appendChild(document.createElement("div"));
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 4, "");
|
|
is(records[0].target, doc.body, "");
|
|
is(records[0].previousSibling, null, "");
|
|
is(records[0].nextSibling, d1, "");
|
|
is(records[1].target, d2, "");
|
|
is(records[1].previousSibling, null, "");
|
|
is(records[1].nextSibling, null, "");
|
|
is(records[2].target, d3, "");
|
|
is(records[2].previousSibling, null, "");
|
|
is(records[2].nextSibling, null, "");
|
|
is(records[3].target, doc.body, "");
|
|
is(records[3].previousSibling, d4, "");
|
|
is(records[3].nextSibling, null, "");
|
|
observer.disconnect();
|
|
then(testSyncXHR);
|
|
m = null;
|
|
});
|
|
m.observe(doc, { childList: true, subtree: true });
|
|
d1.insertAdjacentHTML("beforebegin", "<div></div><div></div>");
|
|
d2.insertAdjacentHTML("afterbegin", "<div></div><div></div>");
|
|
d3.insertAdjacentHTML("beforeend", "<div></div><div></div>");
|
|
d4.insertAdjacentHTML("afterend", "<div></div><div></div>");
|
|
}
|
|
|
|
|
|
var callbackHandled = false;
|
|
|
|
function testSyncXHR() {
|
|
div.textContent = null;
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 1, "");
|
|
is(records[0].addedNodes.length, 1, "");
|
|
callbackHandled = true;
|
|
observer.disconnect();
|
|
m = null;
|
|
});
|
|
m.observe(div, { childList: true, subtree: true });
|
|
div.innerHTML = "<div>hello</div>";
|
|
var x = new XMLHttpRequest();
|
|
x.open("GET", window.location, false);
|
|
x.send();
|
|
ok(!callbackHandled, "Shouldn't have called the mutation callback!");
|
|
setTimeout(testSyncXHR2, 0);
|
|
}
|
|
|
|
function testSyncXHR2() {
|
|
ok(callbackHandled, "Should have called the mutation callback!");
|
|
then(testModalDialog);
|
|
}
|
|
|
|
function testModalDialog() {
|
|
var didHandleCallback = false;
|
|
div.innerHTML = "<span>1</span><span>2</span>";
|
|
m = new M(function(records, observer) {
|
|
is(records[0].type, "childList", "Should have got childList");
|
|
is(records[0].removedNodes.length, 2, "Should have got removedNodes");
|
|
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
|
observer.disconnect();
|
|
m = null;
|
|
didHandleCallback = true;
|
|
});
|
|
m.observe(div, { childList: true });
|
|
div.innerHTML = "<span><span>foo</span></span>";
|
|
try {
|
|
window.showModalDialog("mutationobserver_dialog.html");
|
|
ok(didHandleCallback, "Should have called the callback while showing modal dialog!");
|
|
} catch(e) {
|
|
todo(false, "showModalDialog not implemented on this platform");
|
|
}
|
|
then(testTakeRecords);
|
|
}
|
|
|
|
function testTakeRecords() {
|
|
var s = "<span>1</span><span>2</span>";
|
|
div.innerHTML = s;
|
|
var takenRecords;
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 3, "Should have got 3 records");
|
|
|
|
is(records[0].type, "attributes", "Should have got attributes");
|
|
is(records[0].attributeName, "foo", "");
|
|
is(records[0].attributeNamespace, null, "");
|
|
is(records[0].prevValue, null, "");
|
|
is(records[1].type, "childList", "Should have got childList");
|
|
is(records[1].removedNodes.length, 2, "Should have got removedNodes");
|
|
is(records[1].addedNodes.length, 2, "Should have got addedNodes");
|
|
is(records[2].type, "attributes", "Should have got attributes");
|
|
is(records[2].attributeName, "foo", "");
|
|
|
|
is(records.length, takenRecords.length, "Should have had similar mutations");
|
|
is(records[0].type, takenRecords[0].type, "Should have had similar mutations");
|
|
is(records[1].type, takenRecords[1].type, "Should have had similar mutations");
|
|
is(records[2].type, takenRecords[2].type, "Should have had similar mutations");
|
|
|
|
is(records[1].removedNodes.length, takenRecords[1].removedNodes.length, "Should have had similar mutations");
|
|
is(records[1].addedNodes.length, takenRecords[1].addedNodes.length, "Should have had similar mutations");
|
|
|
|
is(m.takeRecords().length, 0, "Shouldn't have any records");
|
|
observer.disconnect();
|
|
then(testMutationObserverAndEvents);
|
|
m = null;
|
|
});
|
|
m.observe(div, { childList: true, attributes: true });
|
|
div.setAttribute("foo", "bar");
|
|
div.innerHTML = s;
|
|
div.removeAttribute("foo");
|
|
takenRecords = m.takeRecords();
|
|
div.setAttribute("foo", "bar");
|
|
div.innerHTML = s;
|
|
div.removeAttribute("foo");
|
|
}
|
|
|
|
function testTakeRecords() {
|
|
function mutationListener(e) {
|
|
++mutationEventCount;
|
|
is(e.attrChange, MutationEvent.ADDITION, "unexpected change");
|
|
}
|
|
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 2, "Should have got 2 records");
|
|
is(records[0].type, "attributes", "Should have got attributes");
|
|
is(records[0].attributeName, "foo", "");
|
|
is(records[0].oldValue, null, "");
|
|
is(records[1].type, "attributes", "Should have got attributes");
|
|
is(records[1].attributeName, "foo", "");
|
|
is(records[1].oldValue, "bar", "");
|
|
observer.disconnect();
|
|
div.removeEventListener("DOMAttrModified", mutationListener);
|
|
then(testExpandos);
|
|
m = null;
|
|
});
|
|
m.observe(div, { attributes: true, attributeOldValue: true });
|
|
// Note, [0] points to a mutation observer which is there for a leak test!
|
|
ok(SpecialPowers.compare(SpecialPowers.wrap(div).getBoundMutationObservers()[1], m));
|
|
var mutationEventCount = 0;
|
|
div.addEventListener("DOMAttrModified", mutationListener);
|
|
div.setAttribute("foo", "bar");
|
|
div.setAttribute("foo", "bar");
|
|
is(mutationEventCount, 1, "Should have got only one mutation event!");
|
|
}
|
|
|
|
function testExpandos() {
|
|
var m2 = new M(function(records, observer) {
|
|
is(observer.expandoProperty, true);
|
|
observer.disconnect();
|
|
then(testOutsideShadowDOM);
|
|
});
|
|
m2.expandoProperty = true;
|
|
m2.observe(div, { attributes: true });
|
|
m2 = null;
|
|
if (SpecialPowers) {
|
|
// Run GC several times to see if the expando property disappears.
|
|
|
|
SpecialPowers.gc();
|
|
SpecialPowers.gc();
|
|
SpecialPowers.gc();
|
|
SpecialPowers.gc();
|
|
}
|
|
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");
|
|
is(records[0].type, "attributes", "record.type");
|
|
is(records[0].attributeName, "style", "record.attributeName");
|
|
is(records[0].oldValue, null, "record.oldValue");
|
|
isnot(div.getAttribute("style"), null, "style attribute after creation");
|
|
observer.disconnect();
|
|
m = null;
|
|
div.removeAttribute("style");
|
|
then(testStyleModify);
|
|
});
|
|
m.observe(div, { attributes: true, attributeOldValue: true });
|
|
is(div.getAttribute("style"), null, "style attribute before creation");
|
|
div.style.color = "blue";
|
|
}
|
|
|
|
function testStyleModify() {
|
|
div.style.color = "yellow";
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 1, "number of records");
|
|
is(records[0].type, "attributes", "record.type");
|
|
is(records[0].attributeName, "style", "record.attributeName");
|
|
isnot(div.getAttribute("style"), null, "style attribute after modification");
|
|
observer.disconnect();
|
|
m = null;
|
|
div.removeAttribute("style");
|
|
then(testStyleRead);
|
|
});
|
|
m.observe(div, { attributes: true });
|
|
isnot(div.getAttribute("style"), null, "style attribute before modification");
|
|
div.style.color = "blue";
|
|
}
|
|
|
|
function testStyleRead() {
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 1, "number of records");
|
|
is(records[0].type, "attributes", "record.type");
|
|
is(records[0].attributeName, "data-test", "record.attributeName");
|
|
is(div.getAttribute("style"), null, "style attribute after read");
|
|
observer.disconnect();
|
|
div.removeAttribute("data-test");
|
|
m = null;
|
|
then(testStyleRemoveProperty);
|
|
});
|
|
m.observe(div, { attributes: true });
|
|
is(div.getAttribute("style"), null, "style attribute before read");
|
|
var value = div.style.color; // shouldn't generate any mutation records
|
|
div.setAttribute("data-test", "a");
|
|
}
|
|
|
|
function testStyleRemoveProperty() {
|
|
div.style.color = "blue";
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 1, "number of records");
|
|
is(records[0].type, "attributes", "record.type");
|
|
is(records[0].attributeName, "style", "record.attributeName");
|
|
isnot(div.getAttribute("style"), null, "style attribute after successful removeProperty");
|
|
observer.disconnect();
|
|
m = null;
|
|
div.removeAttribute("style");
|
|
then(testStyleRemoveProperty2);
|
|
});
|
|
m.observe(div, { attributes: true });
|
|
isnot(div.getAttribute("style"), null, "style attribute before successful removeProperty");
|
|
div.style.removeProperty("color");
|
|
}
|
|
|
|
function testStyleRemoveProperty2() {
|
|
m = new M(function(records, observer) {
|
|
is(records.length, 1, "number of records");
|
|
is(records[0].type, "attributes", "record.type");
|
|
is(records[0].attributeName, "data-test", "record.attributeName");
|
|
is(div.getAttribute("style"), null, "style attribute after unsuccessful removeProperty");
|
|
observer.disconnect();
|
|
m = null;
|
|
div.removeAttribute("data-test");
|
|
then(testAttributeRecordMerging1);
|
|
});
|
|
m.observe(div, { attributes: true });
|
|
is(div.getAttribute("style"), null, "style attribute before unsuccessful removeProperty");
|
|
div.style.removeProperty("color"); // shouldn't generate any mutation records
|
|
div.setAttribute("data-test", "a");
|
|
}
|
|
|
|
function testAttributeRecordMerging1() {
|
|
ok(true, "testAttributeRecordMerging1");
|
|
var m = new M(function(records, observer) {
|
|
is(records.length, 2);
|
|
is(records[0].type, "attributes");
|
|
is(records[0].target, div);
|
|
is(records[0].attributeName, "foo");
|
|
is(records[0].attributeNamespace, null);
|
|
is(records[0].oldValue, null);
|
|
|
|
is(records[1].type, "attributes");
|
|
is(records[1].target, div.firstChild);
|
|
is(records[1].attributeName, "foo");
|
|
is(records[1].attributeNamespace, null);
|
|
is(records[1].oldValue, null);
|
|
observer.disconnect();
|
|
div.innerHTML = "";
|
|
div.removeAttribute("foo");
|
|
then(testAttributeRecordMerging2);
|
|
});
|
|
m.observe(div, {
|
|
attributes: true,
|
|
subtree: true
|
|
});
|
|
SpecialPowers.wrap(m).mergeAttributeRecords = true;
|
|
|
|
div.setAttribute("foo", "bar_1");
|
|
div.setAttribute("foo", "bar_2");
|
|
div.innerHTML = "<div></div>";
|
|
div.firstChild.setAttribute("foo", "bar_1");
|
|
div.firstChild.setAttribute("foo", "bar_2");
|
|
}
|
|
|
|
function testAttributeRecordMerging2() {
|
|
ok(true, "testAttributeRecordMerging2");
|
|
var m = new M(function(records, observer) {
|
|
is(records.length, 2);
|
|
is(records[0].type, "attributes");
|
|
is(records[0].target, div);
|
|
is(records[0].attributeName, "foo");
|
|
is(records[0].attributeNamespace, null);
|
|
is(records[0].oldValue, "initial");
|
|
|
|
is(records[1].type, "attributes");
|
|
is(records[1].target, div.firstChild);
|
|
is(records[1].attributeName, "foo");
|
|
is(records[1].attributeNamespace, null);
|
|
is(records[1].oldValue, "initial");
|
|
observer.disconnect();
|
|
div.innerHTML = "";
|
|
div.removeAttribute("foo");
|
|
then(testAttributeRecordMerging3);
|
|
});
|
|
|
|
div.setAttribute("foo", "initial");
|
|
div.innerHTML = "<div></div>";
|
|
div.firstChild.setAttribute("foo", "initial");
|
|
m.observe(div, {
|
|
attributes: true,
|
|
subtree: true,
|
|
attributeOldValue: true
|
|
});
|
|
SpecialPowers.wrap(m).mergeAttributeRecords = true;
|
|
|
|
div.setAttribute("foo", "bar_1");
|
|
div.setAttribute("foo", "bar_2");
|
|
div.firstChild.setAttribute("foo", "bar_1");
|
|
div.firstChild.setAttribute("foo", "bar_2");
|
|
}
|
|
|
|
function testAttributeRecordMerging3() {
|
|
ok(true, "testAttributeRecordMerging3");
|
|
var m = new M(function(records, observer) {
|
|
is(records.length, 4);
|
|
is(records[0].type, "attributes");
|
|
is(records[0].target, div);
|
|
is(records[0].attributeName, "foo");
|
|
is(records[0].attributeNamespace, null);
|
|
is(records[0].oldValue, "initial");
|
|
|
|
is(records[1].type, "attributes");
|
|
is(records[1].target, div.firstChild);
|
|
is(records[1].attributeName, "foo");
|
|
is(records[1].attributeNamespace, null);
|
|
is(records[1].oldValue, "initial");
|
|
|
|
is(records[2].type, "attributes");
|
|
is(records[2].target, div);
|
|
is(records[2].attributeName, "foo");
|
|
is(records[2].attributeNamespace, null);
|
|
is(records[2].oldValue, "bar_1");
|
|
|
|
is(records[3].type, "attributes");
|
|
is(records[3].target, div.firstChild);
|
|
is(records[3].attributeName, "foo");
|
|
is(records[3].attributeNamespace, null);
|
|
is(records[3].oldValue, "bar_1");
|
|
|
|
observer.disconnect();
|
|
div.innerHTML = "";
|
|
div.removeAttribute("foo");
|
|
then(testAttributeRecordMerging4);
|
|
});
|
|
|
|
div.setAttribute("foo", "initial");
|
|
div.innerHTML = "<div></div>";
|
|
div.firstChild.setAttribute("foo", "initial");
|
|
m.observe(div, {
|
|
attributes: true,
|
|
subtree: true,
|
|
attributeOldValue: true
|
|
});
|
|
SpecialPowers.wrap(m).mergeAttributeRecords = true;
|
|
|
|
// No merging should happen.
|
|
div.setAttribute("foo", "bar_1");
|
|
div.firstChild.setAttribute("foo", "bar_1");
|
|
div.setAttribute("foo", "bar_2");
|
|
div.firstChild.setAttribute("foo", "bar_2");
|
|
}
|
|
|
|
function testAttributeRecordMerging4() {
|
|
ok(true, "testAttributeRecordMerging4");
|
|
var m = new M(function(records, observer) {
|
|
});
|
|
|
|
div.setAttribute("foo", "initial");
|
|
div.innerHTML = "<div></div>";
|
|
div.firstChild.setAttribute("foo", "initial");
|
|
m.observe(div, {
|
|
attributes: true,
|
|
subtree: true,
|
|
attributeOldValue: true
|
|
});
|
|
SpecialPowers.wrap(m).mergeAttributeRecords = true;
|
|
|
|
div.setAttribute("foo", "bar_1");
|
|
div.setAttribute("foo", "bar_2");
|
|
div.firstChild.setAttribute("foo", "bar_1");
|
|
div.firstChild.setAttribute("foo", "bar_2");
|
|
|
|
var records = m.takeRecords();
|
|
|
|
is(records.length, 2);
|
|
is(records[0].type, "attributes");
|
|
is(records[0].target, div);
|
|
is(records[0].attributeName, "foo");
|
|
is(records[0].attributeNamespace, null);
|
|
is(records[0].oldValue, "initial");
|
|
|
|
is(records[1].type, "attributes");
|
|
is(records[1].target, div.firstChild);
|
|
is(records[1].attributeName, "foo");
|
|
is(records[1].attributeNamespace, null);
|
|
is(records[1].oldValue, "initial");
|
|
m.disconnect();
|
|
div.innerHTML = "";
|
|
div.removeAttribute("foo");
|
|
then(testChromeOnly);
|
|
}
|
|
|
|
function testChromeOnly() {
|
|
// Content can't access nativeAnonymousChildList
|
|
try {
|
|
var mo = new M(function(records, observer) { });
|
|
mo.observe(div, { nativeAnonymousChildList: true });
|
|
ok(false, "Should have thrown when trying to observe with chrome-only init");
|
|
} catch (e) {
|
|
ok(true, "Throws when trying to observe with chrome-only init");
|
|
}
|
|
|
|
then();
|
|
}
|
|
|
|
SimpleTest.waitForExplicitFinish();
|
|
|
|
</script>
|
|
</pre>
|
|
<div id="log">
|
|
</div>
|
|
</body>
|
|
</html>
|