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.

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

mercurial