| <!DOCTYPE html> |
| <html> |
| <body> |
| <meta name="assert" content="Selection's composed range should be updated when its associated legacy uncomposed range changes"> |
| <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="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> |