| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <title>InputEvent.getTargetRanges() behavior</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-actions.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <p>To manually run this test, please follow the steps below:<br/> |
| 1. Place caret at the end of 'hel<i>lo wo</i><b>rld</b>'.<br/> |
| 2. Press Ctrl-Backspace (Alt-Backspace on macOS) to delete word backwards.<br/> |
| 3. Place caret at the end of 'test2' => Press 'a' key.<br/> |
| 4. Select 'test2a' => Press 'b' key.<br/> |
| 5. Select 'b' => Bold text through context menu or Command-b on macOS.<br/> |
| 6. Place caret at the end of 'test3' => Press 'a' key => Press Backspace key.<br/> |
| <br/> |
| If a "PASS" result appears the test passes, otherwise it fails</p> |
| <p id="test1_editable" contenteditable>hel<i>lo wo</i><b>rld</b></p> |
| <p id="test2_editable" contenteditable>test2</p> |
| <textarea id="test3_plain">test3</textarea> |
| <script> |
| function resolveWhen(condition) { |
| return new Promise((resolve, reject) => { |
| function tick() { |
| if (condition()) |
| resolve(); |
| else |
| requestAnimationFrame(tick.bind(this)); |
| } |
| tick(); |
| }); |
| } |
| |
| let modifier_key = "\uE009"; |
| if(navigator.platform.includes('Mac')) |
| modifier_key = "\uE03D"; |
| const commands = { |
| COPY: 'copy', |
| CUT: 'cut', |
| PASTE: 'paste', |
| SELECTALL: 'select all', |
| DELETEALL: 'delete all', |
| BOLD: 'bold', |
| } |
| const backspace = "\uE003"; |
| |
| function clickOnTarget(target) { |
| return new test_driver.Actions() |
| .pointerMove(0, 0, {origin: target}) |
| .pointerDown() |
| .pointerUp() |
| .send(); |
| } |
| |
| function sendTextCommand(command) { |
| let command_key = ""; |
| if(command == "copy") |
| command_key = "c"; |
| else if (command == "cut") |
| command_key = "x"; |
| else if (command == "paste") |
| command_key = "v"; |
| else if (command == "select all") |
| command_key = "a"; |
| else if (command == "delete all") |
| command_key = backspace; |
| else if (command == "bold") |
| command_key = "b"; |
| return new test_driver.Actions() |
| .keyDown(modifier_key) |
| .keyDown(command_key) |
| .keyUp(command_key) |
| .keyUp(modifier_key) |
| .send(); |
| } |
| |
| function sendTextCommandAtTarget(target, command) { |
| return clickOnTarget(target).then(() => { |
| return sendTextCommand(command); |
| }); |
| } |
| |
| function addTextAtTarget(target, char) { |
| return test_driver.send_keys(target, char); |
| } |
| |
| promise_test(async test => { |
| const test1_editable = document.getElementById('test1_editable'); |
| let lastBeforeInput; |
| test1_editable.addEventListener('beforeinput', test.step_func(function() { |
| assert_equals(event.inputType, 'deleteWordBackward'); |
| const ranges = event.getTargetRanges(); |
| assert_equals(ranges.length, 1); |
| const range = ranges[0]; |
| assert_true(range instanceof StaticRange); |
| assert_equals(range.startOffset, 3); |
| assert_equals(range.startContainer.textContent, 'lo wo'); |
| assert_equals(range.endOffset, 3); |
| assert_equals(range.endContainer.textContent, 'rld'); |
| assert_equals(test1_editable.innerHTML, 'hel<i>lo wo</i><b>rld</b>'); |
| lastBeforeInput = event; |
| })); |
| |
| test1_editable.addEventListener('input', test.step_func(function() { |
| assert_equals(event.inputType, 'deleteWordBackward'); |
| assert_equals(test1_editable.innerHTML, 'hel<i>lo </i>'); |
| assert_equals(lastBeforeInput.inputType, 'deleteWordBackward'); |
| assert_equals(lastBeforeInput.getTargetRanges().length, 0, |
| 'getTargetRanges() should be empty after the event has finished dispatching.'); |
| })); |
| |
| await sendTextCommandAtTarget(test1_editable, commands.DELETEALL); |
| await resolveWhen(() => { return test1_editable.innerHTML == 'hel<i>lo </i>' }); |
| }, 'getTargetRanges() returns correct range and cleared after dispatch.'); |
| |
| promise_test(async test => { |
| const expectedEventLog = ['test2-5-test2-5', 'test2a-0-test2a-6', 'b-0-b-1']; |
| const actualEventLog = []; |
| |
| const test2_editable = document.getElementById('test2_editable'); |
| test2_editable.addEventListener('beforeinput', test.step_func(function() { |
| const ranges = event.getTargetRanges(); |
| assert_equals(ranges.length, 1); |
| const range = ranges[0]; |
| actualEventLog.push( |
| `${range.startContainer.textContent}-${range.startOffset}-${range.endContainer.textContent}-${range.endOffset}`); |
| })); |
| |
| await addTextAtTarget(test2_editable, "a"); |
| await sendTextCommandAtTarget(test2_editable, commands.SELECTALL); |
| await addTextAtTarget(test2_editable, "b"); |
| await sendTextCommandAtTarget(test2_editable, commands.SELECTALL); |
| await sendTextCommand(commands.BOLD); |
| await resolveWhen(() => { return actualEventLog.length == expectedEventLog.length }); |
| assert_array_equals(actualEventLog, expectedEventLog, |
| `Expected: ${expectedEventLog}; Actual: ${actualEventLog}.`); |
| }, 'Actions other than deletion should have current selection as target ranges.'); |
| |
| promise_test(async test => { |
| const test3_plain = document.getElementById('test3_plain'); |
| let event_type; |
| test3_plain.addEventListener('beforeinput', test.step_func(function() { |
| assert_equals(event.getTargetRanges().length, 0, |
| 'getTargetRanges() should return empty array on textarea.'); |
| |
| if (event.inputType === 'deleteContentBackward') |
| event_type = event.inputType; |
| })); |
| |
| await addTextAtTarget(test3_plain, "a"); |
| await addTextAtTarget(test3_plain, backspace); |
| await resolveWhen(() => { return event_type == 'deleteContentBackward' }); |
| }, 'Textarea should have empty target range.'); |
| </script> |