tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>