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(() => {
  const sel = getSelection();
  container.innerHTML = 'a<div id="host"></div>b';
  const host = container.querySelector('#host');
  const shadowRoot = host.attachShadow({ mode });
  shadowRoot.innerHTML = 'hello, world';
  sel.setBaseAndExtent(shadowRoot.firstChild, 7, container, 2);
  const rangeBefore = sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0];
  host.remove();
  const rangeAfter = sel.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(() => {
  const sel = getSelection();
  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';
  sel.setBaseAndExtent(shadowRoot.firstChild, 4, shadowRoot.firstChild, 7);
  wrapper.remove();
  const rangeAfter = sel.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(() => {
 const sel = getSelection();
  container.innerHTML = '<div id="hello">Hello,</div><div id="world"> World</div>';
  sel.setBaseAndExtent(hello.firstChild, 1, world.firstChild, 3);
  hello.firstChild.remove();
  const rangeAfter = sel.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(() => {
 const sel = getSelection();
 container.innerHTML = 'a<div id="host"></div>b';
  const host = container.querySelector('#host');
  const shadowRoot = host.attachShadow({ mode });
  shadowRoot.innerHTML = 'hello, world';
  sel.setBaseAndExtent(shadowRoot.firstChild, 7, container, 2);
  shadowRoot.innerHTML = '';
  const rangeAfter = sel.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(() => {
  const sel = getSelection();
  container.innerHTML = 'a<div id="outerhost"></div>b';
  const outerHost = container.querySelector('#outerhost');
  const outerRoot = outerHost.attachShadow({ mode });
  outerRoot.innerHTML = 'c<div id="innerHost"></div>d';
  const innerHost = outerRoot.querySelector('#innerHost');
  const innerRoot = innerHost.attachShadow({ mode });
  innerRoot.innerHTML = 'hello, world';
  sel.setBaseAndExtent(container.firstChild, 0, innerRoot.firstChild, 4);
  outerHost.remove();
  const rangeAfter = sel.getComposedRanges({ shadowRoots: [innerRoot, outerRoot] })[0];
  assert_equals(rangeAfter.startContainer, container.firstChild);
  assert_equals(rangeAfter.startOffset, 0);
  assert_equals(rangeAfter.endContainer, container);
  assert_equals(rangeAfter.endOffset, 1);
}, 'Range is across shadow trees. Removing ancestor shadow host rescopes composed range end to parent.');
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 = sel.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>