Selection API accepts any DOM node in the non-flattened tree as a container of a range boundary. Therefore, even if a node is not formed in the flattened tree, e.g., inclusive descendants of unassigned children of a shadow host element or a fallback content node of `<slot>` which has some assigned nodes, the node is a valid container of selection range boundaries and valid argument of Selection API and Range API. Therefore, when climbing up the flattened tree for selection, the internal APIs should treat the non-flattened nodes are also connected to the flattened tree. In this patch, the non-flattened children are connected at end of the corresponding shadow root or `<slot>. The former is defined as so in the shadow-including tree order [1]. However, I've not found the definition for the latter. Therefore, I align the behavior to the former behavior for making the code simpler. When comparing the range boundaries in the flattened tree for selection with the non-flattened nodes, we may need to use the position in the end of the flattened node. However, setting `nsRange::mCrossShadowBoundaryRange` boundaries to the nodes formed in the flattened tree does not work well with `getComposedRange()`. Therefore, this patch does not touch around that. On the other hand, Chrome returns `false` from `Selection.containsNode` if the node parameter is set to a non-flattened node. The spec [2] defines with a fuzzy word, "visually". It's possible to understand that non-flattened nodes never visually appear. And I tried to make it compare with treating the positions of non-flattened nodes as end of the corresponding shadow root or `<slot>`. However, it needs complicated code and causes regressions in some tests, and anyway it's incompatible with Chrome. Therefore, this patch makes `Selection::ContainsNode` returns `false` if the given node is a non-flattened node. I.e., the behavior is compatible with Chrome. 1. https://dom.spec.whatwg.org/#concept-shadow-including-tree-order 2. https://w3c.github.io/selection-api/#dom-selection-containsnode Differential Revision: https://phabricator.services.mozilla.com/D307949
83 lines
2.3 KiB
JavaScript
83 lines
2.3 KiB
JavaScript
class SelectionTestUtils {
|
|
static getSelectionRangeArray(selection) {
|
|
const ranges = [];
|
|
for (let i = 0; i < selection.rangeCount; i++) {
|
|
ranges.push(selection.getRangeAt(i));
|
|
}
|
|
return ranges;
|
|
}
|
|
|
|
static getRangeArrayDescription(arrayOfRanges) {
|
|
if (arrayOfRanges === null) {
|
|
return "null";
|
|
}
|
|
if (arrayOfRanges === undefined) {
|
|
return "undefined";
|
|
}
|
|
if (!Array.isArray(arrayOfRanges)) {
|
|
return "Unknown Object";
|
|
}
|
|
if (arrayOfRanges.length === 0) {
|
|
return "[]";
|
|
}
|
|
let result = "";
|
|
for (let range of arrayOfRanges) {
|
|
if (result === "") {
|
|
result = "[";
|
|
} else {
|
|
result += ",";
|
|
}
|
|
result += `{${SelectionTestUtils.getRangeDescription(range)}}`;
|
|
}
|
|
result += "]";
|
|
return result;
|
|
}
|
|
|
|
static getNodeDescription(node) {
|
|
if (!node) {
|
|
return "null";
|
|
}
|
|
switch (node.nodeType) {
|
|
case Node.TEXT_NODE:
|
|
case Node.COMMENT_NODE:
|
|
case Node.CDATA_SECTION_NODE:
|
|
return `${node.nodeName} "${node.data.replaceAll("\n", "\\\\n")}"`;
|
|
case Node.ELEMENT_NODE:
|
|
return `<${node.nodeName.toLowerCase()}${
|
|
node.hasAttribute("id") ? ` id="${node.getAttribute("id")}"` : ""
|
|
}${
|
|
node.hasAttribute("class") ? ` class="${node.getAttribute("class")}"` : ""
|
|
}${
|
|
node.hasAttribute("contenteditable")
|
|
? ` contenteditable="${node.getAttribute("contenteditable")}"`
|
|
: ""
|
|
}${
|
|
node.inert ? ` inert` : ""
|
|
}${
|
|
node.hidden ? ` hidden` : ""
|
|
}${
|
|
node.readonly ? ` readonly` : ""
|
|
}${
|
|
node.disabled ? ` disabled` : ""
|
|
}>`;
|
|
default:
|
|
return `${node.nodeName}`;
|
|
}
|
|
}
|
|
|
|
static getRangeDescription(range) {
|
|
if (range === null) {
|
|
return "null";
|
|
}
|
|
if (range === undefined) {
|
|
return "undefined";
|
|
}
|
|
return range.startContainer == range.endContainer &&
|
|
range.startOffset == range.endOffset
|
|
? `(${SelectionTestUtils.getNodeDescription(range.startContainer)}, ${range.startOffset})`
|
|
: `(${SelectionTestUtils.getNodeDescription(range.startContainer)}, ${
|
|
range.startOffset
|
|
}) - (${SelectionTestUtils.getNodeDescription(range.endContainer)}, ${range.endOffset})`;
|
|
}
|
|
}
|