Skip to content
This repository has been archived by the owner on Dec 7, 2022. It is now read-only.

Avoid walking entire parent tree unnecessarily in isElementOrAncestorHidden. #262

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions src/js/AccessibilityUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,6 @@ axs.utils.getContrastRatioForElement = function(element) {
* @return {?number}
*/
axs.utils.getContrastRatioForElementWithComputedStyle = function(style, element) {
if (axs.utils.isElementHidden(element))
return null;

var bgColor = axs.utils.getBgColor(style, element);
if (!bgColor)
return null;
Expand Down Expand Up @@ -569,22 +566,41 @@ axs.utils.isElementDisabled = function(element) {

/**
* @param {Element} element An element to check.
* @return {boolean} True if the element is hidden from accessibility.
* @return {boolean} True if an element itself has a style attribute which causes it
* not to be visible.
*/
axs.utils.isElementHidden = function(element) {
if (!(element instanceof element.ownerDocument.defaultView.HTMLElement))
return false;

if (element.hasAttribute('chromevoxignoreariahidden'))
var chromevoxignoreariahidden = true;

axs.utils.elementHasNonVisibleStyle = function(element) {
var style = window.getComputedStyle(element, null);
if (style.display == 'none' || style.visibility == 'hidden')
return true;
return false;
}

/**
* @param {Element} element An element to check.
* @return {boolean} True if the element is not visible to any user.
*/
axs.utils.elementIsNotVisible = function(element) {
if (axs.utils.elementHasNonVisibleStyle(element))
return true;
var boundingClientRect = element.getBoundingClientRect();
if (boundingClientRect.width === 0 && boundingClientRect.height === 0 &&
boundingClientRect.top === 0 && boundingClientRect.bottom === 0 &&
boundingClientRect.left === 0 && boundingClientRect.right === 0) {
return true;
}
return false;
}

/**
* @param {Element} element An element to check.
* @return {boolean} True if the element is hidden from accessibility.
*/
axs.utils.elementIsAriaHidden = function(element) {
if (element.hasAttribute('aria-hidden') &&
element.getAttribute('aria-hidden').toLowerCase() == 'true') {
return !chromevoxignoreariahidden;
element.getAttribute('aria-hidden').toLowerCase() == 'true' &&
!document.documentElement.hasAttribute('chromevoxignoreariahidden')) {
return true;
}

return false;
Expand All @@ -596,7 +612,9 @@ axs.utils.isElementHidden = function(element) {
* hidden from accessibility.
*/
axs.utils.isElementOrAncestorHidden = function(element) {
if (axs.utils.isElementHidden(element))
if (axs.utils.elementIsNotVisible(element))
return true;
if (axs.utils.elementIsAriaHidden(element))
return true;

if (axs.dom.parentElement(element))
Expand Down
10 changes: 5 additions & 5 deletions test/audits/aria-on-reserved-element-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

test('Reserved element with role and aria- attributes', function() {
var fixture = document.getElementById('qunit-fixture');
var widget = fixture.appendChild(document.createElement('meta'));
var widget = fixture.appendChild(document.createElement('map'));
widget.setAttribute('role', 'spinbutton');
widget.setAttribute('aria-hidden', 'false'); // global
widget.setAttribute('aria-required', 'true'); // supported
Expand All @@ -59,23 +59,23 @@

test('Reserved element with role only', function() {
var fixture = document.getElementById('qunit-fixture');
var widget = fixture.appendChild(document.createElement('meta'));
var widget = fixture.appendChild(document.createElement('map'));
widget.setAttribute('role', 'spinbutton');
var expected = { elements: [widget], result: axs.constants.AuditResult.FAIL };
deepEqual(rule.run({ scope: fixture }), expected, 'Reserved elements can\'t take any ARIA attributes.');
});

test('Reserved element with aria-attributes only', function() {
var fixture = document.getElementById('qunit-fixture');
var widget = fixture.appendChild(document.createElement('meta'));
var widget = fixture.appendChild(document.createElement('map'));
widget.setAttribute('aria-hidden', 'false'); // global
var expected = { elements: [widget], result: axs.constants.AuditResult.FAIL };
deepEqual(rule.run({ scope: fixture }), expected, 'Reserved elements can\'t take any ARIA attributes.');
});

test('Using ignoreSelectors, reserved element with aria-attributes only', function() {
var fixture = document.getElementById('qunit-fixture');
var widget = fixture.appendChild(document.createElement('meta'));
var widget = fixture.appendChild(document.createElement('map'));
var ignoreSelectors = ['#' + (widget.id = 'ignoreMe')];
widget.setAttribute('aria-hidden', 'false'); // global
var expected = { result: axs.constants.AuditResult.NA };
Expand All @@ -84,7 +84,7 @@

test('Reserved element with no ARIA attributes', function() {
var fixture = document.getElementById('qunit-fixture');
fixture.appendChild(document.createElement('meta'));
fixture.appendChild(document.createElement('map'));
var expected = { elements: [], result: axs.constants.AuditResult.PASS };
deepEqual(rule.run({ scope: fixture }), expected, 'A reserved element with no ARIA attributes should pass');
});
Expand Down
2 changes: 1 addition & 1 deletion test/audits/bad-aria-attribute-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
*/
test('Element with role and global but missing supported and required attributes', function() {
var fixture = document.getElementById('qunit-fixture');
var widget = fixture.appendChild(document.createElement('meta')); // note, a reserved HTML element
var widget = fixture.appendChild(document.createElement('map')); // note, a reserved HTML element
widget.setAttribute('role', 'spinbutton');
widget.setAttribute('aria-hidden', 'false'); // global (so the audit will encounter this element)
var expected = { elements: [], result: axs.constants.AuditResult.PASS };
Expand Down
2 changes: 1 addition & 1 deletion test/audits/unsupported-aria-attribute-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
*/
test('Element with role and global but missing supported and required attributes', function() {
var fixture = document.getElementById('qunit-fixture');
var widget = fixture.appendChild(document.createElement('meta')); // note, a reserved HTML element
var widget = fixture.appendChild(document.createElement('map')); // note, a reserved HTML element
var expected = { elements: [], result: axs.constants.AuditResult.PASS };
widget.setAttribute('role', 'spinbutton');
widget.setAttribute('aria-hidden', 'false'); // global (so the audit will encounter this element)
Expand Down