[MutationObservers] attributeFilter should be case sensitive at all times
[WebKit-https.git] / LayoutTests / fast / mutation / observe-attributes.html
1 <!DOCTYPE html>
2 <script src="../js/resources/js-test-pre.js"></script>
3 <script>
4
5 window.jsTestIsAsync = true;
6 var mutations, mutations2, mutationsWithOldValue;
7 var calls;
8 var div;
9
10 function testBasic() {
11     var div;
12     var observer;
13
14     function start() {
15         debug('Testing basic aspects of attribute observation.');
16
17         mutations = null;
18         div = document.createElement('div');
19         div.setAttribute('bar', 'foo');
20
21         observer = new WebKitMutationObserver(function(m) {
22             mutations = m;
23         });
24
25         observer.observe(div, { attributes: true, characterData: true });
26         div.setAttribute('foo', 'bar');
27         div.removeAttribute('bar');
28         setTimeout(checkDisconnectAndMutate, 0);
29     }
30
31     function checkDisconnectAndMutate() {
32         debug('...can attribute changes be observed at all');
33
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');
41
42         mutations = null;
43         observer.disconnect();
44         div.setAttribute('foo', 'baz');
45         setTimeout(checkNotDeliveredAndMutateMultiple, 0);
46     }
47
48     function checkNotDeliveredAndMutateMultiple() {
49         debug('...observer.disconnect() should prevent further delivery of mutations.');
50
51         shouldBe('mutations', 'null');
52         observer.observe(div, { attributes: true });
53         div.setAttribute('foo', 'bat');
54         div.setAttribute('bar', 'foo');
55         setTimeout(finish);
56     }
57
58     function finish() {
59         debug('...re-observing after disconnect works with the same observer.');
60
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();
69         debug('');
70         runNextTest();
71     }
72
73     start();
74 }
75
76 function testWrongType() {
77     var div;
78     var observer;
79
80     function start() {
81         debug('Testing that observing without specifying "attributes" does not result in hearing about attribute changes.');
82
83         mutations = null;
84         div = document.createElement('div');
85         observer = new WebKitMutationObserver(function(m) {
86             mutations = m;
87         });
88
89         observer.observe(div, { childList: true, characterData: true });
90         div.setAttribute('foo', 'bar');
91         setTimeout(finish, 0);
92     }
93
94     function finish() {
95         shouldBe('mutations', 'null');
96         observer.disconnect();
97         debug('');
98         runNextTest();
99     }
100
101     start();
102 }
103
104 function testMultipleRegistration() {
105     var div;
106     var observer;
107
108     function start() {
109         debug('Testing that re-observing the same node with the same observer has the effect of resetting the options.');
110
111                 calls = 0;
112         mutations = null;
113         div = document.createElement('div');
114         observer = new WebKitMutationObserver(function(m) {
115             mutations = m;
116                         calls++;
117         });
118
119         observer.observe(div, { attributes: true, characterData: true });
120         observer.observe(div, { attributes: true });
121         div.setAttribute('foo', 'bar');
122         setTimeout(checkDisconnectAndMutate, 0);
123     }
124
125     function checkDisconnectAndMutate() {
126         shouldBe('calls', '1');
127         shouldBe('mutations.length', '1');
128         shouldBe('mutations[0].type', '"attributes"');
129         shouldBe('mutations[0].attributeName', '"foo"');
130         mutations = null;
131         observer.observe(div, { attributes: true, characterData: true });
132         observer.observe(div, { childList: true });
133         div.setAttribute('foo', 'baz');
134         setTimeout(finish, 0);
135     }
136
137     function finish() {
138         shouldBe('mutations', 'null');
139         observer.disconnect();
140         debug('');
141         runNextTest();
142     }
143
144     start();
145 }
146
147 function testMultipleObservers() {
148     var div;
149     var observer;
150     var observer2;
151
152     function start() {
153         debug('Testing that multiple observers can be registered to a given node and both receive mutations.');
154         mutations = null;
155         div = document.createElement('div');
156         observer = new WebKitMutationObserver(function(m) {
157             mutations = m;
158         });
159         observer2 = new WebKitMutationObserver(function(m) {
160             mutations2 = m;
161         });
162         observer.observe(div, { attributes: true });
163         observer2.observe(div, { attributes: true });
164         div.setAttribute('foo', 'bar');
165         setTimeout(finish, 0);
166     }
167
168     function finish() {
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();
177         debug('');
178         runNextTest();
179     }
180
181     start();
182 }
183
184 function testNamespaceURI() {
185     var div;
186     var observer;
187
188     function start() {
189         debug('Testing that "attributeNamespace" value is delivered properly.');
190         mutations = null;
191         div = document.createElement('div');
192         observer = new WebKitMutationObserver(function(m) {
193             mutations = m;
194         });
195
196         observer.observe(div, { attributes: true, childList: true });
197         div.setAttributeNS('http://www.foo.com/bar', 'foo', 'bar');
198         setTimeout(finish, 0);    
199     }
200
201     function finish() {
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();
207         debug('');
208         runNextTest();
209     }
210
211     start();
212 }
213
214 function testPropertyAccess() {
215     var img, a;
216     var observer;
217
218     function start() {
219         debug('Testing that modifications to node properties which delegate to attribute storage deliver mutations.');
220         mutations = null;
221         img = document.createElement('img');
222         a = document.createElement('a');
223
224         observer = new WebKitMutationObserver(function(m) {
225             mutations = m;
226         });
227
228         observer.observe(img, { attributes: true });
229         observer.observe(a, { attributes: true });
230
231         img.src = 'baz.png';
232         a.href = 'foo.html';
233
234         setTimeout(finish, 0);
235     }
236
237     function finish() {
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();
244         debug('');
245         runNextTest();
246     }
247
248     start();
249 }
250
251 function testOrderingWrtDOMSubtreeModified() {
252     var div, div2, subDiv;
253     var observer;
254     var listener;
255
256     function start() {
257         debug('Testing mutation records are enqueued for attributes before DOMSubtreeModified is dispatched.');
258
259         mutations = null;
260         div = document.body.appendChild(document.createElement('div'));
261         div2 = document.body.appendChild(document.createElement('div'));
262
263         subDiv = div.appendChild(document.createElement('div'));
264
265         observer = new WebKitMutationObserver(function(m) {
266             mutations = m;
267         });
268
269         listener = function(e) {
270             div2.setAttribute('baz', 'bat');
271         }
272
273         div.addEventListener('DOMSubtreeModified', listener);
274         observer.observe(subDiv, { attributes: true });
275         observer.observe(div2, { attributes: true });
276
277         subDiv.setAttribute('foo', 'bar');
278
279         setTimeout(finish, 0);
280     }
281
282     function finish() {
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();
291         debug('');
292         runNextTest();
293     }
294
295     start();
296 }
297
298 function testOldValue() {
299     var div;
300     var observer;
301
302     function start() {
303         debug('Testing basic oldValue delivery.');
304         mutations = null;
305         div = document.createElement('div');
306         div.setAttribute('bar', 'boo');
307         
308         observer = new WebKitMutationObserver(function(mutations) {
309             window.mutations = mutations;
310         });
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);
317     }
318
319     function finish() {
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();
331         debug('');
332         runNextTest();
333     }
334
335     start();
336 }
337
338 function testOldValueAsRequested() {
339     var div;
340     var observerWithOldValue;
341     var observer;
342
343     function start() {
344         debug('Testing that oldValue is delivered as requested (or not).');
345         mutationsWithOldValue = null;
346         mutations = null;
347         div = document.createElement('div');
348         div.setAttribute('foo', 'bar');
349         observerWithOldValue = new WebKitMutationObserver(function(mutations) {
350             window.mutationsWithOldValue = mutations;
351         });
352         observer = new WebKitMutationObserver(function(mutations) {
353             window.mutations = mutations;
354         });
355         observerWithOldValue.observe(div, { attributes: true, attributeOldValue: true });
356         observer.observe(div, { attributes: true });
357         div.setAttribute('foo', 'baz');
358         setTimeout(finish, 0);
359     }
360
361     function finish() {
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();
372         debug('');
373         runNextTest();
374     }
375
376     start();
377 }
378
379 function testOldValueUnionMultipleObservations() {
380     var div;
381     var span;
382     var observer;
383
384     function start() {
385         debug('An observer with multiple observations will get attributeOldValue if any entries request it.');
386         mutations = null;
387         div = document.createElement('div');
388         span = div.appendChild(document.createElement('span'));
389         span.setAttribute('foo', 'bar');
390         observer = new WebKitMutationObserver(function(mutations) {
391             window.mutations = mutations;
392         });
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);
397     }
398
399     function finish() {
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();
405         debug('');
406         runNextTest();
407     }
408
409     start();
410 }
411
412 function testIDLAttribute() {
413     var div;
414     var observer;
415
416     function start() {
417         debug('Testing setting an attribute via reflected IDL attribute.');
418         mutations = null;
419         div = document.createElement('div');
420         observer = new WebKitMutationObserver(function(mutations) {
421             window.mutations = mutations;
422         });
423         observer.observe(div, { attributes: true, attributeOldValue: true });
424         div.id = 'foo';
425         div.id = 'bar';
426         div.id = null;
427         setTimeout(finish, 0);
428     }
429
430     function finish() {
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();
442         debug('');
443         runNextTest();
444     }
445
446     start();
447 }
448
449 function testAttributeFilter() {
450     var div, path;
451     var observer;
452
453     function start() {
454         debug('Testing that attributeFilter works as expected and observes case with HTML elements.');
455
456         mutations = null;
457         observer = new WebKitMutationObserver(function(m) {
458             mutations = m;
459         });
460
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');
467
468         setTimeout(finish, 0);
469     }
470
471     function finish() {
472         debug('...only foo and bar should be received.');
473
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();
482         debug('');
483         runNextTest();
484     }
485
486     start();
487 }
488
489 function testAttributeFilterSubtree() {
490     var div, div2, div3;
491     var observer;
492
493     function start() {
494         debug('Testing the behavior of attributeFilter when the same observer observes at multiple nodes in a subtree with different filter options.');
495
496         mutations = null;
497         observer = new WebKitMutationObserver(function(m) {
498             mutations = m;
499         });
500
501         div = document.createElement('div');
502         div2 = div.appendChild(document.createElement('div'));
503         div3 = div2.appendChild(document.createElement('div'));
504
505         observer.observe(div, { attributes: true, subtree: true, attributeFilter: ['foo', 'bar'] });
506         observer.observe(div2, { attributes: true, subtree: true, attributeFilter: ['bar', 'bat'] });
507
508         div3.setAttribute('foo', 'foo');
509         div3.setAttribute('bar', 'bar');
510         div3.setAttribute('bat', 'bat');
511         div3.setAttribute('baz', 'baz');
512
513         setTimeout(checkAndObserveAll, 0);
514     }
515
516     function checkAndObserveAll() {
517         debug('...only foo, bar & bat should be received.');
518
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');
529
530         observer.observe(div2, { attributes: true, subtree: true });
531         div3.setAttribute('bar', 'bar');
532         div3.setAttribute('bat', 'bat');
533         div3.setAttribute('baz', 'baz');
534
535         setTimeout(finish, 0);
536     }
537
538     function finish() {
539         debug('...bar, bat & baz should all be received.');
540
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');
551
552         observer.disconnect();
553         debug('');
554         runNextTest();
555     }
556
557     start();
558 }
559
560 function testAttributeFilterNonHTMLElement() {
561     var path;
562     var observer;
563
564     function start() {
565         debug('Testing that attributeFilter respects case with non-HTML elements.');
566
567         mutations = null;
568         observer = new WebKitMutationObserver(function(m) {
569             mutations = m;
570         });
571
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');
575         path.setAttributeNS('http://www.w3.org/2000/svg', 'pathlength', '200');
576
577         setTimeout(finish, 0);
578     }
579
580     function finish() {
581         debug('...pathLength should be received.');
582
583         shouldBe('mutations.length', '1');
584         shouldBe('mutations[0].type', '"attributes"');
585         shouldBe('mutations[0].attributeName', '"pathLength"');
586         shouldBe('mutations[0].attributeNamespace', '"http://www.w3.org/2000/svg"');
587         observer.disconnect();
588         debug('');
589         runNextTest();
590     }
591
592     start();
593 }
594
595 function testAttributeFilterNonHTMLDocument() {
596     var svgDoc, div, path;
597     var observer;
598
599     function start() {
600         debug('Testing that attributeFilter respects case with non-HTML elements.');
601
602         svgDoc = document.implementation.createDocument('http://www.w3.org/2000/svg', 'svg', 'svg');
603         mutations = null;
604         observer = new WebKitMutationObserver(function(m) {
605             mutations = m;
606         });
607
608         div = svgDoc.createElement('div');
609         observer.observe(div, { attributes: true, attributeFilter: ['ID', 'id', 'booM'] });
610         div.setAttribute('ID', 'ID');
611         div.setAttribute('id', 'id');
612         div.setAttribute('baz', 'baz');
613         div.setAttribute('booM', 'boom');
614         div.setAttribute('BOOm', 'boom');
615
616         path = svgDoc.createElementNS('http://www.w3.org/2000/svg', 'path');
617         observer.observe(path, { attributes: true, attributeFilter: ['pathLength'] });
618         path.setAttributeNS('http://www.w3.org/2000/svg', 'pathLength', '200');
619         path.setAttributeNS('http://www.w3.org/2000/svg', 'pathlength', '200');
620
621         setTimeout(finish, 0);
622     }
623
624     function finish() {
625         debug('...only ID, id, booM, pathLength should be received.');
626
627         shouldBe('mutations.length', '4');
628         shouldBe('mutations[0].type', '"attributes"');
629         shouldBe('mutations[0].attributeName', '"ID"');
630         shouldBe('mutations[0].attributeNamespace', 'null');
631         shouldBe('mutations[1].type', '"attributes"');
632         shouldBe('mutations[1].attributeName', '"id"');
633         shouldBe('mutations[1].attributeNamespace', 'null');
634         shouldBe('mutations[2].type', '"attributes"');
635         shouldBe('mutations[2].attributeName', '"booM"');
636         shouldBe('mutations[2].attributeNamespace', 'null');
637         shouldBe('mutations[3].type', '"attributes"');
638         shouldBe('mutations[3].attributeName', '"pathLength"');
639         shouldBe('mutations[3].attributeNamespace', '"http://www.w3.org/2000/svg"');
640
641         observer.disconnect();
642         debug('');
643         runNextTest();
644     }
645
646     start();
647 }
648
649 function testStyleAttributePropertyAccess() {
650     var div, path;
651     var observer;
652
653     function start() {
654         debug('Testing that modifying an elements style property dispatches Mutation Records.');
655
656         mutations = null;
657         observer = new WebKitMutationObserver(function(m) {
658             mutations = m;
659         });
660
661         div = document.createElement('div');
662         div.setAttribute('style', 'color: yellow; width: 100px; ');
663         observer.observe(div, { attributes: true });
664         div.style.color = 'red';
665         div.style.width = '200px';
666         div.style.color = 'blue';
667
668         setTimeout(checkAndContinue, 0);
669     }
670
671     function checkAndContinue() {
672         shouldBe('mutations.length', '3');
673         shouldBe('mutations[0].type', '"attributes"');
674         shouldBe('mutations[0].attributeName', '"style"');
675         shouldBe('mutations[0].oldValue', 'null');
676         shouldBe('mutations[1].type', '"attributes"');
677         shouldBe('mutations[1].attributeName', '"style"');
678         shouldBe('mutations[1].oldValue', 'null');
679         shouldBe('mutations[2].type', '"attributes"');
680         shouldBe('mutations[2].attributeName', '"style"');
681         shouldBe('mutations[2].oldValue', 'null');
682
683         mutations = null;
684         div.getAttribute('style');
685         setTimeout(finish, 0);
686     }
687
688     function finish() {
689         debug('...mutation record created.');
690
691         shouldBe('mutations', 'null');
692
693         observer.disconnect();
694         debug('');
695         runNextTest();
696     }
697
698     start();
699 }
700
701 function testStyleAttributePropertyAccessOldValue() {
702     var div, path;
703     var observer;
704
705     function start() {
706         debug('Testing that modifying an elements style property dispatches Mutation Records with correct oldValues.');
707
708         mutations = null;
709         observer = new WebKitMutationObserver(function(m) {
710             mutations = m;
711         });
712
713         div = document.createElement('div');
714         div.setAttribute('style', 'color: yellow; width: 100px; ');
715         observer.observe(div, { attributes: true, attributeOldValue: true });
716         div.style.color = 'red';
717         div.style.width = '200px';
718         div.style.color = 'blue';
719
720         setTimeout(checkAndContinue, 0);
721     }
722
723     function checkAndContinue() {
724         shouldBe('mutations.length', '3');
725         shouldBe('mutations[0].type', '"attributes"');
726         shouldBe('mutations[0].attributeName', '"style"');
727         shouldBe('mutations[0].oldValue', '"color: yellow; width: 100px; "');
728         shouldBe('mutations[1].type', '"attributes"');
729         shouldBe('mutations[1].attributeName', '"style"');
730         shouldBe('mutations[1].oldValue', '"width: 100px; color: red; "');
731         shouldBe('mutations[2].type', '"attributes"');
732         shouldBe('mutations[2].attributeName', '"style"');
733         shouldBe('mutations[2].oldValue', '"color: red; width: 200px; "');
734
735         mutations = null;
736         div.getAttribute('style');
737         setTimeout(finish, 0);
738     }
739
740     function finish() {
741         debug('...mutation record created.');
742
743         shouldBe('mutations', 'null');
744
745         observer.disconnect();
746         debug('');
747         runNextTest();
748     }
749
750     start();
751 }
752
753 function testStyleAttributePropertyAccessIgnoreNoop() {
754     var div, path;
755     var observer;
756
757     function start() {
758         debug('Testing that a no-op style property mutation does not create Mutation Records.');
759
760         mutations = null;
761         observer = new WebKitMutationObserver(function(m) {
762             mutations = m;
763         });
764
765         div = document.createElement('div');
766         div.setAttribute('style', 'color: yellow; width: 100px; ');
767         observer.observe(div, { attributes: true });
768         div.style.removeProperty('height');
769
770         setTimeout(finish, 0);
771     }
772
773     function finish() {
774         shouldBe('mutations', 'null');
775
776         observer.disconnect();
777         debug('');
778         runNextTest();
779     }
780
781     start();
782 }
783
784 function testMutateThroughAttrNodeValue() {
785     var observer;
786
787     function start() {
788         debug('Test that mutating an attribute through an attr node delivers mutation records');
789
790         mutations = null;
791         observer = new WebKitMutationObserver(function(mutations) {
792             window.mutations = mutations;
793         });
794
795         div = document.createElement('div');
796         div.setAttribute('data-test', 'foo');
797         observer.observe(div, { attributes: true, attributeOldValue: true });
798         div.attributes['data-test'].value = 'bar';
799
800         setTimeout(finish, 0);
801     }
802
803     function finish() {
804         shouldBe('mutations.length', '1');
805         shouldBe('mutations[0].target', 'div');
806         shouldBe('mutations[0].type', '"attributes"');
807         shouldBe('mutations[0].attributeName', '"data-test"');
808         shouldBe('mutations[0].oldValue', '"foo"');
809
810         observer.disconnect();
811         debug('');
812         runNextTest();
813     }
814
815     start();
816 }
817
818 function testMutateThroughAttrNodeChild() {
819     var observer;
820
821     function start() {
822         debug('Test that mutating an attribute by attaching a child to an attr node delivers mutation records');
823
824         mutations = null;
825         observer = new WebKitMutationObserver(function(mutations) {
826             window.mutations = mutations;
827         });
828
829         div = document.createElement('div');
830         div.setAttribute('data-test', 'foo');
831         observer.observe(div, { attributes: true, attributeOldValue: true });
832         div.attributes['data-test'].appendChild(document.createTextNode('bar'));
833
834         setTimeout(finish, 0);
835     }
836
837     function finish() {
838         shouldBe('mutations.length', '1');
839         shouldBe('mutations[0].target', 'div');
840         shouldBe('mutations[0].type', '"attributes"');
841         shouldBe('mutations[0].attributeName', '"data-test"');
842         shouldBe('mutations[0].oldValue', '"foo"');
843
844         observer.disconnect();
845         debug('');
846         runNextTest();
847     }
848
849     start();
850 }
851
852 function testSetAndRemoveAttributeNode() {
853     var observer;
854
855     function start() {
856         debug('Test that mutating via setAttributeNode delivers mutation records');
857
858         mutations = null;
859         observer = new WebKitMutationObserver(function(mutations) {
860             window.mutations = mutations;
861         });
862
863         div = document.createElement('div');
864         div.id = 'myId';
865         div.setAttribute('data-test', 'foo');
866         observer.observe(div, { attributes: true, attributeOldValue: true });
867         var attr = document.createAttribute('data-test');
868         attr.value = 'bar';
869         div.setAttributeNode(attr);
870         attr = document.createAttribute('data-other');
871         attr.value = 'baz';
872         div.setAttributeNode(attr);
873         div.removeAttributeNode(div.attributes['id']);
874
875         setTimeout(finish, 0);
876     }
877
878     function finish() {
879         shouldBe('mutations.length', '3');
880         shouldBe('mutations[0].target', 'div');
881         shouldBe('mutations[0].type', '"attributes"');
882         shouldBe('mutations[0].attributeName', '"data-test"');
883         shouldBe('mutations[0].oldValue', '"foo"');
884         shouldBe('mutations[1].target', 'div');
885         shouldBe('mutations[1].type', '"attributes"');
886         shouldBe('mutations[1].attributeName', '"data-other"');
887         shouldBe('mutations[1].oldValue', 'null');
888         shouldBe('mutations[2].target', 'div');
889         shouldBe('mutations[2].type', '"attributes"');
890         shouldBe('mutations[2].attributeName', '"id"');
891         shouldBe('mutations[2].oldValue', '"myId"');
892
893         observer.disconnect();
894         debug('');
895         runNextTest();
896     }
897
898     start();
899 }
900
901 function testMixedNodeAndElementOperations() {
902     var observer;
903
904     function start() {
905         debug('Test that setAttribute on an attribute with an existing Attr delivers mutation records');
906
907         mutations = null;
908         observer = new WebKitMutationObserver(function(mutations) {
909             window.mutations = mutations;
910         });
911
912         div = document.createElement('div');
913         var attr = document.createAttribute('data-test');
914         attr.value = 'foo';
915         div.setAttributeNode(attr);
916         observer.observe(div, { attributes: true, attributeOldValue: true });
917         div.setAttribute('data-test', 'bar');
918
919         setTimeout(finish, 0);
920     }
921
922     function finish() {
923         shouldBe('mutations.length', '1');
924         shouldBe('mutations[0].target', 'div');
925         shouldBe('mutations[0].type', '"attributes"');
926         shouldBe('mutations[0].attributeName', '"data-test"');
927         shouldBe('mutations[0].oldValue', '"foo"');
928
929         observer.disconnect();
930         debug('');
931         runNextTest();
932     }
933
934     start();
935 }
936
937 function testNamedNodeMapOperations() {
938     var observer;
939
940     function start() {
941         debug('Test that setNamedItem and removeNamedItem deliver mutation records');
942
943         mutations = null;
944         observer = new WebKitMutationObserver(function(mutations) {
945             window.mutations = mutations;
946         });
947
948         div = document.createElement('div');
949         div.setAttribute('data-test', 'foo');
950         observer.observe(div, { attributes: true, attributeOldValue: true });
951         var attr = document.createAttribute('data-test');
952         attr.value = 'bar';
953         div.attributes.setNamedItem(attr);
954         div.attributes.removeNamedItem('data-test');
955
956         setTimeout(finish, 0);
957     }
958
959     function finish() {
960         shouldBe('mutations.length', '2');
961         shouldBe('mutations[0].target', 'div');
962         shouldBe('mutations[0].type', '"attributes"');
963         shouldBe('mutations[0].attributeName', '"data-test"');
964         shouldBe('mutations[0].oldValue', '"foo"');
965         shouldBe('mutations[1].target', 'div');
966         shouldBe('mutations[1].type', '"attributes"');
967         shouldBe('mutations[1].attributeName', '"data-test"');
968         shouldBe('mutations[1].oldValue', '"bar"');
969
970         observer.disconnect();
971         debug('');
972         runNextTest();
973     }
974
975     start();
976 }
977
978 var tests = [
979     testBasic,
980     testWrongType,
981     testMultipleRegistration,
982     testMultipleObservers,
983     testNamespaceURI,
984     testPropertyAccess,
985     testOrderingWrtDOMSubtreeModified,
986     testOldValue,
987     testOldValueAsRequested,
988     testOldValueUnionMultipleObservations,
989     testIDLAttribute,
990     testAttributeFilter,
991     testAttributeFilterSubtree,
992     testAttributeFilterNonHTMLElement,
993     testAttributeFilterNonHTMLDocument,
994     testStyleAttributePropertyAccess,
995     testStyleAttributePropertyAccessOldValue,
996     testStyleAttributePropertyAccessIgnoreNoop,
997     testMutateThroughAttrNodeValue,
998     testMutateThroughAttrNodeChild,
999     testSetAndRemoveAttributeNode,
1000     testMixedNodeAndElementOperations,
1001     testNamedNodeMapOperations
1002 ];
1003 var testIndex = 0;
1004
1005 function runNextTest() {
1006     if (testIndex < tests.length)
1007         tests[testIndex++]();
1008     else
1009         finishJSTest();
1010 }
1011
1012 description('Test WebKitMutationObserver.observe on attributes');
1013
1014 if (!window.WebKitMutationObserver)
1015     testFailed('This test requires ENABLE(MUTATION_OBSERVERS)');
1016 else
1017     runNextTest();
1018
1019 </script>
1020 <script src="../js/resources/js-test-post.js"></script>