content/base/test/test_mutationobservers.html

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:822211a02bf0
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>

mercurial