Schedule/fire an event when selection changes for select() and setRangeText().
https://bugs.webkit.org/show_bug.cgi?id=241366

Reviewed by Darin Adler.

As per step 6 at [1], if either extent or direction of the text control to be modified,
we need to queue an element task on the user interaction task source given the element
to fire an select event with the bubbles attribute initialized to true. We addressed
some cases in Bug 238142. This is to fix the missing cases.

This patch also introduces WPT test select-event.html back to WebKit. For some reason, it
was removed in a previous resync patch.

[1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-range

* LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event.html: Added.
* LayoutTests/platform/gtk/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt
* Source/WebCore/html/HTMLTextFormControlElement.cpp:
(WebCore::HTMLTextFormControlElement::select):
(WebCore::HTMLTextFormControlElement::setRangeText):

Canonical link: https://commits.webkit.org/251716@main


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@295711 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt
new file mode 100644
index 0000000..c169a3e
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt
@@ -0,0 +1,272 @@
+
+PASS textarea: select()
+PASS textarea: select() a second time (must not fire select)
+PASS textarea: select() disconnected node
+PASS textarea: select() event queue
+FAIL textarea: select() twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionStart
+PASS textarea: selectionStart a second time (must not fire select)
+PASS textarea: selectionStart disconnected node
+PASS textarea: selectionStart event queue
+FAIL textarea: selectionStart twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionEnd
+PASS textarea: selectionEnd a second time (must not fire select)
+PASS textarea: selectionEnd disconnected node
+PASS textarea: selectionEnd event queue
+FAIL textarea: selectionEnd twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionDirection
+FAIL textarea: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS textarea: selectionDirection disconnected node
+PASS textarea: selectionDirection event queue
+FAIL textarea: selectionDirection twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: setSelectionRange()
+PASS textarea: setSelectionRange() a second time (must not fire select)
+PASS textarea: setSelectionRange() disconnected node
+PASS textarea: setSelectionRange() event queue
+FAIL textarea: setSelectionRange() twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: setRangeText()
+PASS textarea: setRangeText() a second time (must not fire select)
+PASS textarea: setRangeText() disconnected node
+PASS textarea: setRangeText() event queue
+FAIL textarea: setRangeText() twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 15
+PASS textarea: selectionStart out of range
+PASS textarea: selectionStart out of range a second time (must not fire select)
+FAIL textarea: selectionStart out of range disconnected node assert_true: event didn't fire expected true got false
+FAIL textarea: selectionStart out of range event queue assert_true: event didn't fire expected true got false
+FAIL textarea: selectionStart out of range twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionEnd out of range
+PASS textarea: selectionEnd out of range a second time (must not fire select)
+FAIL textarea: selectionEnd out of range disconnected node assert_true: event didn't fire expected true got false
+FAIL textarea: selectionEnd out of range event queue assert_true: event didn't fire expected true got false
+FAIL textarea: selectionEnd out of range twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: setSelectionRange out of range
+PASS textarea: setSelectionRange out of range a second time (must not fire select)
+FAIL textarea: setSelectionRange out of range disconnected node assert_true: event didn't fire expected true got false
+FAIL textarea: setSelectionRange out of range event queue assert_true: event didn't fire expected true got false
+FAIL textarea: setSelectionRange out of range twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS input type text: select()
+PASS input type text: select() a second time (must not fire select)
+PASS input type text: select() disconnected node
+PASS input type text: select() event queue
+PASS input type text: select() twice in disconnected node (must fire select only once)
+PASS input type text: selectionStart
+PASS input type text: selectionStart a second time (must not fire select)
+PASS input type text: selectionStart disconnected node
+PASS input type text: selectionStart event queue
+PASS input type text: selectionStart twice in disconnected node (must fire select only once)
+PASS input type text: selectionEnd
+PASS input type text: selectionEnd a second time (must not fire select)
+PASS input type text: selectionEnd disconnected node
+PASS input type text: selectionEnd event queue
+PASS input type text: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type text: selectionDirection
+FAIL input type text: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type text: selectionDirection disconnected node
+PASS input type text: selectionDirection event queue
+PASS input type text: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type text: setSelectionRange()
+PASS input type text: setSelectionRange() a second time (must not fire select)
+PASS input type text: setSelectionRange() disconnected node
+PASS input type text: setSelectionRange() event queue
+PASS input type text: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type text: setRangeText()
+PASS input type text: setRangeText() a second time (must not fire select)
+PASS input type text: setRangeText() disconnected node
+PASS input type text: setRangeText() event queue
+PASS input type text: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type text: selectionStart out of range
+PASS input type text: selectionStart out of range a second time (must not fire select)
+PASS input type text: selectionStart out of range disconnected node
+PASS input type text: selectionStart out of range event queue
+PASS input type text: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type text: selectionEnd out of range
+PASS input type text: selectionEnd out of range a second time (must not fire select)
+PASS input type text: selectionEnd out of range disconnected node
+PASS input type text: selectionEnd out of range event queue
+PASS input type text: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type text: setSelectionRange out of range
+PASS input type text: setSelectionRange out of range a second time (must not fire select)
+PASS input type text: setSelectionRange out of range disconnected node
+PASS input type text: setSelectionRange out of range event queue
+PASS input type text: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type search: select()
+PASS input type search: select() a second time (must not fire select)
+PASS input type search: select() disconnected node
+PASS input type search: select() event queue
+PASS input type search: select() twice in disconnected node (must fire select only once)
+PASS input type search: selectionStart
+PASS input type search: selectionStart a second time (must not fire select)
+PASS input type search: selectionStart disconnected node
+PASS input type search: selectionStart event queue
+PASS input type search: selectionStart twice in disconnected node (must fire select only once)
+PASS input type search: selectionEnd
+PASS input type search: selectionEnd a second time (must not fire select)
+PASS input type search: selectionEnd disconnected node
+PASS input type search: selectionEnd event queue
+PASS input type search: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type search: selectionDirection
+FAIL input type search: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type search: selectionDirection disconnected node
+PASS input type search: selectionDirection event queue
+PASS input type search: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type search: setSelectionRange()
+PASS input type search: setSelectionRange() a second time (must not fire select)
+PASS input type search: setSelectionRange() disconnected node
+PASS input type search: setSelectionRange() event queue
+PASS input type search: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type search: setRangeText()
+PASS input type search: setRangeText() a second time (must not fire select)
+PASS input type search: setRangeText() disconnected node
+PASS input type search: setRangeText() event queue
+PASS input type search: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type search: selectionStart out of range
+PASS input type search: selectionStart out of range a second time (must not fire select)
+PASS input type search: selectionStart out of range disconnected node
+PASS input type search: selectionStart out of range event queue
+PASS input type search: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type search: selectionEnd out of range
+PASS input type search: selectionEnd out of range a second time (must not fire select)
+PASS input type search: selectionEnd out of range disconnected node
+PASS input type search: selectionEnd out of range event queue
+PASS input type search: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type search: setSelectionRange out of range
+PASS input type search: setSelectionRange out of range a second time (must not fire select)
+PASS input type search: setSelectionRange out of range disconnected node
+PASS input type search: setSelectionRange out of range event queue
+PASS input type search: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type tel: select()
+PASS input type tel: select() a second time (must not fire select)
+PASS input type tel: select() disconnected node
+PASS input type tel: select() event queue
+PASS input type tel: select() twice in disconnected node (must fire select only once)
+PASS input type tel: selectionStart
+PASS input type tel: selectionStart a second time (must not fire select)
+PASS input type tel: selectionStart disconnected node
+PASS input type tel: selectionStart event queue
+PASS input type tel: selectionStart twice in disconnected node (must fire select only once)
+PASS input type tel: selectionEnd
+PASS input type tel: selectionEnd a second time (must not fire select)
+PASS input type tel: selectionEnd disconnected node
+PASS input type tel: selectionEnd event queue
+PASS input type tel: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type tel: selectionDirection
+FAIL input type tel: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type tel: selectionDirection disconnected node
+PASS input type tel: selectionDirection event queue
+PASS input type tel: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type tel: setSelectionRange()
+PASS input type tel: setSelectionRange() a second time (must not fire select)
+PASS input type tel: setSelectionRange() disconnected node
+PASS input type tel: setSelectionRange() event queue
+PASS input type tel: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type tel: setRangeText()
+PASS input type tel: setRangeText() a second time (must not fire select)
+PASS input type tel: setRangeText() disconnected node
+PASS input type tel: setRangeText() event queue
+PASS input type tel: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type tel: selectionStart out of range
+PASS input type tel: selectionStart out of range a second time (must not fire select)
+PASS input type tel: selectionStart out of range disconnected node
+PASS input type tel: selectionStart out of range event queue
+PASS input type tel: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type tel: selectionEnd out of range
+PASS input type tel: selectionEnd out of range a second time (must not fire select)
+PASS input type tel: selectionEnd out of range disconnected node
+PASS input type tel: selectionEnd out of range event queue
+PASS input type tel: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type tel: setSelectionRange out of range
+PASS input type tel: setSelectionRange out of range a second time (must not fire select)
+PASS input type tel: setSelectionRange out of range disconnected node
+PASS input type tel: setSelectionRange out of range event queue
+PASS input type tel: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type url: select()
+PASS input type url: select() a second time (must not fire select)
+PASS input type url: select() disconnected node
+PASS input type url: select() event queue
+PASS input type url: select() twice in disconnected node (must fire select only once)
+PASS input type url: selectionStart
+PASS input type url: selectionStart a second time (must not fire select)
+PASS input type url: selectionStart disconnected node
+PASS input type url: selectionStart event queue
+PASS input type url: selectionStart twice in disconnected node (must fire select only once)
+PASS input type url: selectionEnd
+PASS input type url: selectionEnd a second time (must not fire select)
+PASS input type url: selectionEnd disconnected node
+PASS input type url: selectionEnd event queue
+PASS input type url: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type url: selectionDirection
+FAIL input type url: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type url: selectionDirection disconnected node
+PASS input type url: selectionDirection event queue
+PASS input type url: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type url: setSelectionRange()
+PASS input type url: setSelectionRange() a second time (must not fire select)
+PASS input type url: setSelectionRange() disconnected node
+PASS input type url: setSelectionRange() event queue
+PASS input type url: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type url: setRangeText()
+PASS input type url: setRangeText() a second time (must not fire select)
+PASS input type url: setRangeText() disconnected node
+PASS input type url: setRangeText() event queue
+PASS input type url: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type url: selectionStart out of range
+PASS input type url: selectionStart out of range a second time (must not fire select)
+PASS input type url: selectionStart out of range disconnected node
+PASS input type url: selectionStart out of range event queue
+PASS input type url: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type url: selectionEnd out of range
+PASS input type url: selectionEnd out of range a second time (must not fire select)
+PASS input type url: selectionEnd out of range disconnected node
+PASS input type url: selectionEnd out of range event queue
+PASS input type url: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type url: setSelectionRange out of range
+PASS input type url: setSelectionRange out of range a second time (must not fire select)
+PASS input type url: setSelectionRange out of range disconnected node
+PASS input type url: setSelectionRange out of range event queue
+PASS input type url: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type password: select()
+PASS input type password: select() a second time (must not fire select)
+PASS input type password: select() disconnected node
+PASS input type password: select() event queue
+PASS input type password: select() twice in disconnected node (must fire select only once)
+PASS input type password: selectionStart
+PASS input type password: selectionStart a second time (must not fire select)
+PASS input type password: selectionStart disconnected node
+PASS input type password: selectionStart event queue
+PASS input type password: selectionStart twice in disconnected node (must fire select only once)
+PASS input type password: selectionEnd
+PASS input type password: selectionEnd a second time (must not fire select)
+PASS input type password: selectionEnd disconnected node
+PASS input type password: selectionEnd event queue
+PASS input type password: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type password: selectionDirection
+FAIL input type password: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type password: selectionDirection disconnected node
+PASS input type password: selectionDirection event queue
+PASS input type password: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type password: setSelectionRange()
+PASS input type password: setSelectionRange() a second time (must not fire select)
+PASS input type password: setSelectionRange() disconnected node
+PASS input type password: setSelectionRange() event queue
+PASS input type password: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type password: setRangeText()
+PASS input type password: setRangeText() a second time (must not fire select)
+PASS input type password: setRangeText() disconnected node
+PASS input type password: setRangeText() event queue
+PASS input type password: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type password: selectionStart out of range
+PASS input type password: selectionStart out of range a second time (must not fire select)
+PASS input type password: selectionStart out of range disconnected node
+PASS input type password: selectionStart out of range event queue
+PASS input type password: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type password: selectionEnd out of range
+PASS input type password: selectionEnd out of range a second time (must not fire select)
+PASS input type password: selectionEnd out of range disconnected node
+PASS input type password: selectionEnd out of range event queue
+PASS input type password: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type password: setSelectionRange out of range
+PASS input type password: setSelectionRange out of range a second time (must not fire select)
+PASS input type password: setSelectionRange out of range disconnected node
+PASS input type password: setSelectionRange out of range event queue
+PASS input type password: setSelectionRange out of range twice in disconnected node (must fire select only once)
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event.html b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event.html
new file mode 100644
index 0000000..6ed07bc
--- /dev/null
+++ b/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event.html
@@ -0,0 +1,144 @@
+<!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 waitForRender() {
+  return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+
+function initialize(el) {
+  el.setRangeText("foobar", 0, el.value.length, "start");
+  // Make sure to flush async dispatches
+  return waitForRender();
+}
+
+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 waitForRender();
+      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 waitForRender();
+      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 waitForRender();
+      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 waitForRender();
+      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/LayoutTests/platform/gtk/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt b/LayoutTests/platform/gtk/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt
new file mode 100644
index 0000000..c79d5e9
--- /dev/null
+++ b/LayoutTests/platform/gtk/imported/w3c/web-platform-tests/html/semantics/forms/textfieldselection/select-event-expected.txt
@@ -0,0 +1,272 @@
+
+PASS textarea: select()
+PASS textarea: select() a second time (must not fire select)
+PASS textarea: select() disconnected node
+PASS textarea: select() event queue
+FAIL textarea: select() twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionStart
+PASS textarea: selectionStart a second time (must not fire select)
+PASS textarea: selectionStart disconnected node
+PASS textarea: selectionStart event queue
+FAIL textarea: selectionStart twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionEnd
+PASS textarea: selectionEnd a second time (must not fire select)
+PASS textarea: selectionEnd disconnected node
+PASS textarea: selectionEnd event queue
+FAIL textarea: selectionEnd twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionDirection
+FAIL textarea: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS textarea: selectionDirection disconnected node
+PASS textarea: selectionDirection event queue
+FAIL textarea: selectionDirection twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: setSelectionRange()
+PASS textarea: setSelectionRange() a second time (must not fire select)
+PASS textarea: setSelectionRange() disconnected node
+PASS textarea: setSelectionRange() event queue
+FAIL textarea: setSelectionRange() twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: setRangeText()
+PASS textarea: setRangeText() a second time (must not fire select)
+PASS textarea: setRangeText() disconnected node
+PASS textarea: setRangeText() event queue
+FAIL textarea: setRangeText() twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 15
+PASS textarea: selectionStart out of range
+PASS textarea: selectionStart out of range a second time (must not fire select)
+PASS textarea: selectionStart out of range disconnected node
+PASS textarea: selectionStart out of range event queue
+FAIL textarea: selectionStart out of range twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: selectionEnd out of range
+PASS textarea: selectionEnd out of range a second time (must not fire select)
+PASS textarea: selectionEnd out of range disconnected node
+PASS textarea: selectionEnd out of range event queue
+FAIL textarea: selectionEnd out of range twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS textarea: setSelectionRange out of range
+PASS textarea: setSelectionRange out of range a second time (must not fire select)
+PASS textarea: setSelectionRange out of range disconnected node
+PASS textarea: setSelectionRange out of range event queue
+FAIL textarea: setSelectionRange out of range twice in disconnected node (must fire select only once) assert_equals: expected 0 but got 6
+PASS input type text: select()
+PASS input type text: select() a second time (must not fire select)
+PASS input type text: select() disconnected node
+PASS input type text: select() event queue
+PASS input type text: select() twice in disconnected node (must fire select only once)
+PASS input type text: selectionStart
+PASS input type text: selectionStart a second time (must not fire select)
+PASS input type text: selectionStart disconnected node
+PASS input type text: selectionStart event queue
+PASS input type text: selectionStart twice in disconnected node (must fire select only once)
+PASS input type text: selectionEnd
+PASS input type text: selectionEnd a second time (must not fire select)
+PASS input type text: selectionEnd disconnected node
+PASS input type text: selectionEnd event queue
+PASS input type text: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type text: selectionDirection
+FAIL input type text: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type text: selectionDirection disconnected node
+PASS input type text: selectionDirection event queue
+PASS input type text: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type text: setSelectionRange()
+PASS input type text: setSelectionRange() a second time (must not fire select)
+PASS input type text: setSelectionRange() disconnected node
+PASS input type text: setSelectionRange() event queue
+PASS input type text: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type text: setRangeText()
+PASS input type text: setRangeText() a second time (must not fire select)
+PASS input type text: setRangeText() disconnected node
+PASS input type text: setRangeText() event queue
+PASS input type text: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type text: selectionStart out of range
+PASS input type text: selectionStart out of range a second time (must not fire select)
+PASS input type text: selectionStart out of range disconnected node
+PASS input type text: selectionStart out of range event queue
+PASS input type text: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type text: selectionEnd out of range
+PASS input type text: selectionEnd out of range a second time (must not fire select)
+PASS input type text: selectionEnd out of range disconnected node
+PASS input type text: selectionEnd out of range event queue
+PASS input type text: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type text: setSelectionRange out of range
+PASS input type text: setSelectionRange out of range a second time (must not fire select)
+PASS input type text: setSelectionRange out of range disconnected node
+PASS input type text: setSelectionRange out of range event queue
+PASS input type text: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type search: select()
+PASS input type search: select() a second time (must not fire select)
+PASS input type search: select() disconnected node
+PASS input type search: select() event queue
+PASS input type search: select() twice in disconnected node (must fire select only once)
+PASS input type search: selectionStart
+PASS input type search: selectionStart a second time (must not fire select)
+PASS input type search: selectionStart disconnected node
+PASS input type search: selectionStart event queue
+PASS input type search: selectionStart twice in disconnected node (must fire select only once)
+PASS input type search: selectionEnd
+PASS input type search: selectionEnd a second time (must not fire select)
+PASS input type search: selectionEnd disconnected node
+PASS input type search: selectionEnd event queue
+PASS input type search: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type search: selectionDirection
+FAIL input type search: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type search: selectionDirection disconnected node
+PASS input type search: selectionDirection event queue
+PASS input type search: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type search: setSelectionRange()
+PASS input type search: setSelectionRange() a second time (must not fire select)
+PASS input type search: setSelectionRange() disconnected node
+PASS input type search: setSelectionRange() event queue
+PASS input type search: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type search: setRangeText()
+PASS input type search: setRangeText() a second time (must not fire select)
+PASS input type search: setRangeText() disconnected node
+PASS input type search: setRangeText() event queue
+PASS input type search: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type search: selectionStart out of range
+PASS input type search: selectionStart out of range a second time (must not fire select)
+PASS input type search: selectionStart out of range disconnected node
+PASS input type search: selectionStart out of range event queue
+PASS input type search: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type search: selectionEnd out of range
+PASS input type search: selectionEnd out of range a second time (must not fire select)
+PASS input type search: selectionEnd out of range disconnected node
+PASS input type search: selectionEnd out of range event queue
+PASS input type search: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type search: setSelectionRange out of range
+PASS input type search: setSelectionRange out of range a second time (must not fire select)
+PASS input type search: setSelectionRange out of range disconnected node
+PASS input type search: setSelectionRange out of range event queue
+PASS input type search: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type tel: select()
+PASS input type tel: select() a second time (must not fire select)
+PASS input type tel: select() disconnected node
+PASS input type tel: select() event queue
+PASS input type tel: select() twice in disconnected node (must fire select only once)
+PASS input type tel: selectionStart
+PASS input type tel: selectionStart a second time (must not fire select)
+PASS input type tel: selectionStart disconnected node
+PASS input type tel: selectionStart event queue
+PASS input type tel: selectionStart twice in disconnected node (must fire select only once)
+PASS input type tel: selectionEnd
+PASS input type tel: selectionEnd a second time (must not fire select)
+PASS input type tel: selectionEnd disconnected node
+PASS input type tel: selectionEnd event queue
+PASS input type tel: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type tel: selectionDirection
+FAIL input type tel: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type tel: selectionDirection disconnected node
+PASS input type tel: selectionDirection event queue
+PASS input type tel: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type tel: setSelectionRange()
+PASS input type tel: setSelectionRange() a second time (must not fire select)
+PASS input type tel: setSelectionRange() disconnected node
+PASS input type tel: setSelectionRange() event queue
+PASS input type tel: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type tel: setRangeText()
+PASS input type tel: setRangeText() a second time (must not fire select)
+PASS input type tel: setRangeText() disconnected node
+PASS input type tel: setRangeText() event queue
+PASS input type tel: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type tel: selectionStart out of range
+PASS input type tel: selectionStart out of range a second time (must not fire select)
+PASS input type tel: selectionStart out of range disconnected node
+PASS input type tel: selectionStart out of range event queue
+PASS input type tel: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type tel: selectionEnd out of range
+PASS input type tel: selectionEnd out of range a second time (must not fire select)
+PASS input type tel: selectionEnd out of range disconnected node
+PASS input type tel: selectionEnd out of range event queue
+PASS input type tel: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type tel: setSelectionRange out of range
+PASS input type tel: setSelectionRange out of range a second time (must not fire select)
+PASS input type tel: setSelectionRange out of range disconnected node
+PASS input type tel: setSelectionRange out of range event queue
+PASS input type tel: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type url: select()
+PASS input type url: select() a second time (must not fire select)
+PASS input type url: select() disconnected node
+PASS input type url: select() event queue
+PASS input type url: select() twice in disconnected node (must fire select only once)
+PASS input type url: selectionStart
+PASS input type url: selectionStart a second time (must not fire select)
+PASS input type url: selectionStart disconnected node
+PASS input type url: selectionStart event queue
+PASS input type url: selectionStart twice in disconnected node (must fire select only once)
+PASS input type url: selectionEnd
+PASS input type url: selectionEnd a second time (must not fire select)
+PASS input type url: selectionEnd disconnected node
+PASS input type url: selectionEnd event queue
+PASS input type url: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type url: selectionDirection
+FAIL input type url: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type url: selectionDirection disconnected node
+PASS input type url: selectionDirection event queue
+PASS input type url: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type url: setSelectionRange()
+PASS input type url: setSelectionRange() a second time (must not fire select)
+PASS input type url: setSelectionRange() disconnected node
+PASS input type url: setSelectionRange() event queue
+PASS input type url: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type url: setRangeText()
+PASS input type url: setRangeText() a second time (must not fire select)
+PASS input type url: setRangeText() disconnected node
+PASS input type url: setRangeText() event queue
+PASS input type url: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type url: selectionStart out of range
+PASS input type url: selectionStart out of range a second time (must not fire select)
+PASS input type url: selectionStart out of range disconnected node
+PASS input type url: selectionStart out of range event queue
+PASS input type url: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type url: selectionEnd out of range
+PASS input type url: selectionEnd out of range a second time (must not fire select)
+PASS input type url: selectionEnd out of range disconnected node
+PASS input type url: selectionEnd out of range event queue
+PASS input type url: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type url: setSelectionRange out of range
+PASS input type url: setSelectionRange out of range a second time (must not fire select)
+PASS input type url: setSelectionRange out of range disconnected node
+PASS input type url: setSelectionRange out of range event queue
+PASS input type url: setSelectionRange out of range twice in disconnected node (must fire select only once)
+PASS input type password: select()
+PASS input type password: select() a second time (must not fire select)
+PASS input type password: select() disconnected node
+PASS input type password: select() event queue
+PASS input type password: select() twice in disconnected node (must fire select only once)
+PASS input type password: selectionStart
+PASS input type password: selectionStart a second time (must not fire select)
+PASS input type password: selectionStart disconnected node
+PASS input type password: selectionStart event queue
+PASS input type password: selectionStart twice in disconnected node (must fire select only once)
+PASS input type password: selectionEnd
+PASS input type password: selectionEnd a second time (must not fire select)
+PASS input type password: selectionEnd disconnected node
+PASS input type password: selectionEnd event queue
+PASS input type password: selectionEnd twice in disconnected node (must fire select only once)
+PASS input type password: selectionDirection
+FAIL input type password: selectionDirection a second time (must not fire select) assert_unreached: the select event must not fire the second time Reached unreachable code
+PASS input type password: selectionDirection disconnected node
+PASS input type password: selectionDirection event queue
+PASS input type password: selectionDirection twice in disconnected node (must fire select only once)
+PASS input type password: setSelectionRange()
+PASS input type password: setSelectionRange() a second time (must not fire select)
+PASS input type password: setSelectionRange() disconnected node
+PASS input type password: setSelectionRange() event queue
+PASS input type password: setSelectionRange() twice in disconnected node (must fire select only once)
+PASS input type password: setRangeText()
+PASS input type password: setRangeText() a second time (must not fire select)
+PASS input type password: setRangeText() disconnected node
+PASS input type password: setRangeText() event queue
+PASS input type password: setRangeText() twice in disconnected node (must fire select only once)
+PASS input type password: selectionStart out of range
+PASS input type password: selectionStart out of range a second time (must not fire select)
+PASS input type password: selectionStart out of range disconnected node
+PASS input type password: selectionStart out of range event queue
+PASS input type password: selectionStart out of range twice in disconnected node (must fire select only once)
+PASS input type password: selectionEnd out of range
+PASS input type password: selectionEnd out of range a second time (must not fire select)
+PASS input type password: selectionEnd out of range disconnected node
+PASS input type password: selectionEnd out of range event queue
+PASS input type password: selectionEnd out of range twice in disconnected node (must fire select only once)
+PASS input type password: setSelectionRange out of range
+PASS input type password: setSelectionRange out of range a second time (must not fire select)
+PASS input type password: setSelectionRange out of range disconnected node
+PASS input type password: setSelectionRange out of range event queue
+PASS input type password: setSelectionRange out of range twice in disconnected node (must fire select only once)
+
diff --git a/Source/WebCore/html/HTMLTextFormControlElement.cpp b/Source/WebCore/html/HTMLTextFormControlElement.cpp
index 6ecddba..e1e8dcf 100644
--- a/Source/WebCore/html/HTMLTextFormControlElement.cpp
+++ b/Source/WebCore/html/HTMLTextFormControlElement.cpp
@@ -218,7 +218,8 @@
 
 void HTMLTextFormControlElement::select(SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent)
 {
-    setSelectionRange(0, std::numeric_limits<unsigned>::max(), SelectionHasNoDirection, revealMode, intent);
+    if (setSelectionRange(0, std::numeric_limits<unsigned>::max(), SelectionHasNoDirection, revealMode, intent))
+        scheduleSelectEvent();
 }
 
 String HTMLTextFormControlElement::selectedText() const
@@ -285,7 +286,8 @@
             newSelectionEnd = start + replacementLength;
     }
 
-    setSelectionRange(newSelectionStart, newSelectionEnd, SelectionHasNoDirection);
+    if (setSelectionRange(newSelectionStart, newSelectionEnd, SelectionHasNoDirection))
+        scheduleSelectEvent();
 
     return { };
 }