summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/forms/textfieldselection
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/forms/textfieldselection')
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html28
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json1
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html154
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html144
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html40
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html153
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html206
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html127
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html206
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html18
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml20
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html155
-rw-r--r--testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html304
14 files changed, 1668 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html
new file mode 100644
index 0000000000..be965bf5cf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/defaultSelection.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>foo</textarea>
+<input type="text" value="foo"></input>
+<script>
+
+for (let el of [document.querySelector("textarea"), document.querySelector("input")]) {
+ test(function() {
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, `Default selectionStart and selectionEnd for ${el}`);
+
+ test(function() {
+ el.value="foo";
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, `selectionStart and selectionEnd do not change when same value set again for ${el}`);
+
+ test(function() {
+ el.value="Foo";
+ assert_equals(el.selectionStart, 3);
+ assert_equals(el.selectionEnd, 3);
+ }, `selectionStart and selectionEnd change when value changed to upper case for ${el}`);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json b/testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json
new file mode 100644
index 0000000000..d9fe435856
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/original-id.json
@@ -0,0 +1 @@
+{"original_id":"textFieldSelection"} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html
new file mode 100644
index 0000000000..d1b46a22d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>text field selection: select()</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+
+<textarea>foobar</textarea>
+<input type="text" value="foobar">
+<input type="search" value="foobar">
+<input type="tel" value="1234">
+<input type="url" value="https://example.com/">
+<input type="password" value="hunter2">
+
+<script>
+"use strict";
+
+const els = [document.querySelector("textarea"), ...document.querySelectorAll("input")];
+
+const actions = [
+ {
+ label: "select()",
+ action: el => el.select()
+ },
+ {
+ label: "selectionStart",
+ action: el => el.selectionStart = 1
+ },
+ {
+ label: "selectionEnd",
+ action: el => el.selectionEnd = el.value.length - 1
+ },
+ {
+ label: "selectionDirection",
+ action: el => el.selectionDirection = "backward"
+ },
+ {
+ label: "setSelectionRange()",
+ action: el => el.setSelectionRange(1, el.value.length - 1) // changes direction implicitly to none/forward
+ },
+ {
+ label: "setRangeText()",
+ action: el => el.setRangeText("newmiddle", el.selectionStart, el.selectionEnd, "select")
+ },
+ {
+ label: "selectionStart out of range",
+ action: el => el.selectionStart = 1000
+ },
+ {
+ label: "selectionEnd out of range",
+ action: el => el.selectionEnd = 1000
+ },
+ {
+ label: "setSelectionRange out of range",
+ action: el => el.setSelectionRange(1000, 2000)
+ }
+];
+
+function waitForEvents() {
+ // Engines differ in when these events are sent (see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1785615) so wait for both a
+ // frame to be rendered, and a timeout.
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ resolve();
+ });
+ });
+ });
+ });
+}
+
+function initialize(el) {
+ el.setRangeText("foobar", 0, el.value.length, "start");
+ // Make sure to flush async dispatches
+ return waitForEvents();
+}
+
+els.forEach((el) => {
+ const elLabel = el.localName === "textarea" ? "textarea" : "input type " + el.type;
+
+ actions.forEach((action) => {
+ // promise_test instead of async_test is important because these need to happen in sequence (to test that events
+ // fire if and only if the selection changes).
+ promise_test(async t => {
+ await initialize(el);
+
+ const watcher = new EventWatcher(t, el, "select");
+
+ const promise = watcher.wait_for("select").then(e => {
+ assert_true(e.isTrusted, "isTrusted must be true");
+ assert_true(e.bubbles, "bubbles must be true");
+ assert_false(e.cancelable, "cancelable must be false");
+ });
+
+ action.action(el);
+
+ return promise;
+ }, `${elLabel}: ${action.label}`);
+
+ promise_test(async t => {
+ el.onselect = t.unreached_func("the select event must not fire the second time");
+
+ action.action(el);
+
+ await waitForEvents();
+ el.onselect = null;
+ }, `${elLabel}: ${action.label} a second time (must not fire select)`);
+
+ promise_test(async t => {
+ const element = el.cloneNode(true);
+ let fired = false;
+ element.addEventListener('select', () => fired = true, { once: true });
+
+ action.action(element);
+
+ await waitForEvents();
+ assert_true(fired, "event didn't fire");
+
+ }, `${elLabel}: ${action.label} disconnected node`);
+
+ // Intentionally still using promise_test, as assert_unreachable does not
+ // make the test fail inside a listener while t.unreached_func() does.
+ promise_test(async t => {
+ const element = el.cloneNode(true);
+ let fired = false;
+ element.addEventListener('select', () => fired = true, { once: true });
+
+ action.action(element);
+
+ assert_false(fired, "the select event must not fire synchronously");
+ await waitForEvents();
+ assert_true(fired, "event didn't fire");
+ }, `${elLabel}: ${action.label} event queue`);
+
+ promise_test(async t => {
+ const element = el.cloneNode(true);
+ let selectCount = 0;
+ element.addEventListener('select', () => ++selectCount);
+ assert_equals(element.selectionEnd, 0);
+
+ action.action(element);
+ action.action(element);
+
+ await waitForEvents();
+ assert_equals(selectCount, 1, "the select event must not fire twice");
+ }, `${elLabel}: ${action.label} twice in disconnected node (must fire select only once)`);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html
new file mode 100644
index 0000000000..60390085c6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-after-content-change.html
@@ -0,0 +1,144 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Selection indices after content change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<input id="i1" type="text" value="hello">
+<textarea id="t1">hello</textarea>
+
+<script>
+"use strict";
+
+// This helper ensures that when the selection direction is reset, it always is reset to the same value consistently
+// (which must be one of either "none" or "forward"). This helps catch bugs like one observed in Chrome, where textareas
+// reset to "none" but inputs reset to "forward".
+let observedResetSelectionDirection;
+function assertSelectionDirectionIsReset(element) {
+ if (!observedResetSelectionDirection) {
+ assert_in_array(element.selectionDirection, ["none", "forward"],
+ "selectionDirection must be set to either none or forward");
+ observedResetSelectionDirection = element.selectionDirection;
+ } else {
+ assert_equals(element.selectionDirection, observedResetSelectionDirection,
+ `selectionDirection must be reset to ${observedResetSelectionDirection} (which was previously observed to be ` +
+ `the value after resetting the selection direction)`);
+ }
+}
+
+runInputTest("input out of document", () => {
+ const input = document.createElement("input");
+ input.value = "hello";
+ return input;
+});
+
+runInputTest("input in document", () => {
+ const input = document.querySelector("#i1");
+ input.value = "hello";
+ return input;
+});
+
+runInputTest("input in document, with focus", () => {
+ const input = document.querySelector("#i1");
+ input.value = "hello";
+ input.focus();
+ return input;
+});
+
+runTextareaTest("textarea out of document", () => {
+ const textarea = document.createElement("textarea");
+ textarea.value = "hello";
+ return textarea;
+});
+
+runTextareaTest("textarea in document", () => {
+ const textarea = document.querySelector("#t1");
+ textarea.value = "hello";
+ return textarea;
+});
+
+runTextareaTest("textarea in document, with focus", () => {
+ const textarea = document.querySelector("#t1");
+ textarea.value = "hello";
+ textarea.focus();
+ return textarea;
+});
+
+function runTest(descriptor, elementFactory) {
+ test(() => {
+ const element = elementFactory();
+ element.setSelectionRange(1, 3, "backward");
+
+ assert_equals(element.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(element.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(element.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ element.value = "hello";
+
+ assert_equals(element.selectionStart, 1, "selectionStart must not change");
+ assert_equals(element.selectionEnd, 3, "selectionEnd must not change");
+ assert_equals(element.selectionDirection, "backward", "selectionDirection must not change");
+ }, `${descriptor}: selection must not change when setting the same value`);
+
+ test(() => {
+ const element = elementFactory();
+ element.setSelectionRange(1, 3, "backward");
+
+ assert_equals(element.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(element.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(element.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ element.value = "hello2";
+
+ assert_equals(element.selectionStart, element.value.length, "selectionStart must be reset to the end");
+ assert_equals(element.selectionEnd, element.value.length, "selectionEnd must be reset to the end");
+ assertSelectionDirectionIsReset(element);
+ }, `${descriptor}: selection must change when setting a different value`);
+}
+
+function runInputTest(descriptor, elementFactory) {
+ runTest(descriptor, elementFactory);
+
+ test(() => {
+ const input = elementFactory();
+ input.setSelectionRange(1, 3, "backward");
+
+ assert_equals(input.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(input.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(input.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ input.value = "he\nllo";
+
+ assert_equals(input.selectionStart, 1, "selectionStart must not change");
+ assert_equals(input.selectionEnd, 3, "selectionEnd must not change");
+ assert_equals(input.selectionDirection, "backward", "selectionDirection must not change");
+ }, `${descriptor}: selection must not change when setting a value that becomes the same after the value ` +
+ `sanitization algorithm`);
+}
+
+function runTextareaTest(descriptor, elementFactory) {
+ runTest(descriptor, elementFactory);
+
+ test(() => {
+ const textarea = elementFactory();
+ textarea.value = "hell\no";
+ textarea.setSelectionRange(1, 3, "backward");
+
+ assert_equals(textarea.selectionStart, 1, "Sanity check: selectionStart was set correctly");
+ assert_equals(textarea.selectionEnd, 3, "Sanity check: selectionEnd was set correctly");
+ assert_equals(textarea.selectionDirection, "backward", "Sanity check: selectionDirection was set correctly");
+
+ textarea.value = "hell\r\no";
+
+ assert_equals(textarea.selectionStart, 1, "selectionStart must not change when setting to CRLF");
+ assert_equals(textarea.selectionEnd, 3, "selectionEnd must not change when setting to CRLF");
+ assert_equals(textarea.selectionDirection, "backward", "selectionDirection must not change when setting to CRLF");
+
+ textarea.value = "hell\ro";
+
+ assert_equals(textarea.selectionStart, 1, "selectionStart must not change when setting to CR");
+ assert_equals(textarea.selectionEnd, 3, "selectionEnd must not change when setting to CR");
+ assert_equals(textarea.selectionDirection, "backward", "selectionDirection must not change when setting to CR");
+ }, `${descriptor}: selection must not change when setting the same normalized value`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html
new file mode 100644
index 0000000000..48c6313f32
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application-textarea.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>text field selection (textarea)</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ test(function() {
+ var el = document.createElement("textarea");
+ assert_equals(el.selectionStart, 0, "initial selectionStart");
+ assert_equals(el.selectionEnd, 0, "initial selectionEnd");
+ // The initial selection direction must be "none" if the platform supports that
+ // direction, or "forward" otherwise.
+ assert_in_array(el.selectionDirection, ["none", "forward"]);
+
+ const initialDirection = el.selectionDirection;
+ el.selectionDirection = "none";
+ assert_equals(el.selectionDirection, initialDirection);
+
+ el.value = "foo";
+ el.selectionStart = 1;
+ el.selectionEnd = 1;
+ el.selectionDirection = "forward";
+ assert_equals(el.selectionStart, 1, "updated selectionStart");
+ assert_equals(el.selectionEnd, 1, "updated selectionEnd");
+ assert_equals(el.selectionDirection, "forward", "updated selectionDirection");
+
+ el.setRangeText("foobar");
+ el.setSelectionRange(0, 1);
+ assert_equals(el.selectionStart, 0, "final selectionStart");
+ assert_equals(el.selectionEnd, 1, "final selectionEnd");
+ assert_in_array(el.selectionDirection, ["none", "forward"]);
+
+ const finalDirection = el.selectionDirection;
+ el.finalDirection = "forward";
+ assert_equals(el.selectionDirection, finalDirection);
+ }, "text field selection for the input textarea");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html
new file mode 100644
index 0000000000..063836cd23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-not-application.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name=variant content="?default">
+<meta name=variant content="?week,month">
+<title>text field selection</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ var nonApplicableTypes = ["hidden", "email", "datetime-local", "date", "time", "number", "range", "color", "checkbox", "radio", "file", "submit", "image", "reset", "button"];
+ var applicableTypes = ["text", "search", "tel", "url", "password", "aninvalidtype", null];
+
+ if (location.search !== "?default") {
+ // For non default case, use the parameters passed in through query string
+ // instead of nonApplicableTypes.
+ nonApplicableTypes = location.search.substr(1).split(',');
+ }
+
+ nonApplicableTypes.forEach(function(type){
+ var el = document.createElement("input");
+ el.type = type;
+
+ test(() => {
+ assert_equals(el.selectionStart, null);
+ }, `selectionStart on an input[type=${type}] returns null`);
+
+ test(() => {
+ assert_equals(el.selectionEnd, null);
+ }, `selectionEnd on an input[type=${type}] returns null`);
+
+ test(() => {
+ assert_equals(el.selectionDirection, null);
+ }, `selectionDirection on an input[type=${type}] returns null`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.selectionStart = 0;
+ });
+ }, `assigning selectionStart on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.selectionEnd = 0;
+ });
+ }, `assigning selectionEnd on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.selectionDirection = 'none';
+ });
+ }, `assigning selectionDirection on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.setRangeText("foobar");
+ });
+ }, `setRangeText on an input[type=${type}] throws InvalidStateError`);
+
+ test(() => {
+ assert_throws_dom("InvalidStateError", function(){
+ el.setSelectionRange(0, 1);
+ });
+ }, `setSelectionRange on an input[type=${type}] throws InvalidStateError`);
+ });
+
+ applicableTypes.forEach(function(type) {
+ const el = document.createElement("input");
+ if (type) {
+ el.type = type;
+ }
+ const initialDirection = el.selectionDirection;
+
+ test(() => {
+ assert_equals(el.selectionStart, 0);
+ }, `selectionStart on an input[type=${type}] returns a value`);
+
+ test(() => {
+ assert_equals(el.selectionEnd, 0);
+ }, `selectionEnd on an input[type=${type}] returns a value`);
+
+ test(() => {
+ assert_in_array(el.selectionDirection, ["forward", "none"]);
+ }, `selectionDirection on an input[type=${type}] returns a value`);
+
+ test(() => {
+ el.selectionDirection = "none";
+ assert_equals(el.selectionDirection, initialDirection);
+ }, `assigning selectionDirection "none" on an input[type=${type}] should give the initial direction`);
+
+ test(() => {
+ el.selectionStart = 1;
+ }, `assigning selectionStart on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.selectionEnd = 1;
+ }, `assigning selectionEnd on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.selectionDirection = "forward";
+ }, `assigning selectionDirection on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.setRangeText("foobar");
+ }, `setRangeText on an input[type=${type}] doesn't throw an exception`);
+
+ test(() => {
+ el.setSelectionRange(0, 1);
+ }, `setSelectionRange on an input[type=${type}] doesn't throw an exception`);
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html
new file mode 100644
index 0000000000..c8ba83bb98
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end-extra.html
@@ -0,0 +1,153 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<form id="form"><input id="form-input" type="text" value="abc" /></form>
+<script>
+ // * Should we test setting the dirty flag in any way that isn't
+ // setting the value?
+ // * How to simulate users typing?
+
+ test(function() {
+ var el = document.createElement("textarea");
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ el.defaultValue = "123";
+ assert_equals(el.value.length, 3);
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Setting defaultValue in a textarea should NOT move the cursor to the end");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.value = "abcdef";
+ assert_equals(el.selectionStart, 6);
+ assert_equals(el.selectionEnd, 6);
+ el.defaultValue = "123";
+ assert_equals(el.value.length, 6);
+ assert_equals(el.selectionStart, 6);
+ assert_equals(el.selectionEnd, 6);
+ }, "Setting defaultValue in a textarea with a value should NOT make any difference");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.appendChild(document.createTextNode("abcdef"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ el.textContent = "abcdef123456";
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Setting textContent in a textarea should NOT move selection{Start,End} to the end");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.appendChild(document.createTextNode("abcdef"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ el.appendChild(document.createTextNode("123456"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Adding children to a textarea should NOT move selection{Start,End} to the end");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.appendChild(document.createTextNode("abcdef"));
+ el.appendChild(document.createTextNode("123"));
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+
+ el.removeChild(el.firstChild);
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Removing children from a textarea should NOT update selection{Start,End}");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.textContent = "abcdef\nwhatevs";
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+
+ el.firstChild.data = "abcdef\r\nwhatevs";
+ assert_equals(el.selectionStart, 3);
+ assert_equals(el.selectionEnd, 5);
+ }, "Setting the same value (with different newlines) in a textarea should NOT update selection{Start,End}");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.textContent = "foobar";
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+ el.firstChild.remove();
+ assert_equals(el.selectionStart, 0, 'selectionStart after node removal');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after node removal');
+ el.appendChild(document.createTextNode("foobar"));
+ assert_equals(el.selectionStart, 0, 'selectionStart after appendChild');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after appendChild');
+
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+ el.textContent = "foobar2"; // This removes the child node first.
+ assert_equals(el.selectionStart, 0, 'selectionStart after textContent setter');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after textContent setter');
+
+ el.selectionStart = 3;
+ el.selectionEnd = 5;
+ el.defaultValue = "foobar"; // Same as textContent setter.
+ assert_equals(el.selectionStart, 0, 'selectionStart after defaultValue setter');
+ assert_equals(el.selectionEnd, 0, 'selectionEnd after defaultValue setter');
+
+ }, "Removing child nodes in non-dirty textarea should make selection{Start,End} 0");
+
+ test(function() {
+ var el = document.createElement("textarea");
+ el.defaultValue = "123";
+ assert_equals(el.value.length, 3);
+ el.selectionStart = 3;
+ el.selectionEnd = 3;
+ el.value = "12";
+ assert_equals(el.value.length, 2);
+ assert_equals(el.selectionStart, 2);
+ assert_equals(el.selectionEnd, 2);
+ }, "Setting value to a shorter string than defaultValue should correct the cursor position");
+
+ test(function() {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = "http://example.com ";
+ assert_equals(el.selectionStart, 21);
+ assert_equals(el.selectionEnd, 21);
+ el.type = "url";
+ assert_equals(el.selectionStart, 18);
+ assert_equals(el.selectionEnd, 18);
+ }, "Shortening value by turning the input type into 'url' should correct selection{Start,End}");
+
+ test(function() {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = "#123456xx";
+ assert_equals(el.selectionStart, 9);
+ assert_equals(el.selectionEnd, 9);
+ el.type = "color";
+ el.type = "text";
+ // https://html.spec.whatwg.org/C/input.html#the-input-element:attr-input-type-15
+ // 9. If previouslySelectable is false and nowSelectable is true, set the
+ // element's text entry cursor position to the beginning of the text
+ // control, ...
+ assert_equals(el.selectionStart, 0);
+ assert_equals(el.selectionEnd, 0);
+ }, "Shortening value by turning the input type into 'color' and back to 'text' should correct selection{Start,End}");
+
+ test(function() {
+ var form = document.getElementById("form");
+ var el = document.getElementById("form-input");
+
+ el.value = "abcde";
+ assert_equals(el.value.length, 5);
+ form.reset();
+ assert_equals(el.value.length, 3);
+ assert_equals(el.selectionStart, 3);
+ assert_equals(el.selectionEnd, 3);
+ }, "Resetting a value to a shorter string than defaultValue should correct the cursor position");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html
new file mode 100644
index 0000000000..768bce4129
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-start-end.html
@@ -0,0 +1,206 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+ function createInputElement(value, append, suffix) {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = value;
+ el.id = "input" + (append ? "-appended" : "-not-appended") + (suffix ? suffix : "");
+ if (append) {
+ document.body.appendChild(el);
+ }
+ return el;
+ };
+
+ function createTextareaElement(value, append, suffix) {
+ var el = document.createElement("textarea");
+ el.value = value;
+
+ el.id = "textarea" + (append ? "-appended" : "-not-appended") + (suffix ? suffix : "");
+ if (append) {
+ document.body.appendChild(el);
+ }
+ return el;
+ };
+
+ function createPrefocusedInputElement(value, append) {
+ var el = createInputElement(value, append, "-prefocused");
+ el.focus();
+ el.blur();
+ return el;
+ }
+
+ function createPrefocusedTextareaElement(value, append) {
+ var el = createTextareaElement(value, append, "-prefocused");
+ el.focus();
+ el.blur();
+ return el;
+ }
+
+ function createClonedTextareaWithNoDirtyValueFlag(text) {
+ const textarea = document.createElement("textarea");
+ textarea.textContent = text;
+ return textarea.cloneNode(true);
+ }
+
+ function createTestElements(value) {
+ return [ createInputElement(value, true),
+ createInputElement(value, false),
+ createPrefocusedInputElement(value, true),
+ createPrefocusedInputElement(value, false),
+ createTextareaElement(value, true),
+ createTextareaElement(value, false),
+ createPrefocusedTextareaElement(value, true),
+ createPrefocusedTextareaElement(value, false),
+ createClonedTextareaWithNoDirtyValueFlag(value)
+ ];
+ }
+
+ var testValue = "abcdefghij";
+
+ test(function() {
+ assert_equals(testValue.length, 10);
+ }, "Sanity check for testValue length; if this fails, variou absolute offsets in the test below need to be adjusted to be less than testValue.length");
+
+ for (let prop of ["selectionStart", "selectionEnd"]) {
+ for (let el of createTestElements(testValue)) {
+ if (el.defaultValue !== el.value) {
+ test(function() {
+ assert_equals(el[prop], testValue.length);
+ }, `Initial .value set on ${el.id} should set ${prop} to end of value`);
+ }
+ }
+ }
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ var t = async_test(`onselect should fire when selectionStart is changed on ${el.id}`);
+ el.onselect = t.step_func_done(function(e) {
+ assert_equals(e.type, "select");
+ el.remove();
+ });
+ el.selectionStart = 2;
+ }
+ }, "onselect should fire when selectionStart is changed");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ var t = async_test(`onselect should fire when selectionEnd is changed on ${el.id}`);
+ el.onselect = t.step_func_done(function(e) {
+ assert_equals(e.type, "select");
+ el.remove();
+ });
+ el.selectionEnd = 2;
+ }
+ }, "onselect should fire when selectionEnd is changed");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionStart = 0;
+ el.selectionEnd = 5;
+ el.selectionStart = 8;
+ assert_equals(el.selectionStart, 8, `selectionStart on ${el.id}`);
+ assert_equals(el.selectionEnd, 8, `selectionEnd on ${el.id}`);
+ el.remove();
+ }
+ }, "Setting selectionStart to a value larger than selectionEnd should increase selectionEnd");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionStart = 8;
+ el.selectionEnd = 5;
+ assert_equals(el.selectionStart, 5, `selectionStart on ${el.id}`);
+ assert_equals(el.selectionEnd, 5, `selectionEnd on ${el.id}`);
+ el.remove();
+ }
+ }, "Setting selectionEnd to a value smaller than selectionStart should decrease selectionStart");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionStart = 0;
+ assert_equals(el.selectionStart, 0, `We just set it on ${el.id}`);
+ el.selectionStart = -1;
+ assert_equals(el.selectionStart, testValue.length,
+ `selectionStart setter on ${el.id} should convert -1 to 2^32-1`);
+ el.selectionStart = Math.pow(2, 32);
+ assert_equals(el.selectionStart, 0,
+ `selectionStart setter on ${el.id} should convert 2^32 to 0`);
+ el.selectionStart = Math.pow(2, 32) - 1;
+ assert_equals(el.selectionStart, testValue.length,
+ `selectionStart setter on ${el.id} should leave 2^32-1 as-is`);
+ el.remove();
+ }
+ }, "selectionStart edge-case values");
+
+ test(function() {
+ for (let el of createTestElements(testValue)) {
+ el.selectionEnd = 0;
+ assert_equals(el.selectionEnd, 0, `We just set it on ${el.id}`);
+ el.selectionEnd = -1;
+ assert_equals(el.selectionEnd, testValue.length,
+ `selectionEnd setter on ${el.id} should convert -1 to 2^32-1`);
+ el.selectionEnd = Math.pow(2, 32);
+ assert_equals(el.selectionEnd, 0,
+ `selectionEnd setter on ${el.id} should convert 2^32 to 0`);
+ el.selectionEnd = Math.pow(2, 32) - 1;
+ assert_equals(el.selectionEnd, testValue.length,
+ `selectionEnd setter on ${el.id} should leave 2^32-1 as-is`);
+ el.remove();
+ }
+ }, "selectionEnd edge-case values");
+
+ test(() => {
+ for (const el of createTestElements(testValue)) {
+ el.selectionStart = 200;
+ assert_equals(el.selectionStart, testValue.length);
+ el.remove();
+ }
+ }, "selectionStart should be clamped by the current value length");
+
+ test(() => {
+ for (const el of createTestElements(testValue)) {
+ el.selectionStart = 300;
+ assert_equals(el.selectionEnd, testValue.length);
+ el.remove();
+ }
+ }, "selectionEnd should be clamped by the current value length");
+
+ test(() => {
+ for (const el of createTestElements(testValue)) {
+ el.setSelectionRange(200, 300);
+ assert_equals(el.selectionStart, testValue.length);
+ assert_equals(el.selectionEnd, testValue.length);
+ el.remove();
+ }
+ }, "setSelectionRange should be clamped by the current value length");
+
+ test(() => {
+ for (let el of createTestElements(testValue)) {
+ const start = 1;
+ const end = testValue.length - 1;
+
+ el.setSelectionRange(start, end);
+
+ assert_equals(el.selectionStart, start, `selectionStart on ${el.id}`);
+ assert_equals(el.selectionEnd, end, `selectionEnd on ${el.id}`);
+
+ el.selectionDirection = "backward";
+
+ assert_equals(el.selectionStart, start,
+ `selectionStart on ${el.id} after setting selectionDirection to "backward"`);
+ assert_equals(el.selectionEnd, end,
+ `selectionEnd on ${el.id} after setting selectionDirection to "backward"`);
+
+ el.selectionDirection = "forward";
+
+ assert_equals(el.selectionStart, start,
+ `selectionStart on ${el.id} after setting selectionDirection to "forward"`);
+ assert_equals(el.selectionEnd, end,
+ `selectionEnd on ${el.id} after setting selectionDirection to "forward"`);
+ }
+ }, "selectionStart and selectionEnd should remain the same when selectionDirection is changed");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
new file mode 100644
index 0000000000..0fd8a5b182
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
@@ -0,0 +1,127 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=target></div>
+<script>
+ var target = document.getElementById("target");
+ var sometext = "something";
+ var shorttext = "abc";
+ var elemData = [
+ {
+ desc: "textarea not in body",
+ factory: () => document.createElement("textarea"),
+ },
+ {
+ desc: "input not in body",
+ factory: () => document.createElement("input"),
+ },
+ {
+ desc: "textarea in body",
+ factory: () => document.body.appendChild(document.createElement("textarea")),
+ },
+ {
+ desc: "input in body",
+ factory: () => document.body.appendChild(document.createElement("input")),
+ },
+ {
+ desc: "textarea in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<textarea>abcdefghij</textarea>"
+ return target.querySelector("textarea");
+ },
+ },
+ {
+ desc: "input in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<input value='abcdefghij'>"
+ return target.querySelector("input");
+ },
+ },
+ {
+ desc: "focused textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ return t;
+ },
+ },
+ {
+ desc: "focused input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ return i;
+ },
+ },
+ {
+ desc: "focused then blurred textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ t.blur();
+ return t;
+ },
+ },
+ {
+ desc: "focused then blurred input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ i.blur()
+ return i;
+ },
+ },
+ ];
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.defaultValue = sometext;
+ assert_true(sometext.length > 8,
+ "sometext too short, test won't work right");
+ el.selectionStart = 4;
+ el.selectionEnd = 6;
+ el.setRangeText("xyz");
+ el.defaultValue = "set range text";
+ assert_equals(el.value, sometext.slice(0, 4) + "xyz" + sometext.slice(6),
+ "Calling setRangeText should set the value dirty flag");
+ }, `value dirty flag behavior after setRangeText on ${data.desc}`);
+}
+
+for (var tag of ['input', 'textarea']) {
+ test(function() {
+ var el = document.createElement(tag);
+ document.body.appendChild(el);
+ this.add_cleanup(() => el.remove());
+
+ for (let val of ["", "foo", "foobar"]) {
+ el.value = val;
+ assert_equals(el.selectionStart, val.length,
+ "element.selectionStart should be value.length");
+ assert_equals(el.selectionEnd, val.length,
+ "element.selectionEnd should be value.length");
+ }
+ }, `selection is collapsed to the end after changing values on ${tag}`);
+
+ test(function() {
+ var el = document.createElement(tag);
+ document.body.appendChild(el);
+ this.add_cleanup(() => el.remove());
+
+ el.value = "foobar"
+ el.selectionStart = 2
+ el.selectionEnd = 4
+ el.value = "foobar"
+
+ assert_equals(el.selectionStart, 2,
+ "element.selectionStart should be unchanged");
+ assert_equals(el.selectionEnd, 4,
+ "element.selectionEnd should be unchanged");
+ }, `selection is not collapsed to the end when value is set to its existing value on ${tag}`);
+}
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html
new file mode 100644
index 0000000000..04ab7298e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/selection.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML>
+<title>test if select() API returns correct attributes</title>
+<meta charset="UTF-8">
+<meta name="timeout" content="long">
+<link rel="author" title="Koji Tashiro" href="mailto:koji.tashiro@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/association-of-controls-and-forms.html#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+ var body = document.getElementsByTagName("body").item(0);
+ var dirs = ['forward', 'backward', 'none'];
+ var sampleText = "0123456789";
+
+ var createInputElement = function(value, append = true) {
+ var el = document.createElement("input");
+ el.type = "text";
+ el.value = value;
+ el.id = "input" + (append ? "-appended" : "-not-appended");
+ if (append) {
+ body.appendChild(el);
+ }
+ return el;
+ };
+
+ var createTextareaElement = function(value, append = true) {
+ var el = document.createElement("textarea");
+ el.value = value;
+ el.id = "textarea" + (append ? "-appended" : "-not-appended");
+ if (append) {
+ body.appendChild(el);
+ }
+ return el;
+ };
+
+
+ test(function() {
+ var text = 'a';
+ for (var i=0; i<255; i++) {
+ var el = createInputElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'a';
+ }
+ }, "test if selection text is correct for input");
+
+
+ test(function() {
+ var text = 'a';
+ for (var i=0; i<255; i++) {
+ var el = createTextareaElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'a';
+ }
+ }, "test if selection text is correct for textarea");
+
+
+ test(function() {
+ var text = 'あ';
+ for (var i=0; i<255; i++) {
+ var el = createInputElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'あ';
+ }
+ }, "test if non-ascii selection text is correct for input");
+
+
+ test(function() {
+ var text = 'あ';
+ for (var i=0; i<255; i++) {
+ var el = createTextareaElement(text);
+ el.select();
+ var selectionText = el.value.substring(el.selectionStart, el.selectionEnd);
+ assert_equals(selectionText, text, "Selection text mismatched");
+ el.parentNode.removeChild(el);
+ text += 'あ';
+ }
+ }, "test if non-ascii selection text is correct for textarea");
+
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createInputElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionStart, el.value.length,
+ "SelectionStart offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionStart, 0, "SelectionStart offset");
+ el.remove();
+ }, "test SelectionStart offset for input that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createTextareaElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionStart, el.value.length,
+ "SelectionStart offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionStart, 0, "SelectionStart offset");
+ el.remove();
+ }, "test SelectionStart offset for textarea that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createInputElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionEnd, el.value.length,
+ "SelectionEnd offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionEnd, el.value.length, "SelectionEnd offset");
+ el.remove();
+ }, "test SelectionEnd offset for input that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+
+ for (var append of [true, false]) {
+ test(function() {
+ var el = createTextareaElement(sampleText, append);
+ // If there is no selection, then it must return the offset(in logical order)
+ // to the character that immediately follows the text entry cursor.
+ assert_equals(el.selectionEnd, el.value.length,
+ "SelectionEnd offset without selection in " + el.id);
+ el.select();
+ assert_equals(el.selectionEnd, el.value.length, "SelectionEnd offset");
+ el.remove();
+ }, "test SelectionEnd offset for textarea that is " +
+ (append ? "appended" : " not appended"));
+ }
+
+ test(function() {
+ var el = createInputElement(sampleText);
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.select();
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.parentNode.removeChild(el);
+ }, "test SelectionDirection for input");
+
+
+ test(function() {
+ var el = createTextareaElement(sampleText);
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.select();
+ assert_in_array(el.selectionDirection, dirs, "SelectionDirection");
+ el.parentNode.removeChild(el);
+ }, "test SelectionDirection for textarea");
+
+ promise_test(async () => {
+ // cause a layout overflow
+ const el = createInputElement(sampleText.repeat(100));
+ el.selectionEnd = 0;
+ await new Promise(requestAnimationFrame);
+ assert_equals(el.scrollLeft, 0);
+
+ el.select();
+ await new Promise(requestAnimationFrame);
+ assert_equals(el.scrollLeft, 0);
+ el.remove();
+ }, `test scrollLeft for input`);
+
+ promise_test(async () => {
+ // cause a layout overflow
+ const el = createInputElement(sampleText.repeat(100));
+ el.scrollLeft = 33;
+
+ el.select();
+ await new Promise(requestAnimationFrame);
+ assert_equals(el.scrollLeft, 33);
+ el.remove();
+ }, `test scrollLeft preservation for input`);
+
+ for (const localName of ["input", "textarea"]) {
+ promise_test(async () => {
+ const container = document.createElement("div");
+ container.style.height = "100px";
+ container.style.overflow = "scroll";
+ const element = document.createElement(localName);
+ element.style.marginTop = "120px";
+ container.append(element);
+ document.body.append(container);
+
+ element.select();
+ await new Promise(requestAnimationFrame);
+ assert_equals(container.scrollTop, 0);
+
+ container.remove();
+ }, `test container.scrollTop for ${localName}`);
+ }
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html
new file mode 100644
index 0000000000..bdf52a77f8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/setSelectionRange.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>
+
+</textarea>
+<script>
+test(function() {
+ let textarea = document.querySelector('textarea');
+ assert_equals(textarea.selectionStart, 0);
+ assert_equals(textarea.selectionEnd, 0);
+ textarea.setSelectionRange(0, 1);
+ assert_equals(textarea.selectionStart, 0);
+ assert_equals(textarea.selectionEnd, 1);
+}, "setSelectionRange on line boundaries");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml
new file mode 100644
index 0000000000..57326bb4a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textarea-selection-while-parsing.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>
+a
+<script>document.querySelector('textarea').value = 'ggg';</script>
+b
+</textarea>
+<script>
+test(() => {
+ let ta = document.querySelector('textarea');
+ assert_equals(ta.selectionStart, 3);
+ assert_equals(ta.selectionEnd, 3);
+}, 'Value setter while parsing textarea children should move ' +
+ 'selection{Start,End} to the end');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html
new file mode 100644
index 0000000000..b435fe7881
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setRangeText.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>text field selection: setRangeText</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #display_none {display:none;}
+</style>
+<div id="log"></div>
+<input type=text id=text value="foobar">
+<input type=search id=search value="foobar">
+<input type=tel id=tel value="foobar">
+<input type=url id=url value="foobar">
+<input type=password id=password value="foobar">
+<input id=display_none value="foobar">
+<textarea id=textarea>foobar</textarea>
+<script>
+ var input = document.createElement("input");
+ input.id = "input_not_in_doc";
+ input.value = "foobar";
+
+ var elements = [
+ document.getElementById("text"),
+ document.getElementById("search"),
+ document.getElementById("tel"),
+ document.getElementById("url"),
+ document.getElementById("password"),
+ document.getElementById("display_none"),
+ document.getElementById("textarea"),
+ input,
+ ]
+
+ function untilEvent(element, eventName) {
+ return new Promise((resolve) => {
+ element.addEventListener(eventName, resolve, { once: true });
+ });
+ }
+
+ elements.forEach(function(element) {
+ test(function() {
+ element.value = "foobar";
+ element.selectionStart = 0;
+ element.selectionEnd = 3;
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 3);
+ element.setRangeText("foobar2");
+ assert_equals(element.value, "foobar2bar");
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 7);
+ element.setRangeText("foobar3", 7, 10);
+ assert_equals(element.value, "foobar2foobar3");
+ }, element.id + " setRangeText with only one argument replaces the value between selectionStart and selectionEnd, otherwise replaces the value between 2nd and 3rd arguments");
+
+ test(function(){
+ element.value = "foobar";
+ element.selectionStart = 0;
+ element.selectionEnd = 0;
+
+ element.setRangeText("foobar2", 0, 3); // no 4th arg, default "preserve"
+ assert_equals(element.value, "foobar2bar");
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 0);
+ }, element.id + " selectionMode missing");
+
+ test(function(){
+ element.value = "foobar"
+ element.setRangeText("foo", 3, 6, "select");
+ assert_equals(element.value, "foofoo");
+ assert_equals(element.selectionStart, 3);
+ assert_equals(element.selectionEnd, 6);
+ }, element.id + " selectionMode 'select'");
+
+ test(function(){
+ element.value = "foobar"
+ element.setRangeText("foo", 3, 6, "start");
+ assert_equals(element.value, "foofoo");
+ assert_equals(element.selectionStart, 3);
+ assert_equals(element.selectionEnd, 3);
+ }, element.id + " selectionMode 'start'");
+
+ test(function(){
+ element.value = "foobar"
+ element.setRangeText("foobar", 3, 6, "end");
+ assert_equals(element.value, "foofoobar");
+ assert_equals(element.selectionStart, 9);
+ assert_equals(element.selectionEnd, 9);
+ }, element.id + " selectionMode 'end'");
+
+ test(function(){
+ element.value = "foobar"
+ element.selectionStart = 0;
+ element.selectionEnd = 5;
+ assert_equals(element.selectionStart, 0);
+ element.setRangeText("", 3, 6, "preserve");
+ assert_equals(element.value, "foo");
+ assert_equals(element.selectionStart, 0);
+ assert_equals(element.selectionEnd, 3);
+ }, element.id + " selectionMode 'preserve'");
+
+ test(function(){
+ assert_throws_dom("INDEX_SIZE_ERR", function() {
+ element.setRangeText("barfoo", 2, 1);
+ });
+ }, element.id + " setRangeText with 3rd argument greater than 2nd argument throws an IndexSizeError exception");
+
+ test(function(){
+ assert_throws_js(TypeError, function() {
+ element.setRangeText();
+ });
+ }, element.id + " setRangeText without argument throws a type error");
+
+ promise_test(async (t) => {
+ // At this point there are already "select" events queued up on
+ // "element". Give them time to fire; otherwise we can get spurious
+ // passes.
+ //
+ // This is unfortunately racy in that we might _still_ get spurious
+ // passes. I'm not sure how best to handle that.
+ t.step_timeout(function() {
+ var q = false;
+ element.onselect = t.step_func_done(function(e) {
+ assert_true(q, "event should be queued");
+ assert_true(e.isTrusted, "event is trusted");
+ assert_true(e.bubbles, "event bubbles");
+ assert_false(e.cancelable, "event is not cancelable");
+ });
+ element.setRangeText("foobar2", 0, 6);
+ q = true;
+ }, 10);
+ }, element.id + " setRangeText fires a select event");
+
+ promise_test(async () => {
+ element.value = "XXXXXXXXXXXXXXXXXXX";
+ const { length } = element.value;
+ element.setSelectionRange(0, length);
+ await untilEvent(element, "select");
+ element.setRangeText("foo", 2, 2);
+ await untilEvent(element, "select");
+ assert_equals(element.selectionStart, 0, ".selectionStart");
+ assert_equals(element.selectionEnd, length + 3, ".selectionEnd");
+ }, element.id + " setRangeText fires a select event when fully selected");
+
+ promise_test(async () => {
+ element.value = "XXXXXXXXXXXXXXXXXXX";
+ element.select();
+ await untilEvent(element, "select");
+ element.setRangeText("foo", 2, 2);
+ await untilEvent(element, "select");
+ assert_equals(element.selectionStart, 0, ".selectionStart");
+ assert_equals(element.selectionEnd, element.value.length, ".selectionEnd");
+ }, element.id + " setRangeText fires a select event after select()");
+ })
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
new file mode 100644
index 0000000000..3aba6b7adb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html
@@ -0,0 +1,304 @@
+<!DOCTYPE HTML>
+<title>Test of text field setSelectionRange</title>
+<link rel="author" title="Takeharu.Oshida" href="mailto:georgeosddev@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="hide" style="display: block">
+ <input id="a" type="text" value="abcde">
+ <textarea id="b">abcde</textarea>
+</div>
+<script>
+var expected_direction_none;
+setup(function() {
+ var input = document.createElement("input");
+ input.setSelectionRange(0, 1, "none");
+ var direction = input.selectionDirection;
+ if (direction !== "none" && direction !== "forward") {
+ throw new Error("Unexpected direction");
+ }
+ expected_direction_none = direction;
+});
+
+test(function() {
+ var input = document.getElementById("a");
+ test(function() {
+ assert_equals(typeof(input.setSelectionRange), "function", "element must have 'setSelectionRange' function");
+ },"input typeof(input.setSelectionRange)'");
+
+ test(function() {
+ assert_equals(input.setSelectionRange(0,1,"forward"),undefined,"setSelectionRange is void functuon");
+ },"input setSelectionRange return void");
+
+ test(function() {
+ input.setSelectionRange(0,1)
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(0,1)');
+
+ test(function() {
+ input.setSelectionRange(0,input.value.length+1)
+ assert_equals(input.selectionEnd, input.value.length, "Arguments greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ },'input setSelectionRange(0,input.value.length+1)');
+
+ test(function() {
+ input.setSelectionRange(input.value.length+1,input.value.length+1)
+ assert_equals(input.selectionStart, input.value.length, "Arguments (start) greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ assert_equals(input.selectionEnd, input.value.length, "Arguments (end) greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ },'input setSelectionRange(input.value.length+1,input.value.length+1)');
+
+ test(function() {
+ input.setSelectionRange(input.value.length+1,1)
+ assert_equals(input.selectionStart, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(input.value.length+1,1)');
+
+ test(function() {
+ input.setSelectionRange(2,2)
+ assert_equals(input.selectionStart, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'input setSelectionRange(2,2)');
+
+ test(function() {
+ input.setSelectionRange(2,1)
+ assert_equals(input.selectionStart, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'input setSelectionRange(2,1)');
+
+ test(function() {
+ input.setSelectionRange(0,1,"backward")
+ assert_equals(input.selectionDirection, "backward", 'The direction of the selection must be set to backward if direction is a case-sensitive match for the string "backward"');
+ },'input direction of setSelectionRange(0,1,"backward")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"forward")
+ assert_equals(input.selectionDirection, "forward", 'The direction of the selection must be set to forward if direction is a case-sensitive match for the string "forward"');
+ },'input direction of setSelectionRange(0,1,"forward")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"none")
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1,"none")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"hoge")
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1,"hoge")');
+
+ test(function() {
+ input.setSelectionRange(0,1,"BACKWARD")
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1,"BACKWARD")');
+
+ test(function() {
+ input.setSelectionRange(0,1)
+ assert_equals(input.selectionDirection, expected_direction_none);
+ },'input direction of setSelectionRange(0,1)');
+
+ test(function() {
+ input.setSelectionRange(1,-1);
+ assert_equals(input.selectionStart, 1, "element.selectionStart should be 1");
+ assert_equals(input.selectionEnd, input.value.length, "ECMAScript conversion to unsigned long");
+ },'input setSelectionRange(1,-1)');
+
+ test(function() {
+ input.setSelectionRange(-1,1);
+ assert_equals(input.selectionStart, 1, "ECMAScript conversion to unsigned long + if end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(-1,1)');
+
+ test(function() {
+ input.setSelectionRange("string",1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange("string",1)');
+
+ test(function() {
+ input.setSelectionRange(true,1);
+ assert_equals(input.selectionStart, 1, "element.selectionStart should be 1");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(true,1)');
+
+ test(function() {
+ input.setSelectionRange([],1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange([],1)');
+
+ test(function() {
+ input.setSelectionRange({},1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange({},1)');
+
+ test(function() {
+ input.setSelectionRange(NaN,1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(NaN,1)');
+
+ test(function() {
+ input.setSelectionRange(null,1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(null,1)');
+
+ test(function() {
+ input.setSelectionRange(undefined,1);
+ assert_equals(input.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(input.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'input setSelectionRange(undefined,1)');
+
+ test(function() {
+ input.setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1);
+ assert_equals(input.selectionStart, input.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(input.selectionEnd, input.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'input setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1)');
+
+ test(function() {
+ input.setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1);
+ assert_equals(input.selectionStart, input.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(input.selectionEnd, input.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'input setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1)');
+},"test of input.setSelectionRange");
+
+async_test(function() {
+ var q = false;
+ var input = document.getElementById("a");
+ input.addEventListener("select", this.step_func_done(function(e) {
+ assert_true(q, "event should be queued");
+ assert_true(e.isTrusted, "event is trusted");
+ assert_true(e.bubbles, "event bubbles");
+ assert_false(e.cancelable, "event is not cancelable");
+ }));
+ input.setSelectionRange(0, 1);
+ q = true;
+}, "input setSelectionRange fires a select event");
+
+test(function() {
+ var textarea = document.getElementById("b");
+ test(function() {
+ assert_equals(typeof(textarea.setSelectionRange), "function", "element must have 'setSelectionRange' function");
+ },"textarea typeof(input.setSelectionRange)'");
+
+ test(function() {
+ assert_equals(textarea.setSelectionRange(0,1,"forward"),undefined,"setSelectionRange is void functuon");
+ },"textarea setSelectionRange return void");
+
+ test(function() {
+ textarea.setSelectionRange(0,1)
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionEnd should be 1");
+ },'textarea setSelectionRange(0,1)');
+
+ test(function() {
+ textarea.setSelectionRange(0,textarea.value.length+1)
+ assert_equals(textarea.selectionEnd, textarea.value.length, "Arguments greater than the length of the value of the text field must be treated as pointing at the end of the text field");
+ },'textarea setSelectionRange(0,textarea.value.length+1)');
+
+ test(function() {
+ textarea.setSelectionRange(2,2)
+ assert_equals(textarea.selectionStart, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(textarea.selectionEnd, 2, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'textarea setSelectionRange(2,2)');
+
+ test(function() {
+ textarea.setSelectionRange(2,1)
+ assert_equals(textarea.selectionStart, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ assert_equals(textarea.selectionEnd, 1, "If end is less than or equal to start then the start of the selection and the end of the selection must both be placed immediately before the character with offset end");
+ },'textarea setSelectionRange(2,1)');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"backward")
+ assert_equals(textarea.selectionDirection, "backward", 'The direction of the selection must be set to backward if direction is a case-sensitive match for the string "backward"');
+ },'textarea direction of setSelectionRange(0,1,"backward")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"forward")
+ assert_equals(textarea.selectionDirection, "forward", 'The direction of the selection must be set to forward if direction is a case-sensitive match for the string "forward"');
+ },'textarea direction of setSelectionRange(0,1,"forward")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"none")
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1,"none")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"hoge")
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1,"hoge")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1,"BACKWARD")
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1,"BACKWARD")');
+
+ test(function() {
+ textarea.setSelectionRange(0,1)
+ assert_equals(textarea.selectionDirection, expected_direction_none);
+ },'textarea direction of setSelectionRange(0,1)');
+
+ test(function() {
+ textarea.setSelectionRange("string",1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange("string",1)');
+
+ test(function() {
+ textarea.setSelectionRange(true,1);
+ assert_equals(textarea.selectionStart, 1, "element.selectionStart should be 1");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(true,1)');
+
+ test(function() {
+ textarea.setSelectionRange([],1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange([],1)');
+
+ test(function() {
+ textarea.setSelectionRange({},1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange({},1)');
+
+ test(function() {
+ textarea.setSelectionRange(NaN,1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(NaN,1)');
+
+ test(function() {
+ textarea.setSelectionRange(null,1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(null,1)');
+
+ test(function() {
+ textarea.setSelectionRange(undefined,1);
+ assert_equals(textarea.selectionStart, 0, "element.selectionStart should be 0");
+ assert_equals(textarea.selectionEnd, 1, "element.selectionStart should be 1");
+ },'textarea setSelectionRange(undefined,1)');
+
+ test(function() {
+ textarea.setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1);
+ assert_equals(textarea.selectionStart, textarea.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(textarea.selectionEnd, textarea.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'textarea setSelectionRange(Math.pow(2,32) - 2, Math.pow(2,32) - 1)');
+
+ test(function() {
+ textarea.setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1);
+ assert_equals(textarea.selectionStart, textarea.value.length,
+ "element.selectionStart should be value.length");
+ assert_equals(textarea.selectionEnd, textarea.value.length,
+ "element.selectionEnd should be value.length");
+ }, 'textarea setSelectionRange(Math.pow(2,31), Math.pow(2,32) - 1)');
+},"test of textarea.setSelectionRange");
+</script>