OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <html> | 2 <html> |
3 <head> | 3 <head> |
4 <title>Custom Elements: attributeChangedCallback</title> | 4 <title>Custom Elements: attributeChangedCallback</title> |
5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> | 5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> |
6 <meta name="assert" content="attributeChangedCallback must be enqueued whenever
custom element's attribute is added, changed or removed"> | 6 <meta name="assert" content="attributeChangedCallback must be enqueued whenever
custom element's attribute is added, changed or removed"> |
7 <link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-attr
ibute-changed-callback"> | 7 <link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-attr
ibute-changed-callback"> |
8 <script src="/resources/testharness.js"></script> | 8 <script src="/resources/testharness.js"></script> |
9 <script src="/resources/testharnessreport.js"></script> | 9 <script src="/resources/testharnessreport.js"></script> |
10 <script src="resources/custom-elements-helpers.js"></script> | |
11 </head> | 10 </head> |
12 <body> | 11 <body> |
13 <div id="log"></div> | 12 <div id="log"></div> |
14 <script> | 13 <script> |
15 | 14 |
16 var customElement = define_new_custom_element(['title', 'id', 'r']); | 15 var argumentList = []; |
| 16 class MyCustomElement extends HTMLElement { |
| 17 attributeChangedCallback(name, oldValue, newValue, namespace) { |
| 18 argumentList.push({arguments: arguments, value: this.getAttributeNS(name
space, name)}); |
| 19 } |
| 20 } |
| 21 MyCustomElement.observedAttributes = ['title', 'id', 'r']; |
| 22 customElements.define('my-custom-element', MyCustomElement); |
17 | 23 |
18 test(function () { | 24 test(function () { |
19 const instance = document.createElement(customElement.name); | 25 var instance = document.createElement('my-custom-element'); |
20 assert_array_equals(customElement.takeLog().types(), ['constructed']); | 26 argumentList = []; |
21 | 27 |
22 instance.setAttribute('title', 'foo'); | 28 instance.setAttribute('title', 'foo'); |
23 assert_equals(instance.getAttribute('title'), 'foo'); | 29 assert_equals(instance.getAttribute('title'), 'foo'); |
24 var logEntries = customElement.takeLog(); | 30 assert_equals(argumentList.length, 1); |
25 assert_array_equals(logEntries.types(), ['attributeChanged']); | 31 assert_equals(argumentList[0].value, 'foo'); |
26 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null
, newValue: 'foo', namespace: null}); | 32 assert_array_equals(argumentList[0].arguments, ['title', null, 'foo', null])
; |
27 | 33 |
28 instance.removeAttribute('title'); | 34 instance.removeAttribute('title'); |
29 assert_equals(instance.getAttribute('title'), null); | 35 assert_equals(instance.getAttribute('title'), null); |
30 var logEntries = customElement.takeLog(); | 36 assert_equals(argumentList.length, 2); |
31 assert_array_equals(logEntries.types(), ['attributeChanged']); | 37 assert_equals(argumentList[1].value, null); |
32 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'foo
', newValue: null, namespace: null}); | 38 assert_array_equals(argumentList[1].arguments, ['title', 'foo', null, null])
; |
| 39 |
33 }, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCal
lback'); | 40 }, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCal
lback'); |
34 | 41 |
35 test(function () { | 42 test(function () { |
36 var instance = document.createElement(customElement.name); | 43 var instance = document.createElement('my-custom-element'); |
37 assert_array_equals(customElement.takeLog().types(), ['constructed']); | 44 argumentList = []; |
38 | 45 |
39 instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello'); | 46 instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello'); |
40 assert_equals(instance.getAttribute('title'), 'hello'); | 47 assert_equals(instance.getAttribute('title'), 'hello'); |
41 var logEntries = customElement.takeLog(); | 48 assert_equals(argumentList.length, 1); |
42 assert_array_equals(logEntries.types(), ['attributeChanged']); | 49 assert_equals(argumentList[0].value, 'hello'); |
43 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null
, newValue: 'hello', namespace: 'http://www.w3.org/2000/svg'}); | 50 assert_array_equals(argumentList[0].arguments, ['title', null, 'hello', 'htt
p://www.w3.org/2000/svg']); |
44 | 51 |
45 instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title'); | 52 instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title'); |
46 assert_equals(instance.getAttribute('title'), null); | 53 assert_equals(instance.getAttribute('title'), null); |
47 var logEntries = customElement.takeLog(); | 54 assert_equals(argumentList.length, 2); |
48 assert_array_equals(logEntries.types(), ['attributeChanged']); | 55 assert_equals(argumentList[1].value, null); |
49 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hel
lo', newValue: null, namespace: 'http://www.w3.org/2000/svg'}); | 56 assert_array_equals(argumentList[1].arguments, ['title', 'hello', null, 'htt
p://www.w3.org/2000/svg']); |
| 57 |
50 }, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChange
dCallback'); | 58 }, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChange
dCallback'); |
51 | 59 |
52 test(function () { | 60 test(function () { |
53 var instance = document.createElement(customElement.name); | 61 var instance = document.createElement('my-custom-element'); |
54 assert_array_equals(customElement.takeLog().types(), ['constructed']); | 62 argumentList = []; |
55 | 63 |
56 var attr = document.createAttribute('id'); | 64 var attr = document.createAttribute('id'); |
57 attr.value = 'bar'; | 65 attr.value = 'bar'; |
58 instance.setAttributeNode(attr); | 66 instance.setAttributeNode(attr); |
59 | 67 |
60 assert_equals(instance.getAttribute('id'), 'bar'); | 68 assert_equals(instance.getAttribute('id'), 'bar'); |
61 var logEntries = customElement.takeLog(); | 69 assert_equals(argumentList.length, 1); |
62 assert_array_equals(logEntries.types(), ['attributeChanged']); | 70 assert_equals(argumentList[0].value, 'bar'); |
63 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, n
ewValue: 'bar', namespace: null}); | 71 assert_array_equals(argumentList[0].arguments, ['id', null, 'bar', null]); |
64 | 72 |
65 instance.removeAttributeNode(attr); | 73 instance.removeAttributeNode(attr); |
66 assert_equals(instance.getAttribute('id'), null); | 74 assert_equals(instance.getAttribute('id'), null); |
67 var logEntries = customElement.takeLog(); | 75 assert_equals(argumentList.length, 2); |
68 assert_array_equals(logEntries.types(), ['attributeChanged']); | 76 assert_equals(argumentList[1].value, null); |
69 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: 'bar',
newValue: null, namespace: null}); | 77 assert_array_equals(argumentList[1].arguments, ['id', 'bar', null, null]); |
70 }, 'setAttributeNode and removeAttributeNode must enqueue and invoke attributeCh
angedCallback for an HTML attribute'); | 78 |
| 79 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChan
gedCallback'); |
71 | 80 |
72 test(function () { | 81 test(function () { |
73 const instance = document.createElement(customElement.name); | 82 var instance = document.createElement('my-custom-element'); |
74 assert_array_equals(customElement.takeLog().types(), ['constructed']); | 83 argumentList = []; |
75 | 84 |
76 const attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r'); | 85 var attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r'); |
77 attr.value = '100'; | 86 attr.value = '100'; |
78 instance.setAttributeNode(attr); | 87 instance.setAttributeNode(attr); |
79 | 88 |
80 assert_equals(instance.getAttribute('r'), '100'); | 89 assert_equals(instance.getAttribute('r'), '100'); |
81 var logEntries = customElement.takeLog(); | 90 assert_equals(argumentList.length, 1); |
82 assert_array_equals(logEntries.types(), ['attributeChanged']); | 91 assert_equals(argumentList[0].value, '100'); |
83 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: null, ne
wValue: '100', namespace: 'http://www.w3.org/2000/svg'}); | 92 assert_array_equals(argumentList[0].arguments, ['r', null, '100', 'http://ww
w.w3.org/2000/svg']); |
84 | 93 |
85 instance.removeAttributeNode(attr); | 94 instance.removeAttributeNode(attr); |
86 assert_equals(instance.getAttribute('r'), null); | 95 assert_equals(instance.getAttribute('r'), null); |
87 var logEntries = customElement.takeLog(); | 96 assert_equals(argumentList.length, 2); |
88 assert_array_equals(logEntries.types(), ['attributeChanged']); | 97 assert_equals(argumentList[1].value, null); |
89 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: '100', n
ewValue: null, namespace: 'http://www.w3.org/2000/svg'}); | 98 assert_array_equals(argumentList[1].arguments, ['r', '100', null, 'http://ww
w.w3.org/2000/svg']); |
90 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChan
gedCallback for an SVG attribute'); | 99 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChan
gedCallback'); |
91 | 100 |
92 test(function () { | 101 test(function () { |
93 const callsToOld = []; | 102 var callsToOld = []; |
94 const callsToNew = []; | 103 var callsToNew = []; |
95 class CustomElement extends HTMLElement { } | 104 class CustomElement extends HTMLElement { } |
96 CustomElement.prototype.attributeChangedCallback = function (...args) { | 105 CustomElement.prototype.attributeChangedCallback = function () { |
97 callsToOld.push(create_attribute_changed_callback_log(this, ...args)); | 106 callsToOld.push(Array.from(arguments)); |
98 } | 107 } |
99 CustomElement.observedAttributes = ['title']; | 108 CustomElement.observedAttributes = ['title']; |
100 customElements.define('element-with-mutated-attribute-changed-callback', Cus
tomElement); | 109 customElements.define('element-with-mutated-attribute-changed-callback', Cus
tomElement); |
101 CustomElement.prototype.attributeChangedCallback = function (...args) { | 110 CustomElement.prototype.attributeChangedCallback = function () { |
102 callsToNew.push(create_attribute_changed_callback_log(this, ...args)); | 111 callsToNew.push(Array.from(arguments)); |
103 } | 112 } |
104 | 113 |
105 const instance = document.createElement('element-with-mutated-attribute-chan
ged-callback'); | 114 var instance = document.createElement('element-with-mutated-attribute-change
d-callback'); |
106 instance.setAttribute('title', 'hi'); | 115 instance.setAttribute('title', 'hi'); |
107 assert_equals(instance.getAttribute('title'), 'hi'); | 116 assert_equals(instance.getAttribute('title'), 'hi'); |
108 assert_array_equals(callsToNew, []); | 117 assert_array_equals(callsToNew, []); |
109 assert_equals(callsToOld.length, 1); | 118 assert_equals(callsToOld.length, 1); |
110 assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, ne
wValue: 'hi', namespace: null}); | 119 assert_array_equals(callsToOld[0], ['title', null, 'hi', null]); |
111 }, 'Mutating attributeChangedCallback after calling customElements.define must n
ot affect the callback being invoked'); | 120 }, 'Mutating attributeChangedCallback after calling customElements.define must n
ot affect the callback being invoked'); |
112 | 121 |
113 test(function () { | 122 test(function () { |
114 const calls = []; | 123 var calls = []; |
115 class CustomElement extends HTMLElement { | 124 class CustomElement extends HTMLElement { |
116 attributeChangedCallback(...args) { | 125 attributeChangedCallback() { |
117 calls.push(create_attribute_changed_callback_log(this, ...args)); | 126 calls.push(Array.from(arguments)); |
118 } | 127 } |
119 } | 128 } |
120 CustomElement.observedAttributes = ['title']; | 129 CustomElement.observedAttributes = ['title']; |
121 customElements.define('element-not-observing-id-attribute', CustomElement); | 130 customElements.define('element-not-observing-id-attribute', CustomElement); |
122 | 131 |
123 const instance = document.createElement('element-not-observing-id-attribute'
); | 132 var instance = document.createElement('element-not-observing-id-attribute'); |
124 assert_equals(calls.length, 0); | |
125 instance.setAttribute('title', 'hi'); | 133 instance.setAttribute('title', 'hi'); |
126 assert_equals(calls.length, 1); | 134 assert_equals(calls.length, 1); |
127 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hi', namespace: null}); | 135 assert_array_equals(calls[0], ['title', null, 'hi', null]); |
128 instance.setAttribute('id', 'some'); | 136 instance.setAttribute('id', 'some'); |
129 assert_equals(calls.length, 1); | 137 assert_equals(calls.length, 1); |
130 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hi', namespace: null}); | 138 }, 'attributedChangedCallback must not be invoked when the observed attributes d
oes not contain the attribute.'); |
131 }, 'attributedChangedCallback must not be invoked when the observed attributes d
oes not contain the attribute'); | |
132 | 139 |
133 test(function () { | 140 test(function () { |
134 const calls = []; | 141 var calls = []; |
135 class CustomElement extends HTMLElement { } | 142 class CustomElement extends HTMLElement { } |
136 CustomElement.prototype.attributeChangedCallback = function (...args) { | 143 CustomElement.prototype.attributeChangedCallback = function () { |
137 calls.push(create_attribute_changed_callback_log(this, ...args)); | 144 calls.push(Array.from(arguments)); |
138 } | 145 } |
139 CustomElement.observedAttributes = ['title', 'lang']; | 146 CustomElement.observedAttributes = ['title', 'lang']; |
140 customElements.define('element-with-mutated-observed-attributes', CustomElem
ent); | 147 customElements.define('element-with-mutated-observed-attributes', CustomElem
ent); |
141 CustomElement.observedAttributes = ['title', 'id']; | 148 CustomElement.observedAttributes = ['title', 'id']; |
142 | 149 |
143 const instance = document.createElement('element-with-mutated-observed-attri
butes'); | 150 var instance = document.createElement('element-with-mutated-observed-attribu
tes'); |
144 instance.setAttribute('title', 'hi'); | 151 instance.setAttribute('title', 'hi'); |
145 assert_equals(calls.length, 1); | 152 assert_equals(calls.length, 1); |
146 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hi', namespace: null}); | 153 assert_array_equals(calls[0], ['title', null, 'hi', null]); |
147 | 154 |
148 instance.setAttribute('id', 'some'); | 155 instance.setAttribute('id', 'some'); |
149 assert_equals(calls.length, 1); | 156 assert_equals(calls.length, 1); |
150 | 157 |
151 instance.setAttribute('lang', 'en'); | 158 instance.setAttribute('lang', 'en'); |
152 assert_equals(calls.length, 2); | 159 assert_equals(calls.length, 2); |
153 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue
: 'en', namespace: null}); | 160 assert_array_equals(calls[0], ['title', null, 'hi', null]); |
| 161 assert_array_equals(calls[1], ['lang', null, 'en', null]); |
154 }, 'Mutating observedAttributes after calling customElements.define must not aff
ect the set of attributes for which attributedChangedCallback is invoked'); | 162 }, 'Mutating observedAttributes after calling customElements.define must not aff
ect the set of attributes for which attributedChangedCallback is invoked'); |
155 | 163 |
156 test(function () { | 164 test(function () { |
157 var calls = []; | 165 var calls = []; |
158 class CustomElement extends HTMLElement { } | 166 class CustomElement extends HTMLElement { } |
159 CustomElement.prototype.attributeChangedCallback = function (...args) { | 167 CustomElement.prototype.attributeChangedCallback = function () { |
160 calls.push(create_attribute_changed_callback_log(this, ...args)); | 168 calls.push(Array.from(arguments)); |
161 } | 169 } |
162 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield
'lang'; yield 'style'; } }; | 170 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield
'lang'; yield 'style'; } }; |
163 customElements.define('element-with-generator-observed-attributes', CustomEl
ement); | 171 customElements.define('element-with-generator-observed-attributes', CustomEl
ement); |
164 | 172 |
165 var instance = document.createElement('element-with-generator-observed-attri
butes'); | 173 var instance = document.createElement('element-with-generator-observed-attri
butes'); |
166 instance.setAttribute('lang', 'en'); | 174 instance.setAttribute('lang', 'en'); |
167 assert_equals(calls.length, 1); | 175 assert_equals(calls.length, 1); |
168 assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue
: 'en', namespace: null}); | 176 assert_array_equals(calls[0], ['lang', null, 'en', null]); |
169 | 177 |
170 instance.setAttribute('lang', 'ja'); | 178 instance.setAttribute('lang', 'ja'); |
171 assert_equals(calls.length, 2); | 179 assert_equals(calls.length, 2); |
172 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue
: 'ja', namespace: null}); | 180 assert_array_equals(calls[1], ['lang', 'en', 'ja', null]); |
173 | 181 |
174 instance.setAttribute('title', 'hello'); | 182 instance.setAttribute('title', 'hello'); |
175 assert_equals(calls.length, 2); | 183 assert_equals(calls.length, 2); |
176 | 184 |
177 instance.setAttribute('style', 'font-size: 2rem'); | 185 instance.setAttribute('style', 'font-size: 2rem'); |
178 assert_equals(calls.length, 3); | 186 assert_equals(calls.length, 3); |
179 assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValu
e: 'font-size: 2rem', namespace: null}); | 187 assert_array_equals(calls[2], ['style', null, 'font-size: 2rem', null]); |
180 }, 'attributedChangedCallback must be enqueued for attributes specified in a non
-Array iterable observedAttributes'); | 188 }, 'attributedChangedCallback must be enqueued for attributes specified in a non
-Array iterable observedAttributes'); |
181 | 189 |
182 test(function () { | |
183 var calls = []; | |
184 class CustomElement extends HTMLElement { } | |
185 CustomElement.prototype.attributeChangedCallback = function (...args) { | |
186 calls.push(create_attribute_changed_callback_log(this, ...args)); | |
187 } | |
188 CustomElement.observedAttributes = ['style']; | |
189 customElements.define('element-with-style-attribute-observation', CustomElem
ent); | |
190 | |
191 var instance = document.createElement('element-with-style-attribute-observat
ion'); | |
192 assert_equals(calls.length, 0); | |
193 | |
194 instance.style.fontSize = '10px'; | |
195 assert_equals(calls.length, 1); | |
196 assert_attribute_log_entry(calls[0], {name: 'style', oldValue: null, newValu
e: 'font-size: 10px;', namespace: null}); | |
197 | |
198 instance.style.fontSize = '20px'; | |
199 assert_equals(calls.length, 2); | |
200 assert_attribute_log_entry(calls[1], {name: 'style', oldValue: 'font-size: 1
0px;', newValue: 'font-size: 20px;', namespace: null}); | |
201 | |
202 }, 'attributedChangedCallback must be enqueued for style attribute change by mut
ating inline style declaration'); | |
203 | |
204 test(function () { | |
205 var calls = []; | |
206 class CustomElement extends HTMLElement { } | |
207 CustomElement.prototype.attributeChangedCallback = function (...args) { | |
208 calls.push(create_attribute_changed_callback_log(this, ...args)); | |
209 } | |
210 CustomElement.observedAttributes = ['title']; | |
211 customElements.define('element-with-no-style-attribute-observation', CustomE
lement); | |
212 | |
213 var instance = document.createElement('element-with-no-style-attribute-obser
vation'); | |
214 assert_equals(calls.length, 0); | |
215 instance.style.fontSize = '10px'; | |
216 assert_equals(calls.length, 0); | |
217 instance.title = 'hello'; | |
218 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hello', namespace: null}); | |
219 }, 'attributedChangedCallback must not be enqueued when mutating inline style de
claration if the style attribute is not observed'); | |
220 | |
221 </script> | 190 </script> |
222 </body> | 191 </body> |
223 </html> | 192 </html> |
OLD | NEW |