From 1192c64b46d5c16d93039462deeaa8e5a0a3ad5f Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 21 Sep 2022 15:41:31 -0600 Subject: [PATCH 1/6] fix(aria-hidden-focus): do not fail for focus trap bumper elements --- .../keyboard/focusable-disabled-evaluate.js | 14 ++++- .../focusable-not-tabbable-evaluate.js | 14 ++++- test/checks/keyboard/focusable-disabled.js | 59 +++++++++++-------- .../checks/keyboard/focusable-not-tabbable.js | 53 ++++++++++------- .../aria-hidden-focus/aria-hidden-focus.html | 30 ++++++++-- .../aria-hidden-focus/aria-hidden-focus.json | 3 +- 6 files changed, 118 insertions(+), 55 deletions(-) diff --git a/lib/checks/keyboard/focusable-disabled-evaluate.js b/lib/checks/keyboard/focusable-disabled-evaluate.js index 5ac5955f14..d952da05fd 100644 --- a/lib/checks/keyboard/focusable-disabled-evaluate.js +++ b/lib/checks/keyboard/focusable-disabled-evaluate.js @@ -1,4 +1,5 @@ import { isModalOpen } from '../../commons/dom'; +import { getNodeFromTree } from '../../core/utils'; function focusableDisabledEvaluate(node, options, virtualNode) { const elementsThatCanBeDisabled = [ @@ -29,7 +30,18 @@ function focusableDisabledEvaluate(node, options, virtualNode) { if (relatedNodes.length === 0 || isModalOpen()) { return true; } - return relatedNodes.every(related => related.onfocus) ? undefined : false + return relatedNodes.every(related => { + const vNode = getNodeFromTree(related); + const pointerEvents = vNode.getComputedStylePropertyValue('pointer-events'); + const rect = vNode.boundingClientRect; + + return ( + related.onfocus || + ((rect.width === 0 || rect.height === 0) && pointerEvents === 'none') + ); + }) + ? undefined + : false; } export default focusableDisabledEvaluate; diff --git a/lib/checks/keyboard/focusable-not-tabbable-evaluate.js b/lib/checks/keyboard/focusable-not-tabbable-evaluate.js index 8607d29bfe..a8e9f82ac5 100644 --- a/lib/checks/keyboard/focusable-not-tabbable-evaluate.js +++ b/lib/checks/keyboard/focusable-not-tabbable-evaluate.js @@ -1,4 +1,5 @@ import { isModalOpen } from '../../commons/dom'; +import { getNodeFromTree } from '../../core/utils'; function focusableNotTabbableEvaluate(node, options, virtualNode) { const elementsThatCanBeDisabled = [ @@ -29,7 +30,18 @@ function focusableNotTabbableEvaluate(node, options, virtualNode) { if (relatedNodes.length === 0 || isModalOpen()) { return true; } - return relatedNodes.every(related => related.onfocus) ? undefined : false + return relatedNodes.every(related => { + const vNode = getNodeFromTree(related); + const pointerEvents = vNode.getComputedStylePropertyValue('pointer-events'); + const rect = vNode.boundingClientRect; + + return ( + related.onfocus || + ((rect.width === 0 || rect.height === 0) && pointerEvents === 'none') + ); + }) + ? undefined + : false; } export default focusableNotTabbableEvaluate; diff --git a/test/checks/keyboard/focusable-disabled.js b/test/checks/keyboard/focusable-disabled.js index 96f9537dee..95a9286478 100644 --- a/test/checks/keyboard/focusable-disabled.js +++ b/test/checks/keyboard/focusable-disabled.js @@ -1,4 +1,4 @@ -describe('focusable-disabled', function() { +describe('focusable-disabled', function () { 'use strict'; var check; @@ -8,24 +8,24 @@ describe('focusable-disabled', function() { var checkContext = axe.testUtils.MockCheckContext(); var checkSetup = axe.testUtils.checkSetup; - before(function() { + before(function () { check = checks['focusable-disabled']; }); - afterEach(function() { + afterEach(function () { fixture.innerHTML = ''; axe._tree = undefined; axe._selectorData = undefined; checkContext.reset(); }); - it('returns true when content not focusable by default (no tabbable elements)', function() { + it('returns true when content not focusable by default (no tabbable elements)', function () { var params = checkSetup(''); var actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); - it('returns true when content hidden through CSS (no tabbable elements)', function() { + it('returns true when content hidden through CSS (no tabbable elements)', function () { var params = checkSetup( '' ); @@ -33,7 +33,7 @@ describe('focusable-disabled', function() { assert.isTrue(actual); }); - it('returns true when content made unfocusable through disabled (no tabbable elements)', function() { + it('returns true when content made unfocusable through disabled (no tabbable elements)', function () { var params = checkSetup( '' ); @@ -41,7 +41,7 @@ describe('focusable-disabled', function() { assert.isTrue(actual); }); - it('returns true when content made unfocusable through disabled fieldset', function() { + it('returns true when content made unfocusable through disabled fieldset', function () { var params = checkSetup( '' ); @@ -51,7 +51,7 @@ describe('focusable-disabled', function() { (shadowSupported ? it : xit)( 'returns false when content is in a disabled fieldset but in another shadow tree', - function() { + function () { var fieldset = document.createElement('fieldset'); fieldset.setAttribute('disabled', 'true'); fieldset.setAttribute('aria-hidden', 'true'); @@ -70,7 +70,7 @@ describe('focusable-disabled', function() { } ); - it('returns false when content is in the legend of a disabled fieldset', function() { + it('returns false when content is in the legend of a disabled fieldset', function () { var params = checkSetup( '' ); @@ -78,7 +78,7 @@ describe('focusable-disabled', function() { assert.isFalse(actual); }); - it('returns false when content is in an aria-hidden but not disabled fieldset', function() { + it('returns false when content is in an aria-hidden but not disabled fieldset', function () { var params = checkSetup( '' ); @@ -86,7 +86,7 @@ describe('focusable-disabled', function() { assert.isFalse(actual); }); - it('returns true when focusable off screen link (cannot be disabled)', function() { + it('returns true when focusable off screen link (cannot be disabled)', function () { var params = checkSetup( '' ); @@ -95,7 +95,7 @@ describe('focusable-disabled', function() { assert.lengthOf(checkContext._relatedNodes, 0); }); - it('returns false when focusable form field only disabled through ARIA', function() { + it('returns false when focusable form field only disabled through ARIA', function () { var params = checkSetup( '' ); @@ -108,7 +108,7 @@ describe('focusable-disabled', function() { ); }); - it('returns false when focusable SELECT element that can be disabled', function() { + it('returns false when focusable SELECT element that can be disabled', function () { var params = checkSetup( '