| 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> |
| 10 </head> | 11 </head> |
| 11 <body> | 12 <body> |
| 12 <div id="log"></div> | 13 <div id="log"></div> |
| 13 <script> | 14 <script> |
| 14 | 15 |
| 15 var argumentList = []; | 16 var customElement = define_new_custom_element(['title', 'id', 'r']); |
| 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); | |
| 23 | 17 |
| 24 test(function () { | 18 test(function () { |
| 25 var instance = document.createElement('my-custom-element'); | 19 const instance = document.createElement(customElement.name); |
| 26 argumentList = []; | 20 assert_array_equals(customElement.takeLog().types(), ['constructed']); |
| 27 | 21 |
| 28 instance.setAttribute('title', 'foo'); | 22 instance.setAttribute('title', 'foo'); |
| 29 assert_equals(instance.getAttribute('title'), 'foo'); | 23 assert_equals(instance.getAttribute('title'), 'foo'); |
| 30 assert_equals(argumentList.length, 1); | 24 var logEntries = customElement.takeLog(); |
| 31 assert_equals(argumentList[0].value, 'foo'); | 25 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 32 assert_array_equals(argumentList[0].arguments, ['title', null, 'foo', null])
; | 26 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null
, newValue: 'foo', namespace: null}); |
| 33 | 27 |
| 34 instance.removeAttribute('title'); | 28 instance.removeAttribute('title'); |
| 35 assert_equals(instance.getAttribute('title'), null); | 29 assert_equals(instance.getAttribute('title'), null); |
| 36 assert_equals(argumentList.length, 2); | 30 var logEntries = customElement.takeLog(); |
| 37 assert_equals(argumentList[1].value, null); | 31 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 38 assert_array_equals(argumentList[1].arguments, ['title', 'foo', null, null])
; | 32 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'foo
', newValue: null, namespace: null}); |
| 39 | |
| 40 }, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCal
lback'); | 33 }, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCal
lback'); |
| 41 | 34 |
| 42 test(function () { | 35 test(function () { |
| 43 var instance = document.createElement('my-custom-element'); | 36 var instance = document.createElement(customElement.name); |
| 44 argumentList = []; | 37 assert_array_equals(customElement.takeLog().types(), ['constructed']); |
| 45 | 38 |
| 46 instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello'); | 39 instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello'); |
| 47 assert_equals(instance.getAttribute('title'), 'hello'); | 40 assert_equals(instance.getAttribute('title'), 'hello'); |
| 48 assert_equals(argumentList.length, 1); | 41 var logEntries = customElement.takeLog(); |
| 49 assert_equals(argumentList[0].value, 'hello'); | 42 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 50 assert_array_equals(argumentList[0].arguments, ['title', null, 'hello', 'htt
p://www.w3.org/2000/svg']); | 43 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null
, newValue: 'hello', namespace: 'http://www.w3.org/2000/svg'}); |
| 51 | 44 |
| 52 instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title'); | 45 instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title'); |
| 53 assert_equals(instance.getAttribute('title'), null); | 46 assert_equals(instance.getAttribute('title'), null); |
| 54 assert_equals(argumentList.length, 2); | 47 var logEntries = customElement.takeLog(); |
| 55 assert_equals(argumentList[1].value, null); | 48 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 56 assert_array_equals(argumentList[1].arguments, ['title', 'hello', null, 'htt
p://www.w3.org/2000/svg']); | 49 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hel
lo', newValue: null, namespace: 'http://www.w3.org/2000/svg'}); |
| 57 | |
| 58 }, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChange
dCallback'); | 50 }, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChange
dCallback'); |
| 59 | 51 |
| 60 test(function () { | 52 test(function () { |
| 61 var instance = document.createElement('my-custom-element'); | 53 var instance = document.createElement(customElement.name); |
| 62 argumentList = []; | 54 assert_array_equals(customElement.takeLog().types(), ['constructed']); |
| 63 | 55 |
| 64 var attr = document.createAttribute('id'); | 56 var attr = document.createAttribute('id'); |
| 65 attr.value = 'bar'; | 57 attr.value = 'bar'; |
| 66 instance.setAttributeNode(attr); | 58 instance.setAttributeNode(attr); |
| 67 | 59 |
| 68 assert_equals(instance.getAttribute('id'), 'bar'); | 60 assert_equals(instance.getAttribute('id'), 'bar'); |
| 69 assert_equals(argumentList.length, 1); | 61 var logEntries = customElement.takeLog(); |
| 70 assert_equals(argumentList[0].value, 'bar'); | 62 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 71 assert_array_equals(argumentList[0].arguments, ['id', null, 'bar', null]); | 63 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, n
ewValue: 'bar', namespace: null}); |
| 72 | 64 |
| 73 instance.removeAttributeNode(attr); | 65 instance.removeAttributeNode(attr); |
| 74 assert_equals(instance.getAttribute('id'), null); | 66 assert_equals(instance.getAttribute('id'), null); |
| 75 assert_equals(argumentList.length, 2); | 67 var logEntries = customElement.takeLog(); |
| 76 assert_equals(argumentList[1].value, null); | 68 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 77 assert_array_equals(argumentList[1].arguments, ['id', 'bar', null, null]); | 69 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: 'bar',
newValue: null, namespace: null}); |
| 78 | 70 }, 'setAttributeNode and removeAttributeNode must enqueue and invoke attributeCh
angedCallback for an HTML attribute'); |
| 79 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChan
gedCallback'); | |
| 80 | 71 |
| 81 test(function () { | 72 test(function () { |
| 82 var instance = document.createElement('my-custom-element'); | 73 const instance = document.createElement(customElement.name); |
| 83 argumentList = []; | 74 assert_array_equals(customElement.takeLog().types(), ['constructed']); |
| 84 | 75 |
| 85 var attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r'); | 76 const attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r'); |
| 86 attr.value = '100'; | 77 attr.value = '100'; |
| 87 instance.setAttributeNode(attr); | 78 instance.setAttributeNode(attr); |
| 88 | 79 |
| 89 assert_equals(instance.getAttribute('r'), '100'); | 80 assert_equals(instance.getAttribute('r'), '100'); |
| 90 assert_equals(argumentList.length, 1); | 81 var logEntries = customElement.takeLog(); |
| 91 assert_equals(argumentList[0].value, '100'); | 82 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 92 assert_array_equals(argumentList[0].arguments, ['r', null, '100', 'http://ww
w.w3.org/2000/svg']); | 83 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: null, ne
wValue: '100', namespace: 'http://www.w3.org/2000/svg'}); |
| 93 | 84 |
| 94 instance.removeAttributeNode(attr); | 85 instance.removeAttributeNode(attr); |
| 95 assert_equals(instance.getAttribute('r'), null); | 86 assert_equals(instance.getAttribute('r'), null); |
| 96 assert_equals(argumentList.length, 2); | 87 var logEntries = customElement.takeLog(); |
| 97 assert_equals(argumentList[1].value, null); | 88 assert_array_equals(logEntries.types(), ['attributeChanged']); |
| 98 assert_array_equals(argumentList[1].arguments, ['r', '100', null, 'http://ww
w.w3.org/2000/svg']); | 89 assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: '100', n
ewValue: null, namespace: 'http://www.w3.org/2000/svg'}); |
| 99 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChan
gedCallback'); | 90 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChan
gedCallback for an SVG attribute'); |
| 100 | 91 |
| 101 test(function () { | 92 test(function () { |
| 102 var callsToOld = []; | 93 const callsToOld = []; |
| 103 var callsToNew = []; | 94 const callsToNew = []; |
| 104 class CustomElement extends HTMLElement { } | 95 class CustomElement extends HTMLElement { } |
| 105 CustomElement.prototype.attributeChangedCallback = function () { | 96 CustomElement.prototype.attributeChangedCallback = function (...args) { |
| 106 callsToOld.push(Array.from(arguments)); | 97 callsToOld.push(create_attribute_changed_callback_log(this, ...args)); |
| 107 } | 98 } |
| 108 CustomElement.observedAttributes = ['title']; | 99 CustomElement.observedAttributes = ['title']; |
| 109 customElements.define('element-with-mutated-attribute-changed-callback', Cus
tomElement); | 100 customElements.define('element-with-mutated-attribute-changed-callback', Cus
tomElement); |
| 110 CustomElement.prototype.attributeChangedCallback = function () { | 101 CustomElement.prototype.attributeChangedCallback = function (...args) { |
| 111 callsToNew.push(Array.from(arguments)); | 102 callsToNew.push(create_attribute_changed_callback_log(this, ...args)); |
| 112 } | 103 } |
| 113 | 104 |
| 114 var instance = document.createElement('element-with-mutated-attribute-change
d-callback'); | 105 const instance = document.createElement('element-with-mutated-attribute-chan
ged-callback'); |
| 115 instance.setAttribute('title', 'hi'); | 106 instance.setAttribute('title', 'hi'); |
| 116 assert_equals(instance.getAttribute('title'), 'hi'); | 107 assert_equals(instance.getAttribute('title'), 'hi'); |
| 117 assert_array_equals(callsToNew, []); | 108 assert_array_equals(callsToNew, []); |
| 118 assert_equals(callsToOld.length, 1); | 109 assert_equals(callsToOld.length, 1); |
| 119 assert_array_equals(callsToOld[0], ['title', null, 'hi', null]); | 110 assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, ne
wValue: 'hi', namespace: null}); |
| 120 }, 'Mutating attributeChangedCallback after calling customElements.define must n
ot affect the callback being invoked'); | 111 }, 'Mutating attributeChangedCallback after calling customElements.define must n
ot affect the callback being invoked'); |
| 121 | 112 |
| 122 test(function () { | 113 test(function () { |
| 123 var calls = []; | 114 const calls = []; |
| 124 class CustomElement extends HTMLElement { | 115 class CustomElement extends HTMLElement { |
| 125 attributeChangedCallback() { | 116 attributeChangedCallback(...args) { |
| 126 calls.push(Array.from(arguments)); | 117 calls.push(create_attribute_changed_callback_log(this, ...args)); |
| 127 } | 118 } |
| 128 } | 119 } |
| 129 CustomElement.observedAttributes = ['title']; | 120 CustomElement.observedAttributes = ['title']; |
| 130 customElements.define('element-not-observing-id-attribute', CustomElement); | 121 customElements.define('element-not-observing-id-attribute', CustomElement); |
| 131 | 122 |
| 132 var instance = document.createElement('element-not-observing-id-attribute'); | 123 const instance = document.createElement('element-not-observing-id-attribute'
); |
| 124 assert_equals(calls.length, 0); |
| 133 instance.setAttribute('title', 'hi'); | 125 instance.setAttribute('title', 'hi'); |
| 134 assert_equals(calls.length, 1); | 126 assert_equals(calls.length, 1); |
| 135 assert_array_equals(calls[0], ['title', null, 'hi', null]); | 127 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hi', namespace: null}); |
| 136 instance.setAttribute('id', 'some'); | 128 instance.setAttribute('id', 'some'); |
| 137 assert_equals(calls.length, 1); | 129 assert_equals(calls.length, 1); |
| 138 }, 'attributedChangedCallback must not be invoked when the observed attributes d
oes not contain the attribute.'); | 130 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hi', namespace: null}); |
| 131 }, 'attributedChangedCallback must not be invoked when the observed attributes d
oes not contain the attribute'); |
| 139 | 132 |
| 140 test(function () { | 133 test(function () { |
| 141 var calls = []; | 134 const calls = []; |
| 142 class CustomElement extends HTMLElement { } | 135 class CustomElement extends HTMLElement { } |
| 143 CustomElement.prototype.attributeChangedCallback = function () { | 136 CustomElement.prototype.attributeChangedCallback = function (...args) { |
| 144 calls.push(Array.from(arguments)); | 137 calls.push(create_attribute_changed_callback_log(this, ...args)); |
| 145 } | 138 } |
| 146 CustomElement.observedAttributes = ['title', 'lang']; | 139 CustomElement.observedAttributes = ['title', 'lang']; |
| 147 customElements.define('element-with-mutated-observed-attributes', CustomElem
ent); | 140 customElements.define('element-with-mutated-observed-attributes', CustomElem
ent); |
| 148 CustomElement.observedAttributes = ['title', 'id']; | 141 CustomElement.observedAttributes = ['title', 'id']; |
| 149 | 142 |
| 150 var instance = document.createElement('element-with-mutated-observed-attribu
tes'); | 143 const instance = document.createElement('element-with-mutated-observed-attri
butes'); |
| 151 instance.setAttribute('title', 'hi'); | 144 instance.setAttribute('title', 'hi'); |
| 152 assert_equals(calls.length, 1); | 145 assert_equals(calls.length, 1); |
| 153 assert_array_equals(calls[0], ['title', null, 'hi', null]); | 146 assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValu
e: 'hi', namespace: null}); |
| 154 | 147 |
| 155 instance.setAttribute('id', 'some'); | 148 instance.setAttribute('id', 'some'); |
| 156 assert_equals(calls.length, 1); | 149 assert_equals(calls.length, 1); |
| 157 | 150 |
| 158 instance.setAttribute('lang', 'en'); | 151 instance.setAttribute('lang', 'en'); |
| 159 assert_equals(calls.length, 2); | 152 assert_equals(calls.length, 2); |
| 160 assert_array_equals(calls[0], ['title', null, 'hi', null]); | 153 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue
: 'en', namespace: null}); |
| 161 assert_array_equals(calls[1], ['lang', null, 'en', null]); | |
| 162 }, 'Mutating observedAttributes after calling customElements.define must not aff
ect the set of attributes for which attributedChangedCallback is invoked'); | 154 }, 'Mutating observedAttributes after calling customElements.define must not aff
ect the set of attributes for which attributedChangedCallback is invoked'); |
| 163 | 155 |
| 164 test(function () { | 156 test(function () { |
| 165 var calls = []; | 157 var calls = []; |
| 166 class CustomElement extends HTMLElement { } | 158 class CustomElement extends HTMLElement { } |
| 167 CustomElement.prototype.attributeChangedCallback = function () { | 159 CustomElement.prototype.attributeChangedCallback = function (...args) { |
| 168 calls.push(Array.from(arguments)); | 160 calls.push(create_attribute_changed_callback_log(this, ...args)); |
| 169 } | 161 } |
| 170 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield
'lang'; yield 'style'; } }; | 162 CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield
'lang'; yield 'style'; } }; |
| 171 customElements.define('element-with-generator-observed-attributes', CustomEl
ement); | 163 customElements.define('element-with-generator-observed-attributes', CustomEl
ement); |
| 172 | 164 |
| 173 var instance = document.createElement('element-with-generator-observed-attri
butes'); | 165 var instance = document.createElement('element-with-generator-observed-attri
butes'); |
| 174 instance.setAttribute('lang', 'en'); | 166 instance.setAttribute('lang', 'en'); |
| 175 assert_equals(calls.length, 1); | 167 assert_equals(calls.length, 1); |
| 176 assert_array_equals(calls[0], ['lang', null, 'en', null]); | 168 assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue
: 'en', namespace: null}); |
| 177 | 169 |
| 178 instance.setAttribute('lang', 'ja'); | 170 instance.setAttribute('lang', 'ja'); |
| 179 assert_equals(calls.length, 2); | 171 assert_equals(calls.length, 2); |
| 180 assert_array_equals(calls[1], ['lang', 'en', 'ja', null]); | 172 assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue
: 'ja', namespace: null}); |
| 181 | 173 |
| 182 instance.setAttribute('title', 'hello'); | 174 instance.setAttribute('title', 'hello'); |
| 183 assert_equals(calls.length, 2); | 175 assert_equals(calls.length, 2); |
| 184 | 176 |
| 185 instance.setAttribute('style', 'font-size: 2rem'); | 177 instance.setAttribute('style', 'font-size: 2rem'); |
| 186 assert_equals(calls.length, 3); | 178 assert_equals(calls.length, 3); |
| 187 assert_array_equals(calls[2], ['style', null, 'font-size: 2rem', null]); | 179 assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValu
e: 'font-size: 2rem', namespace: null}); |
| 188 }, 'attributedChangedCallback must be enqueued for attributes specified in a non
-Array iterable observedAttributes'); | 180 }, 'attributedChangedCallback must be enqueued for attributes specified in a non
-Array iterable observedAttributes'); |
| 189 | 181 |
| 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 |
| 190 </script> | 221 </script> |
| 191 </body> | 222 </body> |
| 192 </html> | 223 </html> |
| OLD | NEW |