-
"
`)
diff --git a/src/role-helpers.js b/src/role-helpers.js
index 961c3186..2d49d646 100644
--- a/src/role-helpers.js
+++ b/src/role-helpers.js
@@ -3,6 +3,27 @@ import {prettyDOM} from './pretty-dom'
const elementRoleList = buildElementRoleList(elementRoles)
+/**
+ * @param {Element} element -
+ * @returns {boolean} - `true` if `element` and its subtree are inaccessible
+ */
+function isSubtreeInaccessible(element) {
+ if (element.hidden === true) {
+ return true
+ }
+
+ if (element.getAttribute('aria-hidden') === 'true') {
+ return true
+ }
+
+ const window = element.ownerDocument.defaultView
+ if (window.getComputedStyle(element).display === 'none') {
+ return true
+ }
+
+ return false
+}
+
/**
* Partial implementation https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
* which should only be used for elements with a non-presentational role i.e.
@@ -12,47 +33,27 @@ const elementRoleList = buildElementRoleList(elementRoles)
* Ignores "Child Presentational: True" characteristics
*
* @param {Element} element -
+ * @param {object} [options] -
+ * @param {function (element: Element): boolean} options.isSubtreeInaccessible -
+ * can be used to return cached results from previous isSubtreeInaccessible calls
* @returns {boolean} true if excluded, otherwise false
*/
-function isInaccessible(element) {
+function isInaccessible(element, options = {}) {
+ const {
+ isSubtreeInaccessible: isSubtreeInaccessibleImpl = isSubtreeInaccessible,
+ } = options
const window = element.ownerDocument.defaultView
- const computedStyle = window.getComputedStyle(element)
// since visibility is inherited we can exit early
- if (computedStyle.visibility === 'hidden') {
+ if (window.getComputedStyle(element).visibility === 'hidden') {
return true
}
- // Remove once https://github.com/jsdom/jsdom/issues/2616 is fixed
- const supportsStyleInheritance = computedStyle.visibility !== ''
- let visibility = computedStyle.visibility
-
let currentElement = element
while (currentElement) {
- if (currentElement.hidden === true) {
- return true
- }
-
- if (currentElement.getAttribute('aria-hidden') === 'true') {
+ if (isSubtreeInaccessibleImpl(currentElement)) {
return true
}
- const currentComputedStyle = window.getComputedStyle(currentElement)
-
- if (currentComputedStyle.display === 'none') {
- return true
- }
-
- // this branch is temporary code until jsdom fixes a bug
- // istanbul ignore else
- if (supportsStyleInheritance === false) {
- // we go bottom-up for an inheritable property so we can only set it
- // if it wasn't set already i.e. the parent can't overwrite the child
- if (visibility === '') visibility = currentComputedStyle.visibility
- if (visibility === 'hidden') {
- return true
- }
- }
-
currentElement = currentElement.parentElement
}
From 2b954e2b02ad4044def6f7939f12e1e3385f9915 Mon Sep 17 00:00:00 2001
From: Sebastian Silbermann
Date: Thu, 24 Oct 2019 17:49:08 +0200
Subject: [PATCH 3/4] Cache isSubtreeInaccessible calls
---
src/__tests__/benchmark/byRole.js | 6 +++---
src/queries/role.js | 16 +++++++++++++++-
src/role-helpers.js | 9 ++++++++-
3 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/src/__tests__/benchmark/byRole.js b/src/__tests__/benchmark/byRole.js
index 50584207..74e59ccd 100644
--- a/src/__tests__/benchmark/byRole.js
+++ b/src/__tests__/benchmark/byRole.js
@@ -39,8 +39,8 @@ test('byRole performance', () => {
median: ${Math.round(median * 1000)}µs
avg: ${Math.round(avg * 1000)}µs`).toMatchInlineSnapshot(`
"
- 95th percentile: 6045µs
- median: 4680µs
- avg: 4775µs"
+ 95th percentile: 3341µs
+ median: 2481µs
+ avg: 2666µs"
`)
})
diff --git a/src/queries/role.js b/src/queries/role.js
index be031475..b84c4993 100644
--- a/src/queries/role.js
+++ b/src/queries/role.js
@@ -2,6 +2,7 @@ import {
getImplicitAriaRoles,
prettyRoles,
isInaccessible,
+ isSubtreeInaccessible,
} from '../role-helpers'
import {buildQueries, fuzzyMatches, makeNormalizer, matches} from './all-utils'
@@ -13,6 +14,15 @@ function queryAllByRole(
const matcher = exact ? matches : fuzzyMatches
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
+ const subtreeIsInaccessibleCache = new WeakMap()
+ function cachedIsSubtreeInaccessible(element) {
+ if (!subtreeIsInaccessibleCache.has(element)) {
+ subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element))
+ }
+
+ return subtreeIsInaccessibleCache.get(element)
+ }
+
return Array.from(container.querySelectorAll('*'))
.filter(node => {
const isRoleSpecifiedExplicitly = node.hasAttribute('role')
@@ -28,7 +38,11 @@ function queryAllByRole(
)
})
.filter(element => {
- return hidden === false ? isInaccessible(element) === false : true
+ return hidden === false
+ ? isInaccessible(element, {
+ isSubtreeInaccessible: cachedIsSubtreeInaccessible,
+ }) === false
+ : true
})
}
diff --git a/src/role-helpers.js b/src/role-helpers.js
index 2d49d646..6218461d 100644
--- a/src/role-helpers.js
+++ b/src/role-helpers.js
@@ -156,6 +156,13 @@ function prettyRoles(dom, {hidden}) {
const logRoles = (dom, {hidden = false} = {}) =>
console.log(prettyRoles(dom, {hidden}))
-export {getRoles, logRoles, getImplicitAriaRoles, prettyRoles, isInaccessible}
+export {
+ getRoles,
+ logRoles,
+ getImplicitAriaRoles,
+ isSubtreeInaccessible,
+ prettyRoles,
+ isInaccessible,
+}
/* eslint no-console:0 */
From ca81c9d668f79a98263a7a0d2759e83fd8704214 Mon Sep 17 00:00:00 2001
From: Sebastian Silbermann
Date: Mon, 28 Oct 2019 19:54:42 +0100
Subject: [PATCH 4/4] Remove benchmark
---
src/__tests__/benchmark/byRole.js | 46 -------------------------------
1 file changed, 46 deletions(-)
delete mode 100644 src/__tests__/benchmark/byRole.js
diff --git a/src/__tests__/benchmark/byRole.js b/src/__tests__/benchmark/byRole.js
deleted file mode 100644
index 74e59ccd..00000000
--- a/src/__tests__/benchmark/byRole.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import {cleanup, renderIntoDocument} from '../helpers/test-utils'
-
-test('byRole performance', () => {
- const samples = 1000
- const times = Array(samples)
-
- for (let run = 0; run <= samples; run += 1) {
- const {getAllByRole} = renderIntoDocument(`
-
-
- A list of things to do
-
- A section describing how to do. And now a list with things
-
-
- - do this
- - do that
- - do it now
-
-
-
-
-
-`)
- const start = performance.now()
- getAllByRole('listitem')
- times[run] = performance.now() - start
- cleanup()
- }
-
- const avg = times.reduce((sum, n) => sum + n, 0) / times.length
- const median = times.sort((a, b) => a - b)[Math.floor(times.length / 2)]
- const percentile95 = times.sort((a, b) => a - b)[
- Math.floor(times.length * 0.95)
- ]
-
- expect(`
-95th percentile: ${Math.round(percentile95 * 1000)}µs
-median: ${Math.round(median * 1000)}µs
-avg: ${Math.round(avg * 1000)}µs`).toMatchInlineSnapshot(`
- "
- 95th percentile: 3341µs
- median: 2481µs
- avg: 2666µs"
- `)
-})