diff --git a/lib/commons/dom/get-visible-child-text-rect.js b/lib/commons/dom/get-visible-child-text-rect.js new file mode 100644 index 00000000..acb4213c --- /dev/null +++ b/lib/commons/dom/get-visible-child-text-rect.js @@ -0,0 +1,104 @@ +import { getNodeFromTree, memoize } from '../../core/utils'; +import { sanitize } from '../text'; +import { getIntersectionRect, getRectCenter, isPointInRect } from '../math'; +import getOverflowHiddenAncestors from './get-overflow-hidden-ancestors'; +import cache from '../../core/base/cache'; + +/** + * Get the visible text client rects of a node. + * @method getVisibleChildTextRect + * @memberof axe.commons.dom + * @instance + * @param {Element} node + */ +const getVisibleChildTextRect = memoize( + function getVisibleChildTextRectMemoized(node) { + const vNode = getNodeFromTree(node); + const nodeRect = vNode.boundingClientRect; + const clientRects = []; + const overflowHiddenNodes = getOverflowHiddenAncestors(vNode); + + node.childNodes.forEach(textNode => { + if (textNode.nodeType !== 3 || sanitize(textNode.nodeValue) === '') { + return; + } + + const contentRects = getContentRects(textNode); + if (isOutsideNodeBounds(contentRects, nodeRect) && !cache.get('ruleId')) { + return; + } + + clientRects.push(...filterHiddenRects(contentRects, overflowHiddenNodes)); + }); + + // a11y-engine-domforge change + if (clientRects.length <= 0) { + return []; + } + /** + * if all text rects are larger than the bounds of the node, + * or goes outside of the bounds of the node, we need to use + * the nodes bounding rect so we stay within the bounds of the + * element. + * + * @see https://github.com/dequelabs/axe-core/issues/2178 + * @see https://github.com/dequelabs/axe-core/issues/2483 + * @see https://github.com/dequelabs/axe-core/issues/2681 + * + * also need to resize the nodeRect to fit within the bounds of any overflow: hidden ancestors. + * + * @see https://github.com/dequelabs/axe-core/issues/4253 + */ + return clientRects.length + ? clientRects + : filterHiddenRects([nodeRect], overflowHiddenNodes); + } +); +export default getVisibleChildTextRect; + +function getContentRects(node) { + const range = document.createRange(); + range.selectNodeContents(node); + return Array.from(range.getClientRects()); +} + +/** + * Check to see if the text rect size is outside the of the + * nodes bounding rect. Since we use the midpoint of the element + * when determining the rect stack we will also use the midpoint + * of the text rect to determine out of bounds + */ +function isOutsideNodeBounds(rects, nodeRect) { + return rects.some(rect => { + const centerPoint = getRectCenter(rect); + return !isPointInRect(centerPoint, nodeRect); + }); +} + +/** + * Filter out 0 width and height rects (newline characters) and + * any rects that are outside the bounds of overflow hidden + * ancestors + */ +function filterHiddenRects(contentRects, overflowHiddenNodes) { + const visibleRects = []; + contentRects.forEach(contentRect => { + // ie11 has newline characters return 0.00998, so we'll say if the + // line is < 1 it shouldn't be counted + if (contentRect.width < 1 || contentRect.height < 1) { + return; + } + + // update the rect size to fit inside the bounds of all overflow + // hidden ancestors + const visibleRect = overflowHiddenNodes.reduce((rect, overflowNode) => { + return rect && getIntersectionRect(rect, overflowNode.boundingClientRect); + }, contentRect); + + if (visibleRect) { + visibleRects.push(visibleRect); + } + }); + + return visibleRects; +} diff --git a/lib/commons/dom/index.js b/lib/commons/dom/index.js index 57ae0756..00036f4b 100644 --- a/lib/commons/dom/index.js +++ b/lib/commons/dom/index.js @@ -22,6 +22,7 @@ export { default as getTargetSize } from './get-target-size'; export { default as getTextElementStack } from './get-text-element-stack'; export { default as getViewportSize } from './get-viewport-size'; export { default as getVisibleChildTextRects } from './get-visible-child-text-rects'; +export { default as getVisibleChildTextRect } from './get-visible-child-text-rect'; export { default as hasContentVirtual } from './has-content-virtual'; export { default as hasContent } from './has-content'; export { default as hasLangText } from './has-lang-text'; diff --git a/lib/rules/autocomplete-valid.json b/lib/rules/autocomplete-valid.json index 8d71b20f..9afefdcd 100644 --- a/lib/rules/autocomplete-valid.json +++ b/lib/rules/autocomplete-valid.json @@ -9,8 +9,7 @@ "EN-301-549", "EN-9.1.3.5", "ACT", - "a11y-engine", - "a11y-engine-experimental" + "a11y-engine" ], "actIds": ["73f2c2"], "metadata": {