summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/selection/contenteditable
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/selection/contenteditable')
-rw-r--r--testing/web-platform/tests/selection/contenteditable/cefalse-on-boundaries.html72
-rw-r--r--testing/web-platform/tests/selection/contenteditable/collapse.html43
-rw-r--r--testing/web-platform/tests/selection/contenteditable/initial-selection-on-focus.tentative.html473
-rw-r--r--testing/web-platform/tests/selection/contenteditable/modify.tentative.html60
-rw-r--r--testing/web-platform/tests/selection/contenteditable/modifying-selection-with-middle-mouse-button.tentative.html213
-rw-r--r--testing/web-platform/tests/selection/contenteditable/modifying-selection-with-primary-mouse-button.tentative.html212
6 files changed, 1073 insertions, 0 deletions
diff --git a/testing/web-platform/tests/selection/contenteditable/cefalse-on-boundaries.html b/testing/web-platform/tests/selection/contenteditable/cefalse-on-boundaries.html
new file mode 100644
index 0000000000..945058dfc3
--- /dev/null
+++ b/testing/web-platform/tests/selection/contenteditable/cefalse-on-boundaries.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Selection of contenteditable=false at the beginning and end of contenteditable element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div contenteditable id="host">
+ <div contenteditable="false" id="beginning">&nbsp;</div>
+ <p id="paragraph">Lorem ipsum dolor sit amet.</p>
+ <div contenteditable="false" id="end">&nbsp;</div>
+</div>
+<script>
+test( () => {
+ const range = document.createRange();
+
+ range.setStartBefore( beginning );
+ range.setEndAfter( paragraph );
+ getSelection().removeAllRanges();
+ getSelection().addRange( range );
+
+ const selectedRange = getSelection().getRangeAt( 0 );
+
+ assert_equals( selectedRange.startContainer, host );
+ assert_equals( selectedRange.startOffset, 1 );
+ assert_equals( selectedRange.endContainer, host );
+ assert_equals( selectedRange.endOffset, 4 );
+}, 'Selection can start on cE=false element at the beginning of the cE=true element' );
+
+test( () => {
+ const range = document.createRange();
+
+ range.setStartBefore( paragraph );
+ range.setEndAfter( end );
+ getSelection().removeAllRanges();
+ getSelection().addRange( range );
+
+ const selectedRange = getSelection().getRangeAt( 0 );
+
+ assert_equals( selectedRange.startContainer, host );
+ assert_equals( selectedRange.startOffset, 3 );
+ assert_equals( selectedRange.endContainer, host );
+ assert_equals( selectedRange.endOffset, 6 );
+}, 'Selection can end on cE=false element at the end of the cE=true element' );
+
+test( () => {
+ const range = document.createRange();
+
+ range.setStartBefore( beginning );
+ range.setEndAfter( end );
+ getSelection().removeAllRanges();
+ getSelection().addRange( range );
+
+ const selectedRange = getSelection().getRangeAt( 0 );
+
+ assert_equals( selectedRange.startContainer, host );
+ assert_equals( selectedRange.startOffset, 1 );
+ assert_equals( selectedRange.endContainer, host );
+ assert_equals( selectedRange.endOffset, 6 );
+}, 'Selection can start and end on cE=false elements at the boundaries of cE=true element' );
+
+test( () => {
+ const range = document.createRange();
+
+ range.selectNodeContents( host );
+
+ const selectedRange = getSelection().getRangeAt( 0 );
+
+ assert_equals( selectedRange.startContainer, host );
+ assert_equals( selectedRange.startOffset, 1 );
+ assert_equals( selectedRange.endContainer, host );
+ assert_equals( selectedRange.endOffset, 6 );
+}, 'Range#selectNodeContents() correctly select contents of cE=true element with cE=false elements on boundaries' );
+</script>
diff --git a/testing/web-platform/tests/selection/contenteditable/collapse.html b/testing/web-platform/tests/selection/contenteditable/collapse.html
new file mode 100644
index 0000000000..6081d05516
--- /dev/null
+++ b/testing/web-platform/tests/selection/contenteditable/collapse.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Selection across multiple contenteditable</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<button id="button"></button>
+<div contenteditable id="host1"></div>
+<div contenteditable id="host2"></div>
+<div contenteditable id="host3">
+ <div contenteditable="false">
+ <div contenteditable id="host4"></div>
+ </div>
+</div>
+<script>
+function clearFocus() {
+ // Move focus from editable host to a button to remove selection limiter
+ button.focus();
+}
+test(() => {
+ clearFocus();
+ getSelection().collapse(host1);
+ assert_equals(document.activeElement, host1);
+ getSelection().collapse(host2);
+ assert_equals(document.activeElement, host2);
+}, "Selection.collapse() must succeed across siblings");
+
+test(() => {
+ clearFocus();
+ getSelection().collapse(host4);
+ assert_equals(document.activeElement, host4);
+ getSelection().collapse(host3);
+ assert_equals(document.activeElement, host3);
+}, "Selection.collapse() must succeed for the ancestor");
+
+
+test(() => {
+ clearFocus();
+ getSelection().collapse(host3);
+ assert_equals(document.activeElement, host3);
+ getSelection().collapse(host4);
+ assert_equals(document.activeElement, host4);
+}, "Selection.collapse() must succeed for the descendant");
+</script>
diff --git a/testing/web-platform/tests/selection/contenteditable/initial-selection-on-focus.tentative.html b/testing/web-platform/tests/selection/contenteditable/initial-selection-on-focus.tentative.html
new file mode 100644
index 0000000000..fcaf70d877
--- /dev/null
+++ b/testing/web-platform/tests/selection/contenteditable/initial-selection-on-focus.tentative.html
@@ -0,0 +1,473 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="variant" content="?div">
+<meta name="variant" content="?span">
+<title>initial selection on focus of contenteditable</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>var testsJsLibraryOnly = true</script>
+<script src="../../editing/include/tests.js"></script>
+<body>
+<script>
+"use strict";
+
+(function() {
+ const editingHostTagName = document.location.search.substr(1);
+ const editor = document.createElement(editingHostTagName);
+ editor.style.minHeight = "1em";
+ editor.setAttribute("contenteditable", "");
+ document.body.insertBefore(editor, document.body.firstChild);
+ editor.focus();
+ editor.getBoundingClientRect();
+
+ const tests = [
+ { description: "empty editor should set focus to start of it",
+ content: "{}",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to start of the text node",
+ content: "[]abc",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node",
+ content: "{}<br>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the first <br> node",
+ content: "{}<br><br>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should set selection to start of the text node in the <p> node",
+ content: "<p>[]abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the first visible character in the text node in the <p> node",
+ content: "<p> []abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the <p> node because of preformatted white-space",
+ content: "<p style=\"white-space: pre\">[] abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the <p> node because of preformatted line break",
+ content: "<p style=\"white-space: pre\">[]\nabc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the <p> node",
+ content: "<p>{}<br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the first <br> node in the <p> node",
+ content: "<p>{}<br><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should set selection to start of the text node in the <span> node",
+ content: "<span>[]abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node in the <span> node",
+ content: "<span>{}<br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the first <br> node in the <span> node",
+ content: "<span>{}<br><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should set selection to before the empty <span> node",
+ content: "{}<span></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <b> node",
+ content: "{}<b></b>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <i> node",
+ content: "{}<i></i>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <u> node",
+ content: "{}<u></u>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <s> node",
+ content: "{}<s></s>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <code> node",
+ content: "{}<code></code>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <a> node",
+ content: "{}<a href=\"foo.html\"></a>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the empty <foobar> node",
+ content: "{}<foobar></foobar>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <input> node",
+ content: "{}<input>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <img> node",
+ content: "{}<img alt=\"foo\">",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should set selection to start of the text node in the second <span> node",
+ content: "<span></span><span>[]abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node in the second <span> node",
+ content: "<span></span><span>{}<br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to start of the text node in the first <span> node #1",
+ content: "<span>[]abc</span><span>abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to start of the text node in the first <span> node #2",
+ content: "<span>[]abc</span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node in the first <span> node #1",
+ content: "<span>{}<br></span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node in the first <span> node #2",
+ content: "<span>{}<br></span><span>abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should set selection to start of the text node in the second <span> node since the text node in the first <span> node is only whitespaces",
+ content: "<span> </span><span>[]abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node in the second <span> node since the text node in the first <span> node is only whitespaces",
+ content: "<span> </span><span>{}<br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to start of the text node in the second <span> node even if there is a whitespace only text node before the first <span> node",
+ content: " <span></span><span>[]abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should set selection to before the <br> node in the second <span> node even if there is a whitespace only text node before the first <span> node",
+ content: " <span></span><span>{}<br></span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should set selection to start of the text node in the second <p> node following the empty <p> node",
+ content: "<p></p><p>[]abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the second <p> node following another <p> node containing only a comment node",
+ content: "<p><!-- comment --></p><p>[]abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the second <p> node",
+ content: "<p></p><p>{}<br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the first <p> node #1",
+ content: "<p>[]abc</p><p>abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the first <p> node #2",
+ content: "<p>[]abc</p><p><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the first <p> node #1",
+ content: "<p>{}<br></p><p><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the first <p> node #2",
+ content: "<p>{}<br></p><p>abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should set selection to start of the text node in the second <p> node since the text node in the first <p> node is only whitespaces",
+ content: "<p> </p><p>[]abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the first <p> node whose white-spaces are preformatted",
+ content: "<p style=\"white-space: pre\">[] </p><p>abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the first <p> node whose line breaks are preformatted",
+ content: "<p style=\"white-space: pre\">[]\n</p><p>abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the second <p> node since the text node in the first <p> node is only whitespaces",
+ content: "<p> </p><p>{}<br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the second <p> node even if there is a whitespace only text node before the first <p> node",
+ content: " <p></p><p>[]abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the second <p> node even if there is a whitespace only text node before the first <p> node",
+ content: " <p></p><p>{}<br></p>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should set selection to start of the text node in the <span> node in the second <p> node",
+ content: "<p><span></span></p><p><span>[]abc</span></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the <span> node in the second <p> node",
+ content: "<p><span></span></p><p><span>{}<br></span></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the <span> node in the first <p> node #1",
+ content: "<p><span>[]abc</span></p><p><span>abc</span></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to start of the text node in the <span> node in the first <p> node #2",
+ content: "<p><span>[]abc</span></p><p><span><br></span></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the <span> node in the first <p> node #1",
+ content: "<p><span>{}<br></span></p><p><span><br></span></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should set selection to before the <br> node in the <span> node in the first <p> node #2",
+ content: "<p><span>{}<br></span></p><p><span>abc</span></p>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should collapse selection before the non-editable <span> node",
+ content: "{}<span contenteditable=\"false\"></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable <span> node even if it has a text node",
+ content: "{}<span contenteditable=\"false\">abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable <span> node even if it has a <br> node",
+ content: "{}<span contenteditable=\"false\"><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should collapse selection before the non-editable empty <span> node followed by a text node",
+ content: "{}<span contenteditable=\"false\"></span><span>abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable <span> node having a text node and followed by another text node",
+ content: "{}<span contenteditable=\"false\">abc</span><span>def</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable <span> node having a <br> node and followed by a text node",
+ content: "{}<span contenteditable=\"false\"><br></span><span>abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable empty <span> node followed by a <br> node",
+ content: "{}<span contenteditable=\"false\"></span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable <span> node having text node and followed by a <br> node",
+ content: "{}<span contenteditable=\"false\">abc</span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection before the non-editable <span> node having a <br> node and followed by another <br> node",
+ content: "{}<span contenteditable=\"false\"><br></span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should collapse selection before the non-editable empty <p> node followed by a text node",
+ content: "{}<p contenteditable=\"false\"></p><p>abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection before the non-editable <p> node having a text node and followed by another text node",
+ content: "{}<p contenteditable=\"false\">abc</p><p>def</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection before the non-editable <p> node having a <br> node and followed by a text node",
+ content: "{}<p contenteditable=\"false\"><br></p><p>abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection before the non-editable empty <p> node followed by a <br> node",
+ content: "{}<p contenteditable=\"false\"></p><p><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection before the non-editable <p> node having text node and followed by a <br> node",
+ content: "{}<p contenteditable=\"false\">abc</p><p><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection before the non-editable <p> node having a <br> node and followed by another <br> node",
+ content: "{}<p contenteditable=\"false\"><br></p><p><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should collapse selection to start of itself when there is only empty inline elements before the non-editable node before first editable text node",
+ content: "{}<span></span><span contenteditable=\"false\"></span><span>abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to start of itself when there is only empty inline elements before the non-editable node having a text node before first editable text node",
+ content: "{}<span></span><span contenteditable=\"false\">abc</span><span>def</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to start of itself when there is only empty inline elements before the non-editable node having a <br> node before first editable text node",
+ content: "{}<span></span><span contenteditable=\"false\"><br></span><span>abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to start of itself when there is only empty inline elements before the non-editable node before first editable <br> node",
+ content: "{}<span></span><span contenteditable=\"false\"></span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to start of itself when there is only empty inline elements before the non-editable node having a text node before first editable <br> node",
+ content: "{}<span></span><span contenteditable=\"false\">abc</span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to start of itself when there is only empty inline elements before the non-editable node having a <br> node before first editable <br> node",
+ content: "{}<span></span><span contenteditable=\"false\"><br></span><span><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should collapse selection to start of the first dive element when there is only empty inline elements before the non-editable node before first editable text node",
+ content: "<div>{}<span></span><span contenteditable=\"false\"></span><span>abc</span></div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to start of the first dive element when there is only empty inline elements before the non-editable node having a text node before first editable text node",
+ content: "<div>{}<span></span><span contenteditable=\"false\">abc</span><span>def</span></div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to start of the first dive element when there is only empty inline elements before the non-editable node having a <br> node before first editable text node",
+ content: "<div>{}<span></span><span contenteditable=\"false\"><br></span><span>abc</span></div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to start of the first dive element when there is only empty inline elements before the non-editable node before first editable <br> node",
+ content: "<div>{}<span></span><span contenteditable=\"false\"></span><span><br></span></div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to start of the first dive element when there is only empty inline elements before the non-editable node having a text node before first editable <br> node",
+ content: "<div>{}<span></span><span contenteditable=\"false\">abc</span><span><br></span></div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to start of the first dive element when there is only empty inline elements before the non-editable node having a <br> node before first editable <br> node",
+ content: "<div>{}<span></span><span contenteditable=\"false\"><br></span><span><br></span></div>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should collapse selection to the first editable text node in the first <span> node even if followed by a non-editable node",
+ content: "<span>[]abc</span><span contenteditable=\"false\"></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to the first editable text node in the first <span> node even if followed by a non-editable node having another text node",
+ content: "<span>[]abc</span><span contenteditable=\"false\">def</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to the first editable text node in the first <span> node even if followed by a non-editable node having a <br> node",
+ content: "<span>[]abc</span><span contenteditable=\"false\"><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to the first editable <br> node in the first <span> node even if followed by a non-editable node",
+ content: "<span>{}<br></span><span contenteditable=\"false\"></span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to the first editable <br> node in the first <span> node even if followed by a non-editable node having a text node",
+ content: "<span>{}<br></span><span contenteditable=\"false\">abc</span>",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to the first editable <br> node in the first <span> node even if followed by a non-editable node having a <br> node",
+ content: "<span>{}<br></span><span contenteditable=\"false\"><br></span>",
+ canTestInInlineEditingHost: true,
+ },
+
+ { description: "editor should collapse selection to the first editable text node in the first <p> node even if followed by a non-editable node",
+ content: "<p>[]abc</p><p contenteditable=\"false\"></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the first editable text node in the first <p> node even if followed by a non-editable node having another text node",
+ content: "<p>[]abc</p><p contenteditable=\"false\">def</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the first editable text node in the first <p> node even if followed by a non-editable node having a <br> node",
+ content: "<p>[]abc</p><p contenteditable=\"false\"><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the first editable <br> node in the first <p> node even if followed by a non-editable node",
+ content: "<p>{}<br></p><p contenteditable=\"false\"></p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the first editable <br> node in the first <p> node even if followed by a non-editable node having a text node",
+ content: "<p>{}<br></p><p contenteditable=\"false\">abc</p>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the first editable <br> node in the first <p> node even if followed by a non-editable node having a <br> node",
+ content: "<p>{}<br></p><p contenteditable=\"false\"><br></p>",
+ canTestInInlineEditingHost: false,
+ },
+
+ { description: "editor should collapse selection to start of itself if first content is an input element",
+ content: "{}<input>abc",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to start of itself if first content is an hr element",
+ content: "{}<hr>abc",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to start of itself if first content is an textarea element",
+ content: "{}<textarea>abc</textarea>def",
+ canTestInInlineEditingHost: true,
+ },
+ { description: "editor should collapse selection to the input element",
+ content: "<div>{}<input>abc</div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the hr element",
+ content: "<div>{}<hr>abc</div>",
+ canTestInInlineEditingHost: false,
+ },
+ { description: "editor should collapse selection to the textarea element",
+ content: "<div>{}<textarea>abc</textarea>def</div>",
+ canTestInInlineEditingHost: false,
+ },
+ ];
+
+ const isInlineEditingHost = editingHostTagName == "span";
+
+ const selection = document.getSelection();
+ for (const testData of tests) {
+ if (isInlineEditingHost && !testData.canTestInInlineEditingHost) {
+ continue;
+ }
+ test(function() {
+ editor.blur();
+ selection.removeAllRanges();
+ editor.innerHTML = testData.content.replace(/[{}\[\]]/g, "");
+ editor.focus();
+ editor.getBoundingClientRect();
+
+ assert_equals(selection.rangeCount, 1, "Only one caret should be in the editor");
+ if (selection.rangeCount) {
+ addBrackets(selection.getRangeAt(0));
+ assert_equals(editor.innerHTML, testData.content);
+ }
+ }, testData.description);
+ }
+
+ test(function() {
+ // Check if selection is initialized after temporarily blurred.
+ editor.innerHTML =
+ `<${editingHostTagName}>abc</${editingHostTagName}><${editingHostTagName}>def</${editingHostTagName}>`;
+ editor.focus();
+ // Move selection to the second paragraph.
+ selection.collapse(editor.firstChild.nextSibling.firstChild);
+ // Reset focus.
+ editor.blur();
+ editor.focus();
+ // Then, selection should still be in the second paragraph.
+ assert_equals(selection.rangeCount, 1, "Only one caret should be in the editor");
+ if (selection.rangeCount) {
+ addBrackets(selection.getRangeAt(0));
+ assert_equals(
+ editor.innerHTML,
+ `<${editingHostTagName}>abc</${editingHostTagName}><${editingHostTagName}>[]def</${editingHostTagName}>`
+ );
+ }
+ }, "editor shouldn't reset selection when it gets focus again");
+})();
+</script>
diff --git a/testing/web-platform/tests/selection/contenteditable/modify.tentative.html b/testing/web-platform/tests/selection/contenteditable/modify.tentative.html
new file mode 100644
index 0000000000..a3afd52ea2
--- /dev/null
+++ b/testing/web-platform/tests/selection/contenteditable/modify.tentative.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Selection.modify() inside contenteditable</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div contenteditable id="host">Editable</div>
+<div>Non-editable</div>
+<div id="inlinehosts">
+ Prefix: <span contenteditable title="inline">Editable</span>: Suffix<br>
+ Prefix: <span contenteditable style="display:inline-block;" title="inline-block">Editable</span>: Suffix<br>
+ <span contenteditable title="suffix only">Editable</span>: Suffix<br>
+ Prefix: <span contenteditable title="prefix only">Editable</span><br>
+ <span contenteditable title="standalone">Editable</span><br>
+ Prefix: <span contenteditable title="inline linebreak">Edit<br>able</span>: Suffix<br>
+ Prefix: <span contenteditable style="display:inline-block;" title="inline-block linebreak">Edit<br>able</span>:
+ Suffix<br>
+</div>
+<script>
+ /** @param {Node} parent */
+ function* textNodeEntries(parent) {
+ for (const [i, node] of parent.childNodes.entries()) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ yield [i, node];
+ }
+ }
+ }
+
+ const selection = getSelection();
+ test(() => {
+ selection.collapse(host);
+ selection.modify('extend', 'forward', 'word');
+ selection.modify('extend', 'forward', 'word');
+ assert_equals(selection.focusNode.parentElement, host);
+ }, "Selection.modify() must not select outside of the host");
+
+ /** @type {NodeListOf<HTMLElement>} */
+ const hosts = inlinehosts.querySelectorAll("span[contenteditable]");
+ for (const host of hosts) {
+ test(() => {
+ for (const [i, text] of textNodeEntries(host)) {
+ selection.collapse(text, 1);
+ selection.modify("move", "forward", "lineboundary");
+ if (selection.focusNode === host) {
+ assert_equals(selection.focusOffset, i + 1, "focusOffset should be after the text node");
+ } else {
+ assert_equals(selection.focusNode, text, "focusNode should be the text node");
+ assert_equals(selection.focusOffset, text.textContent.length, "focusOffset should be the length of the text node");
+ }
+ }
+ }, `Selection.modify('move', 'forward', 'lineboundary') must be within the inline editing host: ${host.title}`);
+ test(() => {
+ for (const [i, text] of textNodeEntries(host)) {
+ selection.collapse(text, 1);
+ selection.modify("move", "backward", "lineboundary");
+ assert_equals(selection.focusNode, text, "focusNode should be the text node");
+ assert_equals(selection.focusOffset, 0, "focusOffset should be 0");
+ }
+ }, `Selection.modify('move', 'backward', 'lineboundary') must be within the inline editing host: ${host.title}`);
+ }
+</script>
diff --git a/testing/web-platform/tests/selection/contenteditable/modifying-selection-with-middle-mouse-button.tentative.html b/testing/web-platform/tests/selection/contenteditable/modifying-selection-with-middle-mouse-button.tentative.html
new file mode 100644
index 0000000000..a8287d9002
--- /dev/null
+++ b/testing/web-platform/tests/selection/contenteditable/modifying-selection-with-middle-mouse-button.tentative.html
@@ -0,0 +1,213 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Testing default action of `mousedown` of middle button and `mouseup` of middle button</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<style>
+span {
+ white-space: nowrap;
+}
+</style>
+</head>
+<body>
+<div contenteditable></div>
+<script>
+"use strict";
+
+var editor = document.querySelector("div[contenteditable]");
+var span1, span2, link;
+var selection = getSelection();
+
+function preventDefault(event) {
+ event.preventDefault();
+}
+editor.addEventListener("paste", preventDefault, {capture: true});
+
+function resetEditor() {
+ editor.innerHTML =
+ '<span id="span1">first span.</span><br><span id="span2">second span.</span><br><a id="link" href="#top">link.</a>';
+ span1 = document.getElementById("span1");
+ span2 = document.getElementById("span2");
+ link = document.getElementById("link");
+}
+
+promise_test(async () => {
+ resetEditor();
+ editor.blur();
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span1})
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .send();
+
+ assert_equals(document.activeElement, editor,
+ "The clicked editor should get focus");
+ assert_true(selection.isCollapsed,
+ "Selection should be collapsed after middle button click");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should be collapsed in the first <span> element which was clicked by middle button");
+}, "Middle click should set focus to clicked editable element and collapse selection around the clicked point");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .send();
+
+ assert_equals(document.activeElement, editor,
+ "The clicked editor should keep having focus");
+ assert_true(selection.isCollapsed,
+ "Selection should be collapsed after middle button click");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection should be collapsed in the second <span> element which was clicked by middle button");
+}, "Middle click should move caret in an editable element");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ addEventListener("mousedown", preventDefault);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .send();
+ removeEventListener("mousedown", preventDefault);
+
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should keep collapsed selection in the first <span> element");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should keep collapsed selection at 2 of the first <span> element");
+}, "Middle click shouldn't move caret in an editable element if the default of mousedown event is prevented");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ addEventListener("pointerdown", preventDefault);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .send();
+ removeEventListener("pointerdown", preventDefault);
+
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should keep collapsed selection in the first <span> element");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should keep collapsed selection at 2 of the first <span> element");
+}, "Middle click shouldn't move caret in an editable element if the default of pointerdown event is prevented");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .keyDown("\uE008")
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .keyUp("\uE008")
+ .send();
+
+ assert_equals(selection.anchorNode, span1.firstChild,
+ "Selection#anchorNode should keep in the first <span> element");
+ assert_equals(selection.anchorOffset, 2,
+ "Selection#anchorNode should keep at 2 of the first <span> element");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection#focusNode should be in the second <span> element which was clicked by middle button");
+}, "Shift + Middle click should extend the selection");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: link})
+ .keyDown("\uE008")
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .keyUp("\uE008")
+ .send();
+
+ assert_equals(selection.focusNode, link.firstChild,
+ "Selection#focusNode should be in the <a href> element which was clicked by middle button");
+ assert_true(selection.isCollapsed,
+ "Selection#isCollapsed should be true");
+}, "Shift + Middle click in a link shouldn't extend the selection");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ editor.addEventListener("pointerdown", () => {
+ assert_true(selection.isCollapsed,
+ "Selection shouldn't be modified before pointerdown event");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should stay in the first <span> element when pointerdown event is fired (focusNode)");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should stay in the first <span> element when pointerdown event is fired (focusOffset)");
+ }, {once: true});
+ editor.addEventListener("mousedown", () => {
+ assert_true(selection.isCollapsed,
+ "Selection shouldn't be modified before mousedown event");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should stay in the first <span> element when mousedown event is fired (focusNode)");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should stay in the first <span> element when mousedown event is fired (focusOffset)");
+ }, {once: true});
+ editor.addEventListener("pointerup", () => {
+ assert_true(selection.isCollapsed,
+ "Selection should be collapsed before pointerup event");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection should be collapsed in the second <span> element which was clicked by middle button before pointerup event ");
+ }, {once: true});
+ let focusOffsetAtMouseUp;
+ editor.addEventListener("mouseup", () => {
+ assert_true(selection.isCollapsed,
+ "Selection should be collapsed before mouseup event");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection should be collapsed in the second <span> element which was clicked by middle button before mouseup event ");
+ focusOffsetAtMouseUp = selection.focusOffset;
+ }, {once: true});
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerMove(0, 0, {origin: span1})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .send();
+
+ assert_true(selection.isCollapsed,
+ "Selection shouldn't be extended by pointer moves during pressing middle button");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection#focusNode should stay in the second <span> element");
+ assert_equals(selection.focusOffset, focusOffsetAtMouseUp,
+ "Selection#focusOffset should stay in the second <span> element");
+}, "Middle mouse button down should move caret, but middle mouse button up shouldn't move caret");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/selection/contenteditable/modifying-selection-with-primary-mouse-button.tentative.html b/testing/web-platform/tests/selection/contenteditable/modifying-selection-with-primary-mouse-button.tentative.html
new file mode 100644
index 0000000000..57586a6060
--- /dev/null
+++ b/testing/web-platform/tests/selection/contenteditable/modifying-selection-with-primary-mouse-button.tentative.html
@@ -0,0 +1,212 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Testing default action of `mousedown` of primary button and `mouseup` of primary button</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<style>
+span {
+ white-space: nowrap;
+}
+</style>
+</head>
+<body>
+<div contenteditable></div>
+<script>
+"use strict";
+
+var editor = document.querySelector("div[contenteditable]");
+var span1, span2, link;
+var selection = getSelection();
+
+function preventDefault(event) {
+ event.preventDefault();
+}
+editor.addEventListener("paste", preventDefault, {capture: true});
+
+function resetEditor() {
+ editor.innerHTML =
+ '<span id="span1">first span.</span><br><span id="span2">second span.</span><br><a id="link" href="#top">link.</a>';
+ span1 = document.getElementById("span1");
+ span2 = document.getElementById("span2");
+ link = document.getElementById("link");
+}
+
+promise_test(async () => {
+ resetEditor();
+ editor.blur();
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span1})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+
+ assert_equals(document.activeElement, editor,
+ "The clicked editor should get focus");
+ assert_true(selection.isCollapsed,
+ "Selection should be collapsed after primary button click");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should be collapsed in the first <span> element which was clicked by primary button");
+}, "Primary click should set focus to clicked editable element and collapse selection around the clicked point");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+
+ assert_equals(document.activeElement, editor,
+ "The clicked editor should keep having focus");
+ assert_true(selection.isCollapsed,
+ "Selection should be collapsed after primary button click");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection should be collapsed in the second <span> element which was clicked by primary button");
+}, "Primary click should move caret in an editable element");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ addEventListener("mousedown", preventDefault);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ removeEventListener("mousedown", preventDefault);
+
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should keep collapsed selection in the first <span> element");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should keep collapsed selection at 2 of the first <span> element");
+}, "Primary click shouldn't move caret in an editable element if the default of mousedown event is prevented");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ addEventListener("pointerdown", preventDefault);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ removeEventListener("pointerdown", preventDefault);
+
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should keep collapsed selection in the first <span> element");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should keep collapsed selection at 2 of the first <span> element");
+}, "Primary click shouldn't move caret in an editable element if the default of pointerdown event is prevented");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .keyDown("\uE008")
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .keyUp("\uE008")
+ .send();
+
+ assert_equals(selection.anchorNode, span1.firstChild,
+ "Selection#anchorNode should keep in the first <span> element");
+ assert_equals(selection.anchorOffset, 2,
+ "Selection#anchorNode should keep at 2 of the first <span> element");
+ assert_equals(selection.focusNode, span2.firstChild,
+ "Selection#focusNode should be in the second <span> element which was clicked by primary button");
+}, "Shift + Primary click should extend the selection");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: link})
+ .keyDown("\uE008")
+ .pointerDown({button: actions.ButtonType.MIDDLE})
+ .pointerUp({button: actions.ButtonType.MIDDLE})
+ .keyUp("\uE008")
+ .send();
+
+ assert_equals(selection.focusNode, link.firstChild,
+ "Selection#focusNode should be in the <a href> element which was clicked by primary button");
+ assert_true(selection.isCollapsed,
+ "Selection#isCollapsed should be true");
+}, "Shift + Primary click in a link shouldn't extend the selection");
+
+promise_test(async () => {
+ resetEditor();
+ editor.focus();
+ selection.collapse(span1.firstChild, 2);
+ editor.addEventListener("pointerdown", () => {
+ assert_true(selection.isCollapsed,
+ "Selection shouldn't be modified before pointerdown event");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should stay in the first <span> element when pointerdown event is fired (focusNode)");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should stay in the first <span> element when pointerdown event is fired (focusOffset)");
+ }, {once: true});
+ editor.addEventListener("mousedown", () => {
+ assert_true(selection.isCollapsed,
+ "Selection shouldn't be modified before mousedown event");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should stay in the first <span> element when mousedown event is fired (focusNode)");
+ assert_equals(selection.focusOffset, 2,
+ "Selection should stay in the first <span> element when mousedown event is fired (focusOffset)");
+ }, {once: true});
+ editor.addEventListener("pointerup", () => {
+ assert_true(!selection.isCollapsed,
+ "Selection should be modified before pointerup event");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should be modified to extend the range before pointerup event ");
+ }, {once: true});
+ let anchorOffsetAtMouseUp;
+ editor.addEventListener("mouseup", () => {
+ assert_true(!selection.isCollapsed,
+ "Selection should be modified before mouseup event");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection should be modified to extend the range before mouseup event ");
+ anchorOffsetAtMouseUp = selection.anchorOffset;
+ }, {once: true});
+ let actions = new test_driver.Actions();
+ await actions
+ .pointerMove(0, 0)
+ .pointerMove(0, 0, {origin: span2})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerMove(0, 0, {origin: span1})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+
+ assert_equals(selection.anchorNode, span2.firstChild,
+ "Selection#anchorNode should stay in the second <span> element which mousedown occurred on");
+ assert_equals(selection.anchorOffset, anchorOffsetAtMouseUp,
+ "Selection#anchorOffset should stay in the second <span> element which mousedown occurred on");
+ assert_equals(selection.focusNode, span1.firstChild,
+ "Selection#focusNode should be in the first <span> element which mouseup occurred on");
+}, "Primary mouse button down should move caret, and primary mouse button up should extend the selection");
+</script>
+</body>
+</html>