Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 4 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /selection/shadow-dom/tentative/Selection-getComposedRanges-dom-mutations-removal.html?mode=closed - WPT Dashboard Interop Dashboard
- /selection/shadow-dom/tentative/Selection-getComposedRanges-dom-mutations-removal.html?mode=open - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<html>
<body>
<meta name="author" href="mailto:dizhangg@chromium.org">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta name="variant" content="?mode=closed">
<meta name="variant" content="?mode=open">
<div id="container"></div>
<script>
const mode = (new URLSearchParams(document.location.search)).get("mode");
test(() => {
container.innerHTML = 'a<div id="host"></div>b';
const host = container.querySelector('#host');
const shadowRoot = host.attachShadow({ mode });
shadowRoot.innerHTML = 'hello, world';
getSelection().setBaseAndExtent(shadowRoot.firstChild, 7, container, 2);
const rangeBefore = getSelection().getComposedRanges({ shadowRoots: [shadowRoot] })[0];
host.remove();
const rangeAfter = getSelection().getComposedRanges({ shadowRoots: [shadowRoot] })[0];
assert_equals(rangeBefore.startContainer, shadowRoot.firstChild, 'StaticRange does not update on new mutation.');
assert_equals(rangeBefore.startOffset, 7);
assert_equals(rangeBefore.endContainer, container);
assert_equals(rangeBefore.endOffset, 2);
assert_equals(rangeAfter.startContainer, container, 'collapsed to the host parent: container');
assert_equals(rangeAfter.startOffset, 1);
assert_equals(rangeAfter.endContainer, container);
assert_equals(rangeAfter.endOffset, 1);
}, 'Range is fully in shadow tree. Removing shadow host collapses composed StaticRange. Note it does not update previously returned composed StaticRange.');
test(() => {
container.innerHTML = '<div id="wrapper">a<div id="host"></div>b</div>';
const wrapper = container.querySelector('#wrapper');
const host = container.querySelector('#host');
const shadowRoot = host.attachShadow({ mode });
shadowRoot.innerHTML = 'hello, world';
getSelection().setBaseAndExtent(shadowRoot.firstChild, 4, shadowRoot.firstChild, 7);
wrapper.remove();
const rangeAfter = getSelection().getComposedRanges({ shadowRoots: [shadowRoot] })[0];
assert_equals(rangeAfter.startContainer, container, 'collapsed to parent of removed node');
assert_equals(rangeAfter.startOffset, 0);
assert_equals(rangeAfter.endContainer, container);
assert_equals(rangeAfter.endOffset, 0);
}, 'Range is fully in shadow tree. Removing parent of shadow host collapses composed StaticRange.');
test(() => {
container.innerHTML = '<div id="hello">Hello,</div><div id="world"> World</div>';
getSelection().setBaseAndExtent(hello.firstChild, 1, world.firstChild, 3);
hello.firstChild.remove();
const rangeAfter = getSelection().getComposedRanges()[0];
assert_equals(rangeAfter.startContainer, hello);
assert_equals(rangeAfter.startOffset, 0);
assert_equals(rangeAfter.endContainer, world.firstChild);
assert_equals(rangeAfter.endOffset, 3);
}, 'Range is in light DOM. Removing startContainer rescopes new composed range to its parent.');
test(() => {
container.innerHTML = 'a<div id="host"></div>b';
const host = container.querySelector('#host');
const shadowRoot = host.attachShadow({ mode });
shadowRoot.innerHTML = 'hello, world';
getSelection().setBaseAndExtent(shadowRoot.firstChild, 7, container, 2);
shadowRoot.innerHTML = '';
const rangeAfter = getSelection().getComposedRanges({ shadowRoots: [shadowRoot] })[0];
assert_equals(rangeAfter.startContainer, shadowRoot, 'collapsed to be at the parent shadow root');
assert_equals(rangeAfter.startOffset, 0);
assert_equals(rangeAfter.endContainer, container);
assert_equals(rangeAfter.endOffset, 2);
}, 'Range is across shadow trees. Replacing shadowRoot content rescopes new composed range to the shadowRoot.');
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();
sel.setBaseAndExtent(div1.firstChild, 2, div2.firstChild, 2);
div1.remove();
const rangeAfter = getSelection().getComposedRanges({ shadowRoots: [shadowRoot] })[0];
assert_equals(rangeAfter.startContainer, host);
assert_equals(rangeAfter.startOffset, 0);
assert_equals(rangeAfter.endContainer, div2.firstChild);
assert_equals(rangeAfter.endOffset, 2);
}, 'Range is between two light slotted contents. Removing start container rescopes to its parent in light tree.');
</script>
</body>
</html>