test_mutationobservers.html (30584B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=641821 5 --> 6 <head> 7 <meta charset="utf-8"> 8 <title>Test for Bug 641821</title> 9 <script src="/tests/SimpleTest/SimpleTest.js"></script> 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 11 </head> 12 <body onload="SimpleTest.executeSoon(runTest)"> 13 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=641821">Mozilla Bug 641821</a> 14 <p id="display"></p> 15 <div id="content" style="display: none"> 16 17 </div> 18 <pre id="test"> 19 <script type="application/javascript"> 20 21 /** Test for Bug 641821 */ 22 23 SimpleTest.requestFlakyTimeout("requestFlakyTimeout is silly. (But make sure marquee has time to initialize itself.)"); 24 25 var div = document.createElement("div"); 26 27 var M; 28 if ("MozMutationObserver" in window) { 29 M = window.MozMutationObserver; 30 } else if ("WebKitMutationObserver" in window) { 31 M = window.WebKitMutationObserver; 32 } else { 33 M = window.MutationObserver; 34 } 35 36 function log(str) { 37 var d = document.createElement("div"); 38 d.textContent = str; 39 if (str.includes("PASSED")) { 40 d.setAttribute("style", "color: green;"); 41 } else { 42 d.setAttribute("style", "color: red;"); 43 } 44 document.getElementById("log").appendChild(d); 45 } 46 47 // Some helper functions so that this test runs also outside mochitest. 48 if (!("ok" in window)) { 49 window.ok = function(val, str) { 50 log(str + (val ? " PASSED\n" : " FAILED\n")); 51 } 52 } 53 54 if (!("is" in window)) { 55 window.is = function(val, refVal, str) { 56 log(str + (val == refVal? " PASSED " : " FAILED ") + 57 (val != refVal ? "expected " + refVal + " got " + val + "\n" : "\n")); 58 } 59 } 60 61 if (!("isnot" in window)) { 62 window.isnot = function(val, refVal, str) { 63 log(str + (val != refVal? " PASSED " : " FAILED ") + 64 (val == refVal ? "Didn't expect " + refVal + "\n" : "\n")); 65 } 66 } 67 68 if (!("SimpleTest" in window)) { 69 window.SimpleTest = 70 { 71 finish() { 72 document.getElementById("log").appendChild(document.createTextNode("DONE")); 73 }, 74 waitForExplicitFinish() {} 75 } 76 } 77 78 function then(thenFn) { 79 setTimeout(function() { 80 if (thenFn) { 81 setTimeout(thenFn, 0); 82 } else { 83 SimpleTest.finish(); 84 } 85 }, 0); 86 } 87 88 var m; 89 var m2; 90 var m3; 91 var m4; 92 93 // Checks normal 'this' handling. 94 // Tests also basic attribute handling. 95 function runTest() { 96 m = new M(function(records, observer) { 97 is(observer, m, "2nd parameter should be the mutation observer"); 98 is(observer, this, "2nd parameter should be 'this'"); 99 is(records.length, 1, "Should have one record."); 100 is(records[0].type, "attributes", "Should have got attributes record"); 101 is(records[0].target, div, "Should have got div as target"); 102 is(records[0].attributeName, "foo", "Should have got record about foo attribute"); 103 observer.disconnect(); 104 then(testThisBind); 105 m = null; 106 }); 107 m.observe(div, { attributes: true, attributeFilter: ["foo"] }); 108 div.setAttribute("foo", "bar"); 109 } 110 111 // 'this' handling when fn.bind() is used. 112 function testThisBind() { 113 var child = div.appendChild(document.createElement("div")); 114 var gchild = child.appendChild(document.createElement("div")); 115 m = new M((function(records, observer) { 116 is(observer, m, "2nd parameter should be the mutation observer"); 117 isnot(observer, this, "2nd parameter should be 'this'"); 118 is(records.length, 3, "Should have one record."); 119 is(records[0].type, "attributes", "Should have got attributes record"); 120 is(records[0].target, div, "Should have got div as target"); 121 is(records[0].attributeName, "foo", "Should have got record about foo attribute"); 122 is(records[0].oldValue, "bar", "oldValue should be bar"); 123 is(records[1].type, "attributes", "Should have got attributes record"); 124 is(records[1].target, div, "Should have got div as target"); 125 is(records[1].attributeName, "foo", "Should have got record about foo attribute"); 126 is(records[1].oldValue, "bar2", "oldValue should be bar2"); 127 is(records[2].type, "attributes", "Should have got attributes record"); 128 is(records[2].target, gchild, "Should have got div as target"); 129 is(records[2].attributeName, "foo", "Should have got record about foo attribute"); 130 is(records[2].oldValue, null, "oldValue should be bar2"); 131 observer.disconnect(); 132 then(testCharacterData); 133 m = null; 134 }).bind(window)); 135 m.observe(div, { attributes: true, attributeOldValue: true, subtree: true }); 136 div.setAttribute("foo", "bar2"); 137 div.removeAttribute("foo"); 138 div.removeChild(child); 139 child.removeChild(gchild); 140 div.appendChild(gchild); 141 div.removeChild(gchild); 142 gchild.setAttribute("foo", "bar"); 143 } 144 145 function testCharacterData() { 146 m = new M(function(records, observer) { 147 is(records[0].type, "characterData", "Should have got characterData"); 148 is(records[0].oldValue, null, "Shouldn't have got oldData"); 149 observer.disconnect(); 150 m = null; 151 }); 152 m2 = new M(function(records, observer) { 153 is(records[0].type, "characterData", "Should have got characterData"); 154 is(records[0].oldValue, "foo", "Should have got oldData"); 155 observer.disconnect(); 156 m2 = null; 157 }); 158 m3 = new M(function(records, observer) { 159 ok(false, "This should not be called!"); 160 observer.disconnect(); 161 m3 = null; 162 }); 163 m4 = new M(function(records, observer) { 164 is(records[0].oldValue, null, "Shouldn't have got oldData"); 165 observer.disconnect(); 166 m3.disconnect(); 167 m3 = null; 168 then(testChildList); 169 m4 = null; 170 }); 171 172 div.appendChild(document.createTextNode("foo")); 173 m.observe(div, { characterData: true, subtree: true }); 174 m2.observe(div, { characterData: true, characterDataOldValue: true, subtree: true}); 175 // If observing the same node twice, only the latter option should apply. 176 m3.observe(div, { characterData: true, subtree: true }); 177 m3.observe(div, { characterData: true, subtree: false }); 178 m4.observe(div.firstChild, { characterData: true, subtree: false }); 179 180 div.firstChild.data = "bar"; 181 } 182 183 function testChildList() { 184 var fc = div.firstChild; 185 m = new M(function(records, observer) { 186 is(records[0].type, "childList", "Should have got childList"); 187 is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes"); 188 is(records[0].removedNodes.length, 1, "Should have got removedNodes"); 189 is(records[0].removedNodes[0], fc, "Should have removed a text node"); 190 observer.disconnect(); 191 then(testChildList2); 192 m = null; 193 }); 194 m.observe(div, { childList: true}); 195 div.firstChild.remove(); 196 } 197 198 function testChildList2() { 199 div.innerHTML = "<span>1</span><span>2</span>"; 200 m = new M(function(records, observer) { 201 is(records[0].type, "childList", "Should have got childList"); 202 is(records[0].removedNodes.length, 2, "Should have got removedNodes"); 203 is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 204 observer.disconnect(); 205 then(testChildList3); 206 m = null; 207 }); 208 m.observe(div, { childList: true }); 209 div.innerHTML = "<span><span>foo</span></span>"; 210 } 211 212 function testChildList3() { 213 m = new M(function(records, observer) { 214 is(records[0].type, "childList", "Should have got childList"); 215 is(records[0].removedNodes.length, 1, "Should have got removedNodes"); 216 is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 217 observer.disconnect(); 218 then(testChildList4); 219 m = null; 220 }); 221 m.observe(div, { childList: true }); 222 div.textContent = "hello"; 223 } 224 225 function testChildList4() { 226 div.textContent = null; 227 var df = document.createDocumentFragment(); 228 var t1 = df.appendChild(document.createTextNode("Hello ")); 229 var t2 = df.appendChild(document.createTextNode("world!")); 230 var s1 = div.appendChild(document.createElement("span")); 231 s1.textContent = "foo"; 232 var s2 = div.appendChild(document.createElement("span")); 233 function callback(records, observer) { 234 is(records.length, 3, "Should have got one record for removing nodes from document fragment and one record for adding them to div"); 235 is(records[0].removedNodes.length, 2, "Should have got removedNodes"); 236 is(records[0].removedNodes[0], t1, "Should be the 1st textnode"); 237 is(records[0].removedNodes[1], t2, "Should be the 2nd textnode"); 238 is(records[1].addedNodes.length, 2, "Should have got addedNodes"); 239 is(records[1].addedNodes[0], t1, "Should be the 1st textnode"); 240 is(records[1].addedNodes[1], t2, "Should be the 2nd textnode"); 241 is(records[1].previousSibling, s1, "Should have previousSibling"); 242 is(records[1].nextSibling, s2, "Should have nextSibling"); 243 is(records[2].type, "characterData", "3rd record should be characterData"); 244 is(records[2].target, t1, "target should be the textnode"); 245 is(records[2].oldValue, "Hello ", "oldValue was 'Hello '"); 246 observer.disconnect(); 247 then(testChildList5); 248 m = null; 249 }; 250 m = new M(callback); 251 m.observe(df, { childList: true, characterData: true, characterDataOldValue: true, subtree: true }); 252 m.observe(div, { childList: true }); 253 254 // Make sure transient observers aren't leaked. 255 var leakTest = new M(function(){}); 256 leakTest.observe(div, { characterData: true, subtree: true }); 257 258 div.insertBefore(df, s2); 259 s1.firstChild.data = "bar"; // This should *not* create a record. 260 t1.data = "Hello the whole "; // This should create a record. 261 } 262 263 function testChildList5() { 264 div.textContent = null; 265 var c1 = div.appendChild(document.createElement("div")); 266 var c2 = document.createElement("div"); 267 var div2 = document.createElement("div"); 268 var c3 = div2.appendChild(document.createElement("div")); 269 var c4 = document.createElement("div"); 270 var c5 = document.createElement("div"); 271 var df = document.createDocumentFragment(); 272 var emptyDF = document.createDocumentFragment(); 273 var dfc1 = df.appendChild(document.createElement("div")); 274 var dfc2 = df.appendChild(document.createElement("div")); 275 var dfc3 = df.appendChild(document.createElement("div")); 276 m = new M(function(records, observer) { 277 is(records.length, 6 , ""); 278 is(records[0].removedNodes.length, 1, "Should have got removedNodes"); 279 is(records[0].removedNodes[0], c1, ""); 280 is(records[0].addedNodes.length, 1, "Should have got addedNodes"); 281 is(records[0].addedNodes[0], c2, ""); 282 is(records[0].previousSibling, null, ""); 283 is(records[0].nextSibling, null, ""); 284 is(records[1].removedNodes.length, 1, "Should have got removedNodes"); 285 is(records[1].removedNodes[0], c3, ""); 286 is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes"); 287 is(records[1].previousSibling, null, ""); 288 is(records[1].nextSibling, null, ""); 289 is(records[2].removedNodes.length, 1, "Should have got removedNodes"); 290 is(records[2].removedNodes[0], c2, ""); 291 is(records[2].addedNodes.length, 1, "Should have got addedNodes"); 292 is(records[2].addedNodes[0], c3, ""); 293 is(records[2].previousSibling, null, ""); 294 is(records[2].nextSibling, null, ""); 295 // Check document fragment handling 296 is(records[5].removedNodes.length, 1, ""); 297 is(records[5].removedNodes[0], c4, ""); 298 is(records[5].addedNodes.length, 3, ""); 299 is(records[5].addedNodes[0], dfc1, ""); 300 is(records[5].addedNodes[1], dfc2, ""); 301 is(records[5].addedNodes[2], dfc3, ""); 302 is(records[5].previousSibling, c3, ""); 303 is(records[5].nextSibling, c5, ""); 304 observer.disconnect(); 305 then(testNestedMutations); 306 m = null; 307 }); 308 m.observe(div, { childList: true, subtree: true }); 309 m.observe(div2, { childList: true, subtree: true }); 310 div.replaceChild(c2, c1); 311 div.replaceChild(c3, c2); 312 div.appendChild(c4); 313 div.appendChild(c5); 314 div.replaceChild(df, c4); 315 div.appendChild(emptyDF); // empty document shouldn't cause mutation records 316 } 317 318 function testNestedMutations() { 319 div.textContent = null; 320 div.appendChild(document.createTextNode("foo")); 321 var m2WasCalled = false; 322 m = new M(function(records, observer) { 323 is(records[0].type, "characterData", "Should have got characterData"); 324 observer.disconnect(); 325 m = null; 326 m3 = new M(function(recordsInner, observerInnder) { 327 ok(m2WasCalled, "m2 should have been called before m3!"); 328 is(recordsInner[0].type, "characterData", "Should have got characterData"); 329 observerInnder.disconnect(); 330 then(testAdoptNode); 331 m3 = null; 332 }); 333 m3.observe(div, { characterData: true, subtree: true}); 334 div.firstChild.data = "foo"; 335 }); 336 m2 = new M(function(records, observer) { 337 m2WasCalled = true; 338 is(records[0].type, "characterData", "Should have got characterData"); 339 observer.disconnect(); 340 m2 = null; 341 }); 342 m2.observe(div, { characterData: true, subtree: true}); 343 div.appendChild(document.createTextNode("foo")); 344 m.observe(div, { characterData: true, subtree: true }); 345 346 div.firstChild.data = "bar"; 347 } 348 349 function testAdoptNode() { 350 var d1 = document.implementation.createHTMLDocument(null); 351 var d2 = document.implementation.createHTMLDocument(null); 352 var addedNode; 353 m = new M(function(records, observer) { 354 is(records.length, 3, "Should have 2 records"); 355 is(records[0].target.ownerDocument, d1, "ownerDocument should be the initial document") 356 is(records[1].target.ownerDocument, d2, "ownerDocument should be the new document"); 357 is(records[2].type, "attributes", "Should have got attribute mutation") 358 is(records[2].attributeName, "foo", "Should have got foo attribute mutation") 359 observer.disconnect(); 360 then(testOuterHTML); 361 m = null; 362 }); 363 m.observe(d1, { childList: true, subtree: true, attributes: true }); 364 d2.body.appendChild(d1.body); 365 addedNode = d2.body.lastChild.appendChild(d2.createElement("div")); 366 addedNode.setAttribute("foo", "bar"); 367 } 368 369 function testOuterHTML() { 370 var doc = document.implementation.createHTMLDocument(null); 371 var d1 = doc.body.appendChild(document.createElement("div")); 372 var d2 = doc.body.appendChild(document.createElement("div")); 373 var d3 = doc.body.appendChild(document.createElement("div")); 374 var d4 = doc.body.appendChild(document.createElement("div")); 375 m = new M(function(records, observer) { 376 is(records.length, 4, "Should have 1 record"); 377 is(records[0].removedNodes.length, 1, "Should have 1 removed nodes"); 378 is(records[0].addedNodes.length, 2, "Should have 2 added nodes"); 379 is(records[0].previousSibling, null, ""); 380 is(records[0].nextSibling, d2, ""); 381 is(records[1].removedNodes.length, 1, "Should have 1 removed nodes"); 382 is(records[1].addedNodes.length, 2, "Should have 2 added nodes"); 383 is(records[1].previousSibling, records[0].addedNodes[1], ""); 384 is(records[1].nextSibling, d3, ""); 385 is(records[2].removedNodes.length, 1, "Should have 1 removed nodes"); 386 is(records[2].addedNodes.length, 2, "Should have 2 added nodes"); 387 is(records[2].previousSibling, records[1].addedNodes[1], ""); 388 is(records[2].nextSibling, d4, ""); 389 is(records[3].removedNodes.length, 1, "Should have 1 removed nodes"); 390 is(records[3].addedNodes.length, 0); 391 is(records[3].previousSibling, records[2].addedNodes[1], ""); 392 is(records[3].nextSibling, null, ""); 393 observer.disconnect(); 394 then(testInsertAdjacentHTML); 395 m = null; 396 }); 397 m.observe(doc, { childList: true, subtree: true }); 398 d1.outerHTML = "<div>1</div><div>1</div>"; 399 d2.outerHTML = "<div>2</div><div>2</div>"; 400 d3.outerHTML = "<div>3</div><div>3</div>"; 401 d4.outerHTML = ""; 402 } 403 404 function testInsertAdjacentHTML() { 405 var doc = document.implementation.createHTMLDocument(null); 406 var d1 = doc.body.appendChild(document.createElement("div")); 407 var d2 = doc.body.appendChild(document.createElement("div")); 408 var d3 = doc.body.appendChild(document.createElement("div")); 409 var d4 = doc.body.appendChild(document.createElement("div")); 410 m = new M(function(records, observer) { 411 is(records.length, 4, ""); 412 is(records[0].target, doc.body, ""); 413 is(records[0].previousSibling, null, ""); 414 is(records[0].nextSibling, d1, ""); 415 is(records[1].target, d2, ""); 416 is(records[1].previousSibling, null, ""); 417 is(records[1].nextSibling, null, ""); 418 is(records[2].target, d3, ""); 419 is(records[2].previousSibling, null, ""); 420 is(records[2].nextSibling, null, ""); 421 is(records[3].target, doc.body, ""); 422 is(records[3].previousSibling, d4, ""); 423 is(records[3].nextSibling, null, ""); 424 observer.disconnect(); 425 then(testSyncXHR); 426 m = null; 427 }); 428 m.observe(doc, { childList: true, subtree: true }); 429 d1.insertAdjacentHTML("beforebegin", "<div></div><div></div>"); 430 d2.insertAdjacentHTML("afterbegin", "<div></div><div></div>"); 431 d3.insertAdjacentHTML("beforeend", "<div></div><div></div>"); 432 d4.insertAdjacentHTML("afterend", "<div></div><div></div>"); 433 } 434 435 436 var callbackHandled = false; 437 438 function testSyncXHR() { 439 div.textContent = null; 440 m = new M(function(records, observer) { 441 is(records.length, 1, ""); 442 is(records[0].addedNodes.length, 1, ""); 443 callbackHandled = true; 444 observer.disconnect(); 445 m = null; 446 }); 447 m.observe(div, { childList: true, subtree: true }); 448 div.innerHTML = "<div>hello</div>"; 449 var x = new XMLHttpRequest(); 450 x.open("GET", window.location, false); 451 x.send(); 452 ok(!callbackHandled, "Shouldn't have called the mutation callback!"); 453 setTimeout(testSyncXHR2, 0); 454 } 455 456 function testSyncXHR2() { 457 ok(callbackHandled, "Should have called the mutation callback!"); 458 then(testTakeRecords); 459 } 460 461 function testTakeRecords() { 462 var s = "<span>1</span><span>2</span>"; 463 div.innerHTML = s; 464 var takenRecords; 465 m = new M(function(records, observer) { 466 is(records.length, 3, "Should have got 3 records"); 467 468 is(records[0].type, "attributes", "Should have got attributes"); 469 is(records[0].attributeName, "foo", ""); 470 is(records[0].attributeNamespace, null, ""); 471 is(records[0].prevValue, null, ""); 472 is(records[1].type, "childList", "Should have got childList"); 473 is(records[1].removedNodes.length, 2, "Should have got removedNodes"); 474 is(records[1].addedNodes.length, 2, "Should have got addedNodes"); 475 is(records[2].type, "attributes", "Should have got attributes"); 476 is(records[2].attributeName, "foo", ""); 477 478 is(records.length, takenRecords.length, "Should have had similar mutations"); 479 is(records[0].type, takenRecords[0].type, "Should have had similar mutations"); 480 is(records[1].type, takenRecords[1].type, "Should have had similar mutations"); 481 is(records[2].type, takenRecords[2].type, "Should have had similar mutations"); 482 483 is(records[1].removedNodes.length, takenRecords[1].removedNodes.length, "Should have had similar mutations"); 484 is(records[1].addedNodes.length, takenRecords[1].addedNodes.length, "Should have had similar mutations"); 485 486 is(m.takeRecords().length, 0, "Shouldn't have any records"); 487 observer.disconnect(); 488 then(testMutationObserverAndEvents); 489 m = null; 490 }); 491 m.observe(div, { childList: true, attributes: true }); 492 div.setAttribute("foo", "bar"); 493 div.innerHTML = s; 494 div.removeAttribute("foo"); 495 takenRecords = m.takeRecords(); 496 div.setAttribute("foo", "bar"); 497 div.innerHTML = s; 498 div.removeAttribute("foo"); 499 } 500 501 function testTakeRecords() { 502 m = new M(function(records, observer) { 503 is(records.length, 2, "Should have got 2 records"); 504 is(records[0].type, "attributes", "Should have got attributes"); 505 is(records[0].attributeName, "foo", ""); 506 is(records[0].oldValue, null, ""); 507 is(records[1].type, "attributes", "Should have got attributes"); 508 is(records[1].attributeName, "foo", ""); 509 is(records[1].oldValue, "bar", ""); 510 observer.disconnect(); 511 then(testExpandos); 512 m = null; 513 }); 514 m.observe(div, { attributes: true, attributeOldValue: true }); 515 div.setAttribute("foo", "bar"); 516 div.setAttribute("foo", "bar"); 517 } 518 519 function testExpandos() { 520 m2 = new M(function(records, observer) { 521 is(observer.expandoProperty, true); 522 observer.disconnect(); 523 then(testOutsideShadowDOM); 524 }); 525 m2.expandoProperty = true; 526 m2.observe(div, { attributes: true }); 527 m2 = null; 528 if (SpecialPowers) { 529 // Run GC several times to see if the expando property disappears. 530 531 SpecialPowers.gc(); 532 SpecialPowers.gc(); 533 SpecialPowers.gc(); 534 SpecialPowers.gc(); 535 } 536 div.setAttribute("foo", "bar2"); 537 } 538 539 function testOutsideShadowDOM() { 540 if (!div.attachShadow) { 541 todo(false, "Skipping testOutsideShadowDOM and testInsideShadowDOM " + 542 "because attachShadow is not supported"); 543 then(testMarquee); 544 return; 545 } 546 m = new M(function(records, observer) { 547 is(records.length, 1); 548 is(records[0].type, "attributes", "Should have got attributes"); 549 observer.disconnect(); 550 then(testMarquee); 551 }); 552 m.observe(div, { 553 attributes: true, 554 childList: true, 555 characterData: true, 556 subtree: true 557 }) 558 var sr = div.attachShadow({ mode: "open" }); 559 sr.innerHTML = "<div" + ">text</" + "div>"; 560 sr.firstChild.setAttribute("foo", "bar"); 561 sr.firstChild.firstChild.data = "text2"; 562 sr.firstChild.appendChild(document.createElement("div")); 563 div.setAttribute("foo", "bar"); 564 } 565 566 function testMarquee() { 567 m = new M(function(records, observer) { 568 is(records.length, 1); 569 is(records[0].type, "attributes"); 570 is(records[0].attributeName, "ok"); 571 is(records[0].oldValue, null); 572 observer.disconnect(); 573 then(testStyleCreate); 574 }); 575 var marquee = document.createElement("marquee"); 576 m.observe(marquee, { 577 attributes: true, 578 attributeOldValue: true, 579 childList: true, 580 characterData: true, 581 subtree: true 582 }); 583 document.body.appendChild(marquee); 584 setTimeout(function() {marquee.setAttribute("ok", "ok")}, 500); 585 } 586 587 function testStyleCreate() { 588 m = new M(function(records, observer) { 589 is(records.length, 1, "number of records"); 590 is(records[0].type, "attributes", "record.type"); 591 is(records[0].attributeName, "style", "record.attributeName"); 592 is(records[0].oldValue, null, "record.oldValue"); 593 isnot(div.getAttribute("style"), null, "style attribute after creation"); 594 observer.disconnect(); 595 m = null; 596 div.removeAttribute("style"); 597 then(testStyleModify); 598 }); 599 m.observe(div, { attributes: true, attributeOldValue: true }); 600 is(div.getAttribute("style"), null, "style attribute before creation"); 601 div.style.color = "blue"; 602 } 603 604 function testStyleModify() { 605 div.style.color = "yellow"; 606 m = new M(function(records, observer) { 607 is(records.length, 1, "number of records"); 608 is(records[0].type, "attributes", "record.type"); 609 is(records[0].attributeName, "style", "record.attributeName"); 610 isnot(div.getAttribute("style"), null, "style attribute after modification"); 611 observer.disconnect(); 612 m = null; 613 div.removeAttribute("style"); 614 then(testStyleRead); 615 }); 616 m.observe(div, { attributes: true }); 617 isnot(div.getAttribute("style"), null, "style attribute before modification"); 618 div.style.color = "blue"; 619 } 620 621 function testStyleRead() { 622 m = new M(function(records, observer) { 623 is(records.length, 1, "number of records"); 624 is(records[0].type, "attributes", "record.type"); 625 is(records[0].attributeName, "data-test", "record.attributeName"); 626 is(div.getAttribute("style"), null, "style attribute after read"); 627 observer.disconnect(); 628 div.removeAttribute("data-test"); 629 m = null; 630 then(testStyleRemoveProperty); 631 }); 632 m.observe(div, { attributes: true }); 633 is(div.getAttribute("style"), null, "style attribute before read"); 634 var value = div.style.color; // shouldn't generate any mutation records 635 div.setAttribute("data-test", "a"); 636 } 637 638 function testStyleRemoveProperty() { 639 div.style.color = "blue"; 640 m = new M(function(records, observer) { 641 is(records.length, 1, "number of records"); 642 is(records[0].type, "attributes", "record.type"); 643 is(records[0].attributeName, "style", "record.attributeName"); 644 isnot(div.getAttribute("style"), null, "style attribute after successful removeProperty"); 645 observer.disconnect(); 646 m = null; 647 div.removeAttribute("style"); 648 then(testStyleRemoveProperty2); 649 }); 650 m.observe(div, { attributes: true }); 651 isnot(div.getAttribute("style"), null, "style attribute before successful removeProperty"); 652 div.style.removeProperty("color"); 653 } 654 655 function testStyleRemoveProperty2() { 656 m = new M(function(records, observer) { 657 is(records.length, 1, "number of records"); 658 is(records[0].type, "attributes", "record.type"); 659 is(records[0].attributeName, "data-test", "record.attributeName"); 660 is(div.getAttribute("style"), null, "style attribute after unsuccessful removeProperty"); 661 observer.disconnect(); 662 m = null; 663 div.removeAttribute("data-test"); 664 then(testAttributeRecordMerging1); 665 }); 666 m.observe(div, { attributes: true }); 667 is(div.getAttribute("style"), null, "style attribute before unsuccessful removeProperty"); 668 div.style.removeProperty("color"); // shouldn't generate any mutation records 669 div.setAttribute("data-test", "a"); 670 } 671 672 function testAttributeRecordMerging1() { 673 ok(true, "testAttributeRecordMerging1"); 674 m = new M(function(records, observer) { 675 is(records.length, 2); 676 is(records[0].type, "attributes"); 677 is(records[0].target, div); 678 is(records[0].attributeName, "foo"); 679 is(records[0].attributeNamespace, null); 680 is(records[0].oldValue, null); 681 682 is(records[1].type, "attributes"); 683 is(records[1].target, div.firstChild); 684 is(records[1].attributeName, "foo"); 685 is(records[1].attributeNamespace, null); 686 is(records[1].oldValue, null); 687 observer.disconnect(); 688 div.innerHTML = ""; 689 div.removeAttribute("foo"); 690 then(testAttributeRecordMerging2); 691 }); 692 m.observe(div, { 693 attributes: true, 694 subtree: true 695 }); 696 SpecialPowers.wrap(m).mergeAttributeRecords = true; 697 698 div.setAttribute("foo", "bar_1"); 699 div.setAttribute("foo", "bar_2"); 700 div.innerHTML = "<div></div>"; 701 div.firstChild.setAttribute("foo", "bar_1"); 702 div.firstChild.setAttribute("foo", "bar_2"); 703 } 704 705 function testAttributeRecordMerging2() { 706 ok(true, "testAttributeRecordMerging2"); 707 m = new M(function(records, observer) { 708 is(records.length, 2); 709 is(records[0].type, "attributes"); 710 is(records[0].target, div); 711 is(records[0].attributeName, "foo"); 712 is(records[0].attributeNamespace, null); 713 is(records[0].oldValue, "initial"); 714 715 is(records[1].type, "attributes"); 716 is(records[1].target, div.firstChild); 717 is(records[1].attributeName, "foo"); 718 is(records[1].attributeNamespace, null); 719 is(records[1].oldValue, "initial"); 720 observer.disconnect(); 721 div.innerHTML = ""; 722 div.removeAttribute("foo"); 723 then(testAttributeRecordMerging3); 724 }); 725 726 div.setAttribute("foo", "initial"); 727 div.innerHTML = "<div></div>"; 728 div.firstChild.setAttribute("foo", "initial"); 729 m.observe(div, { 730 attributes: true, 731 subtree: true, 732 attributeOldValue: true 733 }); 734 SpecialPowers.wrap(m).mergeAttributeRecords = true; 735 736 div.setAttribute("foo", "bar_1"); 737 div.setAttribute("foo", "bar_2"); 738 div.firstChild.setAttribute("foo", "bar_1"); 739 div.firstChild.setAttribute("foo", "bar_2"); 740 } 741 742 function testAttributeRecordMerging3() { 743 ok(true, "testAttributeRecordMerging3"); 744 m = new M(function(records, observer) { 745 is(records.length, 4); 746 is(records[0].type, "attributes"); 747 is(records[0].target, div); 748 is(records[0].attributeName, "foo"); 749 is(records[0].attributeNamespace, null); 750 is(records[0].oldValue, "initial"); 751 752 is(records[1].type, "attributes"); 753 is(records[1].target, div.firstChild); 754 is(records[1].attributeName, "foo"); 755 is(records[1].attributeNamespace, null); 756 is(records[1].oldValue, "initial"); 757 758 is(records[2].type, "attributes"); 759 is(records[2].target, div); 760 is(records[2].attributeName, "foo"); 761 is(records[2].attributeNamespace, null); 762 is(records[2].oldValue, "bar_1"); 763 764 is(records[3].type, "attributes"); 765 is(records[3].target, div.firstChild); 766 is(records[3].attributeName, "foo"); 767 is(records[3].attributeNamespace, null); 768 is(records[3].oldValue, "bar_1"); 769 770 observer.disconnect(); 771 div.innerHTML = ""; 772 div.removeAttribute("foo"); 773 then(testAttributeRecordMerging4); 774 }); 775 776 div.setAttribute("foo", "initial"); 777 div.innerHTML = "<div></div>"; 778 div.firstChild.setAttribute("foo", "initial"); 779 m.observe(div, { 780 attributes: true, 781 subtree: true, 782 attributeOldValue: true 783 }); 784 SpecialPowers.wrap(m).mergeAttributeRecords = true; 785 786 // No merging should happen. 787 div.setAttribute("foo", "bar_1"); 788 div.firstChild.setAttribute("foo", "bar_1"); 789 div.setAttribute("foo", "bar_2"); 790 div.firstChild.setAttribute("foo", "bar_2"); 791 } 792 793 function testAttributeRecordMerging4() { 794 ok(true, "testAttributeRecordMerging4"); 795 m = new M(function(records, observer) { 796 }); 797 798 div.setAttribute("foo", "initial"); 799 div.innerHTML = "<div></div>"; 800 div.firstChild.setAttribute("foo", "initial"); 801 m.observe(div, { 802 attributes: true, 803 subtree: true, 804 attributeOldValue: true 805 }); 806 SpecialPowers.wrap(m).mergeAttributeRecords = true; 807 808 div.setAttribute("foo", "bar_1"); 809 div.setAttribute("foo", "bar_2"); 810 div.firstChild.setAttribute("foo", "bar_1"); 811 div.firstChild.setAttribute("foo", "bar_2"); 812 813 var records = m.takeRecords(); 814 815 is(records.length, 2); 816 is(records[0].type, "attributes"); 817 is(records[0].target, div); 818 is(records[0].attributeName, "foo"); 819 is(records[0].attributeNamespace, null); 820 is(records[0].oldValue, "initial"); 821 822 is(records[1].type, "attributes"); 823 is(records[1].target, div.firstChild); 824 is(records[1].attributeName, "foo"); 825 is(records[1].attributeNamespace, null); 826 is(records[1].oldValue, "initial"); 827 m.disconnect(); 828 div.innerHTML = ""; 829 div.removeAttribute("foo"); 830 then(testChromeOnly); 831 } 832 833 function testChromeOnly() { 834 // Content can't access chromeOnlyNodes 835 try { 836 var mo = new M(function(records, observer) { }); 837 mo.observe(div, { chromeOnlyNodes: true }); 838 ok(false, "Should have thrown when trying to observe with chrome-only init"); 839 } catch (e) { 840 ok(true, "Throws when trying to observe with chrome-only init"); 841 } 842 843 then(); 844 } 845 846 SimpleTest.waitForExplicitFinish(); 847 848 </script> 849 </pre> 850 <div id="log"> 851 </div> 852 </body> 853 </html>