content/base/test/test_mutationobservers.html

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 <!DOCTYPE HTML>
michael@0 2 <html>
michael@0 3 <!--
michael@0 4 https://bugzilla.mozilla.org/show_bug.cgi?id=641821
michael@0 5 -->
michael@0 6 <head>
michael@0 7 <meta charset="utf-8">
michael@0 8 <title>Test for Bug 641821</title>
michael@0 9 <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
michael@0 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
michael@0 11 </head>
michael@0 12 <body onload="runTest()">
michael@0 13 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641821">Mozilla Bug 641821</a>
michael@0 14 <p id="display"></p>
michael@0 15 <div id="content" style="display: none">
michael@0 16
michael@0 17 </div>
michael@0 18 <pre id="test">
michael@0 19 <script type="application/javascript">
michael@0 20
michael@0 21 /** Test for Bug 641821 **/
michael@0 22
michael@0 23 var div = document.createElement("div");
michael@0 24
michael@0 25 var M;
michael@0 26 if ("MozMutationObserver" in window) {
michael@0 27 M = window.MozMutationObserver;
michael@0 28 } else if ("WebKitMutationObserver" in window) {
michael@0 29 M = window.WebKitMutationObserver;
michael@0 30 } else {
michael@0 31 M = window.MutationObserver;
michael@0 32 }
michael@0 33
michael@0 34 function log(str) {
michael@0 35 var d = document.createElement("div");
michael@0 36 d.textContent = str;
michael@0 37 if (str.indexOf("PASSED") >= 0) {
michael@0 38 d.setAttribute("style", "color: green;");
michael@0 39 } else {
michael@0 40 d.setAttribute("style", "color: red;");
michael@0 41 }
michael@0 42 document.getElementById("log").appendChild(d);
michael@0 43 }
michael@0 44
michael@0 45 // Some helper functions so that this test runs also outside mochitest.
michael@0 46 if (!("ok" in window)) {
michael@0 47 window.ok = function(val, str) {
michael@0 48 log(str + (val ? " PASSED\n" : " FAILED\n"));
michael@0 49 }
michael@0 50 }
michael@0 51
michael@0 52 if (!("is" in window)) {
michael@0 53 window.is = function(val, refVal, str) {
michael@0 54 log(str + (val == refVal? " PASSED " : " FAILED ") +
michael@0 55 (val != refVal ? "expected " + refVal + " got " + val + "\n" : "\n"));
michael@0 56 }
michael@0 57 }
michael@0 58
michael@0 59 if (!("isnot" in window)) {
michael@0 60 window.isnot = function(val, refVal, str) {
michael@0 61 log(str + (val != refVal? " PASSED " : " FAILED ") +
michael@0 62 (val == refVal ? "Didn't expect " + refVal + "\n" : "\n"));
michael@0 63 }
michael@0 64 }
michael@0 65
michael@0 66 if (!("SimpleTest" in window)) {
michael@0 67 window.SimpleTest =
michael@0 68 {
michael@0 69 finish: function() {
michael@0 70 document.getElementById("log").appendChild(document.createTextNode("DONE"));
michael@0 71 },
michael@0 72 waitForExplicitFinish: function() {}
michael@0 73 }
michael@0 74 }
michael@0 75
michael@0 76 function then(thenFn) {
michael@0 77 setTimeout(function() {
michael@0 78 if (thenFn) {
michael@0 79 setTimeout(thenFn, 0);
michael@0 80 } else {
michael@0 81 SimpleTest.finish();
michael@0 82 }
michael@0 83 }, 0);
michael@0 84 }
michael@0 85
michael@0 86 var m;
michael@0 87 var m2;
michael@0 88 var m3;
michael@0 89 var m4;
michael@0 90
michael@0 91 // Checks basic parameter validation and normal 'this' handling.
michael@0 92 // Tests also basic attribute handling.
michael@0 93 function runTest() {
michael@0 94 m = new M(function(){});
michael@0 95 ok(m, "MutationObserver supported");
michael@0 96
michael@0 97 var e = null;
michael@0 98 try {
michael@0 99 m.observe(document, {});
michael@0 100 } catch (ex) {
michael@0 101 e = ex;
michael@0 102 }
michael@0 103 ok(e, "Should have thrown an exception");
michael@0 104 is(e.name, "SyntaxError", "Should have thrown SyntaxError");
michael@0 105 is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR");
michael@0 106
michael@0 107 e = null;
michael@0 108 try {
michael@0 109 m.observe(document, { childList: true, attributeOldValue: true });
michael@0 110 } catch (ex) {
michael@0 111 e = ex;
michael@0 112 }
michael@0 113 ok(e, "Should have thrown an exception");
michael@0 114 is(e.name, "SyntaxError", "Should have thrown SyntaxError");
michael@0 115 is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR");
michael@0 116
michael@0 117 e = null;
michael@0 118 try {
michael@0 119 m.observe(document, { childList: true, attributeFilter: ["foo"] });
michael@0 120 } catch (ex) {
michael@0 121 e = ex;
michael@0 122 }
michael@0 123 ok(e, "Should have thrown an exception");
michael@0 124 is(e.name, "SyntaxError", "Should have thrown SyntaxError");
michael@0 125 is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR");
michael@0 126
michael@0 127 e = null;
michael@0 128 try {
michael@0 129 m.observe(document, { childList: true, characterDataOldValue: true });
michael@0 130 } catch (ex) {
michael@0 131 e = ex;
michael@0 132 }
michael@0 133 ok(e, "Should have thrown an exception");
michael@0 134 is(e.name, "SyntaxError", "Should have thrown SyntaxError");
michael@0 135 is(e.code, DOMException.SYNTAX_ERR, "Should have thrown DOMException.SYNTAX_ERR");
michael@0 136
michael@0 137 e = null;
michael@0 138 try {
michael@0 139 m.observe(document);
michael@0 140 } catch (ex) {
michael@0 141 e = ex;
michael@0 142 }
michael@0 143 ok(e, "Should have thrown an exception");
michael@0 144
michael@0 145 m = new M(function(records, observer) {
michael@0 146 is(observer, m, "2nd parameter should be the mutation observer");
michael@0 147 is(observer, this, "2nd parameter should be 'this'");
michael@0 148 is(records.length, 1, "Should have one record.");
michael@0 149 is(records[0].type, "attributes", "Should have got attributes record");
michael@0 150 is(records[0].target, div, "Should have got div as target");
michael@0 151 is(records[0].attributeName, "foo", "Should have got record about foo attribute");
michael@0 152 observer.disconnect();
michael@0 153 then(testThisBind);
michael@0 154 m = null;
michael@0 155 });
michael@0 156 m.observe(div, { attributes: true, attributeFilter: ["foo"] });
michael@0 157 is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true);
michael@0 158 is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter.length, 1)
michael@0 159 is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter[0], "foo")
michael@0 160 div.setAttribute("foo", "bar");
michael@0 161 }
michael@0 162
michael@0 163 // 'this' handling when fn.bind() is used.
michael@0 164 function testThisBind() {
michael@0 165 var child = div.appendChild(document.createElement("div"));
michael@0 166 var gchild = child.appendChild(document.createElement("div"));
michael@0 167 m = new M((function(records, observer) {
michael@0 168 is(observer, m, "2nd parameter should be the mutation observer");
michael@0 169 isnot(observer, this, "2nd parameter should be 'this'");
michael@0 170 is(records.length, 3, "Should have one record.");
michael@0 171 is(records[0].type, "attributes", "Should have got attributes record");
michael@0 172 is(records[0].target, div, "Should have got div as target");
michael@0 173 is(records[0].attributeName, "foo", "Should have got record about foo attribute");
michael@0 174 is(records[0].oldValue, "bar", "oldValue should be bar");
michael@0 175 is(records[1].type, "attributes", "Should have got attributes record");
michael@0 176 is(records[1].target, div, "Should have got div as target");
michael@0 177 is(records[1].attributeName, "foo", "Should have got record about foo attribute");
michael@0 178 is(records[1].oldValue, "bar2", "oldValue should be bar2");
michael@0 179 is(records[2].type, "attributes", "Should have got attributes record");
michael@0 180 is(records[2].target, gchild, "Should have got div as target");
michael@0 181 is(records[2].attributeName, "foo", "Should have got record about foo attribute");
michael@0 182 is(records[2].oldValue, null, "oldValue should be bar2");
michael@0 183 observer.disconnect();
michael@0 184 then(testCharacterData);
michael@0 185 m = null;
michael@0 186 }).bind(window));
michael@0 187 m.observe(div, { attributes: true, attributeOldValue: true, subtree: true });
michael@0 188 is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true)
michael@0 189 is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeOldValue, true)
michael@0 190 is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
michael@0 191 div.setAttribute("foo", "bar2");
michael@0 192 div.removeAttribute("foo");
michael@0 193 div.removeChild(child);
michael@0 194 child.removeChild(gchild);
michael@0 195 div.appendChild(gchild);
michael@0 196 div.removeChild(gchild);
michael@0 197 gchild.setAttribute("foo", "bar");
michael@0 198 }
michael@0 199
michael@0 200 function testCharacterData() {
michael@0 201 m = new M(function(records, observer) {
michael@0 202 is(records[0].type, "characterData", "Should have got characterData");
michael@0 203 is(records[0].oldValue, null, "Shouldn't have got oldData");
michael@0 204 observer.disconnect();
michael@0 205 m = null;
michael@0 206 });
michael@0 207 m2 = new M(function(records, observer) {
michael@0 208 is(records[0].type, "characterData", "Should have got characterData");
michael@0 209 is(records[0].oldValue, "foo", "Should have got oldData");
michael@0 210 observer.disconnect();
michael@0 211 m2 = null;
michael@0 212 });
michael@0 213 m3 = new M(function(records, observer) {
michael@0 214 ok(false, "This should not be called!");
michael@0 215 observer.disconnect();
michael@0 216 m3 = null;
michael@0 217 });
michael@0 218 m4 = new M(function(records, observer) {
michael@0 219 is(records[0].oldValue, null, "Shouldn't have got oldData");
michael@0 220 observer.disconnect();
michael@0 221 m3.disconnect();
michael@0 222 m3 = null;
michael@0 223 then(testChildList);
michael@0 224 m4 = null;
michael@0 225 });
michael@0 226
michael@0 227 div.appendChild(document.createTextNode("foo"));
michael@0 228 m.observe(div, { characterData: true, subtree: true });
michael@0 229 m2.observe(div, { characterData: true, characterDataOldValue: true, subtree: true});
michael@0 230 // If observing the same node twice, only the latter option should apply.
michael@0 231 m3.observe(div, { characterData: true, subtree: true });
michael@0 232 m3.observe(div, { characterData: true, subtree: false });
michael@0 233 m4.observe(div.firstChild, { characterData: true, subtree: false });
michael@0 234
michael@0 235 is(SpecialPowers.wrap(div).getBoundMutationObservers().length, 3)
michael@0 236 is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].characterData, true)
michael@0 237 is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].subtree, false)
michael@0 238
michael@0 239 div.firstChild.data = "bar";
michael@0 240 }
michael@0 241
michael@0 242 function testChildList() {
michael@0 243 var fc = div.firstChild;
michael@0 244 m = new M(function(records, observer) {
michael@0 245 is(records[0].type, "childList", "Should have got childList");
michael@0 246 is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
michael@0 247 is(records[0].removedNodes.length, 1, "Should have got removedNodes");
michael@0 248 is(records[0].removedNodes[0], fc, "Should have removed a text node");
michael@0 249 observer.disconnect();
michael@0 250 then(testChildList2);
michael@0 251 m = null;
michael@0 252 });
michael@0 253 m.observe(div, { childList: true});
michael@0 254 div.removeChild(div.firstChild);
michael@0 255 }
michael@0 256
michael@0 257 function testChildList2() {
michael@0 258 div.innerHTML = "<span>1</span><span>2</span>";
michael@0 259 m = new M(function(records, observer) {
michael@0 260 is(records[0].type, "childList", "Should have got childList");
michael@0 261 is(records[0].removedNodes.length, 2, "Should have got removedNodes");
michael@0 262 is(records[0].addedNodes.length, 1, "Should have got addedNodes");
michael@0 263 observer.disconnect();
michael@0 264 then(testChildList3);
michael@0 265 m = null;
michael@0 266 });
michael@0 267 m.observe(div, { childList: true });
michael@0 268 div.innerHTML = "<span><span>foo</span></span>";
michael@0 269 }
michael@0 270
michael@0 271 function testChildList3() {
michael@0 272 m = new M(function(records, observer) {
michael@0 273 is(records[0].type, "childList", "Should have got childList");
michael@0 274 is(records[0].removedNodes.length, 1, "Should have got removedNodes");
michael@0 275 is(records[0].addedNodes.length, 1, "Should have got addedNodes");
michael@0 276 observer.disconnect();
michael@0 277 then(testChildList4);
michael@0 278 m = null;
michael@0 279 });
michael@0 280 m.observe(div, { childList: true });
michael@0 281 div.textContent = "hello";
michael@0 282 }
michael@0 283
michael@0 284 function testChildList4() {
michael@0 285 div.textContent = null;
michael@0 286 var df = document.createDocumentFragment();
michael@0 287 var t1 = df.appendChild(document.createTextNode("Hello "));
michael@0 288 var t2 = df.appendChild(document.createTextNode("world!"));
michael@0 289 var s1 = div.appendChild(document.createElement("span"));
michael@0 290 s1.textContent = "foo";
michael@0 291 var s2 = div.appendChild(document.createElement("span"));
michael@0 292 function callback(records, observer) {
michael@0 293 is(records.length, 3, "Should have got one record for removing nodes from document fragment and one record for adding them to div");
michael@0 294 is(records[0].removedNodes.length, 2, "Should have got removedNodes");
michael@0 295 is(records[0].removedNodes[0], t1, "Should be the 1st textnode");
michael@0 296 is(records[0].removedNodes[1], t2, "Should be the 2nd textnode");
michael@0 297 is(records[1].addedNodes.length, 2, "Should have got addedNodes");
michael@0 298 is(records[1].addedNodes[0], t1, "Should be the 1st textnode");
michael@0 299 is(records[1].addedNodes[1], t2, "Should be the 2nd textnode");
michael@0 300 is(records[1].previousSibling, s1, "Should have previousSibling");
michael@0 301 is(records[1].nextSibling, s2, "Should have nextSibling");
michael@0 302 is(records[2].type, "characterData", "3rd record should be characterData");
michael@0 303 is(records[2].target, t1, "target should be the textnode");
michael@0 304 is(records[2].oldValue, "Hello ", "oldValue was 'Hello '");
michael@0 305 observer.disconnect();
michael@0 306 then(testChildList5);
michael@0 307 m = null;
michael@0 308 };
michael@0 309 m = new M(callback);
michael@0 310 m.observe(df, { childList: true, characterData: true, characterDataOldValue: true, subtree: true });
michael@0 311 is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].childList, true)
michael@0 312 is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterData, true)
michael@0 313 is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterDataOldValue, true)
michael@0 314 is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
michael@0 315 ok(SpecialPowers.compare(SpecialPowers.wrap(df).getBoundMutationObservers()[0].mutationCallback, callback))
michael@0 316 m.observe(div, { childList: true });
michael@0 317 is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo().length, 2)
michael@0 318
michael@0 319 // Make sure transient observers aren't leaked.
michael@0 320 var leakTest = new M(function(){});
michael@0 321 leakTest.observe(div, { characterData: true, subtree: true });
michael@0 322
michael@0 323 div.insertBefore(df, s2);
michael@0 324 s1.firstChild.data = "bar"; // This should *not* create a record.
michael@0 325 t1.data = "Hello the whole "; // This should create a record.
michael@0 326 }
michael@0 327
michael@0 328 function testChildList5() {
michael@0 329 div.textContent = null;
michael@0 330 var c1 = div.appendChild(document.createElement("div"));
michael@0 331 var c2 = document.createElement("div");
michael@0 332 var div2 = document.createElement("div");
michael@0 333 var c3 = div2.appendChild(document.createElement("div"));
michael@0 334 var c4 = document.createElement("div");
michael@0 335 var c5 = document.createElement("div");
michael@0 336 var df = document.createDocumentFragment();
michael@0 337 var emptyDF = document.createDocumentFragment();
michael@0 338 var dfc1 = df.appendChild(document.createElement("div"));
michael@0 339 var dfc2 = df.appendChild(document.createElement("div"));
michael@0 340 var dfc3 = df.appendChild(document.createElement("div"));
michael@0 341 m = new M(function(records, observer) {
michael@0 342 is(records.length, 6 , "");
michael@0 343 is(records[0].removedNodes.length, 1, "Should have got removedNodes");
michael@0 344 is(records[0].removedNodes[0], c1, "");
michael@0 345 is(records[0].addedNodes.length, 1, "Should have got addedNodes");
michael@0 346 is(records[0].addedNodes[0], c2, "");
michael@0 347 is(records[0].previousSibling, null, "");
michael@0 348 is(records[0].nextSibling, null, "");
michael@0 349 is(records[1].removedNodes.length, 1, "Should have got removedNodes");
michael@0 350 is(records[1].removedNodes[0], c3, "");
michael@0 351 is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
michael@0 352 is(records[1].previousSibling, null, "");
michael@0 353 is(records[1].nextSibling, null, "");
michael@0 354 is(records[2].removedNodes.length, 1, "Should have got removedNodes");
michael@0 355 is(records[2].removedNodes[0], c2, "");
michael@0 356 is(records[2].addedNodes.length, 1, "Should have got addedNodes");
michael@0 357 is(records[2].addedNodes[0], c3, "");
michael@0 358 is(records[2].previousSibling, null, "");
michael@0 359 is(records[2].nextSibling, null, "");
michael@0 360 // Check document fragment handling
michael@0 361 is(records[5].removedNodes.length, 1, "");
michael@0 362 is(records[5].removedNodes[0], c4, "");
michael@0 363 is(records[5].addedNodes.length, 3, "");
michael@0 364 is(records[5].addedNodes[0], dfc1, "");
michael@0 365 is(records[5].addedNodes[1], dfc2, "");
michael@0 366 is(records[5].addedNodes[2], dfc3, "");
michael@0 367 is(records[5].previousSibling, c3, "");
michael@0 368 is(records[5].nextSibling, c5, "");
michael@0 369 observer.disconnect();
michael@0 370 then(testAdoptNode);
michael@0 371 m = null;
michael@0 372 });
michael@0 373 m.observe(div, { childList: true, subtree: true });
michael@0 374 m.observe(div2, { childList: true, subtree: true });
michael@0 375 div.replaceChild(c2, c1);
michael@0 376 div.replaceChild(c3, c2);
michael@0 377 div.appendChild(c4);
michael@0 378 div.appendChild(c5);
michael@0 379 div.replaceChild(df, c4);
michael@0 380 div.appendChild(emptyDF); // empty document shouldn't cause mutation records
michael@0 381 }
michael@0 382
michael@0 383 function testAdoptNode() {
michael@0 384 var d1 = document.implementation.createHTMLDocument(null);
michael@0 385 var d2 = document.implementation.createHTMLDocument(null);
michael@0 386 var addedNode;
michael@0 387 m = new M(function(records, observer) {
michael@0 388 is(records.length, 3, "Should have 2 records");
michael@0 389 is(records[0].target.ownerDocument, d1, "ownerDocument should be the initial document")
michael@0 390 is(records[1].target.ownerDocument, d2, "ownerDocument should be the new document");
michael@0 391 is(records[2].type, "attributes", "Should have got attribute mutation")
michael@0 392 is(records[2].attributeName, "foo", "Should have got foo attribute mutation")
michael@0 393 observer.disconnect();
michael@0 394 then(testOuterHTML);
michael@0 395 m = null;
michael@0 396 });
michael@0 397 m.observe(d1, { childList: true, subtree: true, attributes: true });
michael@0 398 d2.body.appendChild(d1.body);
michael@0 399 addedNode = d2.body.lastChild.appendChild(d2.createElement("div"));
michael@0 400 addedNode.setAttribute("foo", "bar");
michael@0 401 }
michael@0 402
michael@0 403 function testOuterHTML() {
michael@0 404 var doc = document.implementation.createHTMLDocument(null);
michael@0 405 var d1 = doc.body.appendChild(document.createElement("div"));
michael@0 406 var d2 = doc.body.appendChild(document.createElement("div"));
michael@0 407 var d3 = doc.body.appendChild(document.createElement("div"));
michael@0 408 var d4 = doc.body.appendChild(document.createElement("div"));
michael@0 409 m = new M(function(records, observer) {
michael@0 410 is(records.length, 4, "Should have 1 record");
michael@0 411 is(records[0].removedNodes.length, 1, "Should have 1 removed nodes");
michael@0 412 is(records[0].addedNodes.length, 2, "Should have 2 added nodes");
michael@0 413 is(records[0].previousSibling, null, "");
michael@0 414 is(records[0].nextSibling, d2, "");
michael@0 415 is(records[1].removedNodes.length, 1, "Should have 1 removed nodes");
michael@0 416 is(records[1].addedNodes.length, 2, "Should have 2 added nodes");
michael@0 417 is(records[1].previousSibling, records[0].addedNodes[1], "");
michael@0 418 is(records[1].nextSibling, d3, "");
michael@0 419 is(records[2].removedNodes.length, 1, "Should have 1 removed nodes");
michael@0 420 is(records[2].addedNodes.length, 2, "Should have 2 added nodes");
michael@0 421 is(records[2].previousSibling, records[1].addedNodes[1], "");
michael@0 422 is(records[2].nextSibling, d4, "");
michael@0 423 is(records[3].removedNodes.length, 1, "Should have 1 removed nodes");
michael@0 424 is(records[3].addedNodes.length, 0);
michael@0 425 is(records[3].previousSibling, records[2].addedNodes[1], "");
michael@0 426 is(records[3].nextSibling, null, "");
michael@0 427 observer.disconnect();
michael@0 428 then(testInsertAdjacentHTML);
michael@0 429 m = null;
michael@0 430 });
michael@0 431 m.observe(doc, { childList: true, subtree: true });
michael@0 432 d1.outerHTML = "<div>1</div><div>1</div>";
michael@0 433 d2.outerHTML = "<div>2</div><div>2</div>";
michael@0 434 d3.outerHTML = "<div>3</div><div>3</div>";
michael@0 435 d4.outerHTML = "";
michael@0 436 }
michael@0 437
michael@0 438 function testInsertAdjacentHTML() {
michael@0 439 var doc = document.implementation.createHTMLDocument(null);
michael@0 440 var d1 = doc.body.appendChild(document.createElement("div"));
michael@0 441 var d2 = doc.body.appendChild(document.createElement("div"));
michael@0 442 var d3 = doc.body.appendChild(document.createElement("div"));
michael@0 443 var d4 = doc.body.appendChild(document.createElement("div"));
michael@0 444 m = new M(function(records, observer) {
michael@0 445 is(records.length, 4, "");
michael@0 446 is(records[0].target, doc.body, "");
michael@0 447 is(records[0].previousSibling, null, "");
michael@0 448 is(records[0].nextSibling, d1, "");
michael@0 449 is(records[1].target, d2, "");
michael@0 450 is(records[1].previousSibling, null, "");
michael@0 451 is(records[1].nextSibling, null, "");
michael@0 452 is(records[2].target, d3, "");
michael@0 453 is(records[2].previousSibling, null, "");
michael@0 454 is(records[2].nextSibling, null, "");
michael@0 455 is(records[3].target, doc.body, "");
michael@0 456 is(records[3].previousSibling, d4, "");
michael@0 457 is(records[3].nextSibling, null, "");
michael@0 458 observer.disconnect();
michael@0 459 then(testSyncXHR);
michael@0 460 m = null;
michael@0 461 });
michael@0 462 m.observe(doc, { childList: true, subtree: true });
michael@0 463 d1.insertAdjacentHTML("beforebegin", "<div></div><div></div>");
michael@0 464 d2.insertAdjacentHTML("afterbegin", "<div></div><div></div>");
michael@0 465 d3.insertAdjacentHTML("beforeend", "<div></div><div></div>");
michael@0 466 d4.insertAdjacentHTML("afterend", "<div></div><div></div>");
michael@0 467 }
michael@0 468
michael@0 469
michael@0 470 var callbackHandled = false;
michael@0 471
michael@0 472 function testSyncXHR() {
michael@0 473 div.textContent = null;
michael@0 474 m = new M(function(records, observer) {
michael@0 475 is(records.length, 1, "");
michael@0 476 is(records[0].addedNodes.length, 1, "");
michael@0 477 callbackHandled = true;
michael@0 478 observer.disconnect();
michael@0 479 m = null;
michael@0 480 });
michael@0 481 m.observe(div, { childList: true, subtree: true });
michael@0 482 div.innerHTML = "<div>hello</div>";
michael@0 483 var x = new XMLHttpRequest();
michael@0 484 x.open("GET", window.location, false);
michael@0 485 x.send();
michael@0 486 ok(!callbackHandled, "Shouldn't have called the mutation callback!");
michael@0 487 setTimeout(testSyncXHR2, 0);
michael@0 488 }
michael@0 489
michael@0 490 function testSyncXHR2() {
michael@0 491 ok(callbackHandled, "Should have called the mutation callback!");
michael@0 492 then(testModalDialog);
michael@0 493 }
michael@0 494
michael@0 495 function testModalDialog() {
michael@0 496 var didHandleCallback = false;
michael@0 497 div.innerHTML = "<span>1</span><span>2</span>";
michael@0 498 m = new M(function(records, observer) {
michael@0 499 is(records[0].type, "childList", "Should have got childList");
michael@0 500 is(records[0].removedNodes.length, 2, "Should have got removedNodes");
michael@0 501 is(records[0].addedNodes.length, 1, "Should have got addedNodes");
michael@0 502 observer.disconnect();
michael@0 503 m = null;
michael@0 504 didHandleCallback = true;
michael@0 505 });
michael@0 506 m.observe(div, { childList: true });
michael@0 507 div.innerHTML = "<span><span>foo</span></span>";
michael@0 508 try {
michael@0 509 window.showModalDialog("mutationobserver_dialog.html");
michael@0 510 ok(didHandleCallback, "Should have called the callback while showing modal dialog!");
michael@0 511 } catch(e) {
michael@0 512 todo(false, "showModalDialog not implemented on this platform");
michael@0 513 }
michael@0 514 then(testTakeRecords);
michael@0 515 }
michael@0 516
michael@0 517 function testTakeRecords() {
michael@0 518 var s = "<span>1</span><span>2</span>";
michael@0 519 div.innerHTML = s;
michael@0 520 var takenRecords;
michael@0 521 m = new M(function(records, observer) {
michael@0 522 is(records.length, 3, "Should have got 3 records");
michael@0 523
michael@0 524 is(records[0].type, "attributes", "Should have got attributes");
michael@0 525 is(records[0].attributeName, "foo", "");
michael@0 526 is(records[0].attributeNamespace, null, "");
michael@0 527 is(records[0].prevValue, null, "");
michael@0 528 is(records[1].type, "childList", "Should have got childList");
michael@0 529 is(records[1].removedNodes.length, 2, "Should have got removedNodes");
michael@0 530 is(records[1].addedNodes.length, 2, "Should have got addedNodes");
michael@0 531 is(records[2].type, "attributes", "Should have got attributes");
michael@0 532 is(records[2].attributeName, "foo", "");
michael@0 533
michael@0 534 is(records.length, takenRecords.length, "Should have had similar mutations");
michael@0 535 is(records[0].type, takenRecords[0].type, "Should have had similar mutations");
michael@0 536 is(records[1].type, takenRecords[1].type, "Should have had similar mutations");
michael@0 537 is(records[2].type, takenRecords[2].type, "Should have had similar mutations");
michael@0 538
michael@0 539 is(records[1].removedNodes.length, takenRecords[1].removedNodes.length, "Should have had similar mutations");
michael@0 540 is(records[1].addedNodes.length, takenRecords[1].addedNodes.length, "Should have had similar mutations");
michael@0 541
michael@0 542 is(m.takeRecords().length, 0, "Shouldn't have any records");
michael@0 543 observer.disconnect();
michael@0 544 then(testMutationObserverAndEvents);
michael@0 545 m = null;
michael@0 546 });
michael@0 547 m.observe(div, { childList: true, attributes: true });
michael@0 548 div.setAttribute("foo", "bar");
michael@0 549 div.innerHTML = s;
michael@0 550 div.removeAttribute("foo");
michael@0 551 takenRecords = m.takeRecords();
michael@0 552 div.setAttribute("foo", "bar");
michael@0 553 div.innerHTML = s;
michael@0 554 div.removeAttribute("foo");
michael@0 555 }
michael@0 556
michael@0 557 function testTakeRecords() {
michael@0 558 function mutationListener(e) {
michael@0 559 ++mutationEventCount;
michael@0 560 is(e.attrChange, MutationEvent.ADDITION, "unexpected change");
michael@0 561 }
michael@0 562
michael@0 563 m = new M(function(records, observer) {
michael@0 564 is(records.length, 2, "Should have got 2 records");
michael@0 565 is(records[0].type, "attributes", "Should have got attributes");
michael@0 566 is(records[0].attributeName, "foo", "");
michael@0 567 is(records[0].oldValue, null, "");
michael@0 568 is(records[1].type, "attributes", "Should have got attributes");
michael@0 569 is(records[1].attributeName, "foo", "");
michael@0 570 is(records[1].oldValue, "bar", "");
michael@0 571 observer.disconnect();
michael@0 572 div.removeEventListener("DOMAttrModified", mutationListener);
michael@0 573 then(testExpandos);
michael@0 574 m = null;
michael@0 575 });
michael@0 576 m.observe(div, { attributes: true, attributeOldValue: true });
michael@0 577 // Note, [0] points to a mutation observer which is there for a leak test!
michael@0 578 ok(SpecialPowers.compare(SpecialPowers.wrap(div).getBoundMutationObservers()[1], m));
michael@0 579 var mutationEventCount = 0;
michael@0 580 div.addEventListener("DOMAttrModified", mutationListener);
michael@0 581 div.setAttribute("foo", "bar");
michael@0 582 div.setAttribute("foo", "bar");
michael@0 583 is(mutationEventCount, 1, "Should have got only one mutation event!");
michael@0 584 }
michael@0 585
michael@0 586 function testExpandos() {
michael@0 587 var m2 = new M(function(records, observer) {
michael@0 588 is(observer.expandoProperty, true);
michael@0 589 observer.disconnect();
michael@0 590 then();
michael@0 591 });
michael@0 592 m2.expandoProperty = true;
michael@0 593 m2.observe(div, { attributes: true });
michael@0 594 m2 = null;
michael@0 595 if (SpecialPowers) {
michael@0 596 // Run GC several times to see if the expando property disappears.
michael@0 597
michael@0 598 SpecialPowers.gc();
michael@0 599 SpecialPowers.gc();
michael@0 600 SpecialPowers.gc();
michael@0 601 SpecialPowers.gc();
michael@0 602 }
michael@0 603 div.setAttribute("foo", "bar2");
michael@0 604 }
michael@0 605
michael@0 606 SimpleTest.waitForExplicitFinish();
michael@0 607
michael@0 608 </script>
michael@0 609 </pre>
michael@0 610 <div id="log">
michael@0 611 </div>
michael@0 612 </body>
michael@0 613 </html>

mercurial