OLD | NEW |
(Empty) | |
| 1 <!DOCTYPE html> |
| 2 <html> |
| 3 <head> |
| 4 <title>InputEvent: beforeinput for Drag and Drop</title> |
| 5 <script src="../../../resources/testharness.js"></script> |
| 6 <script src="../../../resources/testharnessreport.js"></script> |
| 7 <style> |
| 8 div, img { |
| 9 width: 100px; |
| 10 height: 100px; |
| 11 } |
| 12 </style> |
| 13 </head> |
| 14 <body> |
| 15 <div id="editable1" contenteditable><img id="img" src="../resources/greenbox.png
"></div> |
| 16 <div id="editable2" contenteditable></div> |
| 17 <textarea id="textarea1">Text</textarea> |
| 18 <textarea id="barrier"></textarea> |
| 19 <script> |
| 20 function simulateDragDrop(dragElement, dropElement) { |
| 21 eventSender.mouseMoveTo(dragElement.offsetLeft + dragElement.offsetWidth / 2
, |
| 22 dragElement.offsetTop + dragElement.offsetHeight / 2
); |
| 23 eventSender.mouseDown(); |
| 24 eventSender.leapForward(600); |
| 25 eventSender.mouseMoveTo(dropElement.offsetLeft + dropElement.offsetWidth / 2
, |
| 26 dropElement.offsetTop + dropElement.offsetHeight / 2
); |
| 27 eventSender.mouseUp(); |
| 28 } |
| 29 |
| 30 function assertCleanInitialDOM(logInfo) { |
| 31 const editable1 = document.getElementById('editable1'); |
| 32 const editable2 = document.getElementById('editable2'); |
| 33 const textarea1 = document.getElementById('textarea1'); |
| 34 assert_equals(editable1.children.length, 1, `${logInfo}, DOM is dirty`); |
| 35 assert_equals(editable1.children[0].tagName, 'IMG', `${logInfo}, DOM is dirt
y`); |
| 36 assert_equals(editable2.children.length, 0, `${logInfo}, DOM is dirty`); |
| 37 assert_equals(textarea1.value, 'Text', `${logInfo}, DOM is dirty`); |
| 38 } |
| 39 |
| 40 test(function() { |
| 41 assertCleanInitialDOM(); |
| 42 assert_not_equals(window.eventSender, undefined, 'This test requires eventSe
nder.'); |
| 43 assert_not_equals(window.testRunner, undefined, 'This test requires testRunn
er.'); |
| 44 |
| 45 const editable1 = document.getElementById('editable1'); |
| 46 const editable2 = document.getElementById('editable2'); |
| 47 const textarea1 = document.getElementById('textarea1'); |
| 48 |
| 49 function preventDeleteByDragListener(event) { |
| 50 if (event.inputType == 'deleteByDrag') |
| 51 event.preventDefault(); |
| 52 } |
| 53 |
| 54 function preventInsertFromDropListener(event) { |
| 55 if (event.inputType == 'insertFromDrop') |
| 56 event.preventDefault(); |
| 57 } |
| 58 |
| 59 const undoBarrier = document.getElementById('barrier'); |
| 60 undoBarrier.focus(); |
| 61 document.execCommand('insertText', false, 'abc'); |
| 62 function assertBarrierUnchanged() { |
| 63 assert_equals(undoBarrier.value, 'abc'); |
| 64 } |
| 65 |
| 66 // Normally Drag&Drop requires a single Undo. |
| 67 simulateDragDrop(editable1, editable2); |
| 68 testRunner.execCommand('undo'); |
| 69 assertCleanInitialDOM('Normal Drag&Drop'); |
| 70 assertBarrierUnchanged(); |
| 71 |
| 72 // Canceling |DeleteByDrag|, still require a single Undo. |
| 73 editable1.addEventListener('beforeinput', preventDeleteByDragListener); |
| 74 simulateDragDrop(editable1, editable2); |
| 75 testRunner.execCommand('undo'); |
| 76 assertCleanInitialDOM('Canceling |DeleteByDrag|'); |
| 77 editable1.removeEventListener('beforeinput', preventDeleteByDragListener); |
| 78 assertBarrierUnchanged(); |
| 79 |
| 80 // Canceling |InsertFromDrop|, still require a single Undo. |
| 81 editable2.addEventListener('beforeinput', preventInsertFromDropListener); |
| 82 simulateDragDrop(editable1, editable2); |
| 83 testRunner.execCommand('undo'); |
| 84 assertCleanInitialDOM('Canceling |InsertFromDrop|'); |
| 85 editable2.removeEventListener('beforeinput', preventInsertFromDropListener); |
| 86 assertBarrierUnchanged(); |
| 87 |
| 88 // Canceling both, shouldn't create undo entry. |
| 89 editable1.addEventListener('beforeinput', preventDeleteByDragListener); |
| 90 editable2.addEventListener('beforeinput', preventInsertFromDropListener); |
| 91 simulateDragDrop(editable1, editable2); |
| 92 assertCleanInitialDOM('Canceling both'); |
| 93 testRunner.execCommand('undo'); |
| 94 assert_equals(undoBarrier.value, ''); |
| 95 testRunner.execCommand('redo'); |
| 96 assertBarrierUnchanged(); |
| 97 editable1.removeEventListener('beforeinput', preventDeleteByDragListener); |
| 98 editable2.removeEventListener('beforeinput', preventInsertFromDropListener); |
| 99 |
| 100 // Two Drag&Drop, cancel first |InsertFromDrop| and second |DeleteByDrag|, s
hould still create 2 undo entries. |
| 101 editable2.addEventListener('beforeinput', preventInsertFromDropListener); |
| 102 simulateDragDrop(editable1, editable2); |
| 103 editable2.removeEventListener('beforeinput', preventInsertFromDropListener); |
| 104 textarea1.addEventListener('beforeinput', preventDeleteByDragListener); |
| 105 textarea1.select(); |
| 106 simulateDragDrop(textarea1, editable2); |
| 107 textarea1.removeEventListener('beforeinput', preventDeleteByDragListener); |
| 108 assert_equals(editable1.children.length, 0); |
| 109 assert_equals(editable2.innerHTML, 'Text'); |
| 110 assert_equals(textarea1.value, 'Text'); |
| 111 // First undo. |
| 112 testRunner.execCommand('undo'); |
| 113 assert_equals(editable1.children.length, 0); |
| 114 assert_equals(editable2.innerHTML, ''); |
| 115 assert_equals(textarea1.value, 'Text'); |
| 116 // Second undo. |
| 117 testRunner.execCommand('undo'); |
| 118 assert_equals(editable1.children.length, 1); |
| 119 assert_equals(editable2.innerHTML, ''); |
| 120 assert_equals(textarea1.value, 'Text'); |
| 121 // More undo should reach to |undoBarrier|. |
| 122 assertBarrierUnchanged(); |
| 123 testRunner.execCommand('undo'); |
| 124 assert_equals(undoBarrier.value, ''); |
| 125 testRunner.execCommand('redo'); |
| 126 }, 'Testing Drag and Drop, preventDefault() and Undo entry'); |
| 127 |
| 128 test(function() { |
| 129 assertCleanInitialDOM(); |
| 130 assert_not_equals(window.eventSender, undefined, 'This test requires eventSe
nder.'); |
| 131 assert_not_equals(window.testRunner, undefined, 'This test requires testRunn
er.'); |
| 132 |
| 133 const editable1 = document.getElementById('editable1'); |
| 134 const editable2 = document.getElementById('editable2'); |
| 135 var eventOrderRecorder = []; |
| 136 document.addEventListener('beforeinput', event => |
| 137 eventOrderRecorder.push(`beforeinput:${event.target.id}:${event.inputTyp
e}`)); |
| 138 document.addEventListener('input', event => |
| 139 eventOrderRecorder.push(`input:${event.target.id}:${event.inputType}`)); |
| 140 ['drop', 'dragend'].forEach(eventType => document.addEventListener( |
| 141 eventType, () => eventOrderRecorder.push(`${event.target.id}:${event
Type}`))); |
| 142 |
| 143 function testDragDropEventOrder(dragElement, dropElement, expectedOrder) { |
| 144 assert_equals(dragElement.children.length, 1); |
| 145 eventOrderRecorder = []; |
| 146 simulateDragDrop(dragElement, dropElement); |
| 147 assert_array_equals(eventOrderRecorder, expectedOrder, |
| 148 `Testing drag ${dragElement.id} onto ${dropElement.id} actual order:
${eventOrderRecorder}`); |
| 149 } |
| 150 |
| 151 // Test Drag and Drop. |
| 152 testDragDropEventOrder(editable1, editable2, |
| 153 ['editable2:drop', 'beforeinput:img:deleteByDrag', 'input:editable1:dele
teByDrag', |
| 154 'beforeinput:editable2:insertFromDrop', 'input:editable2:insertFromDrop'
, 'editable1:dragend']); |
| 155 testRunner.execCommand('undo'); |
| 156 }, 'Testing Drag and Drop event order'); |
| 157 |
| 158 test(function() { |
| 159 assertCleanInitialDOM(); |
| 160 assert_not_equals(window.eventSender, undefined, 'This test requires eventSe
nder.'); |
| 161 assert_not_equals(window.testRunner, undefined, 'This test requires testRunn
er.'); |
| 162 |
| 163 const editable1 = document.getElementById('editable1'); |
| 164 const editable2 = document.getElementById('editable2'); |
| 165 var lastPlainTextData = {}; |
| 166 var lastHTMLData = {}; |
| 167 document.addEventListener('beforeinput', event => { |
| 168 lastPlainTextData[event.inputType] = event.dataTransfer ? event.dataTran
sfer.getData('text/plain') : null; |
| 169 lastHTMLData[event.inputType] = event.dataTransfer ? event.dataTransfer.
getData('text/html') : null; |
| 170 }); |
| 171 |
| 172 function testDragDropDataTransfer(inputType, dragElement, dropElement, expec
tedPlainText, expectedHTML) { |
| 173 assert_equals(dragElement.children.length, 1); |
| 174 lastPlainTextData = {}; |
| 175 lastHTMLData = {}; |
| 176 simulateDragDrop(dragElement, dropElement); |
| 177 assert_equals(lastPlainTextData[inputType], expectedPlainText, |
| 178 `Testing '${inputType}' getData('text/plain')`); |
| 179 if (expectedHTML && expectedHTML.test) { |
| 180 assert_regexp_match(lastHTMLData[inputType], expectedHTML, |
| 181 `Testing '${inputType}' getData('text/html')`); |
| 182 } else { |
| 183 assert_equals(lastHTMLData[inputType], expectedHTML, |
| 184 `Testing '${inputType}' getData('text/html')`); |
| 185 } |
| 186 } |
| 187 |
| 188 // Test Drag and Drop. |
| 189 testDragDropDataTransfer('deleteByDrag', editable1, editable2, null, null); |
| 190 testRunner.execCommand('undo'); |
| 191 testDragDropDataTransfer('insertFromDrop', editable1, editable2, '', /^<img.
*greenbox\.png".*>$/); |
| 192 testRunner.execCommand('undo'); |
| 193 }, 'Testing Drag and Drop dataTransfer'); |
| 194 |
| 195 test(function() { |
| 196 assertCleanInitialDOM(); |
| 197 assert_not_equals(window.eventSender, undefined, 'This test requires eventSe
nder.'); |
| 198 assert_not_equals(window.testRunner, undefined, 'This test requires testRunn
er.'); |
| 199 |
| 200 const editable1 = document.getElementById('editable1'); |
| 201 const editable2 = document.getElementById('editable2'); |
| 202 var inputTypesToPrevent = []; |
| 203 document.addEventListener('beforeinput', event => { |
| 204 if (inputTypesToPrevent.indexOf(event.inputType) != -1) |
| 205 event.preventDefault(); |
| 206 }); |
| 207 |
| 208 function testDragDropPreventDefault(preventDefaultTypes, dragElement, dropEl
ement, expectedDragElementChildren, expectedDropElementChildren) { |
| 209 assert_equals(dragElement.children.length, 1); |
| 210 inputTypesToPrevent = preventDefaultTypes; |
| 211 simulateDragDrop(dragElement, dropElement); |
| 212 assert_equals(dragElement.children.length, expectedDragElementChildren, |
| 213 'Testing preventDefault() on ${preventDefaultTypes} ${dragElement.id
} children count'); |
| 214 assert_equals(dropElement.children.length, expectedDropElementChildren, |
| 215 'Testing preventDefault() on ${preventDefaultTypes} ${dropElement.id
} children count'); |
| 216 inputTypesToPrevent = []; |
| 217 } |
| 218 |
| 219 // Preventing single 'beforeinput' will only cancel DOM update for one event
, |
| 220 // the remaining DOM update will still update undo stack. |
| 221 testDragDropPreventDefault(['deleteByDrag'], editable1, editable2, 1, 1); |
| 222 testRunner.execCommand('undo'); |
| 223 testDragDropPreventDefault(['insertFromDrop'], editable1, editable2, 0, 0); |
| 224 testRunner.execCommand('undo'); |
| 225 |
| 226 // Adding 'insertHTML' command to undo stack. |
| 227 editable2.focus(); |
| 228 document.execCommand('insertHTML', false, '<b>B</b><i>i</i>'); |
| 229 assert_equals(editable2.children.length, 2, |
| 230 '"editable2" should have 2 children after "insertHTML" command'); |
| 231 // Canceling both |deleteByDrag| and |insertFromDrop| won't modify undo stac
k. |
| 232 testDragDropPreventDefault(['deleteByDrag', 'insertFromDrop'], editable1, ed
itable2, 1, 2); |
| 233 // |undo| will undo last 'insertHTML' command. |
| 234 testRunner.execCommand('undo'); |
| 235 assert_equals(editable2.children.length, 0, |
| 236 '"editable2" should have 0 children after undo "insertHTML"'); |
| 237 }, 'Testing Drag and Drop preventDefault()'); |
| 238 |
| 239 test(function() { |
| 240 assertCleanInitialDOM(); |
| 241 assert_not_equals(window.eventSender, undefined, 'This test requires eventSe
nder.'); |
| 242 assert_not_equals(window.testRunner, undefined, 'This test requires testRunn
er.'); |
| 243 |
| 244 const editable1 = document.getElementById('editable1'); |
| 245 const editable2 = document.getElementById('editable2'); |
| 246 var eventOrderRecorder = []; |
| 247 [editable1, editable2].forEach(editable => { |
| 248 editable.addEventListener('beforeinput', event => |
| 249 eventOrderRecorder.push(`beforeinput:${editable.id}:${event.inputTyp
e}`)); |
| 250 editable.addEventListener('input', event => |
| 251 eventOrderRecorder.push(`input:${editable.id}:${event.inputType}`)); |
| 252 editable.addEventListener('drop', event => |
| 253 eventOrderRecorder.push(`${editable.id}:drop`)); |
| 254 editable.addEventListener('dragend', event => |
| 255 eventOrderRecorder.push(`${editable.id}:dragend`)); |
| 256 }); |
| 257 |
| 258 function testDragDropEventOrder(dragElement, dropElement, expectedOrder) { |
| 259 assert_equals(dragElement.children.length, 1); |
| 260 eventOrderRecorder = []; |
| 261 simulateDragDrop(dragElement, dropElement); |
| 262 assert_array_equals(eventOrderRecorder, expectedOrder, |
| 263 `Testing drag ${dragElement.id} onto ${dropElement.id} actual order:
${eventOrderRecorder}`); |
| 264 } |
| 265 |
| 266 function removeEditable1Listener() { |
| 267 editable1.remove(); |
| 268 } |
| 269 |
| 270 function removeEditable2Listener() { |
| 271 editable2.remove(); |
| 272 } |
| 273 |
| 274 // Testing remove drop target, |editable2| won't get 'beforeinput' as it's d
isconnected. |
| 275 editable1.addEventListener('beforeinput', removeEditable2Listener); |
| 276 testDragDropEventOrder(editable1, editable2, |
| 277 ['editable2:drop', 'beforeinput:editable1:deleteByDrag', 'input:editable
1:deleteByDrag', 'editable1:dragend']); |
| 278 editable1.removeEventListener('beforeinput', removeEditable2Listener); |
| 279 testRunner.execCommand('undo'); |
| 280 document.body.appendChild(editable2); |
| 281 |
| 282 // Testing remove drag target, |editable1| won't receive DOM updates after d
isconnected. |
| 283 editable1.addEventListener('beforeinput', removeEditable1Listener); |
| 284 testDragDropEventOrder(editable1, editable2, |
| 285 ['editable2:drop', 'beforeinput:editable1:deleteByDrag', 'beforeinput:ed
itable2:insertFromDrop', |
| 286 'input:editable2:insertFromDrop', 'editable1:dragend']); |
| 287 editable1.removeEventListener('beforeinput', removeEditable1Listener); |
| 288 testRunner.execCommand('undo'); |
| 289 document.body.appendChild(editable1); |
| 290 }, 'Testing element removed by event handler'); |
| 291 </script> |
| 292 </body> |
| 293 </html> |
OLD | NEW |