|
12 | 12 | * http://polymer.github.io/PATENTS.txt
|
13 | 13 | */
|
14 | 14 |
|
15 |
| -// This isn't ideal. Setting .innerHTML is not compatible with some |
16 |
| -// TrustedTypes CSP policies. Discussion at: |
17 |
| -// https://github.com/mfreed7/declarative-shadow-dom/issues/3 |
18 |
| -let hasNative: boolean|undefined; |
19 |
| -export function hasNativeDeclarativeShadowRoots(): boolean { |
20 |
| - if (hasNative === undefined) { |
21 |
| - const div = document.createElement('div'); |
22 |
| - div.innerHTML = `<div><template shadowroot="open"></template></div>`; |
23 |
| - hasNative = !!div.firstElementChild!.shadowRoot; |
24 |
| - } |
25 |
| - return hasNative; |
26 |
| -} |
| 15 | +import {hasNativeDeclarativeShadowRoots} from './_implementation/feature_detect.js'; |
| 16 | +import {hydrateShadowRoots} from './_implementation/manual_walk.js'; |
27 | 17 |
|
28 |
| -/* |
29 |
| - * Traverses the DOM to find all <template> elements with a `shadowroot` |
30 |
| - * attribute and move their content into a ShadowRoot on their parent element. |
31 |
| - * |
32 |
| - * This processing is done bottom up so that when top-level <template> |
33 |
| - * elements are hydrated, their contents are already hydrated and in the |
34 |
| - * final correct structure of elements and shadow roots. |
35 |
| - */ |
36 |
| -export const hydrateShadowRoots = (root: ParentNode) => { |
37 |
| - if (hasNativeDeclarativeShadowRoots()) { |
38 |
| - return; // nothing to do |
39 |
| - } |
40 |
| - |
41 |
| - // Approaches to try and benchmark: |
42 |
| - // - manual walk (current implementation) |
43 |
| - // - querySelectorAll |
44 |
| - // - TreeWalker |
45 |
| - |
46 |
| - // Stack of nested templates that we're currently processing. Use to |
47 |
| - // remember how to get from a <template>.content DocumentFragment back to |
48 |
| - // its owner <template> |
49 |
| - const templateStack: Array<HTMLTemplateElement> = []; |
50 |
| - |
51 |
| - let currentNode: Element|DocumentFragment|null = root.firstElementChild; |
52 |
| - |
53 |
| - // The outer loop traverses down, looking for <template shadowroot> |
54 |
| - // elements. The inner loop traverses back up, hydrating them in a postorder |
55 |
| - // traversal. |
56 |
| - while (currentNode !== root && currentNode !== null) { |
57 |
| - if (isTemplate(currentNode)) { |
58 |
| - templateStack.push(currentNode); |
59 |
| - currentNode = currentNode.content; |
60 |
| - } else if (currentNode.firstElementChild !== null) { |
61 |
| - // Traverse down |
62 |
| - currentNode = currentNode.firstElementChild; |
63 |
| - } else if ( |
64 |
| - isElement(currentNode) && currentNode.nextElementSibling !== null) { |
65 |
| - // Element is empty, but has a next sibling. Traverse that. |
66 |
| - currentNode = currentNode.nextElementSibling; |
67 |
| - } else { |
68 |
| - // Element is empty and the last child. Traverse to next aunt/grandaunt. |
69 |
| - |
70 |
| - // Store templates we hydrate for one loop so that we can remove them |
71 |
| - // *after* traversing to their successor. |
72 |
| - let template: HTMLTemplateElement|undefined; |
73 |
| - |
74 |
| - while (currentNode !== root && currentNode !== null) { |
75 |
| - if (hasNoParentElement(currentNode)) { |
76 |
| - // We must be at a <template>'s content fragment. |
77 |
| - template = templateStack.pop()!; |
78 |
| - const host = template.parentElement!; |
79 |
| - const mode = template.getAttribute('shadowroot'); |
80 |
| - currentNode = template; |
81 |
| - if (mode === 'open' || mode === 'closed') { |
82 |
| - const delegatesFocus = |
83 |
| - template.hasAttribute('shadowrootdelegatesfocus'); |
84 |
| - try { |
85 |
| - const shadow = host.attachShadow({mode, delegatesFocus}); |
86 |
| - shadow.append(template.content); |
87 |
| - } catch { |
88 |
| - // there was already a closed shadow root, so do nothing, and |
89 |
| - // don't delete the template |
90 |
| - } |
91 |
| - } else { |
92 |
| - template = undefined; |
93 |
| - } |
94 |
| - } else { |
95 |
| - const nextSibling: Element|null|undefined = |
96 |
| - currentNode.nextElementSibling; |
97 |
| - if (nextSibling != null) { |
98 |
| - currentNode = nextSibling; |
99 |
| - if (template !== undefined) { |
100 |
| - template.parentElement!.removeChild(template); |
101 |
| - } |
102 |
| - break; |
103 |
| - } |
104 |
| - const nextAunt: Element|null|undefined = |
105 |
| - currentNode.parentElement?.nextElementSibling; |
106 |
| - if (nextAunt != null) { |
107 |
| - currentNode = nextAunt; |
108 |
| - if (template !== undefined) { |
109 |
| - template.parentElement!.removeChild(template); |
110 |
| - } |
111 |
| - break; |
112 |
| - } |
113 |
| - currentNode = currentNode.parentElement; |
114 |
| - if (template !== undefined) { |
115 |
| - template.parentElement!.removeChild(template); |
116 |
| - template = undefined; |
117 |
| - } |
118 |
| - } |
119 |
| - } |
120 |
| - } |
121 |
| - } |
122 |
| -}; |
123 |
| - |
124 |
| -const hasNoParentElement = |
125 |
| - (e: Element|DocumentFragment): e is DocumentFragment => |
126 |
| - e.parentElement === null; |
127 |
| -const isTemplate = (e: Node): e is HTMLTemplateElement => |
128 |
| - (e as Partial<Element>).tagName === 'TEMPLATE'; |
129 |
| -const isElement = (e: Node): e is HTMLElement => |
130 |
| - e.nodeType === Node.ELEMENT_NODE; |
| 18 | +export {hasNativeDeclarativeShadowRoots, hydrateShadowRoots}; |
0 commit comments