tor-browser

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

FormControlRange-update-event-order.html (4169B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script src="/resources/testdriver.js"></script>
      6 <script src="/resources/testdriver-vendor.js"></script>
      7 <script src="/resources/testdriver-actions.js"></script>
      8 <body></body>
      9 <script>
     10 'use strict';
     11 
     12 // Verifies that FormControlRange updates are applied after the value mutation
     13 // but before 'input' event listeners execute. Specifically, offsets remain at
     14 // their pre-edit positions in 'beforeinput' and reflect the new positions in 'input'.
     15 
     16 const controls = ['input', 'textarea'];
     17 
     18 function setup(control, value) {
     19  document.body.innerHTML = control === 'input' ? '<input type="text">' : '<textarea></textarea>';
     20  const element = document.body.firstElementChild;
     21  element.value = value;
     22  element.focus();
     23  return element;
     24 }
     25 
     26 async function typeKeys(element, text) { await test_driver.send_keys(element, text); }
     27 
     28 function makeRange(element, start, end) {
     29  const range = new FormControlRange();
     30  range.setFormControlRange(element, start, end);
     31  return range;
     32 }
     33 
     34 controls.forEach(control => {
     35  promise_test(async t => {
     36    const element = setup(control, 'ABCDE');
     37    const range = makeRange(element, 1, 3);
     38 
     39    // Place caret before the range, then insert text.
     40    element.setSelectionRange(0, 0);
     41 
     42    const seen = { beforeinput: null, input: null };
     43 
     44    element.addEventListener('beforeinput', t.step_func(e => {
     45      seen.beforeinput = {
     46        value: element.value,
     47        start: range.startOffset,
     48        end: range.endOffset,
     49        rangeText: range.toString()
     50      };
     51      assert_equals(seen.beforeinput.start, 1, 'beforeinput start should be old');
     52      assert_equals(seen.beforeinput.end, 3, 'beforeinput end should be old');
     53    }), { once: true });
     54 
     55    const inputPromise = new Promise(resolve => {
     56      element.addEventListener('input', t.step_func(() => {
     57        seen.input = {
     58          value: element.value,
     59          start: range.startOffset,
     60          end: range.endOffset,
     61          rangeText: range.toString()
     62        };
     63        resolve();
     64      }), { once: true });
     65    });
     66 
     67    await typeKeys(element, 'Z');
     68    await inputPromise;
     69 
     70    // Range should have shifted forward by 1.
     71    assert_equals(seen.input.value, 'ZABCDE', 'value after insertion');
     72    assert_equals(seen.input.start, 2, 'updated start after insertion');
     73    assert_equals(seen.input.end, 4, 'updated end after insertion');
     74    assert_not_equals(seen.beforeinput, null, 'captured beforeinput');
     75    assert_not_equals(seen.input, null, 'captured input');
     76    assert_not_equals(seen.beforeinput.start, seen.input.start, 'start changed between events');
     77    assert_not_equals(seen.beforeinput.end, seen.input.end, 'end changed between events');
     78  }, `Event order: FormControlRange updates between beforeinput and input (${control}).`);
     79 
     80  promise_test(async t => {
     81    const element = setup(control, 'ABCDE');
     82    const range = makeRange(element, 1, 3);
     83    element.setSelectionRange(0, 0);
     84 
     85    let beforeSnapshot = null;
     86    const before = new Promise(resolve => {
     87      element.addEventListener('beforeinput', t.step_func(e => {
     88        e.preventDefault();
     89        beforeSnapshot = {
     90          value: element.value,
     91          start: range.startOffset,
     92          end: range.endOffset,
     93        };
     94        resolve();
     95      }), { once: true });
     96    });
     97 
     98    let sawInput = false;
     99    element.addEventListener('input', t.step_func(() => { sawInput = true; }));
    100 
    101    // Attempt insertion, which will be canceled.
    102    await typeKeys(element, 'Z');
    103    await before;
    104 
    105    // Ensure beforeinput fired and the edit was canceled.
    106    assert_not_equals(beforeSnapshot, null, 'beforeinput captured');
    107    assert_false(sawInput, 'input should not fire when beforeinput is canceled');
    108    assert_equals(element.value, 'ABCDE', 'value unchanged after canceled beforeinput');
    109    assert_equals(range.startOffset, 1, 'range start unchanged after cancel');
    110    assert_equals(range.endOffset, 3, 'range end unchanged after cancel');
    111  }, `Canceled beforeinput leaves FormControlRange unchanged (${control}).`);
    112 });
    113 </script>