text-fragments-ts/src/ whatwg-dom.ts
130 lines
5.7 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-node-length
  22. // “To determine the length of a node node, switch on node:”
  23. export function nodeLength(node: Node): number {
  24. switch (node.nodeType) {
  25. // “DocumentType”
  26. case Node.DOCUMENT_TYPE_NODE:
  27. // “Zero.”
  28. return 0;
  29. // “Text”
  30. case Node.TEXT_NODE:
  31. // “ProcessingInstruction”
  32. case Node.PROCESSING_INSTRUCTION_NODE:
  33. // “Comment”
  34. case Node.COMMENT_NODE:
  35. // “Its data’s length.”
  36. return (node as CharacterData).data.length;
  37. // “Any other node”
  38. default:
  39. // “Its number of children.”
  40. return node.childNodes.length;
  41. }
  42. }
  43. // https://dom.spec.whatwg.org/#concept-shadow-including-tree-order
  44. // “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.”
  45. export function nextNodeInShadowIncludingTreeOrder(node: Node): Node | null {
  46. if (isShadowHost(node)) {
  47. return nextNodeInShadowIncludingTreeOrder(node.shadowRoot);
  48. } else {
  49. return nextNode(node);
  50. }
  51. }
  52. // https://dom.spec.whatwg.org/#element-shadow-host
  53. // “An element is a shadow host if its shadow root is non-null.”
  54. // FIXME (WONTFIX?) Element.shadowRoot is also null if the ShadowRoot exists but its mode is 'closed'. Is there any way around this?
  55. // XXX Might it be desirable to exclude closed shadow roots from a text fragment search?
  56. export type ShadowHost = Element & { shadowRoot: ShadowRoot }
  57. export function isShadowHost(node: Node): node is ShadowHost {
  58. return (isElement(node) && node.shadowRoot !== null);
  59. }
  60. // https://dom.spec.whatwg.org/#concept-shadow-including-descendant
  61. // “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.”
  62. export function isShadowIncludingDescendant(nodeA: Node, nodeB: Node): boolean {
  63. if (isDescendant(nodeA, nodeB))
  64. return true;
  65. const nodeARoot = nodeA.getRootNode();
  66. if (nodeARoot instanceof ShadowRoot && isShadowIncludingInclusiveDescendant(nodeARoot.host, nodeB))
  67. return true;
  68. return false;
  69. }
  70. // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
  71. // “A shadow-including inclusive descendant is an object or one of its shadow-including descendants.”
  72. export function isShadowIncludingInclusiveDescendant(nodeA: Node, nodeB: Node): boolean {
  73. if (nodeA === nodeB)
  74. return true;
  75. if (isShadowIncludingDescendant(nodeA, nodeB))
  76. return true;
  77. return false;
  78. }
  79. // https://dom.spec.whatwg.org/#concept-shadow-including-ancestor
  80. // “An object A is a shadow-including ancestor of an object B, if and only if B is a shadow-including descendant of A.”
  81. export function isShadowIncludingAncestor(nodeA: Node, nodeB: Node): boolean {
  82. return isShadowIncludingDescendant(nodeB, nodeA);
  83. }
  84. // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor
  85. // “A shadow-including inclusive ancestor is an object or one of its shadow-including ancestors.”
  86. export function isShadowIncludingInclusiveAncestor(nodeA: Node, nodeB: Node): boolean {
  87. if (nodeA === nodeB)
  88. return true;
  89. if (isShadowIncludingAncestor(nodeA, nodeB))
  90. return true;
  91. return false;
  92. }
  93. // https://dom.spec.whatwg.org/#concept-cd-substring
  94. // “To substring data with node node, offset offset, and count count, run these steps:”
  95. export function substringData(
  96. node: CharacterData, // XXX The spec says “node node”, but reads “node’s data” which is only defined for CharacterData nodes.
  97. offset: number,
  98. count: Count
  99. ): string {
  100. // 1. “Let length be node’s length.”
  101. const length = nodeLength(node);
  102. // 2. “If offset is greater than length, then throw an "IndexSizeError" DOMException.”
  103. if (offset > length)
  104. throw new DOMException('', 'IndexSizeError');
  105. // 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.”
  106. if (offset + count > length) {
  107. return node.data.substring(offset);
  108. }
  109. // 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.”
  110. return node.data.substring(offset, offset + count);
  111. }
  112. // https://dom.spec.whatwg.org/#concept-range-bp
  113. // “A boundary point is a tuple consisting of a node (a node) and an offset (a non-negative integer).”
  114. export type BoundaryPoint = [Node, NonNegativeInteger];