get-text-element-stack.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import getElementStack from './get-element-stack';
  2. import { createGrid, getRectStack } from './get-rect-stack';
  3. import sanitize from '../text/sanitize';
  4. import cache from '../../core/base/cache';
  5. import { getNodeFromTree } from '../../core/utils';
  6. /**
  7. * Return all elements that are at the center of each text client rect of the passed in node.
  8. * @method getTextElementStack
  9. * @memberof axe.commons.dom
  10. * @param {Node} node
  11. * @return {Array<Node[]>}
  12. */
  13. function getTextElementStack(node) {
  14. if (!cache.get('gridCreated')) {
  15. createGrid();
  16. cache.set('gridCreated', true);
  17. }
  18. const vNode = getNodeFromTree(node);
  19. const grid = vNode._grid;
  20. if (!grid) {
  21. return [];
  22. }
  23. // for code blocks that use syntax highlighting, you can get a ton of client
  24. // rects (See https://github.com/dequelabs/axe-core/issues/1985). they use
  25. // a mixture of text nodes and other nodes (which will contain their own text
  26. // nodes), but all we care about is checking the direct text nodes as the
  27. // other nodes will have their own client rects checked. doing this speeds up
  28. // color contrast significantly for large syntax highlighted code blocks
  29. const nodeRect = vNode.boundingClientRect;
  30. const clientRects = [];
  31. Array.from(node.childNodes).forEach(elm => {
  32. if (elm.nodeType === 3 && sanitize(elm.textContent) !== '') {
  33. const range = document.createRange();
  34. range.selectNodeContents(elm);
  35. const rects = range.getClientRects();
  36. // if any text rect is larger than the bounds of the parent,
  37. // or goes outside of the bounds of the parent, we need to use
  38. // the parent rect so we stay within the bounds of the element.
  39. //
  40. // since we use the midpoint of the element when determining
  41. // the rect stack we will also use the midpoint of the text rect
  42. // to determine out of bounds.
  43. //
  44. // @see https://github.com/dequelabs/axe-core/issues/2178
  45. // @see https://github.com/dequelabs/axe-core/issues/2483
  46. // @see https://github.com/dequelabs/axe-core/issues/2681
  47. const outsideRectBounds = Array.from(rects).some(rect => {
  48. const horizontalMidpoint = rect.left + rect.width / 2;
  49. const verticalMidpoint = rect.top + rect.height / 2;
  50. return (
  51. horizontalMidpoint < nodeRect.left ||
  52. horizontalMidpoint > nodeRect.right ||
  53. verticalMidpoint < nodeRect.top ||
  54. verticalMidpoint > nodeRect.bottom
  55. );
  56. });
  57. if (outsideRectBounds) {
  58. return;
  59. }
  60. for (let i = 0; i < rects.length; i++) {
  61. const rect = rects[i];
  62. // filter out 0 width and height rects (newline characters)
  63. // ie11 has newline characters return 0.00998, so we'll say if the
  64. // line is < 1 it shouldn't be counted
  65. if (rect.width >= 1 && rect.height >= 1) {
  66. clientRects.push(rect);
  67. }
  68. }
  69. }
  70. });
  71. if (!clientRects.length) {
  72. return [getElementStack(node)];
  73. }
  74. return clientRects.map(rect => {
  75. return getRectStack(grid, rect);
  76. });
  77. }
  78. export default getTextElementStack;