blob: 52c640f4e44ffed30c6b7f858b08b24dfc0e351a [file] [log] [blame]
<!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 {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="editable1" contenteditable><b id="boldtext">EditableText</b></div>
<div id="editable2" contenteditable></div>
<textarea id="textarea1">Text</textarea>
<textarea id="barrier"></textarea>
<input id="input_drag">
<input id="input_drop">
<script>
function simulateDragDrop(dragElement, dropElement, start, end) {
if (dragElement.select) {
if (start === undefined || end === undefined) {
dragElement.select();
} else {
dragElement.focus();
dragElement.setSelectionRange(start, end);
}
} else {
var selection = window.getSelection();
selection.collapse(dragElement, 0);
selection.extend(dragElement, 1);
}
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].innerHTML, 'EditableText', `${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(log) {
assert_equals(undoBarrier.value, 'abc', log);
}
// Normally Drag&Drop requires a single Undo.
simulateDragDrop(editable1, editable2);
assert_equals(editable1.children.length, 0, `Normal Drag&Drop should remove data from editable1.`);
assert_equals(editable2.children.length, 1, `Normal Drag&Drop should insert data into editable2.`);
testRunner.execCommand('undo');
assertCleanInitialDOM('Normal Drag&Drop');
assertBarrierUnchanged('step 1');
// 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('step 2');
// 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('step 3');
// 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('step 4');
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('step 5');
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:boldtext: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, 'EditableText', /^.*EditableText<\/b>$/);
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');
test(() => {
assert_not_equals(window.eventSender, undefined, 'This test requires eventSender.');
assert_not_equals(window.testRunner, undefined, 'This test requires testRunner.');
const input_drag = document.getElementById('input_drag');
const input_drop = document.getElementById('input_drop');
input_drag.value = '12345678';
input_drop.value = '4';
simulateDragDrop(input_drag, input_drop, 1, 8);
assert_equals(input_drag.value, '1');
assert_equals(input_drop.value, '42345678');
testRunner.execCommand('undo');
assert_equals(input_drag.value, '12345678');
assert_equals(input_drop.value, '4');
}, 'Undo drag&drop should update input.value');
</script>
</body>
</html>