UNPKG

@webcomponents/custom-elements

Version:
286 lines (256 loc) 7.86 kB
<!DOCTYPE html> <script src="../../resources/testharness.js"></script> <script src="../../resources/testharnessreport.js"></script> <script src="resources/custom-elements-helpers.js"></script> <body> <script> 'use strict'; // Looks up the preceeding element (which should be a template // element) and creates a Promise test. The test name is taken from // the template's data-test attribute. // // The content of the template is loaded into an iframe. On load, f // is passed the frame's content window to run assertions. function test_with_content(f) { let t = document.currentScript.previousElementSibling; test_with_window(f, t.dataset.test, t.innerHTML); } // Searches the document for an iframe with the specified content window. function findFrameWithWindow(w) { return Array.prototype.find.call(document.querySelectorAll('iframe'), (f) => { return f.contentWindow === w; }); } test_with_window((w) => { assert_equals(findFrameWithWindow(w).contentWindow, w, 'should find the frame with this window'); assert_equals(findFrameWithWindow(window), undefined, 'should return undefined if there is no such frame'); }, 'sanity check the findFrameWithWindow function'); </script> <template data-test="the parser synchronously creates elements"> <script> 'use strict'; window.invocations = []; customElements.define('a-a', class extends HTMLElement { constructor() { super(); invocations.push('constructor'); } static get observedAttributes() { return ['x']; } attributeChangedCallback(name, oldValue, newValue, nsuri) { invocations.push(`${name}="${newValue}"`); } connectedCallback() { invocations.push('connected'); } }); </script> <a-a x="y"> <script> 'use strict'; invocations.push('script'); </script> </a-a> </template> <script> 'use strict'; test_with_content((w) => { assert_array_equals(w.invocations, ['constructor', 'x="y"', 'connected', 'script']); }); </script> <template data-test="element creation failure produces unknown element"> <script> 'use strict'; customElements.define('a-a', class extends HTMLElement { constructor() { super(); // Returning this different, in-use element causes element // creation to fail in // https://dom.spec.whatwg.org/#concept-create-element steps // 6.4-9, eg: "If result has children then then throw a // NotSupportedError." return document.documentElement; } }); </script> <a-a> </template> <script> 'use strict'; test_with_content((w) => { let e = w.document.querySelector('a-a'); assert_true(e.matches(':not(:defined)')); assert_equals(Object.getPrototypeOf(e), w.HTMLUnknownElement.prototype); }); </script> <template data-test="modify tree during creation"> <script> 'use strict'; customElements.define('a-a', class extends HTMLElement { constructor() { super(); document.querySelector('b').remove(); } }); </script> <b> <a-a> </b> </template> <script> 'use strict'; test_with_content((w) => { assert_equals(w.document.querySelectorAll('b').length, 0); }); </script> <template data-test="destructive writes are blocked during construction"> <script> // Custom element constructors do not set the insertion point, which // makes document.write() "destructive." However they increment the // ignore-destructive-writes counter, which blocks document.write. // https://html.spec.whatwg.org/#create-an-element-for-the-token // https://github.com/whatwg/html/issues/1630 // https://html.spec.whatwg.org/#document.write() 'use strict'; window.invocations = []; customElements.define('a-a', class extends HTMLElement { constructor() { super(); invocations.push('constructor'); document.write( `<script>'use strict'; invocations.push('written');</scr${'i'}pt>`); } connectedCallback() { invocations.push('connected'); } }); </script> <a-a> <script> 'use strict'; invocations.push('parsed'); </script> </template> <script> 'use strict'; test_with_content((w) => { assert_array_equals( w.invocations, ['constructor', 'connected', 'parsed'], 'the destructive document.write content should have been ignored'); }); </script> <template data-test="non-destructive writes are not blocked"> <script> // Script running sets the insertion point, which makes makes // document.write() "non-destructive." Custom elements do not block // non-destructive writes. // https://html.spec.whatwg.org/#create-an-element-for-the-token // https://html.spec.whatwg.org/#document.write() 'use strict'; window.invocations = []; customElements.define('a-a', class extends HTMLElement { constructor() { super(); invocations.push('constructor'); document.write( `<script>'use strict'; invocations.push('written');</scr${'i'}pt>`); } connectedCallback() { invocations.push('connected'); } }); document.write('<a-a>'); invocations.push('post write'); </script> <script> 'use strict'; invocations.push('parsed'); </script> </template> <script> 'use strict'; test_with_content((w) => { assert_array_equals( w.invocations, ['constructor', 'connected', 'post write', 'written', 'parsed'], 'the non-destructive document.write content should have been inserted'); }); </script> <template data-test="innerHTML is not blocked by custom element constructors"> <script> 'use strict'; window.invocations = []; customElements.define('a-a', class extends HTMLElement { constructor() { super(); invocations.push(`construct ${this.id}`); if (!this.id) { // If the ID attribute is not set, this was created // synchronously by the parser. Adding children at this point // would cause creation to fail, so embiggen the previous // element instead. document.querySelector('span').innerHTML = `<a-a id="r">`; } } connectedCallback() { invocations.push(`connected ${this.parentNode.localName}/${this.id}`); } }); </script> <span></span> <a-a id="q"></a-a> <script> 'use strict'; invocations.push('parsed'); </script> </template> <script> 'use strict'; test_with_content((w) => { assert_array_equals( w.invocations, ['construct ', 'construct r', 'connected span/r', 'connected body/q', 'parsed'], 'custom element constructors should not block innerHTML'); }); </script> <template data-test="parsing without a browsing context should not create custom elements"> <body> <script> 'use strict'; let f = parent.findFrameWithWindow(window); f.invocations = []; customElements.define('a-a', class extends HTMLElement { constructor() { super(); f.invocations.push(this); } }); </script> <a-a></a-a> <script> f.detached = document.implementation.createHTMLDocument(); f.detached.documentElement.appendChild(document.body); </script> <a-a></a-a> </template> <script> 'use strict'; test_with_content((w) => { let f = findFrameWithWindow(w); assert_array_equals(f.invocations, [f.detached.querySelector('a-a:first-of-type')], 'one element should have been constructed'); assert_true(f.invocations[0].matches(':defined'), 'the element should have been created successfully'); let elements = f.detached.querySelectorAll('a-a'); console.log(f.invocations[0].parentNode); assert_equals(elements.length, 2, 'two elements should have been created'); assert_equals(Object.getPrototypeOf(elements[1]), w.HTMLElement.prototype, 'the second element should be un-upgraded, not failed'); }); </script>