Selection-getComposedRanges-dom-mutations-removal.html (5653B)
1 <!DOCTYPE html> 2 <html> 3 <body> 4 <meta name="author" href="mailto:dizhangg@chromium.org"> 5 <link rel="help" href="https://w3c.github.io/selection-api/#dom-selection-getcomposedranges"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <meta name="variant" content="?mode=closed"> 9 <meta name="variant" content="?mode=open"> 10 11 <div id="container"></div> 12 13 <script> 14 15 const mode = (new URLSearchParams(document.location.search)).get("mode"); 16 17 test(() => { 18 const sel = getSelection(); 19 20 container.innerHTML = 'a<div id="host"></div>b'; 21 const host = container.querySelector('#host'); 22 const shadowRoot = host.attachShadow({ mode }); 23 shadowRoot.innerHTML = 'hello, world'; 24 sel.setBaseAndExtent(shadowRoot.firstChild, 7, container, 2); 25 const rangeBefore = sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0]; 26 host.remove(); 27 const rangeAfter = sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0]; 28 29 assert_equals(rangeBefore.startContainer, shadowRoot.firstChild, 'StaticRange does not update on new mutation.'); 30 assert_equals(rangeBefore.startOffset, 7); 31 assert_equals(rangeBefore.endContainer, container); 32 assert_equals(rangeBefore.endOffset, 2); 33 34 assert_equals(rangeAfter.startContainer, container, 'collapsed to the host parent: container'); 35 assert_equals(rangeAfter.startOffset, 1); 36 assert_equals(rangeAfter.endContainer, container); 37 assert_equals(rangeAfter.endOffset, 1); 38 }, 'Range is fully in shadow tree. Removing shadow host collapses composed StaticRange. Note it does not update previously returned composed StaticRange.'); 39 40 test(() => { 41 const sel = getSelection(); 42 43 container.innerHTML = '<div id="wrapper">a<div id="host"></div>b</div>'; 44 const wrapper = container.querySelector('#wrapper'); 45 const host = container.querySelector('#host'); 46 const shadowRoot = host.attachShadow({ mode }); 47 shadowRoot.innerHTML = 'hello, world'; 48 sel.setBaseAndExtent(shadowRoot.firstChild, 4, shadowRoot.firstChild, 7); 49 wrapper.remove(); 50 51 const rangeAfter = sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0]; 52 assert_equals(rangeAfter.startContainer, container, 'collapsed to parent of removed node'); 53 assert_equals(rangeAfter.startOffset, 0); 54 assert_equals(rangeAfter.endContainer, container); 55 assert_equals(rangeAfter.endOffset, 0); 56 }, 'Range is fully in shadow tree. Removing parent of shadow host collapses composed StaticRange.'); 57 58 test(() => { 59 const sel = getSelection(); 60 61 container.innerHTML = '<div id="hello">Hello,</div><div id="world"> World</div>'; 62 sel.setBaseAndExtent(hello.firstChild, 1, world.firstChild, 3); 63 hello.firstChild.remove(); 64 const rangeAfter = sel.getComposedRanges()[0]; 65 66 assert_equals(rangeAfter.startContainer, hello); 67 assert_equals(rangeAfter.startOffset, 0); 68 assert_equals(rangeAfter.endContainer, world.firstChild); 69 assert_equals(rangeAfter.endOffset, 3); 70 }, 'Range is in light DOM. Removing startContainer rescopes new composed range to its parent.'); 71 72 test(() => { 73 const sel = getSelection(); 74 75 container.innerHTML = 'a<div id="host"></div>b'; 76 const host = container.querySelector('#host'); 77 const shadowRoot = host.attachShadow({ mode }); 78 shadowRoot.innerHTML = 'hello, world'; 79 sel.setBaseAndExtent(shadowRoot.firstChild, 7, container, 2); 80 shadowRoot.innerHTML = ''; 81 const rangeAfter = sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0]; 82 83 assert_equals(rangeAfter.startContainer, shadowRoot, 'collapsed to be at the parent shadow root'); 84 assert_equals(rangeAfter.startOffset, 0); 85 assert_equals(rangeAfter.endContainer, container); 86 assert_equals(rangeAfter.endOffset, 2); 87 }, 'Range is across shadow trees. Replacing shadowRoot content rescopes new composed range to the shadowRoot.'); 88 89 test(() => { 90 const sel = getSelection(); 91 92 container.innerHTML = 'a<div id="outerhost"></div>b'; 93 const outerHost = container.querySelector('#outerhost'); 94 const outerRoot = outerHost.attachShadow({ mode }); 95 outerRoot.innerHTML = 'c<div id="innerHost"></div>d'; 96 const innerHost = outerRoot.querySelector('#innerHost'); 97 const innerRoot = innerHost.attachShadow({ mode }); 98 innerRoot.innerHTML = 'hello, world'; 99 sel.setBaseAndExtent(container.firstChild, 0, innerRoot.firstChild, 4); 100 outerHost.remove(); 101 const rangeAfter = sel.getComposedRanges({ shadowRoots: [innerRoot, outerRoot] })[0]; 102 103 assert_equals(rangeAfter.startContainer, container.firstChild); 104 assert_equals(rangeAfter.startOffset, 0); 105 assert_equals(rangeAfter.endContainer, container); 106 assert_equals(rangeAfter.endOffset, 1); 107 }, 'Range is across shadow trees. Removing ancestor shadow host rescopes composed range end to parent.'); 108 109 test(() => { 110 container.innerHTML = [ 111 '<div id=host>', 112 '<div id=div1 slot=slot2>slotted content 1</div>', 113 '<div id=div2 slot=slot1>slotted content 2</div>', 114 '</div>' 115 ].join(''); 116 const shadowRoot = host.attachShadow({mode: 'open'}); 117 shadowRoot.innerHTML = [ 118 '<span>before</span>', 119 '<slot name=slot1></slot>', 120 '<span>between</span>', 121 '<slot name=slot2></slot>', 122 '<span>after</span>', 123 ].join(''); 124 125 const sel = getSelection(); 126 sel.setBaseAndExtent(div1.firstChild, 2, div2.firstChild, 2); 127 div1.remove(); 128 129 const rangeAfter = sel.getComposedRanges({ shadowRoots: [shadowRoot] })[0]; 130 assert_equals(rangeAfter.startContainer, host); 131 assert_equals(rangeAfter.startOffset, 0); 132 assert_equals(rangeAfter.endContainer, div2.firstChild); 133 assert_equals(rangeAfter.endOffset, 2); 134 }, 'Range is between two light slotted contents. Removing start container rescopes to its parent in light tree.'); 135 136 </script> 137 </body> 138 </html>