forked from dequelabs/axe-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathget-flattened-tree.js
More file actions
179 lines (151 loc) · 5.63 KB
/
get-flattened-tree.js
File metadata and controls
179 lines (151 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import isShadowRoot from './is-shadow-root';
import VirtualNode from '../base/virtual-node/virtual-node';
import cache from '../base/cache';
import { cacheNodeSelectors } from './selector-cache';
/**
* This implements the flatten-tree algorithm specified:
* Originally here https://drafts.csswg.org/css-scoping/#flat-tree
* Hopefully soon published here: https://www.w3.org/TR/css-scoping-1/#flat-tree
*
* Some notable information:
******* NOTE: as of Chrome 59, this is broken in Chrome so that tests fail completely
******* removed functionality for now
* 1. <slot> elements do not have boxes by default (i.e. they do not get rendered and
* their CSS properties are ignored)
* 2. <slot> elements can be made to have a box by overriding the display property
* which is 'contents' by default
* 3. Even boxed <slot> elements do not show up in the accessibility tree until
* they have a tabindex applied to them OR they have a role applied to them AND
* they have a box (this is observed behavior in Safari on OS X, I cannot find
* the spec for this)
*/
let hasShadowRoot;
/**
* Recursvely returns an array of the virtual DOM nodes at this level
* excluding comment nodes and the shadow DOM nodes <content> and <slot>
*
* @param {Node} [node=document.documentElement] optional node. NOTE: passing in anything other than body or the documentElement may result in incomplete results.
* @param {String} [shadowId] optional ID of the shadow DOM that is the closest shadow
* ancestor of the node
*/
export default function getFlattenedTree(
node = document.documentElement,
shadowId
) {
hasShadowRoot = false;
const selectorMap = {};
cache.set('nodeMap', new WeakMap());
cache.set('selectorMap', selectorMap);
// specifically pass `null` to the parent to designate the top
// node of the tree. if parent === undefined then we know
// we are in a disconnected tree
const tree = flattenTree(node, shadowId, null);
tree[0]._selectorMap = selectorMap;
// allow rules and checks to know if there is a shadow root attached
// to the current tree
tree[0]._hasShadowRoot = hasShadowRoot;
return tree;
}
/**
* find all the fallback content for a <slot> and return these as an array
* this array will also include any #text nodes
*
* @param node {Node} - the slot Node
* @return Array{Nodes}
*/
function getSlotChildren(node) {
const childNodes = [];
node = node.firstChild;
while (node) {
childNodes.push(node);
node = node.nextSibling;
}
return childNodes;
}
/**
* Create a virtual node
* @param {Node} node the current node
* @param {VirtualNode} parent the parent VirtualNode
* @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow ancestor of the node
* @return {VirtualNode}
*/
function createNode(node, parent, shadowId) {
const vNode = new VirtualNode(node, parent, shadowId);
cacheNodeSelectors(vNode, cache.get('selectorMap'));
return vNode;
}
/**
* Add children to the parent virtual node
* @param {Node[]} childNodes the children of the parent
* @param {VirtualNode} parent the parent VirtualNode
* @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow ancestor of the node
*/
function createChildren(childNodes, parent, shadowId) {
const children = [];
childNodes.forEach(childNode => {
const child = flattenTree(childNode, shadowId, parent);
if (child) {
children.push(...child);
}
});
return children;
}
/**
* Recursively returns an array of the virtual DOM nodes at this level
* excluding comment nodes and the shadow DOM nodes <content> and <slot>
*
* @param {Node} node the current node
* @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow ancestor of the node
* @param {VirtualNode} parent the parent VirtualNode
*/
function flattenTree(node, shadowId, parent) {
let vNode, childNodes;
if (node.documentElement) {
// document
node = node.documentElement;
}
const nodeName = node.nodeName.toLowerCase();
if (isShadowRoot(node)) {
hasShadowRoot = true;
// generate an ID for this shadow root and overwrite the current
// closure shadowId with this value so that it cascades down the tree
vNode = createNode(node, parent, shadowId);
shadowId = 'a' + Math.random().toString().substring(2);
childNodes = Array.from(node.shadowRoot.childNodes);
vNode.children = createChildren(childNodes, vNode, shadowId);
return [vNode];
}
if (
nodeName === 'content' &&
typeof node.getDistributedNodes === 'function'
) {
childNodes = Array.from(node.getDistributedNodes());
return createChildren(childNodes, parent, shadowId);
}
if (nodeName === 'slot' && typeof node.assignedNodes === 'function') {
childNodes = Array.from(node.assignedNodes());
if (!childNodes.length) {
// fallback content
childNodes = getSlotChildren(node);
}
const styl = window.getComputedStyle(node);
// check the display property. intentionally does not run, see notable information at top of file
if (false && styl.display !== 'contents') {
// has a box
vNode = createNode(node, parent, shadowId);
vNode.children = createChildren(childNodes, vNode, shadowId);
return [vNode];
}
return createChildren(childNodes, parent, shadowId);
}
if (node.nodeType === document.ELEMENT_NODE) {
vNode = createNode(node, parent, shadowId);
childNodes = Array.from(node.childNodes);
vNode.children = createChildren(childNodes, vNode, shadowId);
return [vNode];
}
if (node.nodeType === document.TEXT_NODE) {
return [createNode(node, parent)];
}
return undefined;
}