|  | <!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> |