blob: bd467912b999596f1a8523928d7db2cdd09f64c7 [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>
14<script>
15
Ryosuke Niwac7fbe2d2016-10-06 06:19:4716var customElement = define_new_custom_element(['title', 'id', 'r']);
Ryosuke Niwae09776a2016-09-29 07:14:4017
18test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4719 const instance = document.createElement(customElement.name);
20 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4021
22 instance.setAttribute('title', 'foo');
23 assert_equals(instance.getAttribute('title'), 'foo');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4724 var logEntries = customElement.takeLog();
25 assert_array_equals(logEntries.types(), ['attributeChanged']);
26 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'foo', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:4027
28 instance.removeAttribute('title');
29 assert_equals(instance.getAttribute('title'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4730 var logEntries = customElement.takeLog();
31 assert_array_equals(logEntries.types(), ['attributeChanged']);
32 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'foo', newValue: null, namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:4033}, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCallback');
34
35test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4736 var instance = document.createElement(customElement.name);
37 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4038
39 instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello');
40 assert_equals(instance.getAttribute('title'), 'hello');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4741 var logEntries = customElement.takeLog();
42 assert_array_equals(logEntries.types(), ['attributeChanged']);
43 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:4044
45 instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title');
46 assert_equals(instance.getAttribute('title'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4747 var logEntries = customElement.takeLog();
48 assert_array_equals(logEntries.types(), ['attributeChanged']);
49 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:4050}, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChangedCallback');
51
52test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4753 var instance = document.createElement(customElement.name);
54 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4055
56 var attr = document.createAttribute('id');
57 attr.value = 'bar';
58 instance.setAttributeNode(attr);
59
60 assert_equals(instance.getAttribute('id'), 'bar');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4761 var logEntries = customElement.takeLog();
62 assert_array_equals(logEntries.types(), ['attributeChanged']);
63 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'bar', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:4064
65 instance.removeAttributeNode(attr);
66 assert_equals(instance.getAttribute('id'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4767 var logEntries = customElement.takeLog();
68 assert_array_equals(logEntries.types(), ['attributeChanged']);
69 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: 'bar', newValue: null, namespace: null});
70}, 'setAttributeNode and removeAttributeNode must enqueue and invoke attributeChangedCallback for an HTML attribute');
Ryosuke Niwae09776a2016-09-29 07:14:4071
72test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4773 const instance = document.createElement(customElement.name);
74 assert_array_equals(customElement.takeLog().types(), ['constructed']);
Ryosuke Niwae09776a2016-09-29 07:14:4075
Ryosuke Niwac7fbe2d2016-10-06 06:19:4776 const attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r');
Ryosuke Niwae09776a2016-09-29 07:14:4077 attr.value = '100';
78 instance.setAttributeNode(attr);
79
80 assert_equals(instance.getAttribute('r'), '100');
Ryosuke Niwac7fbe2d2016-10-06 06:19:4781 var logEntries = customElement.takeLog();
82 assert_array_equals(logEntries.types(), ['attributeChanged']);
83 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:4084
85 instance.removeAttributeNode(attr);
86 assert_equals(instance.getAttribute('r'), null);
Ryosuke Niwac7fbe2d2016-10-06 06:19:4787 var logEntries = customElement.takeLog();
88 assert_array_equals(logEntries.types(), ['attributeChanged']);
89 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: '100', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
90}, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback for an SVG attribute');
Ryosuke Niwae09776a2016-09-29 07:14:4091
92test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:4793 const callsToOld = [];
94 const callsToNew = [];
Ryosuke Niwae09776a2016-09-29 07:14:4095 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:4796 CustomElement.prototype.attributeChangedCallback = function (...args) {
97 callsToOld.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:4098 }
99 CustomElement.observedAttributes = ['title'];
100 customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47101 CustomElement.prototype.attributeChangedCallback = function (...args) {
102 callsToNew.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40103 }
104
Ryosuke Niwac7fbe2d2016-10-06 06:19:47105 const instance = document.createElement('element-with-mutated-attribute-changed-callback');
Ryosuke Niwae09776a2016-09-29 07:14:40106 instance.setAttribute('title', 'hi');
107 assert_equals(instance.getAttribute('title'), 'hi');
108 assert_array_equals(callsToNew, []);
109 assert_equals(callsToOld.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47110 assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40111}, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
112
113test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47114 const calls = [];
Ryosuke Niwae09776a2016-09-29 07:14:40115 class CustomElement extends HTMLElement {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47116 attributeChangedCallback(...args) {
117 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40118 }
119 }
120 CustomElement.observedAttributes = ['title'];
121 customElements.define('element-not-observing-id-attribute', CustomElement);
122
Ryosuke Niwac7fbe2d2016-10-06 06:19:47123 const instance = document.createElement('element-not-observing-id-attribute');
124 assert_equals(calls.length, 0);
Ryosuke Niwae09776a2016-09-29 07:14:40125 instance.setAttribute('title', 'hi');
126 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47127 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40128 instance.setAttribute('id', 'some');
129 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47130 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
131}, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute');
Ryosuke Niwae09776a2016-09-29 07:14:40132
133test(function () {
Ryosuke Niwac7fbe2d2016-10-06 06:19:47134 const calls = [];
Ryosuke Niwae09776a2016-09-29 07:14:40135 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47136 CustomElement.prototype.attributeChangedCallback = function (...args) {
137 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40138 }
139 CustomElement.observedAttributes = ['title', 'lang'];
140 customElements.define('element-with-mutated-observed-attributes', CustomElement);
141 CustomElement.observedAttributes = ['title', 'id'];
142
Ryosuke Niwac7fbe2d2016-10-06 06:19:47143 const instance = document.createElement('element-with-mutated-observed-attributes');
Ryosuke Niwae09776a2016-09-29 07:14:40144 instance.setAttribute('title', 'hi');
145 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47146 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40147
148 instance.setAttribute('id', 'some');
149 assert_equals(calls.length, 1);
150
151 instance.setAttribute('lang', 'en');
152 assert_equals(calls.length, 2);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47153 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40154}, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
155
156test(function () {
157 var calls = [];
158 class CustomElement extends HTMLElement { }
Ryosuke Niwac7fbe2d2016-10-06 06:19:47159 CustomElement.prototype.attributeChangedCallback = function (...args) {
160 calls.push(create_attribute_changed_callback_log(this, ...args));
Ryosuke Niwae09776a2016-09-29 07:14:40161 }
162 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
163 customElements.define('element-with-generator-observed-attributes', CustomElement);
164
165 var instance = document.createElement('element-with-generator-observed-attributes');
166 instance.setAttribute('lang', 'en');
167 assert_equals(calls.length, 1);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47168 assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40169
170 instance.setAttribute('lang', 'ja');
171 assert_equals(calls.length, 2);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47172 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue: 'ja', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40173
174 instance.setAttribute('title', 'hello');
175 assert_equals(calls.length, 2);
176
177 instance.setAttribute('style', 'font-size: 2rem');
178 assert_equals(calls.length, 3);
Ryosuke Niwac7fbe2d2016-10-06 06:19:47179 assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValue: 'font-size: 2rem', namespace: null});
Ryosuke Niwae09776a2016-09-29 07:14:40180}, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
181
Ryosuke Niwac7fbe2d2016-10-06 06:19:47182test(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', CustomElement);
190
191 var instance = document.createElement('element-with-style-attribute-observation');
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, newValue: '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: 10px;', newValue: 'font-size: 20px;', namespace: null});
201
202}, 'attributedChangedCallback must be enqueued for style attribute change by mutating inline style declaration');
203
204test(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', CustomElement);
212
213 var instance = document.createElement('element-with-no-style-attribute-observation');
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, newValue: 'hello', namespace: null});
219}, 'attributedChangedCallback must not be enqueued when mutating inline style declaration if the style attribute is not observed');
220
Ryosuke Niwae09776a2016-09-29 07:14:40221</script>
222</body>
223</html>