2 <script src=
"../../../resources/js-test.js"></script>
5 window
.jsTestIsAsync
= true;
6 var mutations
, mutations2
, mutationsWithOldValue
;
10 function testBasic() {
15 debug('Testing basic aspects of attribute observation.');
18 div
= document
.createElement('div');
19 div
.setAttribute('bar', 'foo');
21 observer
= new MutationObserver(function(m
) {
25 observer
.observe(div
, { attributes
: true, characterData
: true });
26 div
.setAttribute('foo', 'bar');
27 div
.removeAttribute('bar');
28 setTimeout(checkDisconnectAndMutate
, 0);
31 function checkDisconnectAndMutate() {
32 debug('...can attribute changes be observed at all');
34 shouldBe('mutations.length', '2');
35 shouldBe('mutations[0].type', '"attributes"');
36 shouldBe('mutations[0].attributeName', '"foo"');
37 shouldBe('mutations[0].attributeNamespace', 'null');
38 shouldBe('mutations[1].type', '"attributes"');
39 shouldBe('mutations[1].attributeName', '"bar"');
40 shouldBe('mutations[1].attributeNamespace', 'null');
43 observer
.disconnect();
44 div
.setAttribute('foo', 'baz');
45 setTimeout(checkNotDeliveredAndMutateMultiple
, 0);
48 function checkNotDeliveredAndMutateMultiple() {
49 debug('...observer.disconnect() should prevent further delivery of mutations.');
51 shouldBe('mutations', 'null');
52 observer
.observe(div
, { attributes
: true });
53 div
.setAttribute('foo', 'bat');
54 div
.setAttribute('bar', 'foo');
59 debug('...re-observing after disconnect works with the same observer.');
61 shouldBe('mutations.length', '2');
62 shouldBe('mutations[0].type', '"attributes"');
63 shouldBe('mutations[0].attributeName', '"foo"');
64 shouldBe('mutations[0].attributeNamespace', 'null');
65 shouldBe('mutations[1].type', '"attributes"');
66 shouldBe('mutations[1].attributeName', '"bar"');
67 shouldBe('mutations[1].attributeNamespace', 'null');
68 observer
.disconnect();
76 function testWrongType() {
81 debug('Testing that observing without specifying "attributes" does not result in hearing about attribute changes.');
84 div
= document
.createElement('div');
85 observer
= new MutationObserver(function(m
) {
89 observer
.observe(div
, { childList
: true, characterData
: true });
90 div
.setAttribute('foo', 'bar');
91 setTimeout(finish
, 0);
95 shouldBe('mutations', 'null');
96 observer
.disconnect();
104 function testMultipleRegistration() {
109 debug('Testing that re-observing the same node with the same observer has the effect of resetting the options.');
113 div
= document
.createElement('div');
114 observer
= new MutationObserver(function(m
) {
119 observer
.observe(div
, { attributes
: true, characterData
: true });
120 observer
.observe(div
, { attributes
: true });
121 div
.setAttribute('foo', 'bar');
122 setTimeout(checkDisconnectAndMutate
, 0);
125 function checkDisconnectAndMutate() {
126 shouldBe('calls', '1');
127 shouldBe('mutations.length', '1');
128 shouldBe('mutations[0].type', '"attributes"');
129 shouldBe('mutations[0].attributeName', '"foo"');
131 observer
.observe(div
, { attributes
: true, characterData
: true });
132 observer
.observe(div
, { childList
: true });
133 div
.setAttribute('foo', 'baz');
134 setTimeout(finish
, 0);
138 shouldBe('mutations', 'null');
139 observer
.disconnect();
147 function testMultipleObservers() {
153 debug('Testing that multiple observers can be registered to a given node and both receive mutations.');
155 div
= document
.createElement('div');
156 observer
= new MutationObserver(function(m
) {
159 observer2
= new MutationObserver(function(m
) {
162 observer
.observe(div
, { attributes
: true });
163 observer2
.observe(div
, { attributes
: true });
164 div
.setAttribute('foo', 'bar');
165 setTimeout(finish
, 0);
169 shouldBe('mutations.length', '1');
170 shouldBe('mutations[0].type', '"attributes"');
171 shouldBe('mutations[0].attributeName', '"foo"');
172 shouldBe('mutations2.length', '1');
173 shouldBe('mutations2[0].type', '"attributes"');
174 shouldBe('mutations2[0].attributeName', '"foo"');
175 observer
.disconnect();
176 observer2
.disconnect();
184 function testNamespaceURI() {
189 debug('Testing that "attributeNamespace" value is delivered properly.');
191 div
= document
.createElement('div');
192 observer
= new MutationObserver(function(m
) {
196 observer
.observe(div
, { attributes
: true, childList
: true });
197 div
.setAttributeNS('http://www.foo.com/bar', 'foo', 'bar');
198 setTimeout(finish
, 0);
202 shouldBe('mutations.length', '1');
203 shouldBe('mutations[0].type', '"attributes"');
204 shouldBe('mutations[0].attributeName', '"foo"');
205 shouldBe('mutations[0].attributeNamespace', '"http://www.foo.com/bar"');
206 observer
.disconnect();
214 function testPropertyAccess() {
219 debug('Testing that modifications to node properties which delegate to attribute storage deliver mutations.');
221 img
= document
.createElement('img');
222 a
= document
.createElement('a');
224 observer
= new MutationObserver(function(m
) {
228 observer
.observe(img
, { attributes
: true });
229 observer
.observe(a
, { attributes
: true });
234 setTimeout(finish
, 0);
238 shouldBe('mutations.length', '2');
239 shouldBe('mutations[0].type', '"attributes"');
240 shouldBe('mutations[0].attributeName', '"src"');
241 shouldBe('mutations[1].type', '"attributes"');
242 shouldBe('mutations[1].attributeName', '"href"');
243 observer
.disconnect();
251 function testOrderingWrtDOMSubtreeModified() {
252 var div
, div2
, subDiv
;
257 debug('Testing mutation records are enqueued for attributes before DOMSubtreeModified is dispatched.');
260 div
= document
.body
.appendChild(document
.createElement('div'));
261 div2
= document
.body
.appendChild(document
.createElement('div'));
263 subDiv
= div
.appendChild(document
.createElement('div'));
265 observer
= new MutationObserver(function(m
) {
269 listener = function(e
) {
270 div2
.setAttribute('baz', 'bat');
273 div
.addEventListener('DOMSubtreeModified', listener
);
274 observer
.observe(subDiv
, { attributes
: true });
275 observer
.observe(div2
, { attributes
: true });
277 subDiv
.setAttribute('foo', 'bar');
279 setTimeout(finish
, 0);
283 shouldBe('mutations.length', '2');
284 shouldBe('mutations[0].type', '"attributes"');
285 shouldBe('mutations[0].attributeName', '"foo"');
286 shouldBe('mutations[1].type', '"attributes"');
287 shouldBe('mutations[1].attributeName', '"baz"');
288 div
.removeEventListener('DOMSubtreeModified', listener
);
289 document
.body
.removeChild(div
);
290 observer
.disconnect();
298 function testOldValue() {
303 debug('Testing basic oldValue delivery.');
305 div
= document
.createElement('div');
306 div
.setAttribute('bar', 'boo');
308 observer
= new MutationObserver(function(mutations
) {
309 window
.mutations
= mutations
;
311 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
312 div
.setAttribute('foo', 'bar');
313 div
.setAttribute('foo', 'baz');
314 div
.removeAttribute('bar');
315 div
.removeAttribute('non-existant');
316 setTimeout(finish
, 0);
320 shouldBe('mutations.length', '3');
321 shouldBe('mutations[0].type', '"attributes"');
322 shouldBe('mutations[0].attributeName', '"foo"');
323 shouldBe('mutations[0].oldValue', 'null');
324 shouldBe('mutations[1].type', '"attributes"');
325 shouldBe('mutations[1].attributeName', '"foo"');
326 shouldBe('mutations[1].oldValue', '"bar"');
327 shouldBe('mutations[2].type', '"attributes"');
328 shouldBe('mutations[2].attributeName', '"bar"');
329 shouldBe('mutations[2].oldValue', '"boo"');
330 observer
.disconnect();
338 function testOldValueAsRequested() {
340 var observerWithOldValue
;
344 debug('Testing that oldValue is delivered as requested (or not).');
345 mutationsWithOldValue
= null;
347 div
= document
.createElement('div');
348 div
.setAttribute('foo', 'bar');
349 observerWithOldValue
= new MutationObserver(function(mutations
) {
350 window
.mutationsWithOldValue
= mutations
;
352 observer
= new MutationObserver(function(mutations
) {
353 window
.mutations
= mutations
;
355 observerWithOldValue
.observe(div
, { attributes
: true, attributeOldValue
: true });
356 observer
.observe(div
, { attributes
: true });
357 div
.setAttribute('foo', 'baz');
358 setTimeout(finish
, 0);
362 shouldBe('mutationsWithOldValue.length', '1');
363 shouldBe('mutationsWithOldValue[0].type', '"attributes"');
364 shouldBe('mutationsWithOldValue[0].attributeName', '"foo"');
365 shouldBe('mutationsWithOldValue[0].oldValue', '"bar"');
366 shouldBe('mutations.length', '1');
367 shouldBe('mutations[0].type', '"attributes"');
368 shouldBe('mutations[0].attributeName', '"foo"');
369 shouldBe('mutations[0].oldValue', 'null');
370 observerWithOldValue
.disconnect();
371 observer
.disconnect();
379 function testOldValueUnionMultipleObservations() {
385 debug('An observer with multiple observations will get attributeOldValue if any entries request it.');
387 div
= document
.createElement('div');
388 span
= div
.appendChild(document
.createElement('span'));
389 span
.setAttribute('foo', 'bar');
390 observer
= new MutationObserver(function(mutations
) {
391 window
.mutations
= mutations
;
393 observer
.observe(div
, { attributes
: true, attributeOldValue
: true, subtree
: true });
394 observer
.observe(span
, { attributes
: true });
395 span
.setAttribute('foo', 'baz');
396 setTimeout(finish
, 0);
400 shouldBe('mutations.length', '1');
401 shouldBe('mutations[0].type', '"attributes"');
402 shouldBe('mutations[0].attributeName', '"foo"');
403 shouldBe('mutations[0].oldValue', '"bar"');
404 observer
.disconnect();
412 function testIDLAttribute() {
417 debug('Testing setting an attribute via reflected IDL attribute.');
419 div
= document
.createElement('div');
420 observer
= new MutationObserver(function(mutations
) {
421 window
.mutations
= mutations
;
423 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
427 setTimeout(finish
, 0);
431 shouldBe('mutations.length', '3');
432 shouldBe('mutations[0].type', '"attributes"');
433 shouldBe('mutations[0].attributeName', '"id"');
434 shouldBe('mutations[0].oldValue', 'null');
435 shouldBe('mutations[1].type', '"attributes"');
436 shouldBe('mutations[1].attributeName', '"id"');
437 shouldBe('mutations[1].oldValue', '"foo"');
438 shouldBe('mutations[2].type', '"attributes"');
439 shouldBe('mutations[2].attributeName', '"id"');
440 shouldBe('mutations[2].oldValue', '"bar"');
441 observer
.disconnect();
449 function testAttributeFilter() {
454 debug('Testing that attributeFilter works as expected and observes case with HTML elements.');
457 observer
= new MutationObserver(function(m
) {
461 div
= document
.createElement('div');
462 observer
.observe(div
, { attributes
: true, attributeFilter
: ['foo', 'bar', 'booM'] });
463 div
.setAttribute('foo', 'foo');
464 div
.setAttribute('bar', 'bar');
465 div
.setAttribute('baz', 'baz');
466 div
.setAttribute('BOOm', 'boom');
468 setTimeout(finish
, 0);
472 debug('...only foo and bar should be received.');
474 shouldBe('mutations.length', '2');
475 shouldBe('mutations[0].type', '"attributes"');
476 shouldBe('mutations[0].attributeName', '"foo"');
477 shouldBe('mutations[0].attributeNamespace', 'null');
478 shouldBe('mutations[1].type', '"attributes"');
479 shouldBe('mutations[1].attributeName', '"bar"');
480 shouldBe('mutations[1].attributeNamespace', 'null');
481 observer
.disconnect();
489 function testAttributeFilterSubtree() {
494 debug('Testing the behavior of attributeFilter when the same observer observes at multiple nodes in a subtree with different filter options.');
497 observer
= new MutationObserver(function(m
) {
501 div
= document
.createElement('div');
502 div2
= div
.appendChild(document
.createElement('div'));
503 div3
= div2
.appendChild(document
.createElement('div'));
505 observer
.observe(div
, { attributes
: true, subtree
: true, attributeFilter
: ['foo', 'bar'] });
506 observer
.observe(div2
, { attributes
: true, subtree
: true, attributeFilter
: ['bar', 'bat'] });
508 div3
.setAttribute('foo', 'foo');
509 div3
.setAttribute('bar', 'bar');
510 div3
.setAttribute('bat', 'bat');
511 div3
.setAttribute('baz', 'baz');
513 setTimeout(checkAndObserveAll
, 0);
516 function checkAndObserveAll() {
517 debug('...only foo, bar & bat should be received.');
519 shouldBe('mutations.length', '3');
520 shouldBe('mutations[0].type', '"attributes"');
521 shouldBe('mutations[0].attributeName', '"foo"');
522 shouldBe('mutations[0].attributeNamespace', 'null');
523 shouldBe('mutations[1].type', '"attributes"');
524 shouldBe('mutations[1].attributeName', '"bar"');
525 shouldBe('mutations[1].attributeNamespace', 'null');
526 shouldBe('mutations[2].type', '"attributes"');
527 shouldBe('mutations[2].attributeName', '"bat"');
528 shouldBe('mutations[2].attributeNamespace', 'null');
530 observer
.observe(div2
, { attributes
: true, subtree
: true });
531 div3
.setAttribute('bar', 'bar');
532 div3
.setAttribute('bat', 'bat');
533 div3
.setAttribute('baz', 'baz');
535 setTimeout(finish
, 0);
539 debug('...bar, bat & baz should all be received.');
541 shouldBe('mutations.length', '3');
542 shouldBe('mutations[0].type', '"attributes"');
543 shouldBe('mutations[0].attributeName', '"bar"');
544 shouldBe('mutations[0].attributeNamespace', 'null');
545 shouldBe('mutations[1].type', '"attributes"');
546 shouldBe('mutations[1].attributeName', '"bat"');
547 shouldBe('mutations[1].attributeNamespace', 'null');
548 shouldBe('mutations[2].type', '"attributes"');
549 shouldBe('mutations[2].attributeName', '"baz"');
550 shouldBe('mutations[2].attributeNamespace', 'null');
552 observer
.disconnect();
560 function testAttributeFilterNonHTMLElement() {
565 debug('Testing that setting an attributeFilter filters out namespaced attributes.');
568 observer
= new MutationObserver(function(m
) {
572 path
= document
.createElementNS('http://www.w3.org/2000/svg', 'path');
573 observer
.observe(path
, { attributes
: true, attributeFilter
: ['pathLength'] });
574 path
.setAttributeNS('http://www.w3.org/2000/svg', 'pathLength', '200');
576 setTimeout(finish
, 0);
580 debug('...pathLength should not be received.');
582 shouldBeNull('mutations');
583 observer
.disconnect();
591 function testAttributeFilterNonHTMLDocument() {
592 var svgDoc
, div
, path
;
596 debug('Testing that attributeFilter respects case with non-HTML elements.');
598 svgDoc
= document
.implementation
.createDocument('http://www.w3.org/2000/svg', 'svg');
600 observer
= new MutationObserver(function(m
) {
604 div
= svgDoc
.createElement('div');
605 observer
.observe(div
, { attributes
: true, attributeFilter
: ['ID', 'id', 'booM'] });
606 div
.setAttribute('ID', 'ID');
607 div
.setAttribute('id', 'id');
608 div
.setAttribute('baz', 'baz');
609 div
.setAttribute('booM', 'boom');
610 div
.setAttribute('BOOm', 'boom');
612 setTimeout(finish
, 0);
616 debug('...only ID, id, booM should be received.');
618 shouldBe('mutations.length', '3');
619 shouldBe('mutations[0].type', '"attributes"');
620 shouldBe('mutations[0].attributeName', '"ID"');
621 shouldBe('mutations[0].attributeNamespace', 'null');
622 shouldBe('mutations[1].type', '"attributes"');
623 shouldBe('mutations[1].attributeName', '"id"');
624 shouldBe('mutations[1].attributeNamespace', 'null');
625 shouldBe('mutations[2].type', '"attributes"');
626 shouldBe('mutations[2].attributeName', '"booM"');
627 shouldBe('mutations[2].attributeNamespace', 'null');
629 observer
.disconnect();
637 function testStyleAttributePropertyAccess() {
642 debug('Testing that modifying an elements style property dispatches Mutation Records.');
645 observer
= new MutationObserver(function(m
) {
649 div
= document
.createElement('div');
650 div
.setAttribute('style', 'color: yellow; width: 100px;');
651 observer
.observe(div
, { attributes
: true });
652 div
.style
.color
= 'red';
653 div
.style
.width
= '200px';
654 div
.style
.color
= 'blue';
656 setTimeout(checkAndContinue
, 0);
659 function checkAndContinue() {
660 shouldBe('mutations.length', '3');
661 shouldBe('mutations[0].type', '"attributes"');
662 shouldBe('mutations[0].attributeName', '"style"');
663 shouldBe('mutations[0].oldValue', 'null');
664 shouldBe('mutations[1].type', '"attributes"');
665 shouldBe('mutations[1].attributeName', '"style"');
666 shouldBe('mutations[1].oldValue', 'null');
667 shouldBe('mutations[2].type', '"attributes"');
668 shouldBe('mutations[2].attributeName', '"style"');
669 shouldBe('mutations[2].oldValue', 'null');
672 div
.getAttribute('style');
673 setTimeout(finish
, 0);
677 debug('...mutation record created.');
679 shouldBe('mutations', 'null');
681 observer
.disconnect();
689 function testStyleAttributePropertyAccessOldValue() {
694 debug('Testing that modifying an elements style property dispatches Mutation Records with correct oldValues.');
697 observer
= new MutationObserver(function(m
) {
701 div
= document
.createElement('div');
702 div
.setAttribute('style', 'color: yellow; width: 100px;');
703 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
704 div
.style
.color
= 'red';
705 div
.style
.width
= '200px';
706 div
.style
.color
= 'blue';
708 setTimeout(checkAndContinue
, 0);
711 function checkAndContinue() {
712 shouldBe('mutations.length', '3');
713 shouldBe('mutations[0].type', '"attributes"');
714 shouldBe('mutations[0].attributeName', '"style"');
715 shouldBe('mutations[0].oldValue', '"color: yellow; width: 100px;"');
716 shouldBe('mutations[1].type', '"attributes"');
717 shouldBe('mutations[1].attributeName', '"style"');
718 shouldBe('mutations[1].oldValue', '"color: red; width: 100px;"');
719 shouldBe('mutations[2].type', '"attributes"');
720 shouldBe('mutations[2].attributeName', '"style"');
721 shouldBe('mutations[2].oldValue', '"color: red; width: 200px;"');
724 div
.getAttribute('style');
725 setTimeout(finish
, 0);
729 debug('...mutation record created.');
731 shouldBe('mutations', 'null');
733 observer
.disconnect();
741 function testStyleAttributePropertyAccessIgnoreNoop() {
746 debug('Testing that a no-op style property mutation does not create Mutation Records.');
749 observer
= new MutationObserver(function(m
) {
753 div
= document
.createElement('div');
754 div
.setAttribute('style', 'color: yellow; width: 100px;');
755 observer
.observe(div
, { attributes
: true });
756 div
.style
.removeProperty('height');
758 setTimeout(finish
, 0);
762 shouldBe('mutations', 'null');
764 observer
.disconnect();
772 function testMutateThroughAttrNodeValue() {
776 debug('Test that mutating an attribute through an attr node delivers mutation records');
779 observer
= new MutationObserver(function(mutations
) {
780 window
.mutations
= mutations
;
783 div
= document
.createElement('div');
784 div
.setAttribute('data-test', 'foo');
785 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
786 div
.attributes
['data-test'].value
= 'bar';
788 setTimeout(finish
, 0);
792 shouldBe('mutations.length', '1');
793 shouldBe('mutations[0].target', 'div');
794 shouldBe('mutations[0].type', '"attributes"');
795 shouldBe('mutations[0].attributeName', '"data-test"');
796 shouldBe('mutations[0].oldValue', '"foo"');
798 observer
.disconnect();
806 function testSetAndRemoveAttributeNode() {
810 debug('Test that mutating via setAttributeNode delivers mutation records');
813 observer
= new MutationObserver(function(mutations
) {
814 window
.mutations
= mutations
;
817 div
= document
.createElement('div');
819 div
.setAttribute('data-test', 'foo');
820 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
821 var attr
= document
.createAttribute('data-test');
823 div
.setAttributeNode(attr
);
824 attr
= document
.createAttribute('data-other');
826 div
.setAttributeNode(attr
);
827 div
.removeAttributeNode(div
.attributes
['id']);
829 setTimeout(finish
, 0);
833 shouldBe('mutations.length', '3');
834 shouldBe('mutations[0].target', 'div');
835 shouldBe('mutations[0].type', '"attributes"');
836 shouldBe('mutations[0].attributeName', '"data-test"');
837 shouldBe('mutations[0].oldValue', '"foo"');
838 shouldBe('mutations[1].target', 'div');
839 shouldBe('mutations[1].type', '"attributes"');
840 shouldBe('mutations[1].attributeName', '"data-other"');
841 shouldBe('mutations[1].oldValue', 'null');
842 shouldBe('mutations[2].target', 'div');
843 shouldBe('mutations[2].type', '"attributes"');
844 shouldBe('mutations[2].attributeName', '"id"');
845 shouldBe('mutations[2].oldValue', '"myId"');
847 observer
.disconnect();
855 function testMixedNodeAndElementOperations() {
859 debug('Test that setAttribute on an attribute with an existing Attr delivers mutation records');
862 observer
= new MutationObserver(function(mutations
) {
863 window
.mutations
= mutations
;
866 div
= document
.createElement('div');
867 var attr
= document
.createAttribute('data-test');
869 div
.setAttributeNode(attr
);
870 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
871 div
.setAttribute('data-test', 'bar');
873 setTimeout(finish
, 0);
877 shouldBe('mutations.length', '1');
878 shouldBe('mutations[0].target', 'div');
879 shouldBe('mutations[0].type', '"attributes"');
880 shouldBe('mutations[0].attributeName', '"data-test"');
881 shouldBe('mutations[0].oldValue', '"foo"');
883 observer
.disconnect();
891 function testNamedNodeMapOperations() {
895 debug('Test that setNamedItem and removeNamedItem deliver mutation records');
898 observer
= new MutationObserver(function(mutations
) {
899 window
.mutations
= mutations
;
902 div
= document
.createElement('div');
903 div
.setAttribute('data-test', 'foo');
904 observer
.observe(div
, { attributes
: true, attributeOldValue
: true });
905 var attr
= document
.createAttribute('data-test');
907 div
.attributes
.setNamedItem(attr
);
908 div
.attributes
.removeNamedItem('data-test');
910 setTimeout(finish
, 0);
914 shouldBe('mutations.length', '2');
915 shouldBe('mutations[0].target', 'div');
916 shouldBe('mutations[0].type', '"attributes"');
917 shouldBe('mutations[0].attributeName', '"data-test"');
918 shouldBe('mutations[0].oldValue', '"foo"');
919 shouldBe('mutations[1].target', 'div');
920 shouldBe('mutations[1].type', '"attributes"');
921 shouldBe('mutations[1].attributeName', '"data-test"');
922 shouldBe('mutations[1].oldValue', '"bar"');
924 observer
.disconnect();
935 testMultipleRegistration
,
936 testMultipleObservers
,
939 testOrderingWrtDOMSubtreeModified
,
941 testOldValueAsRequested
,
942 testOldValueUnionMultipleObservations
,
945 testAttributeFilterSubtree
,
946 testAttributeFilterNonHTMLElement
,
947 testAttributeFilterNonHTMLDocument
,
948 testStyleAttributePropertyAccess
,
949 testStyleAttributePropertyAccessOldValue
,
950 testStyleAttributePropertyAccessIgnoreNoop
,
951 testMutateThroughAttrNodeValue
,
952 testSetAndRemoveAttributeNode
,
953 testMixedNodeAndElementOperations
,
954 testNamedNodeMapOperations
958 function runNextTest() {
959 if (testIndex
< tests
.length
)
960 tests
[testIndex
++]();
965 description('Test WebKitMutationObserver.observe on attributes');