Index: third_party/WebKit/LayoutTests/fast/events/inputevents/inputevent-drag-drop.html |
diff --git a/third_party/WebKit/LayoutTests/fast/events/inputevents/inputevent-drag-drop.html b/third_party/WebKit/LayoutTests/fast/events/inputevents/inputevent-drag-drop.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..56eb083951fadec452d1c82307c33375eced044b |
--- /dev/null |
+++ b/third_party/WebKit/LayoutTests/fast/events/inputevents/inputevent-drag-drop.html |
@@ -0,0 +1,293 @@ |
+<!DOCTYPE html> |
+<html> |
+<head> |
+<title>InputEvent: beforeinput for Drag and Drop</title> |
+<script src="../../../resources/testharness.js"></script> |
+<script src="../../../resources/testharnessreport.js"></script> |
+<style> |
+div, img { |
+ width: 100px; |
+ height: 100px; |
+} |
+</style> |
+</head> |
+<body> |
+<div id="editable1" contenteditable><img id="img" src="../resources/greenbox.png"></div> |
+<div id="editable2" contenteditable></div> |
+<textarea id="textarea1">Text</textarea> |
+<textarea id="barrier"></textarea> |
+<script> |
+function simulateDragDrop(dragElement, dropElement) { |
+ eventSender.mouseMoveTo(dragElement.offsetLeft + dragElement.offsetWidth / 2, |
+ dragElement.offsetTop + dragElement.offsetHeight / 2); |
+ eventSender.mouseDown(); |
+ eventSender.leapForward(600); |
+ eventSender.mouseMoveTo(dropElement.offsetLeft + dropElement.offsetWidth / 2, |
+ dropElement.offsetTop + dropElement.offsetHeight / 2); |
+ eventSender.mouseUp(); |
+} |
+ |
+function assertCleanInitialDOM(logInfo) { |
+ const editable1 = document.getElementById('editable1'); |
+ const editable2 = document.getElementById('editable2'); |
+ const textarea1 = document.getElementById('textarea1'); |
+ assert_equals(editable1.children.length, 1, `${logInfo}, DOM is dirty`); |
+ assert_equals(editable1.children[0].tagName, 'IMG', `${logInfo}, DOM is dirty`); |
+ assert_equals(editable2.children.length, 0, `${logInfo}, DOM is dirty`); |
+ assert_equals(textarea1.value, 'Text', `${logInfo}, DOM is dirty`); |
+} |
+ |
+test(function() { |
+ assertCleanInitialDOM(); |
+ assert_not_equals(window.eventSender, undefined, 'This test requires eventSender.'); |
+ assert_not_equals(window.testRunner, undefined, 'This test requires testRunner.'); |
+ |
+ const editable1 = document.getElementById('editable1'); |
+ const editable2 = document.getElementById('editable2'); |
+ const textarea1 = document.getElementById('textarea1'); |
+ |
+ function preventDeleteByDragListener(event) { |
+ if (event.inputType == 'deleteByDrag') |
+ event.preventDefault(); |
+ } |
+ |
+ function preventInsertFromDropListener(event) { |
+ if (event.inputType == 'insertFromDrop') |
+ event.preventDefault(); |
+ } |
+ |
+ const undoBarrier = document.getElementById('barrier'); |
+ undoBarrier.focus(); |
+ document.execCommand('insertText', false, 'abc'); |
+ function assertBarrierUnchanged() { |
+ assert_equals(undoBarrier.value, 'abc'); |
+ } |
+ |
+ // Normally Drag&Drop requires a single Undo. |
+ simulateDragDrop(editable1, editable2); |
+ testRunner.execCommand('undo'); |
+ assertCleanInitialDOM('Normal Drag&Drop'); |
+ assertBarrierUnchanged(); |
+ |
+ // Canceling |DeleteByDrag|, still require a single Undo. |
+ editable1.addEventListener('beforeinput', preventDeleteByDragListener); |
+ simulateDragDrop(editable1, editable2); |
+ testRunner.execCommand('undo'); |
+ assertCleanInitialDOM('Canceling |DeleteByDrag|'); |
+ editable1.removeEventListener('beforeinput', preventDeleteByDragListener); |
+ assertBarrierUnchanged(); |
+ |
+ // Canceling |InsertFromDrop|, still require a single Undo. |
+ editable2.addEventListener('beforeinput', preventInsertFromDropListener); |
+ simulateDragDrop(editable1, editable2); |
+ testRunner.execCommand('undo'); |
+ assertCleanInitialDOM('Canceling |InsertFromDrop|'); |
+ editable2.removeEventListener('beforeinput', preventInsertFromDropListener); |
+ assertBarrierUnchanged(); |
+ |
+ // Canceling both, shouldn't create undo entry. |
+ editable1.addEventListener('beforeinput', preventDeleteByDragListener); |
+ editable2.addEventListener('beforeinput', preventInsertFromDropListener); |
+ simulateDragDrop(editable1, editable2); |
+ assertCleanInitialDOM('Canceling both'); |
+ testRunner.execCommand('undo'); |
+ assert_equals(undoBarrier.value, ''); |
+ testRunner.execCommand('redo'); |
+ assertBarrierUnchanged(); |
+ editable1.removeEventListener('beforeinput', preventDeleteByDragListener); |
+ editable2.removeEventListener('beforeinput', preventInsertFromDropListener); |
+ |
+ // Two Drag&Drop, cancel first |InsertFromDrop| and second |DeleteByDrag|, should still create 2 undo entries. |
+ editable2.addEventListener('beforeinput', preventInsertFromDropListener); |
+ simulateDragDrop(editable1, editable2); |
+ editable2.removeEventListener('beforeinput', preventInsertFromDropListener); |
+ textarea1.addEventListener('beforeinput', preventDeleteByDragListener); |
+ textarea1.select(); |
+ simulateDragDrop(textarea1, editable2); |
+ textarea1.removeEventListener('beforeinput', preventDeleteByDragListener); |
+ assert_equals(editable1.children.length, 0); |
+ assert_equals(editable2.innerHTML, 'Text'); |
+ assert_equals(textarea1.value, 'Text'); |
+ // First undo. |
+ testRunner.execCommand('undo'); |
+ assert_equals(editable1.children.length, 0); |
+ assert_equals(editable2.innerHTML, ''); |
+ assert_equals(textarea1.value, 'Text'); |
+ // Second undo. |
+ testRunner.execCommand('undo'); |
+ assert_equals(editable1.children.length, 1); |
+ assert_equals(editable2.innerHTML, ''); |
+ assert_equals(textarea1.value, 'Text'); |
+ // More undo should reach to |undoBarrier|. |
+ assertBarrierUnchanged(); |
+ testRunner.execCommand('undo'); |
+ assert_equals(undoBarrier.value, ''); |
+ testRunner.execCommand('redo'); |
+}, 'Testing Drag and Drop, preventDefault() and Undo entry'); |
+ |
+test(function() { |
+ assertCleanInitialDOM(); |
+ assert_not_equals(window.eventSender, undefined, 'This test requires eventSender.'); |
+ assert_not_equals(window.testRunner, undefined, 'This test requires testRunner.'); |
+ |
+ const editable1 = document.getElementById('editable1'); |
+ const editable2 = document.getElementById('editable2'); |
+ var eventOrderRecorder = []; |
+ document.addEventListener('beforeinput', event => |
+ eventOrderRecorder.push(`beforeinput:${event.target.id}:${event.inputType}`)); |
+ document.addEventListener('input', event => |
+ eventOrderRecorder.push(`input:${event.target.id}:${event.inputType}`)); |
+ ['drop', 'dragend'].forEach(eventType => document.addEventListener( |
+ eventType, () => eventOrderRecorder.push(`${event.target.id}:${eventType}`))); |
+ |
+ function testDragDropEventOrder(dragElement, dropElement, expectedOrder) { |
+ assert_equals(dragElement.children.length, 1); |
+ eventOrderRecorder = []; |
+ simulateDragDrop(dragElement, dropElement); |
+ assert_array_equals(eventOrderRecorder, expectedOrder, |
+ `Testing drag ${dragElement.id} onto ${dropElement.id} actual order: ${eventOrderRecorder}`); |
+ } |
+ |
+ // Test Drag and Drop. |
+ testDragDropEventOrder(editable1, editable2, |
+ ['editable2:drop', 'beforeinput:img:deleteByDrag', 'input:editable1:deleteByDrag', |
+ 'beforeinput:editable2:insertFromDrop', 'input:editable2:insertFromDrop', 'editable1:dragend']); |
+ testRunner.execCommand('undo'); |
+}, 'Testing Drag and Drop event order'); |
+ |
+test(function() { |
+ assertCleanInitialDOM(); |
+ assert_not_equals(window.eventSender, undefined, 'This test requires eventSender.'); |
+ assert_not_equals(window.testRunner, undefined, 'This test requires testRunner.'); |
+ |
+ const editable1 = document.getElementById('editable1'); |
+ const editable2 = document.getElementById('editable2'); |
+ var lastPlainTextData = {}; |
+ var lastHTMLData = {}; |
+ document.addEventListener('beforeinput', event => { |
+ lastPlainTextData[event.inputType] = event.dataTransfer ? event.dataTransfer.getData('text/plain') : null; |
+ lastHTMLData[event.inputType] = event.dataTransfer ? event.dataTransfer.getData('text/html') : null; |
+ }); |
+ |
+ function testDragDropDataTransfer(inputType, dragElement, dropElement, expectedPlainText, expectedHTML) { |
+ assert_equals(dragElement.children.length, 1); |
+ lastPlainTextData = {}; |
+ lastHTMLData = {}; |
+ simulateDragDrop(dragElement, dropElement); |
+ assert_equals(lastPlainTextData[inputType], expectedPlainText, |
+ `Testing '${inputType}' getData('text/plain')`); |
+ if (expectedHTML && expectedHTML.test) { |
+ assert_regexp_match(lastHTMLData[inputType], expectedHTML, |
+ `Testing '${inputType}' getData('text/html')`); |
+ } else { |
+ assert_equals(lastHTMLData[inputType], expectedHTML, |
+ `Testing '${inputType}' getData('text/html')`); |
+ } |
+ } |
+ |
+ // Test Drag and Drop. |
+ testDragDropDataTransfer('deleteByDrag', editable1, editable2, null, null); |
+ testRunner.execCommand('undo'); |
+ testDragDropDataTransfer('insertFromDrop', editable1, editable2, '', /^<img.*greenbox\.png".*>$/); |
+ testRunner.execCommand('undo'); |
+}, 'Testing Drag and Drop dataTransfer'); |
+ |
+test(function() { |
+ assertCleanInitialDOM(); |
+ assert_not_equals(window.eventSender, undefined, 'This test requires eventSender.'); |
+ assert_not_equals(window.testRunner, undefined, 'This test requires testRunner.'); |
+ |
+ const editable1 = document.getElementById('editable1'); |
+ const editable2 = document.getElementById('editable2'); |
+ var inputTypesToPrevent = []; |
+ document.addEventListener('beforeinput', event => { |
+ if (inputTypesToPrevent.indexOf(event.inputType) != -1) |
+ event.preventDefault(); |
+ }); |
+ |
+ function testDragDropPreventDefault(preventDefaultTypes, dragElement, dropElement, expectedDragElementChildren, expectedDropElementChildren) { |
+ assert_equals(dragElement.children.length, 1); |
+ inputTypesToPrevent = preventDefaultTypes; |
+ simulateDragDrop(dragElement, dropElement); |
+ assert_equals(dragElement.children.length, expectedDragElementChildren, |
+ 'Testing preventDefault() on ${preventDefaultTypes} ${dragElement.id} children count'); |
+ assert_equals(dropElement.children.length, expectedDropElementChildren, |
+ 'Testing preventDefault() on ${preventDefaultTypes} ${dropElement.id} children count'); |
+ inputTypesToPrevent = []; |
+ } |
+ |
+ // Preventing single 'beforeinput' will only cancel DOM update for one event, |
+ // the remaining DOM update will still update undo stack. |
+ testDragDropPreventDefault(['deleteByDrag'], editable1, editable2, 1, 1); |
+ testRunner.execCommand('undo'); |
+ testDragDropPreventDefault(['insertFromDrop'], editable1, editable2, 0, 0); |
+ testRunner.execCommand('undo'); |
+ |
+ // Adding 'insertHTML' command to undo stack. |
+ editable2.focus(); |
+ document.execCommand('insertHTML', false, '<b>B</b><i>i</i>'); |
+ assert_equals(editable2.children.length, 2, |
+ '"editable2" should have 2 children after "insertHTML" command'); |
+ // Canceling both |deleteByDrag| and |insertFromDrop| won't modify undo stack. |
+ testDragDropPreventDefault(['deleteByDrag', 'insertFromDrop'], editable1, editable2, 1, 2); |
+ // |undo| will undo last 'insertHTML' command. |
+ testRunner.execCommand('undo'); |
+ assert_equals(editable2.children.length, 0, |
+ '"editable2" should have 0 children after undo "insertHTML"'); |
+}, 'Testing Drag and Drop preventDefault()'); |
+ |
+test(function() { |
+ assertCleanInitialDOM(); |
+ assert_not_equals(window.eventSender, undefined, 'This test requires eventSender.'); |
+ assert_not_equals(window.testRunner, undefined, 'This test requires testRunner.'); |
+ |
+ const editable1 = document.getElementById('editable1'); |
+ const editable2 = document.getElementById('editable2'); |
+ var eventOrderRecorder = []; |
+ [editable1, editable2].forEach(editable => { |
+ editable.addEventListener('beforeinput', event => |
+ eventOrderRecorder.push(`beforeinput:${editable.id}:${event.inputType}`)); |
+ editable.addEventListener('input', event => |
+ eventOrderRecorder.push(`input:${editable.id}:${event.inputType}`)); |
+ editable.addEventListener('drop', event => |
+ eventOrderRecorder.push(`${editable.id}:drop`)); |
+ editable.addEventListener('dragend', event => |
+ eventOrderRecorder.push(`${editable.id}:dragend`)); |
+ }); |
+ |
+ function testDragDropEventOrder(dragElement, dropElement, expectedOrder) { |
+ assert_equals(dragElement.children.length, 1); |
+ eventOrderRecorder = []; |
+ simulateDragDrop(dragElement, dropElement); |
+ assert_array_equals(eventOrderRecorder, expectedOrder, |
+ `Testing drag ${dragElement.id} onto ${dropElement.id} actual order: ${eventOrderRecorder}`); |
+ } |
+ |
+ function removeEditable1Listener() { |
+ editable1.remove(); |
+ } |
+ |
+ function removeEditable2Listener() { |
+ editable2.remove(); |
+ } |
+ |
+ // Testing remove drop target, |editable2| won't get 'beforeinput' as it's disconnected. |
+ editable1.addEventListener('beforeinput', removeEditable2Listener); |
+ testDragDropEventOrder(editable1, editable2, |
+ ['editable2:drop', 'beforeinput:editable1:deleteByDrag', 'input:editable1:deleteByDrag', 'editable1:dragend']); |
+ editable1.removeEventListener('beforeinput', removeEditable2Listener); |
+ testRunner.execCommand('undo'); |
+ document.body.appendChild(editable2); |
+ |
+ // Testing remove drag target, |editable1| won't receive DOM updates after disconnected. |
+ editable1.addEventListener('beforeinput', removeEditable1Listener); |
+ testDragDropEventOrder(editable1, editable2, |
+ ['editable2:drop', 'beforeinput:editable1:deleteByDrag', 'beforeinput:editable2:insertFromDrop', |
+ 'input:editable2:insertFromDrop', 'editable1:dragend']); |
+ editable1.removeEventListener('beforeinput', removeEditable1Listener); |
+ testRunner.execCommand('undo'); |
+ document.body.appendChild(editable1); |
+}, 'Testing element removed by event handler'); |
+</script> |
+</body> |
+</html> |