govuk_publishing_components 21.22.1 → 21.22.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/app/assets/stylesheets/govuk_publishing_components/components/_breadcrumbs.scss +34 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_document-list.scss +2 -2
- data/app/views/govuk_publishing_components/components/_breadcrumbs.html.erb +0 -3
- data/app/views/govuk_publishing_components/components/_contextual_breadcrumbs.html.erb +0 -2
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/axe-core/CHANGELOG.md +70 -10
- data/node_modules/axe-core/axe.js +1295 -632
- data/node_modules/axe-core/axe.min.js +2 -2
- data/node_modules/axe-core/bower.json +1 -1
- data/node_modules/axe-core/doc/API.md +1 -0
- data/node_modules/axe-core/doc/aria-supported.md +0 -1
- data/node_modules/axe-core/doc/developer-guide.md +2 -2
- data/node_modules/axe-core/doc/rule-descriptions.md +91 -87
- data/node_modules/axe-core/lib/checks/aria/abstractrole.js +10 -1
- data/node_modules/axe-core/lib/checks/aria/abstractrole.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/fallbackrole.js +1 -0
- data/node_modules/axe-core/lib/checks/aria/fallbackrole.json +11 -0
- data/node_modules/axe-core/lib/checks/aria/invalidrole.js +14 -3
- data/node_modules/axe-core/lib/checks/aria/invalidrole.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/required-children.js +41 -30
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.js +24 -9
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +2 -2
- data/node_modules/axe-core/lib/checks/color/color-contrast.js +23 -7
- data/node_modules/axe-core/lib/checks/color/color-contrast.json +7 -1
- data/node_modules/axe-core/lib/checks/keyboard/focusable-disabled.js +5 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-modal-open.js +14 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-modal-open.json +11 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-not-tabbable.js +5 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-has-elm.js +5 -1
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-after.js +2 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-banner.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-contentinfo.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-main.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate.js +14 -2
- data/node_modules/axe-core/lib/checks/lists/only-listitems.js +43 -49
- data/node_modules/axe-core/lib/checks/lists/only-listitems.json +4 -1
- data/node_modules/axe-core/lib/checks/media/no-autoplay-audio.js +93 -0
- data/node_modules/axe-core/lib/checks/media/no-autoplay-audio.json +15 -0
- data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose-after.js +100 -0
- data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose.js +31 -0
- data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose.json +12 -0
- data/node_modules/axe-core/lib/checks/navigation/region.js +42 -8
- data/node_modules/axe-core/lib/checks/navigation/region.json +0 -1
- data/node_modules/axe-core/lib/checks/shared/svg-non-empty-title.js +4 -0
- data/node_modules/axe-core/lib/checks/shared/svg-non-empty-title.json +11 -0
- data/node_modules/axe-core/lib/commons/aria/index.js +12 -4
- data/node_modules/axe-core/lib/commons/color/get-background-color.js +5 -157
- data/node_modules/axe-core/lib/commons/dom/get-element-stack.js +272 -174
- data/node_modules/axe-core/lib/commons/dom/is-focusable.js +3 -1
- data/node_modules/axe-core/lib/commons/dom/is-hidden-with-css.js +2 -2
- data/node_modules/axe-core/lib/commons/dom/is-modal-open.js +98 -0
- data/node_modules/axe-core/lib/commons/dom/is-visible.js +57 -2
- data/node_modules/axe-core/lib/commons/dom/url-props-from-attribute.js +143 -0
- data/node_modules/axe-core/lib/commons/dom/visually-contains.js +62 -12
- data/node_modules/axe-core/lib/commons/matches/attributes.js +12 -8
- data/node_modules/axe-core/lib/commons/matches/from-definition.js +15 -10
- data/node_modules/axe-core/lib/commons/matches/index.js +11 -9
- data/node_modules/axe-core/lib/commons/matches/node-name.js +11 -21
- data/node_modules/axe-core/lib/commons/matches/properties.js +12 -9
- data/node_modules/axe-core/lib/commons/text/unicode.js +27 -24
- data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +11 -3
- data/node_modules/axe-core/lib/core/constants.js +1 -1
- data/node_modules/axe-core/lib/core/reporters/v1.js +6 -3
- data/node_modules/axe-core/lib/core/utils/contains.js +1 -1
- data/node_modules/axe-core/lib/core/utils/is-hidden.js +6 -6
- data/node_modules/axe-core/lib/core/utils/matches.js +263 -0
- data/node_modules/axe-core/lib/core/utils/preload-media.js +65 -0
- data/node_modules/axe-core/lib/core/utils/preload.js +33 -24
- data/node_modules/axe-core/lib/core/utils/qsa.js +7 -208
- data/node_modules/axe-core/lib/rules/aria-hidden-focus.json +5 -1
- data/node_modules/axe-core/lib/rules/aria-roles.json +1 -1
- data/node_modules/axe-core/lib/rules/button-name.json +1 -1
- data/node_modules/axe-core/lib/rules/color-contrast-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/color-contrast.json +0 -3
- data/node_modules/axe-core/lib/rules/html-namespace-matches.js +1 -0
- data/node_modules/axe-core/lib/rules/identical-links-same-purpose-matches.js +13 -0
- data/node_modules/axe-core/lib/rules/identical-links-same-purpose.json +14 -0
- data/node_modules/axe-core/lib/rules/landmark-no-duplicate-banner.json +1 -1
- data/node_modules/axe-core/lib/rules/landmark-no-duplicate-contentinfo.json +1 -1
- data/node_modules/axe-core/lib/rules/landmark-no-duplicate-main.json +12 -0
- data/node_modules/axe-core/lib/rules/landmark-one-main.json +2 -2
- data/node_modules/axe-core/lib/rules/link-name.json +2 -4
- data/node_modules/axe-core/lib/rules/meta-viewport.json +1 -1
- data/node_modules/axe-core/lib/rules/no-autoplay-audio-matches.js +18 -0
- data/node_modules/axe-core/lib/rules/no-autoplay-audio.json +15 -0
- data/node_modules/axe-core/lib/rules/region.json +1 -2
- data/node_modules/axe-core/lib/rules/role-img-alt.json +2 -1
- data/node_modules/axe-core/lib/rules/svg-img-alt.json +24 -0
- data/node_modules/axe-core/lib/rules/svg-namespace-matches.js +1 -0
- data/node_modules/axe-core/locales/da.json +782 -0
- data/node_modules/axe-core/locales/fr.json +221 -42
- data/node_modules/axe-core/locales/ja.json +124 -24
- data/node_modules/axe-core/package.json +29 -26
- data/node_modules/axe-core/sri-history.json +26 -10
- metadata +27 -9
- data/node_modules/axe-core/doc/examples/jasmine/package-lock.json +0 -1489
- data/node_modules/axe-core/doc/examples/jest_react/package-lock.json +0 -7525
- data/node_modules/axe-core/doc/examples/mocha/package-lock.json +0 -1671
- data/node_modules/axe-core/doc/examples/phantomjs/package-lock.json +0 -862
- data/node_modules/axe-core/doc/examples/qunit/package-lock.json +0 -2951
- data/node_modules/axe-core/lib/checks/navigation/region-after.js +0 -1
- data/node_modules/axe-core/typings/axe-core/axe-core-tests.js +0 -151
@@ -1,7 +1,7 @@
|
|
1
|
-
/* global dom */
|
1
|
+
/* global dom, VirtualNode */
|
2
2
|
|
3
3
|
// split the page cells to group elements by the position
|
4
|
-
const gridSize = 200; // arbitrary size, increase to reduce memory (less cells)
|
4
|
+
const gridSize = 200; // arbitrary size, increase to reduce memory use (less cells) but increase time (more nodes per grid to check collision)
|
5
5
|
|
6
6
|
/**
|
7
7
|
* Determine if node produces a stacking context.
|
@@ -11,31 +11,20 @@ const gridSize = 200; // arbitrary size, increase to reduce memory (less cells)
|
|
11
11
|
* @param {VirtualNode} vNode
|
12
12
|
* @return {Boolean}
|
13
13
|
*/
|
14
|
-
function isStackingContext(vNode) {
|
15
|
-
const
|
14
|
+
function isStackingContext(vNode, parentVNode) {
|
15
|
+
const position = vNode.getComputedStylePropertyValue('position');
|
16
|
+
const zIndex = vNode.getComputedStylePropertyValue('z-index');
|
16
17
|
|
17
|
-
//the root element (HTML)
|
18
|
-
|
19
|
-
!node ||
|
20
|
-
node.nodeName === 'HTML' ||
|
21
|
-
node.nodeName === '#document-fragment'
|
22
|
-
) {
|
23
|
-
return true;
|
24
|
-
}
|
18
|
+
// the root element (HTML) is skipped since we always start with a
|
19
|
+
// stacking order of [0]
|
25
20
|
|
26
21
|
// position: fixed or sticky
|
27
|
-
if (
|
28
|
-
vNode.getComputedStylePropertyValue('position') === 'fixed' ||
|
29
|
-
vNode.getComputedStylePropertyValue('position') === 'sticky'
|
30
|
-
) {
|
22
|
+
if (position === 'fixed' || position === 'sticky') {
|
31
23
|
return true;
|
32
24
|
}
|
33
25
|
|
34
26
|
// positioned (absolutely or relatively) with a z-index value other than "auto",
|
35
|
-
if (
|
36
|
-
vNode.getComputedStylePropertyValue('z-index') !== 'auto' &&
|
37
|
-
vNode.getComputedStylePropertyValue('position') !== 'static'
|
38
|
-
) {
|
27
|
+
if (zIndex !== 'auto' && position !== 'static') {
|
39
28
|
return true;
|
40
29
|
}
|
41
30
|
|
@@ -56,34 +45,26 @@ function isStackingContext(vNode) {
|
|
56
45
|
}
|
57
46
|
|
58
47
|
// elements with a mix-blend-mode value other than "normal"
|
59
|
-
|
60
|
-
|
61
|
-
vNode.getComputedStylePropertyValue('mix-blend-mode') !== 'normal'
|
62
|
-
) {
|
48
|
+
const mixBlendMode = vNode.getComputedStylePropertyValue('mix-blend-mode');
|
49
|
+
if (mixBlendMode && mixBlendMode !== 'normal') {
|
63
50
|
return true;
|
64
51
|
}
|
65
52
|
|
66
53
|
// elements with a filter value other than "none"
|
67
|
-
|
68
|
-
|
69
|
-
vNode.getComputedStylePropertyValue('filter') !== 'none'
|
70
|
-
) {
|
54
|
+
const filter = vNode.getComputedStylePropertyValue('filter');
|
55
|
+
if (filter && filter !== 'none') {
|
71
56
|
return true;
|
72
57
|
}
|
73
58
|
|
74
59
|
// elements with a perspective value other than "none"
|
75
|
-
|
76
|
-
|
77
|
-
vNode.getComputedStylePropertyValue('perspective') !== 'none'
|
78
|
-
) {
|
60
|
+
const perspective = vNode.getComputedStylePropertyValue('perspective');
|
61
|
+
if (perspective && perspective !== 'none') {
|
79
62
|
return true;
|
80
63
|
}
|
81
64
|
|
82
65
|
// element with a clip-path value other than "none"
|
83
|
-
|
84
|
-
|
85
|
-
vNode.getComputedStylePropertyValue('clip-path') !== 'none'
|
86
|
-
) {
|
66
|
+
const clipPath = vNode.getComputedStylePropertyValue('clip-path');
|
67
|
+
if (clipPath && clipPath !== 'none') {
|
87
68
|
return true;
|
88
69
|
}
|
89
70
|
|
@@ -92,7 +73,6 @@ function isStackingContext(vNode) {
|
|
92
73
|
vNode.getComputedStylePropertyValue('-webkit-mask') ||
|
93
74
|
vNode.getComputedStylePropertyValue('mask') ||
|
94
75
|
'none';
|
95
|
-
|
96
76
|
if (mask !== 'none') {
|
97
77
|
return true;
|
98
78
|
}
|
@@ -102,7 +82,6 @@ function isStackingContext(vNode) {
|
|
102
82
|
vNode.getComputedStylePropertyValue('-webkit-mask-image') ||
|
103
83
|
vNode.getComputedStylePropertyValue('mask-image') ||
|
104
84
|
'none';
|
105
|
-
|
106
85
|
if (maskImage !== 'none') {
|
107
86
|
return true;
|
108
87
|
}
|
@@ -112,7 +91,6 @@ function isStackingContext(vNode) {
|
|
112
91
|
vNode.getComputedStylePropertyValue('-webkit-mask-border') ||
|
113
92
|
vNode.getComputedStylePropertyValue('mask-border') ||
|
114
93
|
'none';
|
115
|
-
|
116
94
|
if (maskBorder !== 'none') {
|
117
95
|
return true;
|
118
96
|
}
|
@@ -123,10 +101,8 @@ function isStackingContext(vNode) {
|
|
123
101
|
}
|
124
102
|
|
125
103
|
// transform or opacity in will-change even if you don't specify values for these attributes directly
|
126
|
-
|
127
|
-
|
128
|
-
vNode.getComputedStylePropertyValue('will-change') === 'opacity'
|
129
|
-
) {
|
104
|
+
const willChange = vNode.getComputedStylePropertyValue('will-change');
|
105
|
+
if (willChange === 'transform' || willChange === 'opacity') {
|
130
106
|
return true;
|
131
107
|
}
|
132
108
|
|
@@ -146,11 +122,8 @@ function isStackingContext(vNode) {
|
|
146
122
|
}
|
147
123
|
|
148
124
|
// a flex item or gird item with a z-index value other than "auto", that is the parent element display: flex|inline-flex|grid|inline-grid,
|
149
|
-
if (
|
150
|
-
|
151
|
-
vNode.parent
|
152
|
-
) {
|
153
|
-
const parentDsiplay = vNode.parent.getComputedStylePropertyValue('display');
|
125
|
+
if (zIndex !== 'auto' && parentVNode) {
|
126
|
+
const parentDsiplay = parentVNode.getComputedStylePropertyValue('display');
|
154
127
|
if (
|
155
128
|
[
|
156
129
|
'flex',
|
@@ -192,9 +165,7 @@ function getPositionOrder(vNode) {
|
|
192
165
|
}
|
193
166
|
|
194
167
|
// 3. the in-flow, non-inline-level, non-positioned descendants.
|
195
|
-
|
196
|
-
return 0;
|
197
|
-
}
|
168
|
+
return 0;
|
198
169
|
}
|
199
170
|
|
200
171
|
// 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
|
@@ -210,15 +181,6 @@ function getPositionOrder(vNode) {
|
|
210
181
|
*/
|
211
182
|
function visuallySort(a, b) {
|
212
183
|
/*eslint no-bitwise: 0 */
|
213
|
-
|
214
|
-
// 1. The root element forms the root stacking context.
|
215
|
-
if (a.actualNode.nodeName.toLowerCase() === 'html') {
|
216
|
-
return 1;
|
217
|
-
}
|
218
|
-
if (b.actualNode.nodeName.toLowerCase() === 'html') {
|
219
|
-
return -1;
|
220
|
-
}
|
221
|
-
|
222
184
|
for (let i = 0; i < a._stackingOrder.length; i++) {
|
223
185
|
if (typeof b._stackingOrder[i] === 'undefined') {
|
224
186
|
return -1;
|
@@ -236,9 +198,55 @@ function visuallySort(a, b) {
|
|
236
198
|
}
|
237
199
|
|
238
200
|
// nodes are the same stacking order
|
239
|
-
|
240
|
-
|
241
|
-
|
201
|
+
let aNode = a.actualNode;
|
202
|
+
let bNode = b.actualNode;
|
203
|
+
|
204
|
+
// elements don't correctly calculate document position when comparing
|
205
|
+
// across shadow boundaries, so we need to compare the position of a
|
206
|
+
// shared host instead
|
207
|
+
|
208
|
+
// elements have different hosts
|
209
|
+
if (aNode.getRootNode && aNode.getRootNode() !== bNode.getRootNode()) {
|
210
|
+
// keep track of all parent hosts and find the one both nodes share
|
211
|
+
const boundaries = [];
|
212
|
+
while (aNode) {
|
213
|
+
boundaries.push({
|
214
|
+
root: aNode.getRootNode(),
|
215
|
+
node: aNode
|
216
|
+
});
|
217
|
+
aNode = aNode.getRootNode().host;
|
218
|
+
}
|
219
|
+
|
220
|
+
while (
|
221
|
+
bNode &&
|
222
|
+
!boundaries.find(boundary => boundary.root === bNode.getRootNode())
|
223
|
+
) {
|
224
|
+
bNode = bNode.getRootNode().host;
|
225
|
+
}
|
226
|
+
|
227
|
+
// bNode is a node that shares a host with some part of the a parent
|
228
|
+
// shadow tree, find the aNode that shares the same host as bNode
|
229
|
+
aNode = boundaries.find(boundary => boundary.root === bNode.getRootNode())
|
230
|
+
.node;
|
231
|
+
|
232
|
+
// sort child of shadow to it's host node by finding which element is
|
233
|
+
// the child of the host and sorting it before the host
|
234
|
+
if (aNode === bNode) {
|
235
|
+
return a.actualNode.getRootNode() !== aNode.getRootNode() ? -1 : 1;
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
const {
|
240
|
+
DOCUMENT_POSITION_FOLLOWING,
|
241
|
+
DOCUMENT_POSITION_CONTAINS,
|
242
|
+
DOCUMENT_POSITION_CONTAINED_BY
|
243
|
+
} = window.Node;
|
244
|
+
|
245
|
+
const docPosition = aNode.compareDocumentPosition(bNode);
|
246
|
+
const DOMOrder = docPosition & DOCUMENT_POSITION_FOLLOWING ? 1 : -1;
|
247
|
+
const isDescendant =
|
248
|
+
docPosition & DOCUMENT_POSITION_CONTAINS ||
|
249
|
+
docPosition & DOCUMENT_POSITION_CONTAINED_BY;
|
242
250
|
const aPosition = getPositionOrder(a);
|
243
251
|
const bPosition = getPositionOrder(b);
|
244
252
|
|
@@ -256,17 +264,13 @@ function visuallySort(a, b) {
|
|
256
264
|
* @param {VirtualNode}
|
257
265
|
* @return {Number[]}
|
258
266
|
*/
|
259
|
-
function getStackingOrder(vNode) {
|
260
|
-
const stackingOrder =
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
if (vNode.getComputedStylePropertyValue('z-index') !== 'auto') {
|
265
|
-
stackingOrder[stackingOrder.length - 1] = parseInt(
|
266
|
-
vNode.getComputedStylePropertyValue('z-index')
|
267
|
-
);
|
267
|
+
function getStackingOrder(vNode, parentVNode) {
|
268
|
+
const stackingOrder = parentVNode._stackingOrder.slice();
|
269
|
+
const zIndex = vNode.getComputedStylePropertyValue('z-index');
|
270
|
+
if (zIndex !== 'auto') {
|
271
|
+
stackingOrder[stackingOrder.length - 1] = parseInt(zIndex);
|
268
272
|
}
|
269
|
-
if (isStackingContext(vNode)) {
|
273
|
+
if (isStackingContext(vNode, parentVNode)) {
|
270
274
|
stackingOrder.push(0);
|
271
275
|
}
|
272
276
|
|
@@ -278,24 +282,25 @@ function getStackingOrder(vNode) {
|
|
278
282
|
* @param {VirtualNode}
|
279
283
|
* @return {VirtualNode|null}
|
280
284
|
*/
|
281
|
-
function findScrollRegionParent(vNode) {
|
285
|
+
function findScrollRegionParent(vNode, parentVNode) {
|
282
286
|
let scrollRegionParent = null;
|
283
|
-
let vNodeParent = vNode.parent;
|
284
287
|
let checkedNodes = [vNode];
|
285
288
|
|
286
|
-
while (
|
287
|
-
if (
|
288
|
-
scrollRegionParent =
|
289
|
+
while (parentVNode) {
|
290
|
+
if (parentVNode._scrollRegionParent) {
|
291
|
+
scrollRegionParent = parentVNode._scrollRegionParent;
|
289
292
|
break;
|
290
293
|
}
|
291
294
|
|
292
|
-
if (axe.utils.getScroll(
|
293
|
-
scrollRegionParent =
|
295
|
+
if (axe.utils.getScroll(parentVNode.actualNode)) {
|
296
|
+
scrollRegionParent = parentVNode;
|
294
297
|
break;
|
295
298
|
}
|
296
299
|
|
297
|
-
checkedNodes.push(
|
298
|
-
|
300
|
+
checkedNodes.push(parentVNode);
|
301
|
+
parentVNode = axe.utils.getNodeFromTree(
|
302
|
+
parentVNode.actualNode.parentElement || parentVNode.actualNode.parentNode
|
303
|
+
);
|
299
304
|
}
|
300
305
|
|
301
306
|
// cache result of parent scroll region so we don't have to look up the entire
|
@@ -306,21 +311,6 @@ function findScrollRegionParent(vNode) {
|
|
306
311
|
return scrollRegionParent;
|
307
312
|
}
|
308
313
|
|
309
|
-
/**
|
310
|
-
* Get the DOMRect x or y value. IE11 (and Phantom) does not support x/y
|
311
|
-
* on DOMRect.
|
312
|
-
* @param {DOMRect}
|
313
|
-
* @param {String} pos 'x' or 'y'
|
314
|
-
* @return {Number}
|
315
|
-
*/
|
316
|
-
function getDomPosition(rect, pos) {
|
317
|
-
if (pos === 'x') {
|
318
|
-
return 'x' in rect ? rect.x : rect.left;
|
319
|
-
}
|
320
|
-
|
321
|
-
return 'y' in rect ? rect.y : rect.top;
|
322
|
-
}
|
323
|
-
|
324
314
|
/**
|
325
315
|
* Add a node to every cell of the grid it intersects with.
|
326
316
|
* @param {Grid}
|
@@ -332,15 +322,15 @@ function addNodeToGrid(grid, vNode) {
|
|
332
322
|
vNode._grid = grid;
|
333
323
|
|
334
324
|
vNode.clientRects.forEach(rect => {
|
335
|
-
const
|
336
|
-
const
|
325
|
+
const x = rect.left;
|
326
|
+
const y = rect.top;
|
337
327
|
|
338
|
-
|
339
|
-
|
340
|
-
);
|
341
|
-
const
|
342
|
-
|
343
|
-
);
|
328
|
+
// "| 0" is a faster way to do Math.floor
|
329
|
+
// @see https://jsperf.com/math-floor-vs-math-round-vs-parseint/152
|
330
|
+
const startRow = (y / gridSize) | 0;
|
331
|
+
const startCol = (x / gridSize) | 0;
|
332
|
+
const endRow = ((y + rect.height) / gridSize) | 0;
|
333
|
+
const endCol = ((x + rect.width) / gridSize) | 0;
|
344
334
|
|
345
335
|
for (let row = startRow; row <= endRow; row++) {
|
346
336
|
grid.cells[row] = grid.cells[row] || [];
|
@@ -357,72 +347,97 @@ function addNodeToGrid(grid, vNode) {
|
|
357
347
|
}
|
358
348
|
|
359
349
|
/**
|
360
|
-
* Setup the 2d grid and add every element to it
|
350
|
+
* Setup the 2d grid and add every element to it, even elements not
|
351
|
+
* included in the flat tree
|
361
352
|
*/
|
362
|
-
function createGrid(
|
363
|
-
|
353
|
+
function createGrid(
|
354
|
+
root = document.body,
|
355
|
+
rootGrid = {
|
364
356
|
container: null,
|
365
357
|
cells: []
|
366
|
-
}
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
358
|
+
},
|
359
|
+
parentVNode = null
|
360
|
+
) {
|
361
|
+
// by not starting at the htmlElement we don't have to pass a custom
|
362
|
+
// filter function into the treeWalker to filter out head elements,
|
363
|
+
// which would be called for every node
|
364
|
+
if (!parentVNode) {
|
365
|
+
let vNode = axe.utils.getNodeFromTree(document.documentElement);
|
366
|
+
if (!vNode) {
|
367
|
+
vNode = new VirtualNode(document.documentElement);
|
368
|
+
}
|
375
369
|
|
376
|
-
|
370
|
+
vNode._stackingOrder = [0];
|
371
|
+
addNodeToGrid(rootGrid, vNode);
|
377
372
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
373
|
+
if (axe.utils.getScroll(vNode.actualNode)) {
|
374
|
+
const subGrid = {
|
375
|
+
container: vNode,
|
376
|
+
cells: []
|
377
|
+
};
|
378
|
+
vNode._subGrid = subGrid;
|
379
|
+
}
|
380
|
+
}
|
385
381
|
|
386
|
-
|
387
|
-
|
382
|
+
// IE11 requires the first 3 parameters
|
383
|
+
// @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker
|
384
|
+
const treeWalker = document.createTreeWalker(
|
385
|
+
root,
|
386
|
+
window.NodeFilter.SHOW_ELEMENT,
|
387
|
+
null,
|
388
|
+
false
|
389
|
+
);
|
390
|
+
let node = parentVNode ? treeWalker.nextNode() : treeWalker.currentNode;
|
391
|
+
while (node) {
|
392
|
+
let vNode = axe.utils.getNodeFromTree(node);
|
393
|
+
|
394
|
+
// an svg in IE11 does not have a parentElement but instead has a
|
395
|
+
// parentNode. but parentNode could be a shadow root so we need to
|
396
|
+
// verity it's in the tree first
|
397
|
+
if (node.parentElement) {
|
398
|
+
parentVNode = axe.utils.getNodeFromTree(node.parentElement);
|
399
|
+
} else if (node.parentNode && axe.utils.getNodeFromTree(node.parentNode)) {
|
400
|
+
parentVNode = axe.utils.getNodeFromTree(node.parentNode);
|
401
|
+
}
|
388
402
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
cells: []
|
393
|
-
};
|
394
|
-
vNode._subGrid = subGrid;
|
395
|
-
}
|
403
|
+
if (!vNode) {
|
404
|
+
vNode = new VirtualNode(node, parentVNode);
|
405
|
+
}
|
396
406
|
|
397
|
-
|
398
|
-
});
|
399
|
-
}
|
407
|
+
vNode._stackingOrder = getStackingOrder(vNode, parentVNode);
|
400
408
|
|
401
|
-
|
402
|
-
|
403
|
-
* @method getElementStack
|
404
|
-
* @memberof axe.commons.dom
|
405
|
-
* @param {VirtualNode} vNode
|
406
|
-
* @param {Boolean} [recursed] If the function has been called recursively
|
407
|
-
* @return {VirtualNode[]}
|
408
|
-
*/
|
409
|
-
dom.getElementStack = function(vNode, recursed = false) {
|
410
|
-
if (!axe._cache.get('gridCreated')) {
|
411
|
-
createGrid();
|
412
|
-
axe._cache.set('gridCreated', true);
|
413
|
-
}
|
409
|
+
const scrollRegionParent = findScrollRegionParent(vNode, parentVNode);
|
410
|
+
const grid = scrollRegionParent ? scrollRegionParent._subGrid : rootGrid;
|
414
411
|
|
415
|
-
|
412
|
+
if (axe.utils.getScroll(vNode.actualNode)) {
|
413
|
+
const subGrid = {
|
414
|
+
container: vNode,
|
415
|
+
cells: []
|
416
|
+
};
|
417
|
+
vNode._subGrid = subGrid;
|
418
|
+
}
|
416
419
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
+
// filter out any elements with 0 width or height
|
421
|
+
// (we don't do this before so we can calculate stacking context
|
422
|
+
// of parents with 0 width/height)
|
423
|
+
const rect = vNode.boundingClientRect;
|
424
|
+
if (rect.width !== 0 && rect.height !== 0 && dom.isVisible(node)) {
|
425
|
+
addNodeToGrid(grid, vNode);
|
426
|
+
}
|
420
427
|
|
421
|
-
|
428
|
+
// add shadow root elements to the grid
|
429
|
+
if (axe.utils.isShadowRoot(node)) {
|
430
|
+
createGrid(node.shadowRoot, grid, vNode);
|
431
|
+
}
|
422
432
|
|
433
|
+
node = treeWalker.nextNode();
|
434
|
+
}
|
435
|
+
}
|
436
|
+
|
437
|
+
function getRectStack(grid, rect, recursed = false) {
|
423
438
|
// use center point of rect
|
424
|
-
let x =
|
425
|
-
let y =
|
439
|
+
let x = rect.left + rect.width / 2;
|
440
|
+
let y = rect.top + rect.height / 2;
|
426
441
|
|
427
442
|
// NOTE: there is a very rare edge case in Chrome vs Firefox that can
|
428
443
|
// return different results of `document.elementsFromPoint`. If the center
|
@@ -430,36 +445,119 @@ dom.getElementStack = function(vNode, recursed = false) {
|
|
430
445
|
// Chrome appears to round the number up and return the element while Firefox
|
431
446
|
// keeps the number as is and won't return the element. In this case, we
|
432
447
|
// went with pixel perfect collision rather than rounding
|
433
|
-
const row =
|
434
|
-
const col =
|
448
|
+
const row = (y / gridSize) | 0;
|
449
|
+
const col = (x / gridSize) | 0;
|
435
450
|
let stack = grid.cells[row][col].filter(gridCellNode => {
|
436
|
-
return gridCellNode.clientRects.find(
|
437
|
-
let
|
438
|
-
let
|
439
|
-
|
440
|
-
let rectWidth = rect.width;
|
441
|
-
let rectHeight = rect.height;
|
442
|
-
let rectX = getDomPosition(rect, 'x');
|
443
|
-
let rectY = getDomPosition(rect, 'y');
|
451
|
+
return gridCellNode.clientRects.find(clientRect => {
|
452
|
+
let rectX = clientRect.left;
|
453
|
+
let rectY = clientRect.top;
|
444
454
|
|
445
455
|
// perform an AABB (axis-aligned bounding box) collision check for the
|
446
456
|
// point inside the rect
|
447
457
|
return (
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
458
|
+
x <= rectX + clientRect.width &&
|
459
|
+
x >= rectX &&
|
460
|
+
y <= rectY + clientRect.height &&
|
461
|
+
y >= rectY
|
452
462
|
);
|
453
463
|
});
|
454
464
|
});
|
455
465
|
|
456
|
-
|
457
|
-
|
466
|
+
const gridContainer = grid.container;
|
467
|
+
if (gridContainer) {
|
468
|
+
stack = getRectStack(
|
469
|
+
gridContainer._grid,
|
470
|
+
gridContainer.boundingClientRect,
|
471
|
+
true
|
472
|
+
).concat(stack);
|
458
473
|
}
|
459
474
|
|
460
475
|
if (!recursed) {
|
461
|
-
stack
|
476
|
+
stack = stack
|
477
|
+
.sort(visuallySort)
|
478
|
+
.map(vNode => vNode.actualNode)
|
479
|
+
// always make sure html is the last element
|
480
|
+
.concat(document.documentElement)
|
481
|
+
// remove duplicates caused by adding client rects of the same node
|
482
|
+
.filter((node, index, array) => array.indexOf(node) === index);
|
462
483
|
}
|
463
484
|
|
464
485
|
return stack;
|
486
|
+
}
|
487
|
+
|
488
|
+
/**
|
489
|
+
* Return all elements that are at the center bounding rect of the passed in node.
|
490
|
+
* @method getElementStack
|
491
|
+
* @memberof axe.commons.dom
|
492
|
+
* @param {Node} node
|
493
|
+
* @return {Node[]}
|
494
|
+
*/
|
495
|
+
dom.getElementStack = function(node) {
|
496
|
+
if (!axe._cache.get('gridCreated')) {
|
497
|
+
createGrid();
|
498
|
+
axe._cache.set('gridCreated', true);
|
499
|
+
}
|
500
|
+
|
501
|
+
const vNode = axe.utils.getNodeFromTree(node);
|
502
|
+
const grid = vNode._grid;
|
503
|
+
|
504
|
+
if (!grid) {
|
505
|
+
return [];
|
506
|
+
}
|
507
|
+
|
508
|
+
return getRectStack(grid, vNode.boundingClientRect);
|
509
|
+
};
|
510
|
+
|
511
|
+
/**
|
512
|
+
* Return all elements that are at the center of each text client rect of the passed in node.
|
513
|
+
* @method getTextElementStack
|
514
|
+
* @memberof axe.commons.dom
|
515
|
+
* @param {Node} node
|
516
|
+
* @return {Array<Node[]>}
|
517
|
+
*/
|
518
|
+
dom.getTextElementStack = function(node) {
|
519
|
+
if (!axe._cache.get('gridCreated')) {
|
520
|
+
createGrid();
|
521
|
+
axe._cache.set('gridCreated', true);
|
522
|
+
}
|
523
|
+
|
524
|
+
const vNode = axe.utils.getNodeFromTree(node);
|
525
|
+
const grid = vNode._grid;
|
526
|
+
|
527
|
+
if (!grid) {
|
528
|
+
return [];
|
529
|
+
}
|
530
|
+
|
531
|
+
// for code blocks that use syntax highlighting, you can get a ton of client
|
532
|
+
// rects (See https://github.com/dequelabs/axe-core/issues/1985). they use
|
533
|
+
// a mixture of text nodes and other nodes (which will contain their own text
|
534
|
+
// nodes), but all we care about is checking the direct text nodes as the
|
535
|
+
// other nodes will have their own client rects checked. doing this speeds up
|
536
|
+
// color contrast significantly for large syntax highlighted code blocks
|
537
|
+
const clientRects = [];
|
538
|
+
Array.from(node.childNodes).forEach(elm => {
|
539
|
+
if (
|
540
|
+
elm.nodeType === 3 &&
|
541
|
+
axe.commons.text.sanitize(elm.textContent) !== ''
|
542
|
+
) {
|
543
|
+
const range = document.createRange();
|
544
|
+
range.selectNodeContents(elm);
|
545
|
+
const rects = range.getClientRects();
|
546
|
+
|
547
|
+
for (let i = 0; i < rects.length; i++) {
|
548
|
+
const rect = rects[i];
|
549
|
+
|
550
|
+
// filter out 0 width and height rects (newline characters)
|
551
|
+
// ie11 has newline characters return 0.00998, so we'll say if the
|
552
|
+
// line is < 1 it shouldn't be counted
|
553
|
+
if (rect.width >= 1 && rect.height >= 1) {
|
554
|
+
clientRects.push(rect);
|
555
|
+
}
|
556
|
+
}
|
557
|
+
}
|
558
|
+
});
|
559
|
+
|
560
|
+
return clientRects.map(rect => {
|
561
|
+
return getRectStack(grid, rect);
|
562
|
+
});
|
465
563
|
};
|