test_replace_text.html (12946B)
1 <!DOCTYPE html> 2 <!-- 3 https://bugzilla.mozilla.org/show_bug.cgi?id=1149826 4 --> 5 <html> 6 <head> 7 <title>Test for replaceText</title> 8 <script src="/tests/SimpleTest/SimpleTest.js"></script> 9 <script src="/tests/SimpleTest/EventUtils.js"></script> 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 11 </head> 12 13 <body> 14 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149826">Mozilla Bug 1149826</a><br> 15 <input type="text"><br> 16 <textarea></textarea> 17 <div contenteditable></div> 18 19 <script> 20 const gDOMWindowUtils = _getDOMWindowUtils(window); 21 const Ci = SpecialPowers.Ci; 22 const IS_WIN = navigator.platform.indexOf("Win") == 0; 23 24 async function testReplaceText(INPUT_TESTS, TEXTAREA_TESTS, CONTENTEDITABLE_TESTS, aPreventSetSelection) { 25 await SimpleTest.promiseFocus(); 26 27 const flags = aPreventSetSelection ? 28 Ci.nsIDOMWindowUtils.CONTENT_COMMAND_FLAG_PREVENT_SET_SELECTION : 29 0; 30 31 const input = document.querySelector("input"); 32 input.focus(); 33 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 34 35 info("for <input>"); 36 37 for (const TEST of INPUT_TESTS) { 38 input.value = TEST.before.value; 39 input.selectionStart = TEST.before.start; 40 input.selectionEnd = TEST.before.end; 41 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 42 43 input.addEventListener("beforeinput", e => { 44 is(e.inputType, "insertReplacementText", 45 "inputType in input must be insertReplacementText by replaceText"); 46 is(input.selectionStart, TEST.replace.start, 47 "Before inputReplacementText, start offset should be changed to the replace start"); 48 is(input.selectionEnd, TEST.replace.start + TEST.replace.src.length, 49 "Before inputReplacementText, end offset should be changed to the replace end"); 50 }, { once: true } ); 51 52 const promiseAfterOnInput = 53 new Promise(resolve => input.addEventListener("input", e => { 54 is(e.inputType, "insertReplacementText", 55 "inputType must be insertReplacementText by replaceText"); 56 resolve(); 57 }, { once: true } )); 58 gDOMWindowUtils.sendContentCommandEvent( 59 "replaceText", 60 null, 61 TEST.replace.value, 62 TEST.replace.start, 63 TEST.replace.src, 64 flags 65 ); 66 await promiseAfterOnInput 67 68 is(input.value, TEST.after.value, 69 "replaceText in input replaces inner text"); 70 is(input.selectionStart, TEST.after.start, 71 "replaceText in input sets expected selection start"); 72 is(input.selectionEnd, TEST.after.end, 73 "replaceText in input sets expected selection end"); 74 } 75 76 const textarea = document.querySelector("textarea"); 77 textarea.focus(); 78 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 79 80 info("for <textarea>"); 81 82 for (const TEST of TEXTAREA_TESTS) { 83 textarea.value = TEST.before.value; 84 textarea.selectionStart = TEST.before.start; 85 textarea.selectionEnd = TEST.before.end; 86 87 textarea.addEventListener("beforeinput", e => { 88 is(e.inputType, "insertReplacementText", 89 "inputType must be insertReplacementText by replaceText"); 90 is(textarea.selectionStart, TEST.replace.start, 91 "Before inputReplacementText, start offset be changed to the replace start"); 92 is(textarea.selectionEnd, TEST.replace.start + TEST.replace.src.length, 93 "Before inputReplacementText, end offset be changed to the replace end"); 94 }, { once: true } ); 95 96 const promiseAfterOnTextarea = 97 new Promise(resolve => textarea.addEventListener("input", e => { 98 is(e.inputType, "insertReplacementText", 99 "inputType must be insertReplacementText by replaceText"); 100 resolve(); 101 }, { once: true } )); 102 gDOMWindowUtils.sendContentCommandEvent( 103 "replaceText", 104 null, 105 TEST.replace.value, 106 TEST.replace.start, 107 TEST.replace.src, 108 flags 109 ); 110 await promiseAfterOnTextarea 111 112 is(textarea.value, TEST.after.value, 113 "replaceText in textarea replaces inner text"); 114 is(textarea.selectionStart, TEST.after.start, 115 "replaceText in textarea sets expected selection start"); 116 is(textarea.selectionEnd, TEST.after.end, 117 "replaceText in textarea sets expected selection end"); 118 } 119 120 const editingHost = document.querySelector("div[contenteditable]"); 121 editingHost.focus(); 122 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 123 124 info("for contenteditable"); 125 126 for (const TEST of CONTENTEDITABLE_TESTS) { 127 editingHost.innerHTML = TEST.before.value; 128 window.getSelection().setBaseAndExtent( 129 // eslint-disable-next-line no-eval 130 eval(TEST.before.focusNode), 131 TEST.before.focusOffset, 132 // eslint-disable-next-line no-eval 133 eval(TEST.before.focusNode), 134 TEST.before.focusOffset 135 ); 136 137 editingHost.addEventListener("beforeinput", e => { 138 const selection = window.getSelection(); 139 is(e.inputType, "insertReplacementText", 140 "inputType must be insertReplacementText by replaceText"); 141 // eslint-disable-next-line no-eval 142 is(selection.anchorNode, eval(TEST.replace.textNode), 143 "Before inputReplacementText, focus node is the Text containing replacing text"); 144 is(selection.anchorOffset, TEST.replace.start, 145 "Before inputReplacementText, focus offset is start of the replace start"); 146 // eslint-disable-next-line no-eval 147 is(selection.focusNode, eval(TEST.replace.textNode), 148 "Before inputReplacementText, focus node is the Text containing replacing text"); 149 is(selection.focusOffset, TEST.replace.start + TEST.replace.src.length, 150 "Before inputReplacementText, focus offset is start of the replace start"); 151 }, { once: true } ); 152 153 const promiseAfterEditingHost = 154 new Promise(resolve => editingHost.addEventListener("input", e => { 155 is(e.inputType, "insertReplacementText", 156 "inputType must be insertReplacementText by replaceText"); 157 resolve(); 158 }, { once: true } )); 159 gDOMWindowUtils.sendContentCommandEvent( 160 "replaceText", 161 null, 162 TEST.replace.value, 163 TEST.replace.start, 164 TEST.replace.src, 165 flags 166 ); 167 await promiseAfterEditingHost 168 169 is(editingHost.textContent, TEST.after.value, 170 "replaceText in contenteditable replaces inner text"); 171 const selection = window.getSelection(); 172 // eslint-disable-next-line no-eval 173 is(selection.focusNode, eval(TEST.after.focusNode), 174 "replaceText in contenteditable sets expected focusNode"); 175 is(selection.focusOffset, TEST.after.focusOffset, 176 "replaceText in contenteditable sets expected focusOffset"); 177 } 178 } 179 180 add_task(async function testReplaceTextWithoutPreventSetSelection() { 181 const INPUT_TESTS = [ 182 { before: { 183 value: "foo", start: 3, end: 3 184 }, 185 replace: { 186 src: "o", value: "bar", start: 1 187 }, 188 after: { 189 value: "fbaro", start: 4, end: 4 190 } 191 }, 192 { before: { 193 value: "foo ", start: 4, end: 4 194 }, 195 replace: { 196 src: "oo", value: "bar", start: 1 197 }, 198 after: { 199 value: "fbar ", start: 4, end: 4 200 } 201 }]; 202 203 const TEXTAREA_TESTS = [ 204 { before: { 205 value: "foo", start: 3, end: 3 206 }, 207 replace: { 208 src: "o", value: "bar", start: 1 209 }, 210 after: { 211 value: "fbaro", start: 4, end: 4 212 } 213 }, 214 { before: { 215 value: "foo ", start: 4, end: 4 216 }, 217 replace: { 218 src: "oo", value: "bar", start: 1 219 }, 220 after: { 221 value: "fbar ", start: 4, end: 4 222 } 223 }]; 224 225 const CONTENTEDITABLE_TESTS = [ 226 { before: { 227 value: "foo", focusNode: "editingHost.firstChild", focusOffset: 3 228 }, 229 replace: { 230 src: "o", value: "bar", start: 1, textNode: "editingHost.firstChild", 231 }, 232 after: { 233 value: "fbaro", focusNode: "editingHost.firstChild", focusOffset: 4, isCollapsed: true 234 }, 235 }, 236 { before: { 237 value: "foo foo", focusNode: "editingHost.firstChild", focusOffset: 4 238 }, 239 replace: { 240 src: "oo", value: "bar", start: 1, textNode: "editingHost.firstChild", 241 }, 242 after: { 243 value: "fbar foo", focusNode: "editingHost.firstChild", focusOffset: 4, isCollapsed: true 244 }, 245 }]; 246 247 await testReplaceText(INPUT_TESTS, TEXTAREA_TESTS, CONTENTEDITABLE_TESTS, false); 248 }); 249 250 add_task(async function testReplaceTextWithPreventSetSelection() { 251 const INPUT_TESTS = [ 252 { before: { 253 value: "foo", start: 3, end: 3 254 }, 255 replace: { 256 src: "o", value: "bar", start: 1 257 }, 258 after: { 259 value: "fbaro", start: 5, end: 5 260 } 261 }, 262 { before: { 263 value: "foo ", start: 4, end: 4 264 }, 265 replace: { 266 src: "oo", value: "bar", start: 1 267 }, 268 after: { 269 value: "fbar ", start: 5, end: 5 270 } 271 }]; 272 273 const TEXTAREA_TESTS = [ 274 { before: { 275 value: "foo", start: 3, end: 3 276 }, 277 replace: { 278 src: "o", value: "bar", start: 1 279 }, 280 after: { 281 value: "fbaro", start: 5, end: 5 282 } 283 }, 284 { before: { 285 value: "foo ", start: 4, end: 4 286 }, 287 replace: { 288 src: "oo", value: "bar", start: 1 289 }, 290 after: { 291 value: "fbar ", start: 5, end: 5 292 } 293 }]; 294 295 const CONTENTEDITABLE_TESTS = [ 296 { before: { 297 value: "foo", focusNode: "editingHost.firstChild", focusOffset: 3 298 }, 299 replace: { 300 src: "o", value: "bar", start: 1, textNode: "editingHost.firstChild", 301 }, 302 after: { 303 value: "fbaro", focusNode: "editingHost.firstChild", focusOffset: 5, isCollapsed: true 304 }, 305 }, 306 { before: { 307 value: "foo foo", focusNode: "editingHost.firstChild", focusOffset: 4 308 }, 309 replace: { 310 src: "oo", value: "bar", start: 1, textNode: "editingHost.firstChild", 311 }, 312 after: { 313 value: "fbar foo", focusNode: "editingHost.firstChild", focusOffset: 5, isCollapsed: true 314 }, 315 }]; 316 317 await testReplaceText(INPUT_TESTS, TEXTAREA_TESTS, CONTENTEDITABLE_TESTS, true); 318 }); 319 320 add_task(async function testReplaceTextWithCompositionText() { 321 await SimpleTest.promiseFocus(); 322 323 // Don't replace text during composition 324 const input = document.querySelector("input"); 325 input.value = ""; 326 input.focus(); 327 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 328 329 let promise = 330 new Promise(resolve => input.addEventListener("compositionupdate", resolve, { once: true })); 331 synthesizeCompositionChange( 332 { "composition": 333 { "string": "foo", 334 "clauses": 335 [ 336 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE } 337 ] 338 }, 339 }); 340 await promise; 341 342 input.addEventListener("input", e => { 343 isnot(e.inputType, "insertReplacementText", 344 "Don't fire insertReplacementText input event by replaceText"); 345 }, { once: true } ); 346 gDOMWindowUtils.sendContentCommandEvent("replaceText", null, "bar", 1, "o"); 347 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 348 349 promise = new Promise(resolve => input.addEventListener("compositionend", resolve, { once: true })); 350 synthesizeComposition({type: "compositioncommitasis", key: {key: "KEY_Enter"}}); 351 await promise; 352 353 is(input.value, "foo", 354 "replaceText doesn't replace inner text when having composition"); 355 is(input.selectionStart, 3, "replaceText sets caret position to next of replaced text"); 356 is(input.selectionStart, input.selectionEnd, "replaceText sets that selection is collapsed"); 357 }); 358 359 add_task(async function testReplaceTextBeforeCallingPreventDefault() { 360 await SimpleTest.promiseFocus(); 361 362 // Call preventDefault on beforeinput 363 const input = document.querySelector("input"); 364 input.value = "foo"; 365 input.focus(); 366 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 367 368 input.selectionStart = 1 369 input.selectionEnd = 2; 370 371 const promise = new Promise(resolve => input.addEventListener("beforeinput", e => { 372 e.preventDefault(); 373 resolve(); 374 }, { once: true })); 375 gDOMWindowUtils.sendContentCommandEvent("replaceText", null, "bar", 1, "o"); 376 await promise; 377 378 is(input.value, "foo", 379 "replaceText doesn't replace inner text of <input> since preventDefault is called"); 380 is(input.selectionStart, 1, "selectionStart isn't changed since preventDefault is called"); 381 is(input.selectionEnd, 2, "selectionEnd isn't changed since preventDefault is called"); 382 }); 383 384 add_task(async function testReplaceTextWithoutMatch() { 385 await SimpleTest.promiseFocus(); 386 387 const input = document.querySelector("input"); 388 input.value = "foo"; 389 input.focus(); 390 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 391 392 input.selectionStart = 1 393 input.selectionEnd = 2; 394 395 gDOMWindowUtils.sendContentCommandEvent("replaceText", null, "bar", 1, "a"); 396 await new Promise(resolve => SimpleTest.executeSoon(resolve)); 397 398 is(input.value, "foo", 399 "replaceText doesn't replace inner text of <input> due to not matched"); 400 is(input.selectionStart, 1, "selectionStart isn't changed due to failed"); 401 is(input.selectionEnd, 2, "selectionEnd isn't changed due to failed"); 402 }); 403 </script> 404 </body> 405 </html>