Skip to content

Commit

Permalink
[Selection API] Use composed DOM tree for getComposedRanges
Browse files Browse the repository at this point in the history
We update the implementation of getComposedRanges to use the cached
selection in DOM Tree to find start/end endpoints. These endpoints
are composed because they are not restricted to be within one TreeScope,
unlike the range associated with the selection.

We also remove the old logic of adding composed_range_ in Range to cache
flat tree endpoints.

Change-Id: Iee7807f74348d09a4b1e7c3dbb4ebe514bde874c
Bug: 356905057
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5921292
Commit-Queue: Di Zhang <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Reviewed-by: Siye Liu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1368897}
  • Loading branch information
dizhang168 authored and chromium-wpt-export-bot committed Oct 15, 2024
1 parent 7275b2c commit 94354b1
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</div>
<div id="host2">
<template shadowrootmode=open>D</template>
<div id=b>B - not slotted</div>
B - not slotted
</div>
</div>

Expand All @@ -23,20 +23,6 @@
const c = host1.shadowRoot;
const d = host2.shadowRoot;

test(() => {
const sel = getSelection();
sel.setBaseAndExtent(b, 0, c, 0);
assert_equals(sel.getRangeAt(0).startContainer, b);
assert_equals(sel.getRangeAt(0).startOffset, 0);
assert_equals(sel.getRangeAt(0).endContainer, b);
assert_equals(sel.getRangeAt(0).endOffset, 0);

assert_equals(sel.getComposedRanges()[0].startContainer, b);
assert_equals(sel.getComposedRanges()[0].startOffset, 0);
assert_equals(sel.getComposedRanges()[0].endContainer, b);
assert_equals(sel.getComposedRanges()[0].endOffset, 0);
}, 'Setting the range to nodes that aren\'t in the same tree collapses both composed and non-composed ranges.');

test(() => {
const sel = getSelection();
sel.setBaseAndExtent(c, 0, d, 0);
Expand Down
124 changes: 124 additions & 0 deletions selection/shadow-dom/tentative/Selection-getComposedRanges-slot.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html>
<body>
<meta name="author" href="mailto:[email protected]">
<meta name="assert" content="Selection's getComposedRanges should return a sequence of static ranges, selecting from slotted content">
<link rel="help" href="https://w3c.github.io/selection-api/#dom-selection-getcomposedranges">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<div id="container"></div>

<script>

test(() => {
container.innerHTML = '<div id=host>Second</div>';
const shadowRoot = host.attachShadow({ mode:"open" });
shadowRoot.innerHTML = 'First <slot></slot> Third';
const second = host.firstChild;
const third = shadowRoot.querySelector('slot').nextSibling;

const sel = getSelection();
// Select from slotted second to shadowed third.
sel.setBaseAndExtent(second, 3, third, 4);

assert_equals(sel.getRangeAt(0).startContainer, second);
assert_equals(sel.getRangeAt(0).startOffset, 3);
assert_equals(sel.getRangeAt(0).endContainer, second, 'Collapsed because crossing shadow tree is not supported for getRangeAt.');
assert_equals(sel.getRangeAt(0).endOffset, 3);

assert_equals(sel.getComposedRanges()[0].startContainer, container);
assert_equals(sel.getComposedRanges()[0].startOffset, 0, 'Rescoped because no shadow roots were provided');
assert_equals(sel.getComposedRanges()[0].endContainer, second);
assert_equals(sel.getComposedRanges()[0].endOffset, 3);

assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startContainer, third);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startOffset, 4);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endContainer, second);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endOffset, 3);

// Repeat the test, but reversing base and extent. This should not affect the range's start and end positions.
sel.setBaseAndExtent(third, 4, second, 3);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startContainer, third);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startOffset, 4);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endContainer, second);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endOffset, 3);
}, 'Setting the range to start on slotted content and end in shadow tree, should follow DOM tree order.');

test(() => {
container.innerHTML = [
'<div id=host>',
'<div id=div1 slot=slot2>slotted content 1</div>',
'<div id=div2 slot=slot1>slotted content 2</div>',
'</div>'
].join('');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.innerHTML = [
'<span>before</span>',
'<slot name=slot1></slot>',
'<span>between</span>',
'<slot name=slot2></slot>',
'<span>after</span>',
].join('');

const sel = getSelection();
// Select from slotted div1 to slotted div2.
sel.setBaseAndExtent(div1.firstChild, 2, div2.firstChild, 2);

assert_equals(sel.getRangeAt(0).startContainer, div1.firstChild);
assert_equals(sel.getRangeAt(0).startOffset, 2);
assert_equals(sel.getRangeAt(0).endContainer, div2.firstChild, 'Not collapsed because we are not crossing shadow trees.');
assert_equals(sel.getRangeAt(0).endOffset, 2);

assert_equals(sel.getComposedRanges()[0].startContainer, div1.firstChild);
assert_equals(sel.getComposedRanges()[0].startOffset, 2);
assert_equals(sel.getComposedRanges()[0].endContainer, div2.firstChild, 'Not rescoped because we are not crossing shadow trees.');
assert_equals(sel.getComposedRanges()[0].endOffset, 2);

assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startContainer, div1.firstChild);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startOffset, 2);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endContainer, div2.firstChild);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endOffset, 2);

// Repeat the test, but reversing base and extent. This should not affect the range's start and end positions.
sel.setBaseAndExtent(div2.firstChild, 2, div1.firstChild, 2);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startContainer, div1.firstChild);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startOffset, 2);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endContainer, div2.firstChild);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endOffset, 2);
}, 'Setting the range to start and end on slotted content, should follow DOM tree order.');

test(() => {
container.innerHTML = '<div id=host>Second</div>';
const shadowRoot = host.attachShadow({ mode:"open" });
shadowRoot.innerHTML = '<span id="first">First</span><span id=third>Third</span>';
const second = host.firstChild;
const third = shadowRoot.getElementById('third').firstChild;

const sel = getSelection();
// Select from unslotted second to shadowed third.
sel.setBaseAndExtent(second, 3, third, 4);

assert_equals(sel.getRangeAt(0).startContainer, second);
assert_equals(sel.getRangeAt(0).startOffset, 3);
assert_equals(sel.getRangeAt(0).endContainer, second, 'Collapsed because crossing shadow tree is not supported for getRangeAt.');
assert_equals(sel.getRangeAt(0).endOffset, 3);

assert_equals(sel.getComposedRanges()[0].startContainer, container);
assert_equals(sel.getComposedRanges()[0].startOffset, 0, 'Rescoped because no shadow roots were provided');
assert_equals(sel.getComposedRanges()[0].endContainer, second);
assert_equals(sel.getComposedRanges()[0].endOffset, 3);

assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startContainer, third);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startOffset, 4);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endContainer, second);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endOffset, 3);

// Repeat the test, but reversing base and extent. This should not affect the range's start and end positions.
sel.setBaseAndExtent(third, 4, second, 3);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startContainer, third);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].startOffset, 4);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endContainer, second);
assert_equals(sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0].endOffset, 3);
}, 'Setting the range to start on unslotted content and end in shadow tree, should follow DOM tree order.');
</script>

0 comments on commit 94354b1

Please sign in to comment.