upgrading.html (13578B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Custom Elements: Enqueue a custom element upgrade reaction</title> 5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> 6 <meta name="assert" content="Enqueue a custom element upgrade reaction must upgrade a custom element"> 7 <link rel="help" href="https://dom.spec.whatwg.org/#concept-create-element"> 8 <link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#concept-try-upgrade"> 9 <link rel="help" href="https://html.spec.whatwg.org/multipage/scripting.html#enqueue-a-custom-element-upgrade-reaction"> 10 <script src="/resources/testharness.js"></script> 11 <script src="/resources/testharnessreport.js"></script> 12 <script src="resources/custom-elements-helpers.js"></script> 13 </head> 14 <body> 15 <infinite-cloning-element-1></infinite-cloning-element-1> 16 <infinite-cloning-element-2 id="a"></infinite-cloning-element-2> 17 <infinite-cloning-element-2 id="b"></infinite-cloning-element-2> 18 <div id="log"></div> 19 <script> 20 setup({allow_uncaught_exception:true}); 21 22 class PredefinedCustomElement extends HTMLElement {} 23 customElements.define('predefined-custom-element', PredefinedCustomElement); 24 25 var customElementNumber = 1; 26 function generateNextCustomElementName() { return 'custom-' + customElementNumber++; } 27 28 // Tests for documents without a browsing context. 29 document_types().filter(function (entry) { return !entry.isOwner && !entry.hasBrowsingContext; }).forEach(function (entry) { 30 var documentName = entry.name; 31 var getDocument = entry.create; 32 33 promise_test(function () { 34 return getDocument().then(function (doc) { 35 assert_false(doc.createElement('predefined-custom-element') instanceof PredefinedCustomElement); 36 }); 37 }, 'Creating an element in ' + documentName + ' must not enqueue a custom element upgrade reaction' 38 + ' because the document does not have a browsing context'); 39 40 promise_test(function () { 41 var name = generateNextCustomElementName(); 42 var unresolvedElement = document.createElement(name); 43 44 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype, 45 '[[Prototype]] internal slot of the unresolved custom element must be the HTMLElement prototype'); 46 47 return getDocument().then(function (doc) { 48 var unresolvedElementInDoc = doc.createElement(name); 49 var prototype = (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml' ? HTMLElement : Element).prototype; 50 51 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), prototype, 52 '[[Prototype]] internal slot of the unresolved custom element must be the ' + prototype.toString() + ' prototype'); 53 var someCustomElement = class extends HTMLElement {}; 54 customElements.define(name, someCustomElement); 55 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), prototype, '"define" must not upgrade a disconnected unresolved custom elements'); 56 doc.documentElement.appendChild(unresolvedElementInDoc); 57 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), prototype, 58 'Inserting an element into a document without a browsing context must not enqueue a custom element upgrade reaction'); 59 }); 60 }, 'Creating an element in ' + documentName + ' and inserting into the document must not enqueue a custom element upgrade reaction'); 61 62 promise_test(function () { 63 var name = generateNextCustomElementName(); 64 var unresolvedElement = document.createElement(name); 65 66 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype, 67 '[[Prototype]] internal slot of the unresolved custom element must be the HTMLElement prototype'); 68 69 return getDocument().then(function (doc) { 70 var unresolvedElementInDoc = doc.createElement(name); 71 var prototype = (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml' ? HTMLElement : Element).prototype; 72 73 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), prototype, 74 '[[Prototype]] internal slot of the unresolved custom element must be the ' + prototype.toString() + ' prototype'); 75 var someCustomElement = class extends HTMLElement {}; 76 customElements.define(name, someCustomElement); 77 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), prototype, '"define" must not upgrade a disconnected unresolved custom elements'); 78 document.body.appendChild(unresolvedElementInDoc); 79 80 if (unresolvedElementInDoc.namespaceURI == 'http://www.w3.org/1999/xhtml') { 81 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), someCustomElement.prototype, 82 'Inserting an element into a document with a browsing context must enqueue a custom element upgrade reaction'); 83 } else { 84 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), prototype, 85 'Looking up a custom element definition must return null if the element is not in the HTML namespace'); 86 } 87 }); 88 }, 'Creating an element in ' + documentName + ' and adopting back to a document with browsing context must enqueue a custom element upgrade reaction'); 89 90 }); 91 92 // Tests for documents with a browsing context. 93 document_types().filter(function (entry) { return !entry.isOwner && entry.hasBrowsingContext; }).forEach(function (entry) { 94 var documentName = entry.name; 95 var getDocument = entry.create; 96 97 promise_test(function () { 98 return getDocument().then(function (doc) { 99 assert_false(doc.createElement('predefined-custom-element') instanceof PredefinedCustomElement); 100 }); 101 }, 'Creating an element in ' + documentName + ' must not enqueue a custom element upgrade reaction if there is no matching definition'); 102 103 promise_test(function () { 104 return getDocument().then(function (doc) { 105 var docWindow = doc.defaultView; 106 class DistinctPredefinedCustomElement extends docWindow.HTMLElement { }; 107 docWindow.customElements.define('predefined-custom-element', DistinctPredefinedCustomElement); 108 assert_true(doc.createElement('predefined-custom-element') instanceof DistinctPredefinedCustomElement); 109 }); 110 }, 'Creating an element in ' + documentName + ' must enqueue a custom element upgrade reaction if there is a matching definition'); 111 112 promise_test(function () { 113 var unresolvedElement = document.createElement('unresolved-element'); 114 return getDocument().then(function (doc) { 115 var docWindow = doc.defaultView; 116 class UnresolvedElement extends docWindow.HTMLElement { }; 117 var unresolvedElementInDoc = doc.createElement('unresolved-element'); 118 119 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype); 120 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype); 121 122 docWindow.customElements.define('unresolved-element', UnresolvedElement); 123 124 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype); 125 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype); 126 127 }); 128 }, '"define" in ' + documentName + ' must not enqueue a custom element upgrade reaction on a disconnected unresolved custom element'); 129 130 promise_test(function () { 131 var unresolvedElement = document.createElement('unresolved-element'); 132 return getDocument().then(function (doc) { 133 var docWindow = doc.defaultView; 134 class UnresolvedElement extends docWindow.HTMLElement { }; 135 var unresolvedElementInDoc = doc.createElement('unresolved-element'); 136 137 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype); 138 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype); 139 140 docWindow.customElements.define('unresolved-element', UnresolvedElement); 141 doc.documentElement.appendChild(unresolvedElementInDoc); 142 143 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype); 144 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), UnresolvedElement.prototype); 145 }); 146 }, 'Inserting an unresolved custom element into ' + documentName + ' must enqueue a custom element upgrade reaction'); 147 148 promise_test(function () { 149 var unresolvedElement = document.createElement('unresolved-element'); 150 return getDocument().then(function (doc) { 151 var docWindow = doc.defaultView; 152 class UnresolvedElement extends docWindow.HTMLElement { }; 153 var unresolvedElementInDoc = doc.createElement('unresolved-element'); 154 doc.documentElement.appendChild(unresolvedElementInDoc); 155 156 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype); 157 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), docWindow.HTMLElement.prototype); 158 159 docWindow.customElements.define('unresolved-element', UnresolvedElement); 160 161 assert_equals(Object.getPrototypeOf(unresolvedElement), HTMLElement.prototype); 162 assert_equals(Object.getPrototypeOf(unresolvedElementInDoc), UnresolvedElement.prototype); 163 }); 164 }, '"define" in ' + documentName + ' must enqueue a custom element upgrade reaction on a connected unresolved custom element'); 165 166 promise_test(function () { 167 var unresolvedElement = document.createElement('unresolved-element'); 168 return getDocument().then(function (doc) { 169 var docWindow = doc.defaultView; 170 class UnresolvedElement extends docWindow.HTMLElement { }; 171 assert_false(unresolvedElement instanceof UnresolvedElement); 172 docWindow.customElements.define('unresolved-element', UnresolvedElement); 173 doc.adoptNode(unresolvedElement); 174 assert_false(unresolvedElement instanceof UnresolvedElement); 175 }); 176 }, 'Adopting (and leaving disconnceted) an unresolved custom element into ' + documentName + ' must not enqueue a custom element upgrade reaction'); 177 178 promise_test(function () { 179 var unresolvedElement = document.createElement('unresolved-element'); 180 return getDocument().then(function (doc) { 181 var docWindow = doc.defaultView; 182 class UnresolvedElement extends docWindow.HTMLElement { }; 183 assert_false(unresolvedElement instanceof UnresolvedElement); 184 docWindow.customElements.define('unresolved-element', UnresolvedElement); 185 doc.documentElement.appendChild(unresolvedElement); 186 assert_true(unresolvedElement instanceof UnresolvedElement); 187 }); 188 }, 'Adopting and inserting an unresolved custom element into ' + documentName + ' must enqueue a custom element upgrade reaction'); 189 190 }); 191 192 test(() => { 193 class ShadowDisabledElement extends HTMLElement { 194 static get disabledFeatures() { return ['shadow']; } 195 } 196 let error = null; 197 window.addEventListener('error', e => { error = e.error; }, {once: true}); 198 let element = document.createElement('shadow-disabled'); 199 element.attachShadow({mode: 'open'}); 200 customElements.define('shadow-disabled', ShadowDisabledElement); 201 customElements.upgrade(element); 202 assert_false(element instanceof ShadowDisabledElement, 203 'Upgrading should fail.'); 204 assert_true(error instanceof DOMException); 205 assert_equals(error.name, 'NotSupportedError'); 206 }, 'If definition\'s disable shadow is true and element\'s shadow root is ' + 207 'non-null, then throw a "NotSupportedError" DOMException.'); 208 209 test(() => { 210 var log = []; 211 212 customElements.define('infinite-cloning-element-1',class extends HTMLElement { 213 constructor() { 214 super(); 215 log.push([this, 'begin']); 216 // Potential infinite recursion: 217 customElements.upgrade(this); 218 log.push([this, 'end']); 219 } 220 }); 221 222 assert_equals(log.length, 2); 223 const instance = document.querySelector("infinite-cloning-element-1"); 224 assert_array_equals(log[0], [instance, 'begin']); 225 assert_array_equals(log[1], [instance, 'end']); 226 }, 'Infinite constructor recursion with upgrade(this) should not be possible'); 227 228 test(() => { 229 var log = []; 230 231 customElements.define('infinite-cloning-element-2',class extends HTMLElement { 232 constructor() { 233 super(); 234 log.push([this, 'begin']); 235 const b = document.querySelector("#b"); 236 b.remove(); 237 // While this constructor is running for "a", "b" is still 238 // undefined, and so inserting it into the document will enqueue a 239 // second upgrade reaction for "b" in addition to the one enqueued 240 // by defining x-foo. 241 document.body.appendChild(b); 242 log.push([this, 'end']); 243 } 244 }); 245 246 assert_equals(log.length, 4); 247 const instanceA = document.querySelector("#a"); 248 const instanceB = document.querySelector("#b"); 249 assert_array_equals(log[0], [instanceA, 'begin']); 250 assert_array_equals(log[1], [instanceB, 'begin']); 251 assert_array_equals(log[2], [instanceB, 'end']); 252 assert_array_equals(log[3], [instanceA, 'end']); 253 }, 'Infinite constructor recursion with appendChild should not be possible'); 254 255 256 </script> 257 </body> 258 </html>