edit-context-input.tentative.html (12714B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>EditContext: The HTMLElement.editContext property</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/resources/testdriver.js"></script> 8 <script src="/resources/testdriver-actions.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 </head> 11 <body> 12 <script> 13 const kBackspaceKey = "\uE003"; 14 const kDeleteKey = "\uE017"; 15 16 async function testBasicTestInput(element) { 17 const editContext = new EditContext(); 18 let textForView = ""; 19 document.body.appendChild(element); 20 let beforeInputType = null; 21 let beforeInputTargetRanges = null; 22 element.addEventListener("beforeinput", e => { 23 beforeInputType = e.inputType; 24 beforeInputTargetRanges = e.getTargetRanges().map( 25 staticRange => [staticRange.startOffset, staticRange.endOffset]); 26 }); 27 editContext.addEventListener("textupdate", e => { 28 textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`; 29 }); 30 element.editContext = editContext; 31 element.focus(); 32 await test_driver.send_keys(element, 'a'); 33 assert_equals(editContext.text, "a"); 34 assert_equals(textForView, "a"); 35 assert_equals(beforeInputType, "insertText"); 36 if (element instanceof HTMLCanvasElement) { 37 // DOM selection doesn't work inside <canvas>, so events 38 // in <canvas> can't have target ranges. 39 assert_equals(beforeInputTargetRanges.length, 0); 40 } else { 41 assert_equals(beforeInputTargetRanges.length, 1); 42 assert_array_equals(beforeInputTargetRanges[0], [0, 0]); 43 } 44 45 element.remove(); 46 } 47 48 promise_test(testBasicTestInput.bind(null, document.createElement("div")), "Basic text input with div"); 49 promise_test(testBasicTestInput.bind(null, document.createElement("canvas")), "Basic text input with canvas"); 50 51 async function testBasicTestInputWithExistingSelection(element) { 52 const editContext = new EditContext(); 53 let textForView = ""; 54 document.body.appendChild(element); 55 editContext.addEventListener("textupdate", e => { 56 textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`; 57 }); 58 element.editContext = editContext; 59 element.focus(); 60 61 editContext.updateText(0, 0, "abcd"); 62 textForView = "abcd"; 63 assert_equals(editContext.text, "abcd"); 64 editContext.updateSelection(2, 3); 65 await test_driver.send_keys(element, 'Z'); 66 assert_equals(editContext.text, "abZd"); 67 assert_equals(textForView, "abZd"); 68 69 editContext.updateSelection(2, 1); 70 await test_driver.send_keys(element, 'Y'); 71 assert_equals(editContext.text, "aYZd"); 72 assert_equals(textForView, "aYZd"); 73 74 element.remove(); 75 } 76 77 promise_test(testBasicTestInputWithExistingSelection.bind(null, document.createElement("div")), "Text insertion with non-collapsed selection with div"); 78 promise_test(testBasicTestInputWithExistingSelection.bind(null, document.createElement("canvas")), "Text insertion with non-collapsed selection with canvas"); 79 80 promise_test(async function() { 81 const editContext = new EditContext(); 82 assert_not_equals(editContext, null); 83 const test = document.createElement("div"); 84 document.body.appendChild(test); 85 test.editContext = editContext; 86 test.focus(); 87 await test_driver.send_keys(test, 'a'); 88 assert_equals(test.innerHTML, ""); 89 test.remove(); 90 }, 'EditContext should disable DOM mutation'); 91 92 promise_test(async function() { 93 const editContext = new EditContext(); 94 assert_not_equals(editContext, null); 95 const test = document.createElement("div"); 96 document.body.appendChild(test); 97 test.focus(); 98 test.editContext = editContext; 99 test.addEventListener("beforeinput", e => { 100 if (e.inputType === "insertText") { 101 e.preventDefault(); 102 } 103 }); 104 await test_driver.send_keys(test, 'a'); 105 assert_equals(editContext.text, ""); 106 test.remove(); 107 }, 'beforeInput(insertText) should be cancelable'); 108 109 promise_test(async () => { 110 let div = document.createElement("div"); 111 document.body.appendChild(div); 112 let divText = "Hello World"; 113 div.innerText = divText; 114 div.editContext = new EditContext(); 115 div.focus(); 116 let got_before_input_event = false; 117 div.addEventListener("beforeinput", e => { 118 got_before_input_event = true; 119 }); 120 let got_textupdate_event = false; 121 div.editContext.addEventListener("textupdate", e => { 122 got_textupdate_event = true; 123 }); 124 125 div.editContext = null; 126 await test_driver.send_keys(div, "a"); 127 128 assert_false(got_textupdate_event, "Shouldn't have received textupdate event after editContext was detached"); 129 assert_false(got_before_input_event, "Shouldn't have received beforeinput event after editContext was detached"); 130 131 div.remove(); 132 }, "EditContext should not receive events after being detached from element"); 133 134 async function testBackspaceAndDelete(element) { 135 const editContext = new EditContext(); 136 let textForView = "hello there"; 137 document.body.appendChild(element); 138 let beforeInputType = null; 139 let beforeInputTargetRanges = null; 140 element.addEventListener("beforeinput", e => { 141 beforeInputType = e.inputType; 142 beforeInputTargetRanges = e.getTargetRanges().map( 143 staticRange => [staticRange.startOffset, staticRange.endOffset]); 144 }); 145 let textUpdateSelection = null; 146 editContext.addEventListener("textupdate", e => { 147 textUpdateSelection = [e.selectionStart, e.selectionEnd]; 148 textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`; 149 }); 150 element.editContext = editContext; 151 editContext.updateText(0, 11, "hello there"); 152 editContext.updateSelection(10, 10); 153 const selection = window.getSelection(); 154 155 await test_driver.send_keys(element, kBackspaceKey); 156 assert_equals(textForView, "hello thee"); 157 assert_array_equals(textUpdateSelection, [9, 9]); 158 assert_equals(beforeInputType, "deleteContentBackward"); 159 assert_equals(beforeInputTargetRanges.length, 0, "Backspace should not have a target range in EditContext"); 160 161 await test_driver.send_keys(element, kDeleteKey); 162 assert_equals(textForView, "hello the"); 163 assert_array_equals(textUpdateSelection, [9, 9]); 164 assert_equals(beforeInputType, "deleteContentForward"); 165 assert_equals(beforeInputTargetRanges.length, 0, "Delete should not have a target range in EditContext"); 166 element.remove(); 167 } 168 169 promise_test(testBackspaceAndDelete.bind(null, document.createElement("div")), "Backspace and delete in EditContext with div"); 170 promise_test(testBackspaceAndDelete.bind(null, document.createElement("canvas")) , "Backspace and delete in EditContext with canvas"); 171 172 async function testBackspaceAndDeleteWithExistingSelection(element) { 173 const editContext = new EditContext(); 174 let textForView = "hello there"; 175 document.body.appendChild(element); 176 let beforeInputType = null; 177 let beforeInputTargetRanges = null; 178 element.addEventListener("beforeinput", e => { 179 beforeInputType = e.inputType; 180 beforeInputTargetRanges = e.getTargetRanges().map( 181 staticRange => [staticRange.startOffset, staticRange.endOffset]); 182 }); 183 let textUpdateSelection = null; 184 editContext.addEventListener("textupdate", e => { 185 textUpdateSelection = [e.selectionStart, e.selectionEnd]; 186 textForView = `${textForView.substring(0, e.updateRangeStart)}${e.text}${textForView.substring(e.updateRangeEnd)}`; 187 }); 188 element.editContext = editContext; 189 const initialText = "abcdefghijklmnopqrstuvwxyz"; 190 editContext.updateText(0, initialText.length, initialText); 191 textForView = initialText; 192 element.focus(); 193 194 editContext.updateSelection(3, 6); 195 await test_driver.send_keys(element, kBackspaceKey); 196 assert_equals(editContext.text, "abcghijklmnopqrstuvwxyz"); 197 assert_equals(textForView, "abcghijklmnopqrstuvwxyz"); 198 assert_array_equals(textUpdateSelection, [3, 3]); 199 assert_equals(beforeInputType, "deleteContentBackward"); 200 assert_equals(beforeInputTargetRanges.length, 0, "Backspace should not have a target range in EditContext"); 201 202 editContext.updateSelection(3, 6); 203 await test_driver.send_keys(element, kDeleteKey); 204 assert_equals(editContext.text, "abcjklmnopqrstuvwxyz"); 205 assert_equals(textForView, "abcjklmnopqrstuvwxyz"); 206 assert_array_equals(textUpdateSelection, [3, 3]); 207 assert_equals(beforeInputType, "deleteContentForward"); 208 assert_equals(beforeInputTargetRanges.length, 0, "Delete should not have a target range in EditContext"); 209 210 editContext.updateSelection(6, 3); 211 await test_driver.send_keys(element, kBackspaceKey); 212 assert_equals(editContext.text, "abcmnopqrstuvwxyz"); 213 assert_equals(textForView, "abcmnopqrstuvwxyz"); 214 assert_array_equals(textUpdateSelection, [3, 3]); 215 assert_equals(beforeInputType, "deleteContentBackward"); 216 assert_equals(beforeInputTargetRanges.length, 0, "Backspace should not have a target range in EditContext"); 217 218 editContext.updateSelection(6, 3); 219 await test_driver.send_keys(element, kDeleteKey); 220 assert_equals(editContext.text, "abcpqrstuvwxyz"); 221 assert_equals(textForView, "abcpqrstuvwxyz"); 222 assert_array_equals(textUpdateSelection, [3, 3]); 223 assert_equals(beforeInputType, "deleteContentForward"); 224 assert_equals(beforeInputTargetRanges.length, 0, "Delete should not have a target range in EditContext"); 225 226 element.remove(); 227 } 228 229 promise_test(testBackspaceAndDeleteWithExistingSelection.bind(null, document.createElement("div")), "Backspace and delete with existing selection with div"); 230 promise_test(testBackspaceAndDeleteWithExistingSelection.bind(null, document.createElement("canvas")) , "Backspace and delete with existing selection with canvas"); 231 232 promise_test(async function() { 233 const iframe = document.createElement("iframe"); 234 document.body.appendChild(iframe); 235 const editContext = new EditContext(); 236 iframe.contentDocument.body.editContext = editContext; 237 iframe.contentDocument.body.focus(); 238 let got_textupdate_event = false; 239 editContext.addEventListener("textupdate", e => { 240 got_textupdate_event = true; 241 }); 242 await test_driver.send_keys(iframe.contentDocument.body, "a"); 243 assert_equals(iframe.contentDocument.body.innerHTML, "", "EditContext should disable DOM modification in iframe."); 244 assert_true(got_textupdate_event, "Input in iframe EditContext should trigger textupdate event"); 245 iframe.remove(); 246 }, 'EditContext constructed outside iframe can be used in iframe'); 247 248 promise_test(async function () { 249 const div = document.createElement("div"); 250 document.body.appendChild(div); 251 const editContext = new EditContext(); 252 div.editContext = editContext; 253 let textupdateEventCount = 0; 254 editContext.addEventListener("textupdate", e => { 255 textupdateEventCount++; 256 }); 257 258 div.focus(); 259 await test_driver.send_keys(div, 'a'); 260 assert_equals(textupdateEventCount, 1); 261 assert_equals(div.innerHTML, ''); 262 263 const iframe = document.createElement('iframe'); 264 document.body.appendChild(iframe); 265 iframe.contentDocument.body.appendChild(div); 266 267 div.focus(); 268 await test_driver.send_keys(div, 'b'); 269 assert_equals(textupdateEventCount, 2); 270 assert_equals(div.innerHTML, ''); 271 272 iframe.remove(); 273 }, 'Textupdate event should be fired on edit context when the editor element is moved to an iframe'); 274 275 promise_test(async function() { 276 const div = document.createElement("div"); 277 const input = document.createElement("input"); 278 document.body.appendChild(div); 279 document.body.appendChild(input); 280 const editContext = new EditContext(); 281 div.editContext = editContext; 282 div.focus(); 283 div.remove(); 284 input.focus(); 285 await test_driver.send_keys(input, "a"); 286 assert_equals(input.value, "a", "input should have received text input"); 287 288 input.remove(); 289 }, 'Removing EditContext-associated element with focus doesn\'t prevent further text input on the page'); 290 </script> 291 </body> 292 </html>