blob: 5090bfbfbfecdfbfbfb7c52579824cd333c93cdc [file] [log] [blame]
Ryosuke Niwae09776a2016-09-29 07:14:401<!DOCTYPE html>
2<html>
3<head>
4<title>Custom Elements: attributeChangedCallback</title>
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">
7<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-attribute-changed-callback">
8<script src="/resources/testharness.js"></script>
9<script src="/resources/testharnessreport.js"></script>
Ryosuke Niwac7fbe2d2016-10-06 06:19:4710<script src="resources/custom-elements-helpers.js"></script>
Ryosuke Niwae09776a2016-09-29 07:14:4011</head>
12<body>
13<div id="log"></div>
Edgar Chend4888ba2018-01-23 15:27:5514<parser-created-element title></parser-created-element>
Ryosuke Niwae09776a2016-09-29 07:14:4015<script>
16
Ryosuke Niwac7fbe2d2016-10-06 06:19:4717var customElement = define_new_custom_element(['title', 'id', 'r']);
Ryosuke Niwae09776a2016-09-29 07:14:4018
19test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4720 const instance = document.createElement(customElement.name);
21 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4022
23 instance.setAttribute('title', 'foo');
24 assert_equals(instance.getAttribute('title'), 'foo');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4725 var logEntries = customElement.takeLog();
26 assert_array_equals(logEntries.types(), ['attributeChanged']);
27 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'foo', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:4028
29 instance.removeAttribute('title');
30 assert_equals(instance.getAttribute('title'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4731 var logEntries = customElement.takeLog();
32 assert_array_equals(logEntries.types(), ['attributeChanged']);
33 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'foo', newValue: null, namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:4034}, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCallback');
35
36test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4737 var instance = document.createElement(customElement.name);
38 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4039
40 instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello');
41 assert_equals(instance.getAttribute('title'), 'hello');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4742 var logEntries = customElement.takeLog();
43 assert_array_equals(logEntries.types(), ['attributeChanged']);
44 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'hello', namespace: 'http://www.w3.org/2000/svg'});
Ryosuke Niwae09776a2016-09-29 07:14:4045
46 instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title');
47 assert_equals(instance.getAttribute('title'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4748 var logEntries = customElement.takeLog();
49 assert_array_equals(logEntries.types(), ['attributeChanged']);
50 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
Ryosuke Niwae09776a2016-09-29 07:14:4051}, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChangedCallback');
52
53test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4754 var instance = document.createElement(customElement.name);
55 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4056
57 var attr = document.createAttribute('id');
58 attr.value = 'bar';
59 instance.setAttributeNode(attr);
60
61 assert_equals(instance.getAttribute('id'), 'bar');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4762 var logEntries = customElement.takeLog();
63 assert_array_equals(logEntries.types(), ['attributeChanged']);
64 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'bar', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:4065
66 instance.removeAttributeNode(attr);
67 assert_equals(instance.getAttribute('id'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4768 var logEntries = customElement.takeLog();
69 assert_array_equals(logEntries.types(), ['attributeChanged']);
70 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: 'bar', newValue: null, namespace: null});
71}, 'setAttributeNode and removeAttributeNode must enqueue and invoke attributeChangedCallback for an HTML attribute');
Ryosuke Niwae09776a2016-09-29 07:14:4072
73test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4774 const instance = document.createElement(customElement.name);
75 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4076
Ryosuke Niwac7fbe2d2016-10-06 06:19:4777 const attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r');
Ryosuke Niwae09776a2016-09-29 07:14:4078 attr.value = '100';
79 instance.setAttributeNode(attr);
80
81 assert_equals(instance.getAttribute('r'), '100');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4782 var logEntries = customElement.takeLog();
83 assert_array_equals(logEntries.types(), ['attributeChanged']);
84 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: null, newValue: '100', namespace: 'http://www.w3.org/2000/svg'});
Ryosuke Niwae09776a2016-09-29 07:14:4085
86 instance.removeAttributeNode(attr);
87 assert_equals(instance.getAttribute('r'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4788 var logEntries = customElement.takeLog();
89 assert_array_equals(logEntries.types(), ['attributeChanged']);
90 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: '100', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
91}, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback for an SVG attribute');
Ryosuke Niwae09776a2016-09-29 07:14:4092
93test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4794 const callsToOld = [];
95 const callsToNew = [];
Ryosuke Niwae09776a2016-09-29 07:14:4096 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:4797 CustomElement.prototype.attributeChangedCallback = function (...args) {
98 callsToOld.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:4099 }
100 CustomElement.observedAttributes = ['title'];
101 customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47102 CustomElement.prototype.attributeChangedCallback = function (...args) {
103 callsToNew.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40104 }
105
Ryosuke Niwac7fbe2d2016-10-06 06:19:47106 const instance = document.createElement('element-with-mutated-attribute-changed-callback');
Ryosuke Niwae09776a2016-09-29 07:14:40107 instance.setAttribute('title', 'hi');
108 assert_equals(instance.getAttribute('title'), 'hi');
109 assert_array_equals(callsToNew, []);
110 assert_equals(callsToOld.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47111 assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40112}, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
113
114test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47115 const calls = [];
Ryosuke Niwae09776a2016-09-29 07:14:40116 class CustomElement extends HTMLElement {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47117 attributeChangedCallback(...args) {
118 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40119 }
120 }
121 CustomElement.observedAttributes = ['title'];
122 customElements.define('element-not-observing-id-attribute', CustomElement);
123
Ryosuke Niwac7fbe2d2016-10-06 06:19:47124 const instance = document.createElement('element-not-observing-id-attribute');
125 assert_equals(calls.length, 0);
Ryosuke Niwae09776a2016-09-29 07:14:40126 instance.setAttribute('title', 'hi');
127 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47128 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40129 instance.setAttribute('id', 'some');
130 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47131 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
132}, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute');
Ryosuke Niwae09776a2016-09-29 07:14:40133
134test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47135 const calls = [];
Ryosuke Niwae09776a2016-09-29 07:14:40136 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47137 CustomElement.prototype.attributeChangedCallback = function (...args) {
138 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40139 }
140 CustomElement.observedAttributes = ['title', 'lang'];
141 customElements.define('element-with-mutated-observed-attributes', CustomElement);
142 CustomElement.observedAttributes = ['title', 'id'];
143
Ryosuke Niwac7fbe2d2016-10-06 06:19:47144 const instance = document.createElement('element-with-mutated-observed-attributes');
Ryosuke Niwae09776a2016-09-29 07:14:40145 instance.setAttribute('title', 'hi');
146 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47147 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40148
149 instance.setAttribute('id', 'some');
150 assert_equals(calls.length, 1);
151
152 instance.setAttribute('lang', 'en');
153 assert_equals(calls.length, 2);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47154 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40155}, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
156
157test(function () {
158 var calls = [];
159 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47160 CustomElement.prototype.attributeChangedCallback = function (...args) {
161 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40162 }
163 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
164 customElements.define('element-with-generator-observed-attributes', CustomElement);
165
166 var instance = document.createElement('element-with-generator-observed-attributes');
167 instance.setAttribute('lang', 'en');
168 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47169 assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40170
171 instance.setAttribute('lang', 'ja');
172 assert_equals(calls.length, 2);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47173 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue: 'ja', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40174
175 instance.setAttribute('title', 'hello');
176 assert_equals(calls.length, 2);
177
178 instance.setAttribute('style', 'font-size: 2rem');
179 assert_equals(calls.length, 3);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47180 assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValue: 'font-size: 2rem', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40181}, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
182
Ryosuke Niwac7fbe2d2016-10-06 06:19:47183test(function () {
184 var calls = [];
185 class CustomElement extends HTMLElement { }
186 CustomElement.prototype.attributeChangedCallback = function (...args) {
187 calls.push(create_attribute_changed_callback_log(this, ...args));
188 }
189 CustomElement.observedAttributes = ['style'];
190 customElements.define('element-with-style-attribute-observation', CustomElement);
191
192 var instance = document.createElement('element-with-style-attribute-observation');
193 assert_equals(calls.length, 0);
194
195 instance.style.fontSize = '10px';
196 assert_equals(calls.length, 1);
197 assert_attribute_log_entry(calls[0], {name: 'style', oldValue: null, newValue: 'font-size: 10px;', namespace: null});
198
199 instance.style.fontSize = '20px';
200 assert_equals(calls.length, 2);
201 assert_attribute_log_entry(calls[1], {name: 'style', oldValue: 'font-size: 10px;', newValue: 'font-size: 20px;', namespace: null});
202
203}, 'attributedChangedCallback must be enqueued for style attribute change by mutating inline style declaration');
204
205test(function () {
206 var calls = [];
207 class CustomElement extends HTMLElement { }
208 CustomElement.prototype.attributeChangedCallback = function (...args) {
209 calls.push(create_attribute_changed_callback_log(this, ...args));
210 }
211 CustomElement.observedAttributes = ['title'];
212 customElements.define('element-with-no-style-attribute-observation', CustomElement);
213
214 var instance = document.createElement('element-with-no-style-attribute-observation');
215 assert_equals(calls.length, 0);
216 instance.style.fontSize = '10px';
217 assert_equals(calls.length, 0);
218 instance.title = 'hello';
219 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hello', namespace: null});
220}, 'attributedChangedCallback must not be enqueued when mutating inline style declaration if the style attribute is not observed');
221
Edgar Chend4888ba2018-01-23 15:27:55222test(function () {
223 var calls = [];
224 class CustomElement extends HTMLElement { }
225 CustomElement.prototype.attributeChangedCallback = function (...args) {
226 calls.push(create_attribute_changed_callback_log(this, ...args));
227 }
228 CustomElement.observedAttributes = ['title'];
229 customElements.define('parser-created-element', CustomElement);
230 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
231}, 'Upgrading a parser created element must enqueue and invoke attributeChangedCallback for an HTML attribute');
232
233test(function () {
234 var calls = [];
235 class CustomElement extends HTMLElement { }
236 CustomElement.prototype.attributeChangedCallback = function (...args) {
237 calls.push(create_attribute_changed_callback_log(this, ...args));
238 }
239 CustomElement.observedAttributes = ['title'];
240 customElements.define('cloned-element-with-attribute', CustomElement);
241
242 var instance = document.createElement('cloned-element-with-attribute');
243 assert_equals(calls.length, 0);
244 instance.title = '';
245 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
246
247 calls = [];
248 var clone = instance.cloneNode(false);
249 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
250}, 'Upgrading a cloned element must enqueue and invoke attributeChangedCallback for an HTML attribute');
251
Ryosuke Niwae09776a2016-09-29 07:14:40252</script>
253</body>
254</html>