tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>