axe-core
Version:
Accessibility engine for automated Web UI testing
107 lines (91 loc) • 3.19 kB
JavaScript
import * as dom from '../../commons/dom';
import * as aria from '../../commons/aria';
import * as standards from '../../commons/standards';
import matches from '../../commons/matches';
import cache from '../../core/base/cache';
const landmarkRoles = standards.getAriaRolesByType('landmark');
const implicitAriaLiveRoles = ['alert', 'log', 'status'];
// Check if the current element is a landmark
function isRegion(virtualNode, options) {
const node = virtualNode.actualNode;
const role = aria.getRole(virtualNode);
const ariaLive = (node.getAttribute('aria-live') || '').toLowerCase().trim();
// Ignore content inside of aria-live
if (
['assertive', 'polite'].includes(ariaLive) ||
implicitAriaLiveRoles.includes(role)
) {
return true;
}
// Check if the node matches a landmark role
if (landmarkRoles.includes(role)) {
return true;
}
// Check if node matches an option
if (options.regionMatcher && matches(virtualNode, options.regionMatcher)) {
return true;
}
return false;
}
/**
* Find all visible elements not wrapped inside a landmark or skiplink
*/
function findRegionlessElms(virtualNode, options) {
const node = virtualNode.actualNode;
// End recursion if the element is a landmark, skiplink, or hidden content
if (
isRegion(virtualNode, options) ||
(dom.isSkipLink(virtualNode.actualNode) &&
dom.getElementByReference(virtualNode.actualNode, 'href')) ||
!dom.isVisible(node, true)
) {
// Mark each parent node as having region descendant
let vNode = virtualNode;
while (vNode) {
vNode._hasRegionDescendant = true;
vNode = vNode.parent;
}
return [];
// Return the node is a content element. Ignore any direct text children
// of body so we don't report body as being outside of a landmark.
// @see https://github.com/dequelabs/axe-core/issues/2049
} else if (
node !== document.body &&
dom.hasContent(node, /* noRecursion: */ true)
) {
return [virtualNode];
// Recursively look at all child elements
} else {
return virtualNode.children
.filter(({ actualNode }) => actualNode.nodeType === 1)
.map(vNode => findRegionlessElms(vNode, options))
.reduce((a, b) => a.concat(b), []); // flatten the results
}
}
function regionEvaluate(node, options, virtualNode) {
let regionlessNodes = cache.get('regionlessNodes');
if (regionlessNodes) {
return !regionlessNodes.includes(virtualNode);
}
const tree = axe._tree;
regionlessNodes = findRegionlessElms(tree[0], options)
// Find first parent marked as having region descendant (or body) and
// return the node right before it as the "outer" element
.map(vNode => {
while (
vNode.parent &&
!vNode.parent._hasRegionDescendant &&
vNode.parent.actualNode !== document.body
) {
vNode = vNode.parent;
}
return vNode;
})
// Remove duplicate containers
.filter((vNode, index, array) => {
return array.indexOf(vNode) === index;
});
cache.set('regionlessNodes', regionlessNodes);
return !regionlessNodes.includes(virtualNode);
}
export default regionEvaluate;