blob: db2a01196a722a01d2297986a52881561a230597 [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 () {
Rakina Zata Amnif6881fb2019-06-18 13:11:4694 const instance = document.createElement(customElement.name);
95 assert_array_equals(customElement.takeLog().types(), ['constructed']);
96
97 instance.toggleAttribute('title', true);
98 assert_equals(instance.hasAttribute('title'), true);
99 var logEntries = customElement.takeLog();
100 assert_array_equals(logEntries.types(), ['attributeChanged']);
101 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: '', namespace: null});
102
103 instance.toggleAttribute('title');
104 assert_equals(instance.hasAttribute('title'), false);
105 var logEntries = customElement.takeLog();
106 assert_array_equals(logEntries.types(), ['attributeChanged']);
107 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: '', newValue: null, namespace: null});
108}, 'toggleAttribute must enqueue and invoke attributeChangedCallback');
109
110test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47111 const callsToOld = [];
112 const callsToNew = [];
Ryosuke Niwae09776a2016-09-29 07:14:40113 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47114 CustomElement.prototype.attributeChangedCallback = function (...args) {
115 callsToOld.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40116 }
117 CustomElement.observedAttributes = ['title'];
118 customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47119 CustomElement.prototype.attributeChangedCallback = function (...args) {
120 callsToNew.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40121 }
122
Ryosuke Niwac7fbe2d2016-10-06 06:19:47123 const instance = document.createElement('element-with-mutated-attribute-changed-callback');
Ryosuke Niwae09776a2016-09-29 07:14:40124 instance.setAttribute('title', 'hi');
125 assert_equals(instance.getAttribute('title'), 'hi');
126 assert_array_equals(callsToNew, []);
127 assert_equals(callsToOld.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47128 assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40129}, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
130
131test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47132 const calls = [];
Ryosuke Niwae09776a2016-09-29 07:14:40133 class CustomElement extends HTMLElement {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47134 attributeChangedCallback(...args) {
135 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40136 }
137 }
138 CustomElement.observedAttributes = ['title'];
139 customElements.define('element-not-observing-id-attribute', CustomElement);
140
Ryosuke Niwac7fbe2d2016-10-06 06:19:47141 const instance = document.createElement('element-not-observing-id-attribute');
142 assert_equals(calls.length, 0);
Ryosuke Niwae09776a2016-09-29 07:14:40143 instance.setAttribute('title', 'hi');
144 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47145 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40146 instance.setAttribute('id', 'some');
147 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47148 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
149}, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute');
Ryosuke Niwae09776a2016-09-29 07:14:40150
151test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47152 const calls = [];
Ryosuke Niwae09776a2016-09-29 07:14:40153 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47154 CustomElement.prototype.attributeChangedCallback = function (...args) {
155 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40156 }
157 CustomElement.observedAttributes = ['title', 'lang'];
158 customElements.define('element-with-mutated-observed-attributes', CustomElement);
159 CustomElement.observedAttributes = ['title', 'id'];
160
Ryosuke Niwac7fbe2d2016-10-06 06:19:47161 const instance = document.createElement('element-with-mutated-observed-attributes');
Ryosuke Niwae09776a2016-09-29 07:14:40162 instance.setAttribute('title', 'hi');
163 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47164 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40165
166 instance.setAttribute('id', 'some');
167 assert_equals(calls.length, 1);
168
169 instance.setAttribute('lang', 'en');
170 assert_equals(calls.length, 2);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47171 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40172}, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
173
174test(function () {
175 var calls = [];
176 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47177 CustomElement.prototype.attributeChangedCallback = function (...args) {
178 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40179 }
180 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
181 customElements.define('element-with-generator-observed-attributes', CustomElement);
182
183 var instance = document.createElement('element-with-generator-observed-attributes');
184 instance.setAttribute('lang', 'en');
185 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47186 assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40187
188 instance.setAttribute('lang', 'ja');
189 assert_equals(calls.length, 2);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47190 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue: 'ja', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40191
192 instance.setAttribute('title', 'hello');
193 assert_equals(calls.length, 2);
194
195 instance.setAttribute('style', 'font-size: 2rem');
196 assert_equals(calls.length, 3);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47197 assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValue: 'font-size: 2rem', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40198}, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
199
Ryosuke Niwac7fbe2d2016-10-06 06:19:47200test(function () {
201 var calls = [];
202 class CustomElement extends HTMLElement { }
203 CustomElement.prototype.attributeChangedCallback = function (...args) {
204 calls.push(create_attribute_changed_callback_log(this, ...args));
205 }
206 CustomElement.observedAttributes = ['style'];
207 customElements.define('element-with-style-attribute-observation', CustomElement);
208
209 var instance = document.createElement('element-with-style-attribute-observation');
210 assert_equals(calls.length, 0);
211
212 instance.style.fontSize = '10px';
213 assert_equals(calls.length, 1);
214 assert_attribute_log_entry(calls[0], {name: 'style', oldValue: null, newValue: 'font-size: 10px;', namespace: null});
215
216 instance.style.fontSize = '20px';
217 assert_equals(calls.length, 2);
218 assert_attribute_log_entry(calls[1], {name: 'style', oldValue: 'font-size: 10px;', newValue: 'font-size: 20px;', namespace: null});
219
220}, 'attributedChangedCallback must be enqueued for style attribute change by mutating inline style declaration');
221
222test(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('element-with-no-style-attribute-observation', CustomElement);
230
231 var instance = document.createElement('element-with-no-style-attribute-observation');
232 assert_equals(calls.length, 0);
233 instance.style.fontSize = '10px';
234 assert_equals(calls.length, 0);
235 instance.title = 'hello';
236 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hello', namespace: null});
237}, 'attributedChangedCallback must not be enqueued when mutating inline style declaration if the style attribute is not observed');
238
Edgar Chend4888ba2018-01-23 15:27:55239test(function () {
240 var calls = [];
241 class CustomElement extends HTMLElement { }
242 CustomElement.prototype.attributeChangedCallback = function (...args) {
243 calls.push(create_attribute_changed_callback_log(this, ...args));
244 }
245 CustomElement.observedAttributes = ['title'];
246 customElements.define('parser-created-element', CustomElement);
247 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
248}, 'Upgrading a parser created element must enqueue and invoke attributeChangedCallback for an HTML attribute');
249
250test(function () {
251 var calls = [];
252 class CustomElement extends HTMLElement { }
253 CustomElement.prototype.attributeChangedCallback = function (...args) {
254 calls.push(create_attribute_changed_callback_log(this, ...args));
255 }
256 CustomElement.observedAttributes = ['title'];
257 customElements.define('cloned-element-with-attribute', CustomElement);
258
259 var instance = document.createElement('cloned-element-with-attribute');
260 assert_equals(calls.length, 0);
261 instance.title = '';
262 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
263
264 calls = [];
265 var clone = instance.cloneNode(false);
266 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
267}, 'Upgrading a cloned element must enqueue and invoke attributeChangedCallback for an HTML attribute');
268
Ryosuke Niwae09776a2016-09-29 07:14:40269</script>
270</body>
271</html>