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>