| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <title>Cut and Paste should trigger corresponding InputEvent</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> | |
| <script type="text/javascript" src="pointerevent_support.js"></script> | |
| <p>To manually run this test, please follow the steps below:<br/> | |
| 1. Select 'plain' => Cut (e.g. Ctrl/Cmd-X) => Paste (e.g. Ctrl/Cmd-V).<br/> | |
| 2. Select 'rich' => Cut => Paste.<br/> | |
| 3. Select 'prevent' => Paste.<br/> | |
| 4. Select 'prevent' => Cut => Select 'normal' => Paste.<br/> | |
| <br/> | |
| If a "PASS" result appears the test passes, otherwise it fails</p> | |
| <textarea id="test1_plain">plain</textarea> | |
| <p id="test2_editable" contenteditable><b>rich</b></p> | |
| <p id="test3_editable_prevent" contenteditable>prevent</p> | |
| <p id="test3_editable_normal" contenteditable>normal</p> | |
| <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', | |
| } | |
| function selectTextCommand(target, command) { | |
| let command_key = ""; | |
| if(command == "copy") | |
| command_key = "c"; | |
| else if (command == "cut") | |
| command_key = "x"; | |
| else if (command == "paste") | |
| command_key = "v"; | |
| return new test_driver.Actions() | |
| .pointerMove(0, 0, {origin: target}) | |
| .pointerDown() | |
| .pointerUp() | |
| .addTick() | |
| .keyDown(modifier_key) | |
| .keyDown("a") | |
| .keyUp("a") | |
| .keyDown(command_key) | |
| .keyUp(command_key) | |
| .keyUp(modifier_key) | |
| .send(); | |
| } | |
| function selectTextCutAndPaste(target1, target2) { | |
| return selectTextCommand(target1, commands.CUT).then(() => { | |
| return selectTextCommand(target2, commands.PASTE); | |
| }) | |
| } | |
| promise_test(async test => { | |
| const expectedEventLog = [ | |
| 'cut-[null]', 'beforeinput-deleteByCut', 'input-deleteByCut', | |
| 'paste-[null]', 'beforeinput-insertFromPaste', 'input-insertFromPaste']; | |
| const actualEventLog = []; | |
| const text1 = document.getElementById("test1_plain"); | |
| for (let eventType of ['beforeinput', 'input', 'cut', 'paste']) { | |
| text1.addEventListener(eventType, test.step_func(function() { | |
| if (event.type === 'beforeinput' && event.inputType === 'insertFromPaste') { | |
| assert_equals(event.data, 'plain'); | |
| assert_equals(event.dataTransfer, null); | |
| } | |
| actualEventLog.push(`${event.type}-${event.inputType || '[null]'}`); | |
| })); | |
| } | |
| await selectTextCutAndPaste(text1, text1); | |
| await resolveWhen(() => { return actualEventLog.length == expectedEventLog.length }); | |
| assert_array_equals(actualEventLog, expectedEventLog, | |
| `Expected: ${expectedEventLog}; Actual: ${actualEventLog}.`); | |
| }, 'Event order and data on textarea.'); | |
| promise_test(async test => { | |
| const expectedEventLog = [ | |
| 'cut-[null]', 'beforeinput-deleteByCut', 'input-deleteByCut', | |
| 'paste-[null]', 'beforeinput-insertFromPaste', 'input-insertFromPaste']; | |
| const actualEventLog = []; | |
| const text2 = document.getElementById("test2_editable"); | |
| for (let eventType of ['beforeinput', 'input', 'cut', 'paste']) { | |
| text2.addEventListener(eventType, test.step_func(function() { | |
| if (event.type === 'beforeinput' && event.inputType === 'insertFromPaste') { | |
| assert_equals(event.data, null); | |
| assert_equals(event.dataTransfer.getData('text/plain'), 'rich'); | |
| assert_regexp_match(event.dataTransfer.getData('text/html'), /<b.*>rich<\/b>$/); | |
| } | |
| actualEventLog.push(`${event.type}-${event.inputType || '[null]'}`); | |
| })); | |
| } | |
| await selectTextCutAndPaste(text2, text2); | |
| await resolveWhen(() => { return actualEventLog.length == expectedEventLog.length }); | |
| assert_array_equals(actualEventLog, expectedEventLog, | |
| `Expected: ${expectedEventLog}; Actual: ${actualEventLog}.`); | |
| }, 'Event order and dataTransfer on contenteditable.'); | |
| promise_test(async test => { | |
| const prevent = document.getElementById('test3_editable_prevent'); | |
| const normal = document.getElementById('test3_editable_normal'); | |
| prevent.addEventListener('beforeinput', test.step_func(function() { | |
| if (event.inputType === 'deleteByCut' || | |
| event.inputType === 'insertFromPaste') { | |
| event.preventDefault(); | |
| } | |
| })); | |
| normal.addEventListener('input', test.step_func(function() { | |
| if (event.inputType === 'insertFromPaste') { | |
| assert_equals(prevent.textContent, 'prevent'); | |
| assert_equals(normal.textContent, 'prevent'); | |
| } | |
| })); | |
| await selectTextCommand(prevent, commands.PASTE); | |
| await selectTextCutAndPaste(prevent, normal); | |
| await resolveWhen(() => { return normal.textContent == 'prevent' }); | |
| assert_equals(prevent.textContent, 'prevent'); | |
| assert_equals(normal.textContent, 'prevent'); | |
| }, 'preventDefault() should prevent DOM modification but allow clipboard updates.'); | |
| </script> |