select-event.html (4646B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <meta name="timeout" content="long"> 4 <title>text field selection: select()</title> 5 <link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> 6 <link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection"> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <div id="log"></div> 10 11 <textarea>foobar</textarea> 12 <input type="text" value="foobar"> 13 <input type="search" value="foobar"> 14 <input type="tel" value="1234"> 15 <input type="url" value="https://example.com/"> 16 <input type="password" value="hunter2"> 17 18 <script> 19 "use strict"; 20 21 const els = [document.querySelector("textarea"), ...document.querySelectorAll("input")]; 22 23 const actions = [ 24 { 25 label: "select()", 26 action: el => el.select() 27 }, 28 { 29 label: "selectionStart", 30 action: el => el.selectionStart = 1 31 }, 32 { 33 label: "selectionEnd", 34 action: el => el.selectionEnd = el.value.length - 1 35 }, 36 { 37 label: "selectionDirection", 38 action: el => el.selectionDirection = "backward" 39 }, 40 { 41 label: "setSelectionRange()", 42 action: el => el.setSelectionRange(1, el.value.length - 1) // changes direction implicitly to none/forward 43 }, 44 { 45 label: "setRangeText()", 46 action: el => el.setRangeText("newmiddle", el.selectionStart, el.selectionEnd, "select") 47 }, 48 { 49 label: "selectionStart out of range", 50 action: el => el.selectionStart = 1000 51 }, 52 { 53 label: "selectionEnd out of range", 54 action: el => el.selectionEnd = 1000 55 }, 56 { 57 label: "setSelectionRange out of range", 58 action: el => el.setSelectionRange(1000, 2000) 59 } 60 ]; 61 62 function waitForEvents() { 63 // Engines differ in when these events are sent (see: 64 // https://bugzilla.mozilla.org/show_bug.cgi?id=1785615) so wait for both a 65 // frame to be rendered, and a timeout. 66 return new Promise(resolve => { 67 requestAnimationFrame(() => { 68 requestAnimationFrame(() => { 69 setTimeout(() => { 70 resolve(); 71 }); 72 }); 73 }); 74 }); 75 } 76 77 function initialize(el) { 78 el.setRangeText("foobar", 0, el.value.length, "start"); 79 // Make sure to flush async dispatches 80 return waitForEvents(); 81 } 82 83 els.forEach((el) => { 84 const elLabel = el.localName === "textarea" ? "textarea" : "input type " + el.type; 85 86 actions.forEach((action) => { 87 // promise_test instead of async_test is important because these need to happen in sequence (to test that events 88 // fire if and only if the selection changes). 89 promise_test(async t => { 90 await initialize(el); 91 92 const watcher = new EventWatcher(t, el, "select"); 93 94 const promise = watcher.wait_for("select").then(e => { 95 assert_true(e.isTrusted, "isTrusted must be true"); 96 assert_true(e.bubbles, "bubbles must be true"); 97 assert_false(e.cancelable, "cancelable must be false"); 98 }); 99 100 action.action(el); 101 102 return promise; 103 }, `${elLabel}: ${action.label}`); 104 105 promise_test(async t => { 106 el.onselect = t.unreached_func("the select event must not fire the second time"); 107 108 action.action(el); 109 110 await waitForEvents(); 111 el.onselect = null; 112 }, `${elLabel}: ${action.label} a second time (must not fire select)`); 113 114 promise_test(async t => { 115 const element = el.cloneNode(true); 116 let fired = false; 117 element.addEventListener('select', () => fired = true, { once: true }); 118 119 action.action(element); 120 121 await waitForEvents(); 122 assert_true(fired, "event didn't fire"); 123 124 }, `${elLabel}: ${action.label} disconnected node`); 125 126 // Intentionally still using promise_test, as assert_unreachable does not 127 // make the test fail inside a listener while t.unreached_func() does. 128 promise_test(async t => { 129 const element = el.cloneNode(true); 130 let fired = false; 131 element.addEventListener('select', () => fired = true, { once: true }); 132 133 action.action(element); 134 135 assert_false(fired, "the select event must not fire synchronously"); 136 await waitForEvents(); 137 assert_true(fired, "event didn't fire"); 138 }, `${elLabel}: ${action.label} event queue`); 139 140 promise_test(async t => { 141 const element = el.cloneNode(true); 142 let selectCount = 0; 143 element.addEventListener('select', () => ++selectCount); 144 assert_equals(element.selectionEnd, 0); 145 146 action.action(element); 147 action.action(element); 148 149 await waitForEvents(); 150 assert_equals(selectCount, 1, "the select event must not fire twice"); 151 }, `${elLabel}: ${action.label} twice in disconnected node (must fire select only once)`); 152 }); 153 }); 154 </script>