SimpleTest.requestFlakyTimeout("requestFlakyTimeout is silly. (But make sure marquee has time to initialize itself.)");
vardiv = 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.includes("PASSED")) {
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 (!("SimpleTest" in window)) {
window.SimpleTest =
{
finish() {
document.getElementById("log").appendChild(document.createTextNode("DONE"));
},
waitForExplicitFinish() {}
}
}
function then(thenFn) {
setTimeout(function() {
if (thenFn) {
setTimeout(thenFn, 0);
} else {
SimpleTest.finish();
}
}, 0);
}
var m; var m2; var m3; var m4;
// Checks normal 'this' handling.
// Tests also basic attribute handling.
function runTest() {
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"] }); 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 }); 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 });
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.firstChild.remove();
}
function testChildList2() { div.innerHTML = "12";
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 = "foo";
}
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 });
m.observe(div, { childList: true });
// 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(testNestedMutations);
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 testNestedMutations() { div.textContent = null; div.appendChild(document.createTextNode("foo")); var m2WasCalled = false;
m = new M(function(records, observer) {
is(records[0].type, "characterData", "Should have got characterData");
observer.disconnect();
m = null;
m3 = new M(function(recordsInner, observerInnder) {
ok(m2WasCalled, "m2 should have been called before m3!");
is(recordsInner[0].type, "characterData", "Should have got characterData");
observerInnder.disconnect();
then(testAdoptNode);
m3 = null;
});
m3.observe(div, { characterData: true, subtree: true}); div.firstChild.data = "foo";
});
m2 = new M(function(records, observer) {
m2WasCalled = true;
is(records[0].type, "characterData", "Should have got characterData");
observer.disconnect();
m2 = null;
});
m2.observe(div, { characterData: true, subtree: true}); div.appendChild(document.createTextNode("foo"));
m.observe(div, { characterData: true, subtree: true });
div.firstChild.data = "bar";
}
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 = "
1
1
";
d2.outerHTML = "
2
2
";
d3.outerHTML = "
3
3
";
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", "");
d2.insertAdjacentHTML("afterbegin", "");
d3.insertAdjacentHTML("beforeend", "");
d4.insertAdjacentHTML("afterend", "");
}
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 = "
hello
"; 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(testTakeRecords);
}
function testTakeRecords() { var s = "12"; 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");
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 }); 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() {
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.
function testChromeOnly() {
// Content can't access chromeOnlyNodes
try { var mo = new M(function(records, observer) { });
mo.observe(div, { chromeOnlyNodes: 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");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.