1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/base/test/test_mutationobservers.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,613 @@ 1.4 +<!DOCTYPE HTML> 1.5 +<html> 1.6 +<!-- 1.7 +https://bugzilla.mozilla.org/show_bug.cgi?id=641821 1.8 +--> 1.9 +<head> 1.10 + <meta charset="utf-8"> 1.11 + <title>Test for Bug 641821</title> 1.12 + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> 1.13 + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 1.14 +</head> 1.15 +<body onload="runTest()"> 1.16 +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641821">Mozilla Bug 641821</a> 1.17 +<p id="display"></p> 1.18 +<div id="content" style="display: none"> 1.19 + 1.20 +</div> 1.21 +<pre id="test"> 1.22 +<script type="application/javascript"> 1.23 + 1.24 +/** Test for Bug 641821 **/ 1.25 + 1.26 +var div = document.createElement("div"); 1.27 + 1.28 +var M; 1.29 +if ("MozMutationObserver" in window) { 1.30 + M = window.MozMutationObserver; 1.31 +} else if ("WebKitMutationObserver" in window) { 1.32 + M = window.WebKitMutationObserver; 1.33 +} else { 1.34 + M = window.MutationObserver; 1.35 +} 1.36 + 1.37 +function log(str) { 1.38 + var d = document.createElement("div"); 1.39 + d.textContent = str; 1.40 + if (str.indexOf("PASSED") >= 0) { 1.41 + d.setAttribute("style", "color: green;"); 1.42 + } else { 1.43 + d.setAttribute("style", "color: red;"); 1.44 + } 1.45 + document.getElementById("log").appendChild(d); 1.46 +} 1.47 + 1.48 +// Some helper functions so that this test runs also outside mochitest. 1.49 +if (!("ok" in window)) { 1.50 + window.ok = function(val, str) { 1.51 + log(str + (val ? " PASSED\n" : " FAILED\n")); 1.52 + } 1.53 +} 1.54 + 1.55 +if (!("is" in window)) { 1.56 + window.is = function(val, refVal, str) { 1.57 + log(str + (val == refVal? " PASSED " : " FAILED ") + 1.58 + (val != refVal ? "expected " + refVal + " got " + val + "\n" : "\n")); 1.59 + } 1.60 +} 1.61 + 1.62 +if (!("isnot" in window)) { 1.63 + window.isnot = function(val, refVal, str) { 1.64 + log(str + (val != refVal? " PASSED " : " FAILED ") + 1.65 + (val == refVal ? "Didn't expect " + refVal + "\n" : "\n")); 1.66 + } 1.67 +} 1.68 + 1.69 +if (!("SimpleTest" in window)) { 1.70 + window.SimpleTest = 1.71 + { 1.72 + finish: function() { 1.73 + document.getElementById("log").appendChild(document.createTextNode("DONE")); 1.74 + }, 1.75 + waitForExplicitFinish: function() {} 1.76 + } 1.77 +} 1.78 + 1.79 +function then(thenFn) { 1.80 + setTimeout(function() { 1.81 + if (thenFn) { 1.82 + setTimeout(thenFn, 0); 1.83 + } else { 1.84 + SimpleTest.finish(); 1.85 + } 1.86 + }, 0); 1.87 +} 1.88 + 1.89 +var m; 1.90 +var m2; 1.91 +var m3; 1.92 +var m4; 1.93 + 1.94 +// Checks basic parameter validation and normal 'this' handling. 1.95 +// Tests also basic attribute handling. 1.96 +function runTest() { 1.97 + m = new M(function(){}); 1.98 + ok(m, "MutationObserver supported"); 1.99 + 1.100 + var e = null; 1.101 + try { 1.102 + m.observe(document, {}); 1.103 + } catch (ex) { 1.104 + e = ex; 1.105 + } 1.106 + ok(e, "Should have thrown an exception"); 1.107 + is(e.name, "SyntaxError", "Should have thrown SyntaxError"); 1.108 + is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR"); 1.109 + 1.110 + e = null; 1.111 + try { 1.112 + m.observe(document, { childList: true, attributeOldValue: true }); 1.113 + } catch (ex) { 1.114 + e = ex; 1.115 + } 1.116 + ok(e, "Should have thrown an exception"); 1.117 + is(e.name, "SyntaxError", "Should have thrown SyntaxError"); 1.118 + is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR"); 1.119 + 1.120 + e = null; 1.121 + try { 1.122 + m.observe(document, { childList: true, attributeFilter: ["foo"] }); 1.123 + } catch (ex) { 1.124 + e = ex; 1.125 + } 1.126 + ok(e, "Should have thrown an exception"); 1.127 + is(e.name, "SyntaxError", "Should have thrown SyntaxError"); 1.128 + is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR"); 1.129 + 1.130 + e = null; 1.131 + try { 1.132 + m.observe(document, { childList: true, characterDataOldValue: true }); 1.133 + } catch (ex) { 1.134 + e = ex; 1.135 + } 1.136 + ok(e, "Should have thrown an exception"); 1.137 + is(e.name, "SyntaxError", "Should have thrown SyntaxError"); 1.138 + is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR"); 1.139 + 1.140 + e = null; 1.141 + try { 1.142 + m.observe(document); 1.143 + } catch (ex) { 1.144 + e = ex; 1.145 + } 1.146 + ok(e, "Should have thrown an exception"); 1.147 + 1.148 + m = new M(function(records, observer) { 1.149 + is(observer, m, "2nd parameter should be the mutation observer"); 1.150 + is(observer, this, "2nd parameter should be 'this'"); 1.151 + is(records.length, 1, "Should have one record."); 1.152 + is(records[0].type, "attributes", "Should have got attributes record"); 1.153 + is(records[0].target, div, "Should have got div as target"); 1.154 + is(records[0].attributeName, "foo", "Should have got record about foo attribute"); 1.155 + observer.disconnect(); 1.156 + then(testThisBind); 1.157 + m = null; 1.158 + }); 1.159 + m.observe(div, { attributes: true, attributeFilter: ["foo"] }); 1.160 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true); 1.161 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter.length, 1) 1.162 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter[0], "foo") 1.163 + div.setAttribute("foo", "bar"); 1.164 +} 1.165 + 1.166 +// 'this' handling when fn.bind() is used. 1.167 +function testThisBind() { 1.168 + var child = div.appendChild(document.createElement("div")); 1.169 + var gchild = child.appendChild(document.createElement("div")); 1.170 + m = new M((function(records, observer) { 1.171 + is(observer, m, "2nd parameter should be the mutation observer"); 1.172 + isnot(observer, this, "2nd parameter should be 'this'"); 1.173 + is(records.length, 3, "Should have one record."); 1.174 + is(records[0].type, "attributes", "Should have got attributes record"); 1.175 + is(records[0].target, div, "Should have got div as target"); 1.176 + is(records[0].attributeName, "foo", "Should have got record about foo attribute"); 1.177 + is(records[0].oldValue, "bar", "oldValue should be bar"); 1.178 + is(records[1].type, "attributes", "Should have got attributes record"); 1.179 + is(records[1].target, div, "Should have got div as target"); 1.180 + is(records[1].attributeName, "foo", "Should have got record about foo attribute"); 1.181 + is(records[1].oldValue, "bar2", "oldValue should be bar2"); 1.182 + is(records[2].type, "attributes", "Should have got attributes record"); 1.183 + is(records[2].target, gchild, "Should have got div as target"); 1.184 + is(records[2].attributeName, "foo", "Should have got record about foo attribute"); 1.185 + is(records[2].oldValue, null, "oldValue should be bar2"); 1.186 + observer.disconnect(); 1.187 + then(testCharacterData); 1.188 + m = null; 1.189 + }).bind(window)); 1.190 + m.observe(div, { attributes: true, attributeOldValue: true, subtree: true }); 1.191 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true) 1.192 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeOldValue, true) 1.193 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true) 1.194 + div.setAttribute("foo", "bar2"); 1.195 + div.removeAttribute("foo"); 1.196 + div.removeChild(child); 1.197 + child.removeChild(gchild); 1.198 + div.appendChild(gchild); 1.199 + div.removeChild(gchild); 1.200 + gchild.setAttribute("foo", "bar"); 1.201 +} 1.202 + 1.203 +function testCharacterData() { 1.204 + m = new M(function(records, observer) { 1.205 + is(records[0].type, "characterData", "Should have got characterData"); 1.206 + is(records[0].oldValue, null, "Shouldn't have got oldData"); 1.207 + observer.disconnect(); 1.208 + m = null; 1.209 + }); 1.210 + m2 = new M(function(records, observer) { 1.211 + is(records[0].type, "characterData", "Should have got characterData"); 1.212 + is(records[0].oldValue, "foo", "Should have got oldData"); 1.213 + observer.disconnect(); 1.214 + m2 = null; 1.215 + }); 1.216 + m3 = new M(function(records, observer) { 1.217 + ok(false, "This should not be called!"); 1.218 + observer.disconnect(); 1.219 + m3 = null; 1.220 + }); 1.221 + m4 = new M(function(records, observer) { 1.222 + is(records[0].oldValue, null, "Shouldn't have got oldData"); 1.223 + observer.disconnect(); 1.224 + m3.disconnect(); 1.225 + m3 = null; 1.226 + then(testChildList); 1.227 + m4 = null; 1.228 + }); 1.229 + 1.230 + div.appendChild(document.createTextNode("foo")); 1.231 + m.observe(div, { characterData: true, subtree: true }); 1.232 + m2.observe(div, { characterData: true, characterDataOldValue: true, subtree: true}); 1.233 + // If observing the same node twice, only the latter option should apply. 1.234 + m3.observe(div, { characterData: true, subtree: true }); 1.235 + m3.observe(div, { characterData: true, subtree: false }); 1.236 + m4.observe(div.firstChild, { characterData: true, subtree: false }); 1.237 + 1.238 + is(SpecialPowers.wrap(div).getBoundMutationObservers().length, 3) 1.239 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].characterData, true) 1.240 + is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].subtree, false) 1.241 + 1.242 + div.firstChild.data = "bar"; 1.243 +} 1.244 + 1.245 +function testChildList() { 1.246 + var fc = div.firstChild; 1.247 + m = new M(function(records, observer) { 1.248 + is(records[0].type, "childList", "Should have got childList"); 1.249 + is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes"); 1.250 + is(records[0].removedNodes.length, 1, "Should have got removedNodes"); 1.251 + is(records[0].removedNodes[0], fc, "Should have removed a text node"); 1.252 + observer.disconnect(); 1.253 + then(testChildList2); 1.254 + m = null; 1.255 + }); 1.256 + m.observe(div, { childList: true}); 1.257 + div.removeChild(div.firstChild); 1.258 +} 1.259 + 1.260 +function testChildList2() { 1.261 + div.innerHTML = "<span>1</span><span>2</span>"; 1.262 + m = new M(function(records, observer) { 1.263 + is(records[0].type, "childList", "Should have got childList"); 1.264 + is(records[0].removedNodes.length, 2, "Should have got removedNodes"); 1.265 + is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 1.266 + observer.disconnect(); 1.267 + then(testChildList3); 1.268 + m = null; 1.269 + }); 1.270 + m.observe(div, { childList: true }); 1.271 + div.innerHTML = "<span><span>foo</span></span>"; 1.272 +} 1.273 + 1.274 +function testChildList3() { 1.275 + m = new M(function(records, observer) { 1.276 + is(records[0].type, "childList", "Should have got childList"); 1.277 + is(records[0].removedNodes.length, 1, "Should have got removedNodes"); 1.278 + is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 1.279 + observer.disconnect(); 1.280 + then(testChildList4); 1.281 + m = null; 1.282 + }); 1.283 + m.observe(div, { childList: true }); 1.284 + div.textContent = "hello"; 1.285 +} 1.286 + 1.287 +function testChildList4() { 1.288 + div.textContent = null; 1.289 + var df = document.createDocumentFragment(); 1.290 + var t1 = df.appendChild(document.createTextNode("Hello ")); 1.291 + var t2 = df.appendChild(document.createTextNode("world!")); 1.292 + var s1 = div.appendChild(document.createElement("span")); 1.293 + s1.textContent = "foo"; 1.294 + var s2 = div.appendChild(document.createElement("span")); 1.295 + function callback(records, observer) { 1.296 + is(records.length, 3, "Should have got one record for removing nodes from document fragment and one record for adding them to div"); 1.297 + is(records[0].removedNodes.length, 2, "Should have got removedNodes"); 1.298 + is(records[0].removedNodes[0], t1, "Should be the 1st textnode"); 1.299 + is(records[0].removedNodes[1], t2, "Should be the 2nd textnode"); 1.300 + is(records[1].addedNodes.length, 2, "Should have got addedNodes"); 1.301 + is(records[1].addedNodes[0], t1, "Should be the 1st textnode"); 1.302 + is(records[1].addedNodes[1], t2, "Should be the 2nd textnode"); 1.303 + is(records[1].previousSibling, s1, "Should have previousSibling"); 1.304 + is(records[1].nextSibling, s2, "Should have nextSibling"); 1.305 + is(records[2].type, "characterData", "3rd record should be characterData"); 1.306 + is(records[2].target, t1, "target should be the textnode"); 1.307 + is(records[2].oldValue, "Hello ", "oldValue was 'Hello '"); 1.308 + observer.disconnect(); 1.309 + then(testChildList5); 1.310 + m = null; 1.311 + }; 1.312 + m = new M(callback); 1.313 + m.observe(df, { childList: true, characterData: true, characterDataOldValue: true, subtree: true }); 1.314 + is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].childList, true) 1.315 + is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterData, true) 1.316 + is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterDataOldValue, true) 1.317 + is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true) 1.318 + ok(SpecialPowers.compare(SpecialPowers.wrap(df).getBoundMutationObservers()[0].mutationCallback, callback)) 1.319 + m.observe(div, { childList: true }); 1.320 + is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo().length, 2) 1.321 + 1.322 + // Make sure transient observers aren't leaked. 1.323 + var leakTest = new M(function(){}); 1.324 + leakTest.observe(div, { characterData: true, subtree: true }); 1.325 + 1.326 + div.insertBefore(df, s2); 1.327 + s1.firstChild.data = "bar"; // This should *not* create a record. 1.328 + t1.data = "Hello the whole "; // This should create a record. 1.329 +} 1.330 + 1.331 +function testChildList5() { 1.332 + div.textContent = null; 1.333 + var c1 = div.appendChild(document.createElement("div")); 1.334 + var c2 = document.createElement("div"); 1.335 + var div2 = document.createElement("div"); 1.336 + var c3 = div2.appendChild(document.createElement("div")); 1.337 + var c4 = document.createElement("div"); 1.338 + var c5 = document.createElement("div"); 1.339 + var df = document.createDocumentFragment(); 1.340 + var emptyDF = document.createDocumentFragment(); 1.341 + var dfc1 = df.appendChild(document.createElement("div")); 1.342 + var dfc2 = df.appendChild(document.createElement("div")); 1.343 + var dfc3 = df.appendChild(document.createElement("div")); 1.344 + m = new M(function(records, observer) { 1.345 + is(records.length, 6 , ""); 1.346 + is(records[0].removedNodes.length, 1, "Should have got removedNodes"); 1.347 + is(records[0].removedNodes[0], c1, ""); 1.348 + is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 1.349 + is(records[0].addedNodes[0], c2, ""); 1.350 + is(records[0].previousSibling, null, ""); 1.351 + is(records[0].nextSibling, null, ""); 1.352 + is(records[1].removedNodes.length, 1, "Should have got removedNodes"); 1.353 + is(records[1].removedNodes[0], c3, ""); 1.354 + is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes"); 1.355 + is(records[1].previousSibling, null, ""); 1.356 + is(records[1].nextSibling, null, ""); 1.357 + is(records[2].removedNodes.length, 1, "Should have got removedNodes"); 1.358 + is(records[2].removedNodes[0], c2, ""); 1.359 + is(records[2].addedNodes.length, 1, "Should have got addedNodes"); 1.360 + is(records[2].addedNodes[0], c3, ""); 1.361 + is(records[2].previousSibling, null, ""); 1.362 + is(records[2].nextSibling, null, ""); 1.363 + // Check document fragment handling 1.364 + is(records[5].removedNodes.length, 1, ""); 1.365 + is(records[5].removedNodes[0], c4, ""); 1.366 + is(records[5].addedNodes.length, 3, ""); 1.367 + is(records[5].addedNodes[0], dfc1, ""); 1.368 + is(records[5].addedNodes[1], dfc2, ""); 1.369 + is(records[5].addedNodes[2], dfc3, ""); 1.370 + is(records[5].previousSibling, c3, ""); 1.371 + is(records[5].nextSibling, c5, ""); 1.372 + observer.disconnect(); 1.373 + then(testAdoptNode); 1.374 + m = null; 1.375 + }); 1.376 + m.observe(div, { childList: true, subtree: true }); 1.377 + m.observe(div2, { childList: true, subtree: true }); 1.378 + div.replaceChild(c2, c1); 1.379 + div.replaceChild(c3, c2); 1.380 + div.appendChild(c4); 1.381 + div.appendChild(c5); 1.382 + div.replaceChild(df, c4); 1.383 + div.appendChild(emptyDF); // empty document shouldn't cause mutation records 1.384 +} 1.385 + 1.386 +function testAdoptNode() { 1.387 + var d1 = document.implementation.createHTMLDocument(null); 1.388 + var d2 = document.implementation.createHTMLDocument(null); 1.389 + var addedNode; 1.390 + m = new M(function(records, observer) { 1.391 + is(records.length, 3, "Should have 2 records"); 1.392 + is(records[0].target.ownerDocument, d1, "ownerDocument should be the initial document") 1.393 + is(records[1].target.ownerDocument, d2, "ownerDocument should be the new document"); 1.394 + is(records[2].type, "attributes", "Should have got attribute mutation") 1.395 + is(records[2].attributeName, "foo", "Should have got foo attribute mutation") 1.396 + observer.disconnect(); 1.397 + then(testOuterHTML); 1.398 + m = null; 1.399 + }); 1.400 + m.observe(d1, { childList: true, subtree: true, attributes: true }); 1.401 + d2.body.appendChild(d1.body); 1.402 + addedNode = d2.body.lastChild.appendChild(d2.createElement("div")); 1.403 + addedNode.setAttribute("foo", "bar"); 1.404 +} 1.405 + 1.406 +function testOuterHTML() { 1.407 + var doc = document.implementation.createHTMLDocument(null); 1.408 + var d1 = doc.body.appendChild(document.createElement("div")); 1.409 + var d2 = doc.body.appendChild(document.createElement("div")); 1.410 + var d3 = doc.body.appendChild(document.createElement("div")); 1.411 + var d4 = doc.body.appendChild(document.createElement("div")); 1.412 + m = new M(function(records, observer) { 1.413 + is(records.length, 4, "Should have 1 record"); 1.414 + is(records[0].removedNodes.length, 1, "Should have 1 removed nodes"); 1.415 + is(records[0].addedNodes.length, 2, "Should have 2 added nodes"); 1.416 + is(records[0].previousSibling, null, ""); 1.417 + is(records[0].nextSibling, d2, ""); 1.418 + is(records[1].removedNodes.length, 1, "Should have 1 removed nodes"); 1.419 + is(records[1].addedNodes.length, 2, "Should have 2 added nodes"); 1.420 + is(records[1].previousSibling, records[0].addedNodes[1], ""); 1.421 + is(records[1].nextSibling, d3, ""); 1.422 + is(records[2].removedNodes.length, 1, "Should have 1 removed nodes"); 1.423 + is(records[2].addedNodes.length, 2, "Should have 2 added nodes"); 1.424 + is(records[2].previousSibling, records[1].addedNodes[1], ""); 1.425 + is(records[2].nextSibling, d4, ""); 1.426 + is(records[3].removedNodes.length, 1, "Should have 1 removed nodes"); 1.427 + is(records[3].addedNodes.length, 0); 1.428 + is(records[3].previousSibling, records[2].addedNodes[1], ""); 1.429 + is(records[3].nextSibling, null, ""); 1.430 + observer.disconnect(); 1.431 + then(testInsertAdjacentHTML); 1.432 + m = null; 1.433 + }); 1.434 + m.observe(doc, { childList: true, subtree: true }); 1.435 + d1.outerHTML = "<div>1</div><div>1</div>"; 1.436 + d2.outerHTML = "<div>2</div><div>2</div>"; 1.437 + d3.outerHTML = "<div>3</div><div>3</div>"; 1.438 + d4.outerHTML = ""; 1.439 +} 1.440 + 1.441 +function testInsertAdjacentHTML() { 1.442 + var doc = document.implementation.createHTMLDocument(null); 1.443 + var d1 = doc.body.appendChild(document.createElement("div")); 1.444 + var d2 = doc.body.appendChild(document.createElement("div")); 1.445 + var d3 = doc.body.appendChild(document.createElement("div")); 1.446 + var d4 = doc.body.appendChild(document.createElement("div")); 1.447 + m = new M(function(records, observer) { 1.448 + is(records.length, 4, ""); 1.449 + is(records[0].target, doc.body, ""); 1.450 + is(records[0].previousSibling, null, ""); 1.451 + is(records[0].nextSibling, d1, ""); 1.452 + is(records[1].target, d2, ""); 1.453 + is(records[1].previousSibling, null, ""); 1.454 + is(records[1].nextSibling, null, ""); 1.455 + is(records[2].target, d3, ""); 1.456 + is(records[2].previousSibling, null, ""); 1.457 + is(records[2].nextSibling, null, ""); 1.458 + is(records[3].target, doc.body, ""); 1.459 + is(records[3].previousSibling, d4, ""); 1.460 + is(records[3].nextSibling, null, ""); 1.461 + observer.disconnect(); 1.462 + then(testSyncXHR); 1.463 + m = null; 1.464 + }); 1.465 + m.observe(doc, { childList: true, subtree: true }); 1.466 + d1.insertAdjacentHTML("beforebegin", "<div></div><div></div>"); 1.467 + d2.insertAdjacentHTML("afterbegin", "<div></div><div></div>"); 1.468 + d3.insertAdjacentHTML("beforeend", "<div></div><div></div>"); 1.469 + d4.insertAdjacentHTML("afterend", "<div></div><div></div>"); 1.470 +} 1.471 + 1.472 + 1.473 +var callbackHandled = false; 1.474 + 1.475 +function testSyncXHR() { 1.476 + div.textContent = null; 1.477 + m = new M(function(records, observer) { 1.478 + is(records.length, 1, ""); 1.479 + is(records[0].addedNodes.length, 1, ""); 1.480 + callbackHandled = true; 1.481 + observer.disconnect(); 1.482 + m = null; 1.483 + }); 1.484 + m.observe(div, { childList: true, subtree: true }); 1.485 + div.innerHTML = "<div>hello</div>"; 1.486 + var x = new XMLHttpRequest(); 1.487 + x.open("GET", window.location, false); 1.488 + x.send(); 1.489 + ok(!callbackHandled, "Shouldn't have called the mutation callback!"); 1.490 + setTimeout(testSyncXHR2, 0); 1.491 +} 1.492 + 1.493 +function testSyncXHR2() { 1.494 + ok(callbackHandled, "Should have called the mutation callback!"); 1.495 + then(testModalDialog); 1.496 +} 1.497 + 1.498 +function testModalDialog() { 1.499 + var didHandleCallback = false; 1.500 + div.innerHTML = "<span>1</span><span>2</span>"; 1.501 + m = new M(function(records, observer) { 1.502 + is(records[0].type, "childList", "Should have got childList"); 1.503 + is(records[0].removedNodes.length, 2, "Should have got removedNodes"); 1.504 + is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 1.505 + observer.disconnect(); 1.506 + m = null; 1.507 + didHandleCallback = true; 1.508 + }); 1.509 + m.observe(div, { childList: true }); 1.510 + div.innerHTML = "<span><span>foo</span></span>"; 1.511 + try { 1.512 + window.showModalDialog("mutationobserver_dialog.html"); 1.513 + ok(didHandleCallback, "Should have called the callback while showing modal dialog!"); 1.514 + } catch(e) { 1.515 + todo(false, "showModalDialog not implemented on this platform"); 1.516 + } 1.517 + then(testTakeRecords); 1.518 +} 1.519 + 1.520 +function testTakeRecords() { 1.521 + var s = "<span>1</span><span>2</span>"; 1.522 + div.innerHTML = s; 1.523 + var takenRecords; 1.524 + m = new M(function(records, observer) { 1.525 + is(records.length, 3, "Should have got 3 records"); 1.526 + 1.527 + is(records[0].type, "attributes", "Should have got attributes"); 1.528 + is(records[0].attributeName, "foo", ""); 1.529 + is(records[0].attributeNamespace, null, ""); 1.530 + is(records[0].prevValue, null, ""); 1.531 + is(records[1].type, "childList", "Should have got childList"); 1.532 + is(records[1].removedNodes.length, 2, "Should have got removedNodes"); 1.533 + is(records[1].addedNodes.length, 2, "Should have got addedNodes"); 1.534 + is(records[2].type, "attributes", "Should have got attributes"); 1.535 + is(records[2].attributeName, "foo", ""); 1.536 + 1.537 + is(records.length, takenRecords.length, "Should have had similar mutations"); 1.538 + is(records[0].type, takenRecords[0].type, "Should have had similar mutations"); 1.539 + is(records[1].type, takenRecords[1].type, "Should have had similar mutations"); 1.540 + is(records[2].type, takenRecords[2].type, "Should have had similar mutations"); 1.541 + 1.542 + is(records[1].removedNodes.length, takenRecords[1].removedNodes.length, "Should have had similar mutations"); 1.543 + is(records[1].addedNodes.length, takenRecords[1].addedNodes.length, "Should have had similar mutations"); 1.544 + 1.545 + is(m.takeRecords().length, 0, "Shouldn't have any records"); 1.546 + observer.disconnect(); 1.547 + then(testMutationObserverAndEvents); 1.548 + m = null; 1.549 + }); 1.550 + m.observe(div, { childList: true, attributes: true }); 1.551 + div.setAttribute("foo", "bar"); 1.552 + div.innerHTML = s; 1.553 + div.removeAttribute("foo"); 1.554 + takenRecords = m.takeRecords(); 1.555 + div.setAttribute("foo", "bar"); 1.556 + div.innerHTML = s; 1.557 + div.removeAttribute("foo"); 1.558 +} 1.559 + 1.560 +function testTakeRecords() { 1.561 + function mutationListener(e) { 1.562 + ++mutationEventCount; 1.563 + is(e.attrChange, MutationEvent.ADDITION, "unexpected change"); 1.564 + } 1.565 + 1.566 + m = new M(function(records, observer) { 1.567 + is(records.length, 2, "Should have got 2 records"); 1.568 + is(records[0].type, "attributes", "Should have got attributes"); 1.569 + is(records[0].attributeName, "foo", ""); 1.570 + is(records[0].oldValue, null, ""); 1.571 + is(records[1].type, "attributes", "Should have got attributes"); 1.572 + is(records[1].attributeName, "foo", ""); 1.573 + is(records[1].oldValue, "bar", ""); 1.574 + observer.disconnect(); 1.575 + div.removeEventListener("DOMAttrModified", mutationListener); 1.576 + then(testExpandos); 1.577 + m = null; 1.578 + }); 1.579 + m.observe(div, { attributes: true, attributeOldValue: true }); 1.580 + // Note, [0] points to a mutation observer which is there for a leak test! 1.581 + ok(SpecialPowers.compare(SpecialPowers.wrap(div).getBoundMutationObservers()[1], m)); 1.582 + var mutationEventCount = 0; 1.583 + div.addEventListener("DOMAttrModified", mutationListener); 1.584 + div.setAttribute("foo", "bar"); 1.585 + div.setAttribute("foo", "bar"); 1.586 + is(mutationEventCount, 1, "Should have got only one mutation event!"); 1.587 +} 1.588 + 1.589 +function testExpandos() { 1.590 + var m2 = new M(function(records, observer) { 1.591 + is(observer.expandoProperty, true); 1.592 + observer.disconnect(); 1.593 + then(); 1.594 + }); 1.595 + m2.expandoProperty = true; 1.596 + m2.observe(div, { attributes: true }); 1.597 + m2 = null; 1.598 + if (SpecialPowers) { 1.599 + // Run GC several times to see if the expando property disappears. 1.600 + 1.601 + SpecialPowers.gc(); 1.602 + SpecialPowers.gc(); 1.603 + SpecialPowers.gc(); 1.604 + SpecialPowers.gc(); 1.605 + } 1.606 + div.setAttribute("foo", "bar2"); 1.607 +} 1.608 + 1.609 +SimpleTest.waitForExplicitFinish(); 1.610 + 1.611 +</script> 1.612 +</pre> 1.613 +<div id="log"> 1.614 +</div> 1.615 +</body> 1.616 +</html>