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