custom-element-reaction-queue.html (10619B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Custom Elements: Each element must have its own custom element reaction queue</title> 5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> 6 <meta name="assert" content="Each element must have its own custom element reaction queue"> 7 <meta name="help" content="https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reaction-queue"> 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 <script src="./resources/custom-elements-helpers.js"></script> 11 </head> 12 <body> 13 <div id="log"></div> 14 <script> 15 16 test_with_window(function (contentWindow) { 17 const contentDocument = contentWindow.document; 18 contentDocument.write('<test-element id="first-element">'); 19 contentDocument.write('<test-element id="second-element">'); 20 21 const element1 = contentDocument.getElementById('first-element'); 22 const element2 = contentDocument.getElementById('second-element'); 23 assert_equals(Object.getPrototypeOf(element1), contentWindow.HTMLElement.prototype); 24 assert_equals(Object.getPrototypeOf(element2), contentWindow.HTMLElement.prototype); 25 26 let log = []; 27 class TestElement extends contentWindow.HTMLElement { 28 constructor() { 29 super(); 30 log.push(create_constructor_log(this)); 31 } 32 connectedCallback(...args) { 33 log.push(create_connected_callback_log(this, ...args)); 34 } 35 attributeChangedCallback(...args) { 36 log.push(create_attribute_changed_callback_log(this, ...args)); 37 } 38 static get observedAttributes() { return ['id']; } 39 } 40 contentWindow.customElements.define('test-element', TestElement); 41 assert_equals(Object.getPrototypeOf(element1), TestElement.prototype); 42 assert_equals(Object.getPrototypeOf(element2), TestElement.prototype); 43 44 assert_equals(log.length, 6); 45 assert_constructor_log_entry(log[0], element1); 46 assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'first-element', namespace: null}); 47 assert_connected_log_entry(log[2], element1); 48 assert_constructor_log_entry(log[3], element2); 49 assert_attribute_log_entry(log[4], {name: 'id', oldValue: null, newValue: 'second-element', namespace: null}); 50 assert_connected_log_entry(log[5], element2); 51 }, 'Upgrading a custom element must invoke attributeChangedCallback and connectedCallback before start upgrading another element'); 52 53 test_with_window(function (contentWindow) { 54 const contentDocument = contentWindow.document; 55 contentDocument.write('<test-element>'); 56 57 const element = contentDocument.querySelector('test-element'); 58 assert_equals(Object.getPrototypeOf(element), contentWindow.HTMLElement.prototype); 59 60 let log = []; 61 class TestElement extends contentWindow.HTMLElement { 62 constructor() { 63 super(); 64 this.id = "foo"; 65 this.setAttribute('id', 'foo'); 66 this.removeAttribute('id'); 67 this.style.fontSize = '10px'; 68 log.push(create_constructor_log(this)); 69 } 70 connectedCallback(...args) { 71 log.push(create_connected_callback_log(this, ...args)); 72 } 73 attributeChangedCallback(...args) { 74 log.push(create_attribute_changed_callback_log(this, ...args)); 75 } 76 static get observedAttributes() { return ['id', 'style']; } 77 } 78 contentWindow.customElements.define('test-element', TestElement); 79 assert_equals(Object.getPrototypeOf(element), TestElement.prototype); 80 81 assert_equals(log.length, 2); 82 assert_constructor_log_entry(log[0], element); 83 assert_connected_log_entry(log[1], element); 84 }, 'Upgrading a custom element must not invoke attributeChangedCallback for the attribute that is changed during upgrading'); 85 86 test_with_window(function (contentWindow) { 87 const contentDocument = contentWindow.document; 88 contentDocument.write('<test-element>'); 89 90 const element = contentDocument.querySelector('test-element'); 91 assert_equals(Object.getPrototypeOf(element), contentWindow.HTMLElement.prototype); 92 93 let log = []; 94 class TestElement extends contentWindow.HTMLElement { 95 constructor() { 96 super(); 97 this.remove(); 98 log.push(create_constructor_log(this)); 99 } 100 connectedCallback(...args) { 101 log.push(create_connected_callback_log(this, ...args)); 102 } 103 disconnectedCallback(...args) { 104 log.push(create_disconnected_callback_log(this, ...args)); 105 } 106 } 107 contentWindow.customElements.define('test-element', TestElement); 108 assert_equals(Object.getPrototypeOf(element), TestElement.prototype); 109 110 assert_equals(log.length, 2); 111 assert_constructor_log_entry(log[0], element); 112 assert_connected_log_entry(log[1], element); 113 }, 'Upgrading a custom element must not invoke disconnectedCallback if the element is disconnected during upgrading'); 114 115 test_with_window(function (contentWindow) { 116 const contentDocument = contentWindow.document; 117 const element = contentDocument.createElement('test-element'); 118 assert_equals(Object.getPrototypeOf(element), contentWindow.HTMLElement.prototype); 119 120 let log = []; 121 class TestElement extends contentWindow.HTMLElement { 122 constructor() { 123 super(); 124 contentDocument.documentElement.appendChild(this); 125 log.push(create_constructor_log(this)); 126 } 127 connectedCallback(...args) { 128 log.push(create_connected_callback_log(this, ...args)); 129 } 130 } 131 contentWindow.customElements.define('test-element', TestElement); 132 contentWindow.customElements.upgrade(element); 133 134 assert_equals(Object.getPrototypeOf(element), TestElement.prototype); 135 136 assert_equals(log.length, 1); 137 assert_constructor_log_entry(log[0], element); 138 }, 'Upgrading a disconnected custom element must not invoke connectedCallback if the element is connected during upgrading'); 139 140 test_with_window(function (contentWindow) { 141 const contentDocument = contentWindow.document; 142 contentDocument.write('<test-element id="first-element">'); 143 contentDocument.write('<test-element id="second-element">'); 144 145 const element1 = contentDocument.getElementById('first-element'); 146 const element2 = contentDocument.getElementById('second-element'); 147 assert_equals(Object.getPrototypeOf(element1), contentWindow.HTMLElement.prototype); 148 assert_equals(Object.getPrototypeOf(element2), contentWindow.HTMLElement.prototype); 149 150 let log = []; 151 class TestElement extends contentWindow.HTMLElement { 152 constructor() { 153 super(); 154 log.push(create_constructor_log(this)); 155 if (this == element1) { 156 element2.setAttribute('title', 'hi'); 157 element2.removeAttribute('title'); 158 element2.setAttribute('class', 'foo'); 159 } 160 } 161 connectedCallback(...args) { 162 log.push(create_connected_callback_log(this, ...args)); 163 } 164 attributeChangedCallback(...args) { 165 log.push(create_attribute_changed_callback_log(this, ...args)); 166 } 167 static get observedAttributes() { return ['id', 'class', 'title']; } 168 } 169 contentWindow.customElements.define('test-element', TestElement); 170 assert_equals(Object.getPrototypeOf(element1), TestElement.prototype); 171 assert_equals(Object.getPrototypeOf(element2), TestElement.prototype); 172 173 assert_equals(log.length, 7); 174 assert_constructor_log_entry(log[0], element1); 175 assert_attribute_log_entry(log[1], {name: 'id', oldValue: null, newValue: 'first-element', namespace: null}); 176 assert_connected_log_entry(log[2], element1); 177 assert_constructor_log_entry(log[3], element2); 178 assert_attribute_log_entry(log[4], {name: 'id', oldValue: null, newValue: 'second-element', namespace: null}); 179 assert_attribute_log_entry(log[5], {name: 'class', oldValue: null, newValue: 'foo', namespace: null}); 180 assert_connected_log_entry(log[6], element2); 181 }, 'Mutating a undefined custom element while upgrading a custom element must not enqueue or invoke reactions on the mutated element'); 182 183 test_with_window(function (contentWindow) { 184 let log = []; 185 let element1; 186 let element2; 187 class TestElement extends contentWindow.HTMLElement { 188 constructor() { 189 super(); 190 log.push(create_constructor_log(this)); 191 } 192 adoptedCallback(...args) { 193 log.push(create_adopted_callback_log(this, ...args)); 194 if (this == element1) 195 element3.setAttribute('id', 'foo'); 196 } 197 connectedCallback(...args) { 198 log.push(create_connected_callback_log(this, ...args)); 199 } 200 attributeChangedCallback(...args) { 201 log.push(create_attribute_changed_callback_log(this, ...args)); 202 } 203 static get observedAttributes() { return ['id', 'class']; } 204 } 205 206 contentWindow.customElements.define('test-element', TestElement); 207 208 let contentDocument = contentWindow.document; 209 element1 = contentDocument.createElement('test-element'); 210 element2 = contentDocument.createElement('test-element'); 211 element3 = contentDocument.createElement('test-element'); 212 assert_equals(Object.getPrototypeOf(element1), TestElement.prototype); 213 assert_equals(Object.getPrototypeOf(element2), TestElement.prototype); 214 assert_equals(Object.getPrototypeOf(element3), TestElement.prototype); 215 216 assert_equals(log.length, 3); 217 assert_constructor_log_entry(log[0], element1); 218 assert_constructor_log_entry(log[1], element2); 219 assert_constructor_log_entry(log[2], element3); 220 log = []; 221 222 const container = contentDocument.createElement('div'); 223 container.appendChild(element1); 224 container.appendChild(element2); 225 container.appendChild(element3); 226 227 const anotherDocument = document.implementation.createHTMLDocument(); 228 anotherDocument.documentElement.appendChild(container); 229 230 assert_equals(log.length, 7); 231 assert_adopted_log_entry(log[0], element1); 232 assert_adopted_log_entry(log[1], element3); 233 assert_connected_log_entry(log[2], element3); 234 assert_attribute_log_entry(log[3], {name: 'id', oldValue: null, newValue: 'foo', namespace: null}); 235 assert_connected_log_entry(log[4], element1); 236 assert_adopted_log_entry(log[5], element2); 237 assert_connected_log_entry(log[6], element2); 238 239 }, 'Mutating another custom element inside adopted callback must invoke all pending callbacks on the mutated element'); 240 241 242 </script> 243 </body> 244 </html>