test_MozEditableElement_setUserInput.html (32224B)
1 <!DOCTYPE> 2 <html> 3 <head> 4 <title>Test for MozEditableElement.setUserInput()</title> 5 <script src="/tests/SimpleTest/SimpleTest.js"></script> 6 <script src="/tests/SimpleTest/EventUtils.js"></script> 7 <link rel="stylesheet" href="/tests/SimpleTest/test.css"> 8 </head> 9 <body> 10 <div id="display"> 11 </div> 12 <div id="content"></div> 13 <pre id="test"> 14 </pre> 15 16 <script class="testbody" type="application/javascript"> 17 SimpleTest.waitForExplicitFinish(); 18 // eslint-disable-next-line complexity 19 SimpleTest.waitForFocus(async () => { 20 const kSetUserInputCancelable = SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input"); 21 22 let content = document.getElementById("content"); 23 /** 24 * Test structure: 25 * element: the tag name to create. 26 * type: the type attribute value for the element. If unnecessary omit it. 27 * input: the values calling setUserInput() with. 28 * before: used when calling setUserInput() before the element gets focus. 29 * after: used when calling setUserInput() after the element gets focus. 30 * result: the results of calling setUserInput(). 31 * before: the element's expected value of calling setUserInput() before the element gets focus. 32 * after: the element's expected value of calling setUserInput() after the element gets focus. 33 * fireBeforeInputEvent: true if "beforeinput" event should be fired. Otherwise, false. 34 * fireInputEvent: true if "input" event should be fired. Otherwise, false. 35 */ 36 for (let test of [{element: "input", type: "hidden", 37 input: {before: "3", after: "6"}, 38 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}}, 39 {element: "input", type: "text", 40 input: {before: "3", after: "6"}, 41 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 42 {element: "input", type: "search", 43 input: {before: "3", after: "6"}, 44 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 45 {element: "input", type: "tel", 46 input: {before: "3", after: "6"}, 47 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 48 {element: "input", type: "url", 49 input: {before: "3", after: "6"}, 50 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 51 {element: "input", type: "email", 52 input: {before: "3", after: "6"}, 53 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 54 {element: "input", type: "password", 55 input: {before: "3", after: "6"}, 56 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 57 // "date" does not support setUserInput, but dispatches "input" event... 58 {element: "input", type: "date", 59 input: {before: "3", after: "6"}, 60 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 61 // "month" does not support setUserInput, but dispatches "input" event... 62 {element: "input", type: "month", 63 input: {before: "3", after: "6"}, 64 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 65 // "week" does not support setUserInput, but dispatches "input" event... 66 {element: "input", type: "week", 67 input: {before: "3", after: "6"}, 68 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 69 // "time" does not support setUserInput, but dispatches "input" event... 70 {element: "input", type: "time", 71 input: {before: "3", after: "6"}, 72 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 73 // "datetime-local" does not support setUserInput, but dispatches "input" event... 74 {element: "input", type: "datetime-local", 75 input: {before: "3", after: "6"}, 76 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 77 {element: "input", type: "number", 78 input: {before: "3", after: "6"}, 79 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}, 80 {element: "input", type: "range", 81 input: {before: "3", after: "6"}, 82 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 83 // "color" does not support setUserInput, but dispatches "input" event... 84 {element: "input", type: "color", 85 input: {before: "#5C5C5C", after: "#FFFFFF"}, 86 result: {before: "#5c5c5c", after:"#ffffff", fireBeforeInputEvent: false, fireInputEvent: true}}, 87 {element: "input", type: "checkbox", 88 input: {before: "3", after: "6"}, 89 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 90 {element: "input", type: "radio", 91 input: {before: "3", after: "6"}, 92 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}}, 93 // "file" is not supported by setUserInput? But there is a path... 94 {element: "input", type: "file", 95 input: {before: "3", after: "6"}, 96 result: {before: "", after:"", fireBeforeInputEvent: false, fireInputEvent: true}}, 97 {element: "input", type: "submit", 98 input: {before: "3", after: "6"}, 99 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}}, 100 {element: "input", type: "image", 101 input: {before: "3", after: "6"}, 102 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}}, 103 {element: "input", type: "reset", 104 input: {before: "3", after: "6"}, 105 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}}, 106 {element: "input", type: "button", 107 input: {before: "3", after: "6"}, 108 result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}}, 109 {element: "textarea", 110 input: {before: "3", after: "6"}, 111 result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}]) { 112 let tag = 113 test.type !== undefined ? `<${test.element} type="${test.type}">` : 114 `<${test.element}>`; 115 content.innerHTML = 116 test.element !== "input" ? tag : `${tag}</${test.element}>`; 117 content.scrollTop; // Flush pending layout. 118 let target = content.firstChild; 119 120 let inputEvents = [], beforeInputEvents = []; 121 function onBeforeInput(aEvent) { 122 beforeInputEvents.push(aEvent); 123 } 124 function onInput(aEvent) { 125 inputEvents.push(aEvent); 126 } 127 target.addEventListener("beforeinput", onBeforeInput); 128 target.addEventListener("input", onInput); 129 130 // Before setting focus, editor of the element may have not been created yet. 131 let previousValue = target.value; 132 SpecialPowers.wrap(target).setUserInput(test.input.before); 133 if (target.value == previousValue && test.result.before != previousValue) { 134 todo_is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`); 135 } else { 136 is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`); 137 } 138 if (target.value == previousValue) { 139 if (test.type === "date" || test.type === "time" || test.type === "datetime-local") { 140 todo_is(inputEvents.length, 0, 141 `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 142 } else { 143 is(inputEvents.length, 0, 144 `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 145 } 146 } else { 147 if (!test.result.fireBeforeInputEvent) { 148 is(beforeInputEvents.length, 0, 149 `No "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 150 } else { 151 is(beforeInputEvents.length, 1, 152 `Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 153 } 154 if (!test.result.fireInputEvent) { 155 // HTML spec defines that "input" elements whose type are "hidden", 156 // "submit", "image", "reset" and "button" shouldn't fire input event 157 // when its value is changed. 158 // XXX Perhaps, we shouldn't support setUserInput() with such types. 159 if (test.type === "hidden" || 160 test.type === "submit" || 161 test.type === "image" || 162 test.type === "reset" || 163 test.type === "button") { 164 todo_is(inputEvents.length, 0, 165 `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 166 } else { 167 is(inputEvents.length, 0, 168 `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 169 } 170 } else { 171 is(inputEvents.length, 1, 172 `Only one "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 173 } 174 } 175 if (inputEvents.length) { 176 if (SpecialPowers.wrap(target).isInputEventTarget) { 177 if (test.type === "time") { 178 todo(inputEvents[0] instanceof InputEvent, 179 `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 180 } else { 181 if (beforeInputEvents.length && test.result.fireBeforeInputEvent) { 182 is(beforeInputEvents[0].cancelable, kSetUserInputCancelable, 183 `"beforeinput" event for "insertReplacementText" should be cancelable when setUserInput("${test.input.before}") is called before ${tag} gets focus unless it's suppressed by the pref`); 184 is(beforeInputEvents[0].inputType, "insertReplacementText", 185 `inputType of "beforeinput"event should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 186 is(beforeInputEvents[0].data, test.input.before, 187 `data of "beforeinput" event should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 188 is(beforeInputEvents[0].dataTransfer, null, 189 `dataTransfer of "beforeinput" event should be null when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 190 is(beforeInputEvents[0].getTargetRanges().length, 0, 191 `getTargetRanges() of "beforeinput" event should return empty array when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 192 } 193 ok(inputEvents[0] instanceof InputEvent, 194 `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 195 is(inputEvents[0].inputType, "insertReplacementText", 196 `inputType of "input" event should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 197 is(inputEvents[0].data, test.input.before, 198 `data of "input" event should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 199 is(inputEvents[0].dataTransfer, null, 200 `dataTransfer of "input" event should be null when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 201 is(inputEvents[0].getTargetRanges().length, 0, 202 `getTargetRanges() of "input" event should return empty array when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 203 } 204 } else { 205 ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent), 206 `"input" event should be dispatched with Event interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`); 207 } 208 is(inputEvents[0].cancelable, false, 209 `"input" event should be never cancelable (${tag}, before getting focus)`); 210 is(inputEvents[0].bubbles, true, 211 `"input" event should always bubble (${tag}, before getting focus)`); 212 } 213 214 beforeInputEvents = []; 215 inputEvents = []; 216 target.focus(); 217 previousValue = target.value; 218 SpecialPowers.wrap(target).setUserInput(test.input.after); 219 if (target.value == previousValue && test.result.after != previousValue) { 220 todo_is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`); 221 } else { 222 is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`); 223 } 224 if (target.value == previousValue) { 225 if (test.type === "date" || test.type === "time" || test.type === "datetime-local") { 226 todo_is(inputEvents.length, 0, 227 `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 228 } else { 229 is(inputEvents.length, 0, 230 `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 231 } 232 } else { 233 if (!test.result.fireBeforeInputEvent) { 234 is(beforeInputEvents.length, 0, 235 `No "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 236 } else { 237 is(beforeInputEvents.length, 1, 238 `Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 239 } 240 if (!test.result.fireInputEvent) { 241 // HTML spec defines that "input" elements whose type are "hidden", 242 // "submit", "image", "reset" and "button" shouldn't fire input event 243 // when its value is changed. 244 // XXX Perhaps, we shouldn't support setUserInput() with such types. 245 if (test.type === "hidden" || 246 test.type === "submit" || 247 test.type === "image" || 248 test.type === "reset" || 249 test.type === "button") { 250 todo_is(inputEvents.length, 0, 251 `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 252 } else { 253 is(inputEvents.length, 0, 254 `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 255 } 256 } else { 257 is(inputEvents.length, 1, 258 `Only one "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 259 } 260 } 261 if (inputEvents.length) { 262 if (SpecialPowers.wrap(target).isInputEventTarget) { 263 if (test.type === "time") { 264 todo(inputEvents[0] instanceof InputEvent, 265 `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 266 } else { 267 if (beforeInputEvents.length && test.result.fireBeforeInputEvent) { 268 is(beforeInputEvents[0].cancelable, kSetUserInputCancelable, 269 `"beforeinput" event should be cancelable when setUserInput("${test.input.after}") is called after ${tag} gets focus unless it's suppressed by the pref`); 270 is(beforeInputEvents[0].inputType, "insertReplacementText", 271 `inputType of "beforeinput" event should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 272 is(beforeInputEvents[0].data, test.input.after, 273 `data of "beforeinput" should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 274 is(beforeInputEvents[0].dataTransfer, null, 275 `dataTransfer of "beforeinput" should be null when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 276 is(beforeInputEvents[0].getTargetRanges().length, 0, 277 `getTargetRanges() of "beforeinput" should return empty array when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 278 } 279 ok(inputEvents[0] instanceof InputEvent, 280 `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 281 is(inputEvents[0].inputType, "insertReplacementText", 282 `inputType of "input" event should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 283 is(inputEvents[0].data, test.input.after, 284 `data of "input" event should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 285 is(inputEvents[0].dataTransfer, null, 286 `dataTransfer of "input" event should be null when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 287 is(inputEvents[0].getTargetRanges().length, 0, 288 `getTargetRanges() of "input" event should return empty array when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 289 } 290 } else { 291 ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent), 292 `"input" event should be dispatched with Event interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`); 293 } 294 is(inputEvents[0].cancelable, false, 295 `"input" event should be never cancelable (${tag}, after getting focus)`); 296 is(inputEvents[0].bubbles, true, 297 `"input" event should always bubble (${tag}, after getting focus)`); 298 } 299 300 target.removeEventListener("input", onInput); 301 } 302 303 function testValidationMessage(aType, aInvalidValue, aValidValue) { 304 let tag = `<input type="${aType}">` 305 content.innerHTML = tag; 306 content.scrollTop; // Flush pending layout. 307 let target = content.firstChild; 308 309 let inputEvents = []; 310 let validationMessage = ""; 311 312 function reset() { 313 inputEvents = []; 314 validationMessage = ""; 315 } 316 317 function onInput(aEvent) { 318 inputEvents.push(aEvent); 319 validationMessage = aEvent.target.validationMessage; 320 } 321 target.addEventListener("input", onInput); 322 323 reset(); 324 SpecialPowers.wrap(target).setUserInput(aInvalidValue); 325 is(inputEvents.length, 1, 326 `Only one "input" event should be dispatched when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`); 327 isnot(validationMessage, "", 328 `${tag}.validationMessage should not be empty when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`); 329 ok(target.matches(":invalid"), 330 `The target should have invalid pseudo class when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`); 331 332 reset(); 333 SpecialPowers.wrap(target).setUserInput(aValidValue); 334 is(inputEvents.length, 1, 335 `Only one "input" event should be dispatched when setUserInput("${aValidValue}") is called before ${tag} gets focus`); 336 is(validationMessage, "", 337 `${tag}.validationMessage should be empty when setUserInput("${aValidValue}") is called before ${tag} gets focus`); 338 ok(!target.matches(":invalid"), 339 `The target shouldn't have invalid pseudo class when setUserInput("${aValidValue}") is called before ${tag} gets focus`); 340 341 reset(); 342 SpecialPowers.wrap(target).setUserInput(aInvalidValue); 343 is(inputEvents.length, 1, 344 `Only one "input" event should be dispatched again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`); 345 isnot(validationMessage, "", 346 `${tag}.validationMessage should not be empty again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`); 347 ok(target.matches(":invalid"), 348 `The target should have invalid pseudo class again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`); 349 350 target.value = ""; 351 target.focus(); 352 353 reset(); 354 SpecialPowers.wrap(target).setUserInput(aInvalidValue); 355 is(inputEvents.length, 1, 356 `Only one "input" event should be dispatched when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`); 357 isnot(validationMessage, "", 358 `${tag}.validationMessage should not be empty when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`); 359 ok(target.matches(":invalid"), 360 `The target should have invalid pseudo class when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`); 361 362 reset(); 363 SpecialPowers.wrap(target).setUserInput(aValidValue); 364 is(inputEvents.length, 1, 365 `Only one "input" event should be dispatched when setUserInput("${aValidValue}") is called after ${tag} gets focus`); 366 is(validationMessage, "", 367 `${tag}.validationMessage should be empty when setUserInput("${aValidValue}") is called after ${tag} gets focus`); 368 ok(!target.matches(":invalid"), 369 `The target shouldn't have invalid pseudo class when setUserInput("${aValidValue}") is called after ${tag} gets focus`); 370 371 reset(); 372 SpecialPowers.wrap(target).setUserInput(aInvalidValue); 373 is(inputEvents.length, 1, 374 `Only one "input" event should be dispatched again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`); 375 isnot(validationMessage, "", 376 `${tag}.validationMessage should not be empty again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`); 377 ok(target.matches(":invalid"), 378 `The target should have invalid pseudo class again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`); 379 380 target.removeEventListener("input", onInput); 381 } 382 testValidationMessage("email", "f", "foo@example.com"); 383 384 function testValueMissing(aType, aValidValue) { 385 let tag = aType === "textarea" ? "<textarea required>" : `<input type="${aType}" required>`; 386 content.innerHTML = `${tag}${aType === "textarea" ? "</textarea>" : ""}`; 387 content.scrollTop; // Flush pending layout. 388 let target = content.firstChild; 389 390 let inputEvents = [], beforeInputEvents = []; 391 function reset() { 392 beforeInputEvents = []; 393 inputEvents = []; 394 } 395 396 function onBeforeInput(aEvent) { 397 aEvent.validity = aEvent.target.checkValidity(); 398 beforeInputEvents.push(aEvent); 399 } 400 function onInput(aEvent) { 401 aEvent.validity = aEvent.target.checkValidity(); 402 inputEvents.push(aEvent); 403 } 404 target.addEventListener("beforeinput", onBeforeInput); 405 target.addEventListener("input", onInput); 406 407 reset(); 408 SpecialPowers.wrap(target).setUserInput(aValidValue); 409 is(beforeInputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "beforeinput" event (before gets focus)`); 410 if (beforeInputEvents.length) { 411 is(beforeInputEvents[0].validity, false, 412 `The ${tag} should be invalid at "beforeinput" event (before gets focus)`); 413 } 414 is(inputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "input" event (before gets focus)`); 415 if (inputEvents.length) { 416 is(inputEvents[0].validity, true, 417 `The ${tag} should be valid at "input" event (before gets focus)`); 418 } 419 420 target.removeEventListener("beforeinput", onBeforeInput); 421 target.removeEventListener("input", onInput); 422 423 content.innerHTML = ""; 424 content.scrollTop; // Flush pending layout. 425 content.innerHTML = `${tag}${aType === "textarea" ? "</textarea>" : ""}`; 426 content.scrollTop; // Flush pending layout. 427 target = content.firstChild; 428 429 target.focus(); 430 target.addEventListener("beforeinput", onBeforeInput); 431 target.addEventListener("input", onInput); 432 433 reset(); 434 SpecialPowers.wrap(target).setUserInput(aValidValue); 435 is(beforeInputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "beforeinput" event (after gets focus)`); 436 if (beforeInputEvents.length) { 437 is(beforeInputEvents[0].validity, false, 438 `The ${tag} should be invalid at "beforeinput" event (after gets focus)`); 439 } 440 is(inputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "input" event (after gets focus)`); 441 if (inputEvents.length) { 442 is(inputEvents[0].validity, true, 443 `The ${tag} should be valid at "input" event (after gets focus)`); 444 } 445 446 target.removeEventListener("beforeinput", onBeforeInput); 447 target.removeEventListener("input", onInput); 448 } 449 testValueMissing("text", "abc"); 450 testValueMissing("password", "abc"); 451 testValueMissing("textarea", "abc"); 452 testValueMissing("email", "foo@example.com"); 453 testValueMissing("url", "https://example.com/"); 454 455 function testEditorValueAtEachEvent(aType) { 456 let tag = aType === "textarea" ? "<textarea>" : `<input type="${aType}">` 457 let closeTag = aType === "textarea" ? "</textarea>" : ""; 458 content.innerHTML = `${tag}${closeTag}`; 459 content.scrollTop; // Flush pending layout. 460 let target = content.firstChild; 461 target.value = "Old Value"; 462 let description = `Setting new value of ${tag} before setting focus: `; 463 let onBeforeInput = (aEvent) => { 464 is(target.value, "Old Value", 465 `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`); 466 }; 467 let onInput = (aEvent) => { 468 is(target.value, "New Value", 469 `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`); 470 }; 471 target.addEventListener("beforeinput", onBeforeInput); 472 target.addEventListener("input", onInput); 473 SpecialPowers.wrap(target).setUserInput("New Value"); 474 475 description = `Setting new value of ${tag} after setting focus: `; 476 target.value = "Old Value"; 477 target.focus(); 478 SpecialPowers.wrap(target).setUserInput("New Value"); 479 480 target.removeEventListener("beforeinput", onBeforeInput); 481 target.removeEventListener("input", onInput); 482 483 // FYI: This is not realistic situation because we should do nothing 484 // while user composing IME. 485 // TODO: TextControlState should stop returning setting value as the value 486 // while committing composition. 487 description = `Setting new value of ${tag} during composition: `; 488 target.value = ""; 489 target.focus(); 490 synthesizeCompositionChange({ 491 composition: { 492 string: "composition string", 493 clauses: [{length: 18, attr: COMPOSITION_ATTR_RAW_CLAUSE}], 494 }, 495 caret: {start: 18, length: 0}, 496 }); 497 let onCompositionUpdate = (aEvent) => { 498 todo_is(target.value, "composition string", 499 `${description}The value should not have been modified at "compositionupdate" event yet (data: "${aEvent.data}")`); 500 }; 501 let onCompositionEnd = (aEvent) => { 502 todo_is(target.value, "composition string", 503 `${description}The value should not have been modified at "compositionupdate" event yet (data: "${aEvent.data}")`); 504 }; 505 onBeforeInput = (aEvent) => { 506 if (aEvent.inputType === "insertCompositionText") { 507 todo_is(target.value, "composition string", 508 `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`); 509 } else { 510 is(target.value, "composition string", 511 `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`); 512 } 513 }; 514 onInput = (aEvent) => { 515 if (aEvent.inputType === "insertCompositionText") { 516 todo_is(target.value, "composition string", 517 `${description}The value should not have been modified at "input" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`); 518 } else { 519 is(target.value, "New Value", 520 `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`); 521 } 522 }; 523 target.addEventListener("compositionupdate", onCompositionUpdate); 524 target.addEventListener("compositionend", onCompositionEnd); 525 target.addEventListener("beforeinput", onBeforeInput); 526 target.addEventListener("input", onInput); 527 SpecialPowers.wrap(target).setUserInput("New Value"); 528 target.removeEventListener("compositionupdate", onCompositionUpdate); 529 target.removeEventListener("compositionend", onCompositionEnd); 530 target.removeEventListener("beforeinput", onBeforeInput); 531 target.removeEventListener("input", onInput); 532 } 533 testEditorValueAtEachEvent("text"); 534 testEditorValueAtEachEvent("textarea"); 535 536 async function testBeforeInputCancelable(aType) { 537 let tag = aType === "textarea" ? "<textarea>" : `<input type="${aType}">` 538 let closeTag = aType === "textarea" ? "</textarea>" : ""; 539 for (const kShouldBeCancelable of [true, false]) { 540 await SpecialPowers.pushPrefEnv({ 541 set: [["dom.input_event.allow_to_cancel_set_user_input", kShouldBeCancelable]], 542 }); 543 544 content.innerHTML = `${tag}${closeTag}`; 545 content.scrollTop; // Flush pending layout. 546 let target = content.firstChild; 547 target.value = "Old Value"; 548 let description = `Setting new value of ${tag} before setting focus (the pref ${kShouldBeCancelable ? "allows" : "disallows"} to cancel beforeinput): `; 549 let onBeforeInput = (aEvent) => { 550 is(aEvent.cancelable, kShouldBeCancelable, 551 `${description}The "beforeinput" event should be ${kShouldBeCancelable ? "cancelable" : "not be cancelable due to suppressed by the pref"}`); 552 }; 553 let onInput = (aEvent) => { 554 is(aEvent.cancelable, false, 555 `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`); 556 }; 557 target.addEventListener("beforeinput", onBeforeInput); 558 target.addEventListener("input", onInput); 559 SpecialPowers.wrap(target).setUserInput("New Value"); 560 561 description = `Setting new value of ${tag} after setting focus (the pref ${kShouldBeCancelable ? "allows" : "disallows"} to cancel beforeinput): `; 562 target.value = "Old Value"; 563 target.focus(); 564 SpecialPowers.wrap(target).setUserInput("New Value"); 565 566 target.removeEventListener("beforeinput", onBeforeInput); 567 target.removeEventListener("input", onInput); 568 } 569 570 await SpecialPowers.clearUserPref({ 571 clear: [["dom.input_event.allow_to_cancel_set_user_input"]], 572 }); 573 } 574 await testBeforeInputCancelable("text"); 575 await testBeforeInputCancelable("textarea"); 576 577 SimpleTest.finish(); 578 }); 579 </script> 580 </body> 581 </html>