|
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"> |
|
16 |
|
17 </div> |
|
18 <pre id="test"> |
|
19 <script type="application/javascript"> |
|
20 |
|
21 /** Test for Bug 641821 **/ |
|
22 |
|
23 var div = document.createElement("div"); |
|
24 |
|
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 } |
|
33 |
|
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 } |
|
44 |
|
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 } |
|
51 |
|
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 } |
|
58 |
|
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 } |
|
65 |
|
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 } |
|
75 |
|
76 function then(thenFn) { |
|
77 setTimeout(function() { |
|
78 if (thenFn) { |
|
79 setTimeout(thenFn, 0); |
|
80 } else { |
|
81 SimpleTest.finish(); |
|
82 } |
|
83 }, 0); |
|
84 } |
|
85 |
|
86 var m; |
|
87 var m2; |
|
88 var m3; |
|
89 var m4; |
|
90 |
|
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"); |
|
96 |
|
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"); |
|
106 |
|
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"); |
|
116 |
|
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"); |
|
126 |
|
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"); |
|
136 |
|
137 e = null; |
|
138 try { |
|
139 m.observe(document); |
|
140 } catch (ex) { |
|
141 e = ex; |
|
142 } |
|
143 ok(e, "Should have thrown an exception"); |
|
144 |
|
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 } |
|
162 |
|
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 } |
|
199 |
|
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 }); |
|
226 |
|
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 }); |
|
234 |
|
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) |
|
238 |
|
239 div.firstChild.data = "bar"; |
|
240 } |
|
241 |
|
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 } |
|
256 |
|
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 } |
|
270 |
|
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 } |
|
283 |
|
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) |
|
318 |
|
319 // Make sure transient observers aren't leaked. |
|
320 var leakTest = new M(function(){}); |
|
321 leakTest.observe(div, { characterData: true, subtree: true }); |
|
322 |
|
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 } |
|
327 |
|
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 } |
|
382 |
|
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 } |
|
402 |
|
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 } |
|
437 |
|
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 } |
|
468 |
|
469 |
|
470 var callbackHandled = false; |
|
471 |
|
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 } |
|
489 |
|
490 function testSyncXHR2() { |
|
491 ok(callbackHandled, "Should have called the mutation callback!"); |
|
492 then(testModalDialog); |
|
493 } |
|
494 |
|
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 } |
|
516 |
|
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"); |
|
523 |
|
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", ""); |
|
533 |
|
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"); |
|
538 |
|
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"); |
|
541 |
|
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 } |
|
556 |
|
557 function testTakeRecords() { |
|
558 function mutationListener(e) { |
|
559 ++mutationEventCount; |
|
560 is(e.attrChange, MutationEvent.ADDITION, "unexpected change"); |
|
561 } |
|
562 |
|
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 } |
|
585 |
|
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. |
|
597 |
|
598 SpecialPowers.gc(); |
|
599 SpecialPowers.gc(); |
|
600 SpecialPowers.gc(); |
|
601 SpecialPowers.gc(); |
|
602 } |
|
603 div.setAttribute("foo", "bar2"); |
|
604 } |
|
605 |
|
606 SimpleTest.waitForExplicitFinish(); |
|
607 |
|
608 </script> |
|
609 </pre> |
|
610 <div id="log"> |
|
611 </div> |
|
612 </body> |
|
613 </html> |