Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 9 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /selection/shadow-dom/tentative/Selection-getComposedRanges-range-update.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<html>
<body>
<meta name="assert" content="Selection's composed range should be updated when its associated legacy uncomposed range changes">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="light">Start outside shadow DOM</div>
<div id="outerHost">outerHost
<template shadowrootmode="open">
<slot></slot>
<div id="innerHost">innerHost
<template shadowrootmode="open">
<slot></slot>
</template>
</div>
</template>
</div>
<div id="lightEnd">End outside shadow DOM</div>
<script>
const selection = getSelection();
const outerHost = document.getElementById('outerHost')
const outerRoot = outerHost.shadowRoot;
const innerHost = outerRoot.getElementById('innerHost');
const innerRoot = innerHost.shadowRoot;
test(() => {
// Setting a selction crossing to shadow tree
selection.setBaseAndExtent(light.firstChild, 10, innerHost.firstChild, 5);
assert_throws_dom("INDEX_SIZE_ERR", function () { selection.getRangeAt(0) });
}, 'If selection crosses shadow boundaries, getRangeAt(0) should throw an IndexSizeError because the end is not in the document tree.');
test(() => {
// Setting a selection within light tree
selection.setBaseAndExtent(light.firstChild, 10, lightEnd.firstChild, 20);
const liveRange = selection.getRangeAt(0);
const newSpan = document.createElement("span");
liveRange.setStart(newSpan, 0);
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, newSpan);
assert_equals(liveRange.startOffset, 0);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, null);
assert_equals(selection.anchorOffset, 0);
assert_throws_dom("INDEX_SIZE_ERR", function () { selection.getRangeAt(0) });
assert_equals(selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] }).length, 0);
}, 'modify getRangeAt() range: setStart() to disconnected node will collapse and remove the live range from the selection.');
test(() => {
// Setting a selection within light tree
selection.setBaseAndExtent(light.firstChild, 10, light.firstChild, 20);
const liveRange = selection.getRangeAt(0);
let composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_equals(liveRange.startContainer, light.firstChild);
assert_equals(liveRange.startOffset, 10);
assert_equals(liveRange.endContainer, light.firstChild);
assert_equals(liveRange.endOffset, 20);
assert_equals(selection.anchorNode, light.firstChild);
assert_equals(selection.anchorOffset, 10);
assert_equals(selection.focusNode, light.firstChild);
assert_equals(selection.focusOffset, 20);
assert_equals(composedRange.startContainer, light.firstChild);
assert_equals(composedRange.startOffset, 10);
assert_equals(composedRange.endContainer, light.firstChild);
assert_equals(composedRange.endOffset, 20);
liveRange.setEnd(innerHost.firstChild, 5);
composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, innerHost.firstChild);
assert_equals(liveRange.startOffset, 5);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, innerHost.firstChild);
assert_equals(selection.anchorOffset, 5);
assert_equals(composedRange.startContainer, light.firstChild);
assert_equals(composedRange.startOffset, 10);
assert_equals(composedRange.endContainer, innerHost.firstChild);
assert_equals(composedRange.endOffset, 5);
}, 'modify getRangeAt() range: setEnd() crosses shadow boundary into the shadow DOM and after start, which collapses live range. Composed selection range is not collapsed.');
test(() => {
// Setting a selection within light tree
selection.setBaseAndExtent(lightEnd.firstChild, 10, lightEnd.firstChild, 20);
const liveRange = selection.getRangeAt(0);
let composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_equals(liveRange.startContainer, lightEnd.firstChild);
assert_equals(liveRange.startOffset, 10);
assert_equals(liveRange.endContainer, lightEnd.firstChild);
assert_equals(liveRange.endOffset, 20);
assert_equals(selection.anchorNode, lightEnd.firstChild);
assert_equals(selection.anchorOffset, 10);
assert_equals(selection.focusNode, lightEnd.firstChild);
assert_equals(selection.focusOffset, 20);
assert_equals(composedRange.startContainer, lightEnd.firstChild);
assert_equals(composedRange.startOffset, 10);
assert_equals(composedRange.endContainer, lightEnd.firstChild);
assert_equals(composedRange.endOffset, 20);
liveRange.setStart(innerHost.firstChild, 5);
composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, innerHost.firstChild);
assert_equals(liveRange.startOffset, 5);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, innerHost.firstChild);
assert_equals(selection.anchorOffset, 5);
assert_equals(composedRange.startContainer, innerHost.firstChild);
assert_equals(composedRange.startOffset, 5);
assert_equals(composedRange.endContainer, lightEnd.firstChild);
assert_equals(composedRange.endOffset, 20);
}, 'modify getRangeAt() range: setStart() crosses shadow boundary into the shadow DOM and before end, which collapses live range. Composed selection range is not collapsed.');
test(() => {
// Setting a selection within light tree
selection.setBaseAndExtent(light.firstChild, 10, light.firstChild, 20);
const liveRange = selection.getRangeAt(0);
let composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_equals(liveRange.startContainer, light.firstChild);
assert_equals(liveRange.startOffset, 10);
assert_equals(liveRange.endContainer, light.firstChild);
assert_equals(liveRange.endOffset, 20);
assert_equals(selection.anchorNode, light.firstChild);
assert_equals(selection.anchorOffset, 10);
assert_equals(selection.focusNode, light.firstChild);
assert_equals(selection.focusOffset, 20);
assert_equals(composedRange.startContainer, light.firstChild);
assert_equals(composedRange.startOffset, 10);
assert_equals(composedRange.endContainer, light.firstChild);
assert_equals(composedRange.endOffset, 20);
liveRange.setStart(innerHost.firstChild, 5);
composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, innerHost.firstChild);
assert_equals(liveRange.startOffset, 5);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, innerHost.firstChild);
assert_equals(selection.anchorOffset, 5);
assert_true(composedRange.collapsed);
assert_equals(composedRange.startContainer, innerHost.firstChild);
assert_equals(composedRange.startOffset, 5);
}, 'modify getRangeAt() range: setStart() crosses shadow boundary into the shadow DOM and after end, which collapses both live range and composed selection range.');
test(() => {
// Setting a selection within light tree
selection.setBaseAndExtent(light.firstChild, 10, lightEnd.firstChild, 20);
const liveRange = selection.getRangeAt(0);
liveRange.selectNode(innerHost);
const composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_equals(liveRange.startContainer, outerRoot);
assert_equals(liveRange.startOffset, 3);
assert_equals(liveRange.endContainer, outerRoot);
assert_equals(liveRange.endOffset, 4);
assert_equals(selection.anchorNode, outerRoot);
assert_equals(selection.anchorOffset, 3);
assert_equals(selection.focusNode, outerRoot);
assert_equals(selection.focusOffset, 4);
assert_equals(composedRange.startContainer, outerRoot);
assert_equals(composedRange.startOffset, 3);
assert_equals(composedRange.endContainer, outerRoot);
assert_equals(composedRange.endOffset, 4);
}, 'modify getRangeAt() range: selectNode() innerHost for all ranges.');
test(() => {
// Setting a selection within light tree
selection.setBaseAndExtent(light.firstChild, 10, lightEnd.firstChild, 20);
const liveRange = selection.getRangeAt(0);
liveRange.collapse();
const composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, lightEnd.firstChild);
assert_equals(liveRange.startOffset, 20);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, lightEnd.firstChild);
assert_equals(selection.anchorOffset, 20);
assert_true(composedRange.collapsed);
assert_equals(composedRange.startContainer, lightEnd.firstChild);
assert_equals(composedRange.startOffset, 20);
}, 'modify getRangeAt() range: collapse() collapses all ranges.');
test(() => {
// Step 1: Creating a live range and only setting its end/anchor
selection.removeAllRanges();
const liveRange = document.createRange();
liveRange.setEnd(innerHost.firstChild, 5);
const composedRanges = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] });
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, innerHost.firstChild);
assert_equals(liveRange.startOffset, 5);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, null);
assert_equals(selection.anchorOffset, 0);
assert_equals(composedRanges.length, 0, 'range is not added to selection yet.');
// Step 2: Add range to selection so range API updates will change selection
selection.addRange(liveRange);
const composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.endContainer, innerHost.firstChild);
assert_equals(liveRange.endOffset, 5);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, innerHost.firstChild);
assert_equals(selection.anchorOffset, 5);
assert_true(composedRange.collapsed);
assert_equals(composedRange.startContainer, innerHost.firstChild);
assert_equals(composedRange.startOffset, 5);
}, 'modify createRange() range: adding to selection sets the selection');
test(() => {
// Step 1: Creating a live range and only setting its end/anchor
selection.removeAllRanges();
const liveRange = document.createRange();
// Add range to selection so range API updates will change selection
selection.addRange(liveRange);
liveRange.setEnd(innerHost.firstChild, 5);
let composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, innerHost.firstChild);
assert_equals(liveRange.startOffset, 5);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, innerHost.firstChild);
assert_equals(selection.anchorOffset, 5);
assert_equals(composedRange.startContainer, document);
assert_equals(composedRange.startOffset, 0);
assert_equals(composedRange.endContainer, innerHost.firstChild);
assert_equals(composedRange.endOffset, 5);
// Step 2: Update the live range by setting its start/focus
liveRange.setStart(light.firstChild, 10);
composedRange = selection.getComposedRanges({ shadowRoots: [outerRoot, innerRoot] })[0];
assert_true(liveRange.collapsed);
assert_equals(liveRange.startContainer, light.firstChild);
assert_equals(liveRange.startOffset, 10);
assert_true(selection.isCollapsed);
assert_equals(selection.anchorNode, light.firstChild);
assert_equals(selection.anchorOffset, 10);
assert_equals(composedRange.startContainer, light.firstChild);
assert_equals(composedRange.startOffset, 10);
assert_equals(composedRange.endContainer, innerHost.firstChild);
assert_equals(composedRange.endOffset, 5);
}, 'modify createRange() range: added to selection before setStart/setEnd calls.');
</script>