| <!DOCTYPE html> |
| <title>getHTML behavior</title> |
| <link rel='author' href='mailto:masonf@chromium.org'> |
| <link rel='help' href='https://github.com/whatwg/html/issues/8867'> |
| <script src='/resources/testharness.js'></script> |
| <script src='/resources/testharnessreport.js'></script> |
| <script src='../../html/resources/common.js'></script> |
| |
| <body> |
| |
| <script> |
| function testElementType(allowsShadowDom, elementType, runGetHTMLOnShadowRoot, declarativeShadowDom, mode, delegatesFocus, serializable, clonable) { |
| const t = test(t => { |
| // Create and attach element |
| let wrapper; |
| if (runGetHTMLOnShadowRoot) { |
| // This ensures we're testing both Element.getHTML() and ShadowRoot.getHTML(). |
| const host = document.createElement('div'); |
| t.add_cleanup(function() { host.remove(); }); |
| document.body.appendChild(host); |
| wrapper = host.attachShadow({mode: 'open'}); |
| } else { |
| wrapper = document.createElement('div'); |
| t.add_cleanup(function() { wrapper.remove(); }); |
| document.body.appendChild(wrapper); |
| } |
| |
| let shadowRoot; |
| const isOpen = mode === 'open'; |
| let initDict = {mode: mode, delegatesFocus: delegatesFocus, clonable}; |
| let expectedSerializable = null; |
| switch (serializable) { |
| case undefined: expectedSerializable = false; break; |
| case "true": initDict.serializable = expectedSerializable = true; break; |
| case "false": initDict.serializable = expectedSerializable = false; break; |
| default: throw new Error(`Invalid serializable ${serializable}`); |
| } |
| const delegatesAttr = delegatesFocus ? ' shadowrootdelegatesfocus=""' : ''; |
| const serializableAttr = expectedSerializable ? ' serializable=""' : ''; |
| const clonableAttr = clonable ? ' shadowrootclonable=""' : ''; |
| |
| if (allowsShadowDom && declarativeShadowDom) { |
| const html = `<${elementType}><template shadowrootmode=${mode}${delegatesAttr}${serializableAttr}${clonableAttr}>`; |
| wrapper.setHTMLUnsafe(html); |
| if (isOpen) { |
| shadowRoot = wrapper.firstElementChild.shadowRoot; |
| } else { |
| // For closed shadow root, we rely on the behavior of attachShadow to return it to us |
| shadowRoot = wrapper.firstElementChild.attachShadow(initDict); |
| } |
| } else { |
| // Imperative shadow dom |
| const element = document.createElement(elementType); |
| wrapper.appendChild(element); |
| if (allowsShadowDom) { |
| shadowRoot = element.attachShadow(initDict); |
| } |
| } |
| assert_true(!allowsShadowDom || !!shadowRoot); |
| |
| if (allowsShadowDom) { |
| const correctShadowHtml = `<template shadowrootmode="${mode}"${delegatesAttr}${serializableAttr}${clonableAttr}><slot></slot></template>`; |
| const correctHtml = `<${elementType}>${correctShadowHtml}</${elementType}>`; |
| assert_equals(shadowRoot.mode,mode); |
| assert_equals(shadowRoot.delegatesFocus,delegatesFocus); |
| assert_equals(shadowRoot.serializable,expectedSerializable); |
| assert_equals(shadowRoot.clonable,clonable); |
| shadowRoot.appendChild(document.createElement('slot')); |
| const emptyElement = `<${elementType}></${elementType}>`; |
| if (isOpen) { |
| if (expectedSerializable) { |
| assert_equals(wrapper.getHTML({includeShadowRoots: true}), correctHtml); |
| } else { |
| assert_equals(wrapper.getHTML({includeShadowRoots: true}), emptyElement); |
| } |
| } else { |
| // Closed shadow roots should not be returned unless shadowRoots specifically contains the shadow root: |
| assert_equals(wrapper.getHTML({includeShadowRoots: true}), emptyElement); |
| assert_equals(wrapper.getHTML({includeShadowRoots: true, shadowRoots: []}), emptyElement); |
| } |
| // If we provide the shadow root, serialize it, regardless of includeShadowRoots. |
| assert_equals(wrapper.getHTML({includeShadowRoots: true, shadowRoots: [shadowRoot]}),correctHtml); |
| assert_equals(wrapper.getHTML({shadowRoots: [shadowRoot]}),correctHtml); |
| // This should always throw - includeShadowRoots false, but we've provided roots. |
| assert_throws_dom("NotSupportedError",() => wrapper.getHTML({includeShadowRoots: false, shadowRoots: [shadowRoot]})); |
| } else { |
| // For non-shadow hosts, getHTML() should also match .innerHTML |
| assert_equals(wrapper.getHTML({includeShadowRoots: true}),wrapper.innerHTML); |
| } |
| |
| // Either way, make sure getHTML({includeShadowRoots: false}) matches .innerHTML |
| assert_equals(wrapper.getHTML({includeShadowRoots: false}),wrapper.innerHTML,'getHTML() with includeShadowRoots false should return the same as .innerHTML'); |
| // ...and that the default for includeShadowRoots is false. |
| assert_equals(wrapper.getHTML(),wrapper.innerHTML,'The default for includeShadowRoots should be false'); |
| |
| }, `${runGetHTMLOnShadowRoot ? 'ShadowRoot' : 'Element'}.getHTML() on <${elementType}>${allowsShadowDom ? `, with ${declarativeShadowDom ? 'declarative' : 'imperative'} shadow, mode=${mode}, delegatesFocus=${delegatesFocus}, serializable=${serializable}, clonable=${clonable}.` : ''}`); |
| } |
| |
| function runAllTests() { |
| const allElements = [...HTML5_ELEMENTS, 'htmlunknown']; |
| const safelisted = HTML5_SHADOW_ALLOWED_ELEMENTS.filter(el => el != 'body'); |
| for (const elementName of allElements) { |
| const allowsShadowDom = safelisted.includes(elementName); |
| for (const runGetHTMLOnShadowRoot of [false, true]) { |
| if (allowsShadowDom) { |
| for (const declarativeShadowDom of [false, true]) { |
| for (const delegatesFocus of [false, true]) { |
| for (const clonable of [false, true]) { |
| for (const mode of ['open', 'closed']) { |
| for (const serializable of [undefined, 'false', 'true']) { |
| testElementType(true, elementName, runGetHTMLOnShadowRoot, declarativeShadowDom, mode, delegatesFocus, serializable, clonable); |
| } |
| } |
| } |
| } |
| } |
| } else { |
| testElementType(false, elementName, runGetHTMLOnShadowRoot); |
| } |
| } |
| } |
| } |
| |
| runAllTests(); |
| |
| </script> |