text-fragments-ts/src/ whatwg-dom.ts
136 lines
6.0 KiB

  1. /////////////////////////////////////////////
  2. // Required pieces of the WHATWG DOM spec ///
  3. /////////////////////////////////////////////
  4. // Based on the version of 29 June 2020 <https://dom.spec.whatwg.org/commit-snapshots/e191f73a0fcc09c48f9e962188748f811b09c239/>
  5. import {
  6. isElement,
  7. nextNode,
  8. } from './common.js';
  9. type nonNegativeInteger = number;
  10. type count = number;
  11. // https://dom.spec.whatwg.org/#concept-tree-descendant
  12. // “An object A is called a descendant of an object B, if either A is a child of B or A is a child of an object C that is a descendant of B.”
  13. export function isDescendant(nodeA: Node, nodeB: Node): boolean {
  14. if (nodeA.parentNode === nodeB)
  15. return true;
  16. const nodeC = nodeA.parentNode;
  17. if (nodeC && isDescendant(nodeC, nodeB))
  18. return true;
  19. return false;
  20. }
  21. // https://dom.spec.whatwg.org/#concept-tree-following
  22. // “An object A is following an object B if A and B are in the same tree and A comes after B in tree order.”
  23. export function followsInTree(nodeA: Node, nodeB: Node): boolean {
  24. return !!(nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING);
  25. }
  26. // https://dom.spec.whatwg.org/#concept-node-length
  27. // “To determine the length of a node node, switch on node:”
  28. export function nodeLength(node: Node): number {
  29. switch (node.nodeType) {
  30. // “DocumentType”
  31. case Node.DOCUMENT_TYPE_NODE:
  32. // “Zero.”
  33. return 0;
  34. // “Text”
  35. case Node.TEXT_NODE:
  36. // “ProcessingInstruction”
  37. case Node.PROCESSING_INSTRUCTION_NODE:
  38. // “Comment”
  39. case Node.COMMENT_NODE:
  40. // “Its data’s length.”
  41. return (node as CharacterData).data.length;
  42. // “Any other node”
  43. default:
  44. // “Its number of children.”
  45. return node.childNodes.length;
  46. }
  47. }
  48. // https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
  49. // “In shadow-including tree order is shadow-including preorder, depth-first traversal of a node tree. Shadow-including preorder, depth-first traversal of a node tree tree is preorder, depth-first traversal of tree, with for each shadow host encountered in tree, shadow-including preorder, depth-first traversal of that element’s shadow root’s node tree just after it is encountered.”
  50. export function nextNodeInShadowIncludingTreeOrder(node: Node): Node | null {
  51. if (isShadowHost(node)) {
  52. return nextNodeInShadowIncludingTreeOrder(node.shadowRoot);
  53. } else {
  54. return nextNode(node);
  55. }
  56. }
  57. // https://dom.spec.whatwg.org/#element-shadow-host
  58. // “An element is a shadow host if its shadow root is non-null.”
  59. // FIXME (WONTFIX?) Element.shadowRoot is also null if the ShadowRoot exists but its mode is 'closed'. Is there any way around this?
  60. // XXX Might it be desirable to exclude closed shadow roots from a text fragment search?
  61. export type ShadowHost = Element & { shadowRoot: ShadowRoot }
  62. export function isShadowHost(node: Node): node is ShadowHost {
  63. return (isElement(node) && node.shadowRoot !== null);
  64. }
  65. // https://dom.spec.whatwg.org/#concept-shadow-including-descendant
  66. // “An object A is a shadow-including descendant of an object B, if A is a descendant of B, or A’s root is a shadow root and A’s root’s host is a shadow-including inclusive descendant of B.”
  67. export function isShadowIncludingDescendant(nodeA: Node, nodeB: Node): boolean {
  68. if (isDescendant(nodeA, nodeB))
  69. return true;
  70. const nodeARoot = nodeA.getRootNode();
  71. if (nodeARoot instanceof ShadowRoot && isShadowIncludingInclusiveDescendant(nodeARoot.host, nodeB))
  72. return true;
  73. return false;
  74. }
  75. // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
  76. // “A shadow-including inclusive descendant is an object or one of its shadow-including descendants.”
  77. export function isShadowIncludingInclusiveDescendant(nodeA: Node, nodeB: Node): boolean {
  78. if (nodeA === nodeB)
  79. return true;
  80. if (isShadowIncludingDescendant(nodeA, nodeB))
  81. return true;
  82. return false;
  83. }
  84. // https://dom.spec.whatwg.org/#concept-shadow-including-ancestor
  85. // “An object A is a shadow-including ancestor of an object B, if and only if B is a shadow-including descendant of A.”
  86. export function isShadowIncludingAncestor(nodeA: Node, nodeB: Node): boolean {
  87. return isShadowIncludingDescendant(nodeB, nodeA);
  88. }
  89. // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor
  90. // “A shadow-including inclusive ancestor is an object or one of its shadow-including ancestors.”
  91. export function isShadowIncludingInclusiveAncestor(nodeA: Node, nodeB: Node): boolean {
  92. if (nodeA === nodeB)
  93. return true;
  94. if (isShadowIncludingAncestor(nodeA, nodeB))
  95. return true;
  96. return false;
  97. }
  98. // https://dom.spec.whatwg.org/#concept-cd-substring
  99. // “To substring data with node node, offset offset, and count count, run these steps:”
  100. export function substringData(
  101. node: CharacterData, // XXX The spec says “node node”, but reads “node’s data” which is only defined for CharacterData nodes.
  102. offset: number,
  103. count: count
  104. ): string {
  105. // 1. “Let length be node’s length.”
  106. const length = nodeLength(node);
  107. // 2. “If offset is greater than length, then throw an "IndexSizeError" DOMException.”
  108. if (offset > length)
  109. throw new DOMException('', 'IndexSizeError');
  110. // 3. “If offset plus count is greater than length, return a string whose value is the code units from the offsetth code unit to the end of node’s data, and then return.”
  111. if (offset + count > length) {
  112. return node.data.substring(offset);
  113. }
  114. // TODO verify: “Return a string whose value is the code units from the offsetth code unit to the offset+countth code unit in node’s data.”
  115. return node.data.substring(offset, offset + count);
  116. }
  117. // https://dom.spec.whatwg.org/#concept-range-bp
  118. // “A boundary point is a tuple consisting of a node (a node) and an offset (a non-negative integer).”
  119. export type BoundaryPoint = [Node, nonNegativeInteger];