1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 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>
|