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