shadow-elements-from-point.js 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. import getRootNode from './get-root-node';
  2. import visuallyContains from './visually-contains';
  3. import { isShadowRoot } from '../../core/utils';
  4. /**
  5. * Get elements from point across shadow dom boundaries
  6. * @method shadowElementsFromPoint
  7. * @memberof axe.commons.dom
  8. * @instance
  9. * @param {Number} nodeX X coordinates of point
  10. * @param {Number} nodeY Y coordinates of point
  11. * @param {Object} [root] Shadow root or document root
  12. * @return {Array}
  13. */
  14. function shadowElementsFromPoint(nodeX, nodeY, root = document, i = 0) {
  15. if (i > 999) {
  16. throw new Error('Infinite loop detected');
  17. }
  18. return (
  19. // IE can return null when there are no elements
  20. Array.from(root.elementsFromPoint(nodeX, nodeY) || [])
  21. // As of Chrome 66, elementFromPoint will return elements from parent trees.
  22. // We only want to touch each tree once, so we're filtering out nodes on other trees.
  23. .filter(nodes => getRootNode(nodes) === root)
  24. .reduce((stack, elm) => {
  25. if (isShadowRoot(elm)) {
  26. const shadowStack = shadowElementsFromPoint(
  27. nodeX,
  28. nodeY,
  29. elm.shadowRoot,
  30. i + 1
  31. );
  32. stack = stack.concat(shadowStack);
  33. // filter host nodes which get included regardless of overlap
  34. // TODO: refactor multiline overlap checking inside shadow dom
  35. if (stack.length && visuallyContains(stack[0], elm)) {
  36. stack.push(elm);
  37. }
  38. } else {
  39. stack.push(elm);
  40. }
  41. return stack;
  42. }, [])
  43. );
  44. }
  45. export default shadowElementsFromPoint;