test_htmleditor_keyevent_handling.html (32521B)
1 <html> 2 <head> 3 <title>Test for key event handler of HTML editor</title> 4 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 5 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> 6 <link rel="stylesheet" type="text/css" 7 href="chrome://mochikit/content/tests/SimpleTest/test.css" /> 8 </head> 9 <body> 10 <div id="display"> 11 <div id="htmlEditor" contenteditable="true"><br></div> 12 </div> 13 <div id="content" style="display: none"> 14 15 </div> 16 <pre id="test"> 17 </pre> 18 19 <script class="testbody" type="application/javascript"> 20 21 /* eslint-disable no-nested-ternary */ 22 23 SimpleTest.waitForExplicitFinish(); 24 SimpleTest.waitForFocus(runTests, window); 25 26 var htmlEditor = document.getElementById("htmlEditor"); 27 28 const kIsMac = navigator.platform.includes("Mac"); 29 const kIsWin = navigator.platform.includes("Win"); 30 const kIsLinux = navigator.platform.includes("Linux") || navigator.platform.includes("SunOS"); 31 32 async function runTests() { 33 document.execCommand("stylewithcss", false, "true"); 34 document.execCommand("defaultParagraphSeparator", false, "div"); 35 36 var fm = SpecialPowers.Services.focus; 37 38 var capturingPhase = { fired: false, prevented: false }; 39 var bubblingPhase = { fired: false, prevented: false }; 40 41 var listener = { 42 handleEvent: function _hv(aEvent) { 43 is(aEvent.type, "keypress", "unexpected event is handled"); 44 switch (aEvent.eventPhase) { 45 case aEvent.CAPTURING_PHASE: 46 capturingPhase.fired = true; 47 capturingPhase.prevented = aEvent.defaultPrevented; 48 break; 49 case aEvent.BUBBLING_PHASE: 50 bubblingPhase.fired = true; 51 bubblingPhase.prevented = aEvent.defaultPrevented; 52 aEvent.preventDefault(); // prevent the browser default behavior 53 break; 54 default: 55 ok(false, "event is handled in unexpected phase"); 56 } 57 }, 58 }; 59 60 function check(aDescription, 61 aFiredOnCapture, aFiredOnBubbling, aPreventedOnBubbling) { 62 function getDesciption(aExpected) { 63 return aDescription + (aExpected ? " wasn't " : " was "); 64 } 65 is(capturingPhase.fired, aFiredOnCapture, 66 getDesciption(aFiredOnCapture) + "fired on capture phase"); 67 is(bubblingPhase.fired, aFiredOnBubbling, 68 getDesciption(aFiredOnBubbling) + "fired on bubbling phase"); 69 70 // If the event is fired on bubbling phase and it was already prevented 71 // on capture phase, it must be prevented on bubbling phase too. 72 if (capturingPhase.prevented) { 73 todo(false, aDescription + 74 " was consumed already, so, we cannot test the editor behavior actually"); 75 aPreventedOnBubbling = true; 76 } 77 78 is(bubblingPhase.prevented, aPreventedOnBubbling, 79 getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase"); 80 } 81 82 SpecialPowers.wrap(window).addEventListener("keypress", listener, { capture: true, mozSystemGroup: true }); 83 SpecialPowers.wrap(window).addEventListener("keypress", listener, { capture: false, mozSystemGroup: true }); 84 85 // eslint-disable-next-line complexity 86 async function doTest( 87 aElement, 88 aDescription, 89 aIsReadonly, 90 aIsTabbable, 91 aIsPlaintext 92 ) { 93 function reset(aText) { 94 capturingPhase.fired = false; 95 capturingPhase.prevented = false; 96 bubblingPhase.fired = false; 97 bubblingPhase.prevented = false; 98 aElement.innerHTML = aText; 99 var sel = window.getSelection(); 100 var range = document.createRange(); 101 range.setStart(aElement, aElement.childNodes.length); 102 sel.removeAllRanges(); 103 sel.addRange(range); 104 } 105 106 function resetForIndent(aText) { 107 capturingPhase.fired = false; 108 capturingPhase.prevented = false; 109 bubblingPhase.fired = false; 110 bubblingPhase.prevented = false; 111 aElement.innerHTML = aText; 112 var sel = window.getSelection(); 113 var range = document.createRange(); 114 var target = document.getElementById("target").firstChild; 115 range.setStart(target, target.length); 116 sel.removeAllRanges(); 117 sel.addRange(range); 118 } 119 120 if (document.activeElement) { 121 document.activeElement.blur(); 122 } 123 124 aDescription += ": "; 125 126 aElement.focus(); 127 is(SpecialPowers.unwrap(fm.focusedElement), aElement, aDescription + "failed to move focus"); 128 129 // Backspace key: 130 // If native key bindings map the key combination to something, it's consumed. 131 // If editor is readonly, it doesn't consume. 132 // If editor is editable, it consumes backspace and shift+backspace. 133 // Otherwise, editor doesn't consume the event. 134 reset(""); 135 synthesizeKey("KEY_Backspace"); 136 check(aDescription + "Backspace", true, true, true); 137 138 reset(""); 139 synthesizeKey("KEY_Backspace", {shiftKey: true}); 140 check(aDescription + "Shift+Backspace", true, true, true); 141 142 reset(""); 143 synthesizeKey("KEY_Backspace", {ctrlKey: true}); 144 check(aDescription + "Ctrl+Backspace", true, true, aIsReadonly || kIsLinux); 145 146 reset(""); 147 synthesizeKey("KEY_Backspace", {altKey: true}); 148 check(aDescription + "Alt+Backspace", true, true, aIsReadonly || kIsMac); 149 150 reset(""); 151 synthesizeKey("KEY_Backspace", {metaKey: true}); 152 check(aDescription + "Meta+Backspace", true, true, aIsReadonly || kIsMac); 153 154 // Delete key: 155 // If native key bindings map the key combination to something, it's consumed. 156 // If editor is readonly, it doesn't consume. 157 // If editor is editable, delete is consumed. 158 // Otherwise, editor doesn't consume the event. 159 reset(""); 160 synthesizeKey("KEY_Delete"); 161 check(aDescription + "Delete", true, true, !aIsReadonly || kIsMac || kIsLinux); 162 163 reset(""); 164 synthesizeKey("KEY_Delete", {shiftKey: true}); 165 check(aDescription + "Shift+Delete", true, true, kIsMac || kIsLinux); 166 167 reset(""); 168 synthesizeKey("KEY_Delete", {ctrlKey: true}); 169 check(aDescription + "Ctrl+Delete", true, true, kIsLinux); 170 171 reset(""); 172 synthesizeKey("KEY_Delete", {altKey: true}); 173 check(aDescription + "Alt+Delete", true, true, kIsMac); 174 175 reset(""); 176 synthesizeKey("KEY_Delete", {metaKey: true}); 177 check(aDescription + "Meta+Delete", true, true, false); 178 179 // Return key: 180 // If editor is readonly, it doesn't consume. 181 // If editor is editable and not single line editor, it consumes Return 182 // and Shift+Return. 183 // Otherwise, editor doesn't consume the event. 184 reset("a"); 185 synthesizeKey("KEY_Enter"); 186 check(aDescription + "Return", 187 true, true, !aIsReadonly); 188 is(aElement.innerHTML, aIsReadonly ? "a" : "<div>a</div><br>", 189 aDescription + "Return"); 190 191 reset("a"); 192 synthesizeKey("KEY_Enter", {shiftKey: true}); 193 check(aDescription + "Shift+Return", 194 true, true, !aIsReadonly); 195 is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>", 196 aDescription + "Shift+Return"); 197 198 reset("a"); 199 synthesizeKey("KEY_Enter", {ctrlKey: true}); 200 check(aDescription + "Ctrl+Return", true, true, false); 201 is(aElement.innerHTML, "a", aDescription + "Ctrl+Return"); 202 203 reset("a"); 204 synthesizeKey("KEY_Enter", {altKey: true}); 205 check(aDescription + "Alt+Return", true, true, false); 206 is(aElement.innerHTML, "a", aDescription + "Alt+Return"); 207 208 reset("a"); 209 synthesizeKey("KEY_Enter", {metaKey: true}); 210 check(aDescription + "Meta+Return", true, true, false); 211 is(aElement.innerHTML, "a", aDescription + "Meta+Return"); 212 213 // Tab key: 214 // If editor is tabbable, editor doesn't consume all tab key events. 215 // Otherwise, editor consumes tab key event without any modifier keys. 216 reset("a"); 217 synthesizeKey("KEY_Tab"); 218 check(aDescription + "Tab", 219 true, true, !aIsTabbable && !aIsReadonly); 220 is(aElement.innerHTML, 221 (() => { 222 if (aIsTabbable || aIsReadonly) { 223 return "a"; 224 } 225 if (aIsPlaintext) { 226 return "a\t"; 227 } 228 return"a "; 229 })(), 230 aDescription + "Tab"); 231 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 232 aDescription + "focus moved unexpectedly (Tab)"); 233 234 reset("a"); 235 synthesizeKey("KEY_Tab", {shiftKey: true}); 236 check(aDescription + "Shift+Tab", true, true, false); 237 is(aElement.innerHTML, "a", aDescription + "Shift+Tab"); 238 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 239 aDescription + "focus moved unexpectedly (Shift+Tab)"); 240 241 // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress 242 // event should never be fired. 243 reset("a"); 244 synthesizeKey("KEY_Tab", {ctrlKey: true}); 245 check(aDescription + "Ctrl+Tab", false, false, false); 246 is(aElement.innerHTML, "a", aDescription + "Ctrl+Tab"); 247 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 248 aDescription + "focus moved unexpectedly (Ctrl+Tab)"); 249 250 reset("a"); 251 synthesizeKey("KEY_Tab", {altKey: true}); 252 check(aDescription + "Alt+Tab", true, true, false); 253 is(aElement.innerHTML, "a", aDescription + "Alt+Tab"); 254 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 255 aDescription + "focus moved unexpectedly (Alt+Tab)"); 256 257 reset("a"); 258 synthesizeKey("KEY_Tab", {metaKey: true}); 259 check(aDescription + "Meta+Tab", true, true, false); 260 is(aElement.innerHTML, "a", aDescription + "Meta+Tab"); 261 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 262 aDescription + "focus moved unexpectedly (Meta+Tab)"); 263 264 // Indent/Outdent tests: 265 // UL 266 resetForIndent("<ul><li id=\"target\">ul list item</li></ul>"); 267 synthesizeKey("KEY_Tab"); 268 check(aDescription + "Tab on UL", 269 true, true, !aIsTabbable && !aIsReadonly); 270 is(aElement.innerHTML, 271 aIsReadonly || aIsTabbable ? 272 "<ul><li id=\"target\">ul list item</li></ul>" : 273 aIsPlaintext ? "<ul><li id=\"target\">ul list item\t</li></ul>" : 274 "<ul><ul><li id=\"target\">ul list item</li></ul></ul>", 275 aDescription + "Tab on UL"); 276 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 277 aDescription + "focus moved unexpectedly (Tab on UL)"); 278 synthesizeKey("KEY_Tab", {shiftKey: true}); 279 check(aDescription + "Shift+Tab after Tab on UL", 280 true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext); 281 is(aElement.innerHTML, 282 aIsReadonly || aIsTabbable || (!aIsPlaintext) ? 283 "<ul><li id=\"target\">ul list item</li></ul>" : 284 "<ul><li id=\"target\">ul list item\t</li></ul>", 285 aDescription + "Shift+Tab after Tab on UL"); 286 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 287 aDescription + "focus moved unexpectedly (Shift+Tab after Tab on UL)"); 288 289 resetForIndent("<ul><li id=\"target\">ul list item</li></ul>"); 290 synthesizeKey("KEY_Tab", {shiftKey: true}); 291 check(aDescription + "Shift+Tab on UL", 292 true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext); 293 is(aElement.innerHTML, 294 aIsReadonly || aIsTabbable || aIsPlaintext ? 295 "<ul><li id=\"target\">ul list item</li></ul>" : "ul list item", 296 aDescription + "Shift+Tab on UL"); 297 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 298 aDescription + "focus moved unexpectedly (Shift+Tab on UL)"); 299 300 // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress 301 // event should never be fired. 302 resetForIndent("<ul><li id=\"target\">ul list item</li></ul>"); 303 synthesizeKey("KEY_Tab", {ctrlKey: true}); 304 check(aDescription + "Ctrl+Tab on UL", false, false, false); 305 is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>", 306 aDescription + "Ctrl+Tab on UL"); 307 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 308 aDescription + "focus moved unexpectedly (Ctrl+Tab on UL)"); 309 310 resetForIndent("<ul><li id=\"target\">ul list item</li></ul>"); 311 synthesizeKey("KEY_Tab", {altKey: true}); 312 check(aDescription + "Alt+Tab on UL", true, true, false); 313 is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>", 314 aDescription + "Alt+Tab on UL"); 315 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 316 aDescription + "focus moved unexpectedly (Alt+Tab on UL)"); 317 318 resetForIndent("<ul><li id=\"target\">ul list item</li></ul>"); 319 synthesizeKey("KEY_Tab", {metaKey: true}); 320 check(aDescription + "Meta+Tab on UL", true, true, false); 321 is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>", 322 aDescription + "Meta+Tab on UL"); 323 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 324 aDescription + "focus moved unexpectedly (Meta+Tab on UL)"); 325 326 // OL 327 resetForIndent("<ol><li id=\"target\">ol list item</li></ol>"); 328 synthesizeKey("KEY_Tab"); 329 check(aDescription + "Tab on OL", 330 true, true, !aIsTabbable && !aIsReadonly); 331 is(aElement.innerHTML, 332 aIsReadonly || aIsTabbable ? 333 "<ol><li id=\"target\">ol list item</li></ol>" : 334 aIsPlaintext ? "<ol><li id=\"target\">ol list item\t</li></ol>" : 335 "<ol><ol><li id=\"target\">ol list item</li></ol></ol>", 336 aDescription + "Tab on OL"); 337 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 338 aDescription + "focus moved unexpectedly (Tab on OL)"); 339 synthesizeKey("KEY_Tab", {shiftKey: true}); 340 check(aDescription + "Shift+Tab after Tab on OL", 341 true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext); 342 is(aElement.innerHTML, 343 aIsReadonly || aIsTabbable || (!aIsPlaintext) ? 344 "<ol><li id=\"target\">ol list item</li></ol>" : 345 "<ol><li id=\"target\">ol list item\t</li></ol>", 346 aDescription + "Shift+Tab after Tab on OL"); 347 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 348 aDescription + "focus moved unexpectedly (Shift+Tab after Tab on OL)"); 349 350 resetForIndent("<ol><li id=\"target\">ol list item</li></ol>"); 351 synthesizeKey("KEY_Tab", {shiftKey: true}); 352 check(aDescription + "Shift+Tab on OL", 353 true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext); 354 is(aElement.innerHTML, 355 aIsReadonly || aIsTabbable || aIsPlaintext ? 356 "<ol><li id=\"target\">ol list item</li></ol>" : "ol list item", 357 aDescription + "Shfit+Tab on OL"); 358 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 359 aDescription + "focus moved unexpectedly (Shift+Tab on OL)"); 360 361 // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress 362 // event should never be fired. 363 resetForIndent("<ol><li id=\"target\">ol list item</li></ol>"); 364 synthesizeKey("KEY_Tab", {ctrlKey: true}); 365 check(aDescription + "Ctrl+Tab on OL", false, false, false); 366 is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>", 367 aDescription + "Ctrl+Tab on OL"); 368 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 369 aDescription + "focus moved unexpectedly (Ctrl+Tab on OL)"); 370 371 resetForIndent("<ol><li id=\"target\">ol list item</li></ol>"); 372 synthesizeKey("KEY_Tab", {altKey: true}); 373 check(aDescription + "Alt+Tab on OL", true, true, false); 374 is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>", 375 aDescription + "Alt+Tab on OL"); 376 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 377 aDescription + "focus moved unexpectedly (Alt+Tab on OL)"); 378 379 resetForIndent("<ol><li id=\"target\">ol list item</li></ol>"); 380 synthesizeKey("KEY_Tab", {metaKey: true}); 381 check(aDescription + "Meta+Tab on OL", true, true, false); 382 is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>", 383 aDescription + "Meta+Tab on OL"); 384 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 385 aDescription + "focus moved unexpectedly (Meta+Tab on OL)"); 386 387 // TD 388 resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>"); 389 synthesizeKey("KEY_Tab"); 390 check(aDescription + "Tab on TD", 391 true, true, !aIsTabbable && !aIsReadonly); 392 is(aElement.innerHTML, 393 aIsTabbable || aIsReadonly ? 394 "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" : 395 aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" : 396 "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>", 397 aDescription + "Tab on TD"); 398 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 399 aDescription + "focus moved unexpectedly (Tab on TD)"); 400 synthesizeKey("KEY_Tab", {shiftKey: true}); 401 check(aDescription + "Shift+Tab after Tab on TD", 402 true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext); 403 is(aElement.innerHTML, 404 aIsTabbable || aIsReadonly ? 405 "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" : 406 aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" : 407 "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>", 408 aDescription + "Shift+Tab after Tab on TD"); 409 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 410 aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TD)"); 411 412 resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>"); 413 synthesizeKey("KEY_Tab", {shiftKey: true}); 414 check(aDescription + "Shift+Tab on TD", true, true, false); 415 is(aElement.innerHTML, 416 "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>", 417 aDescription + "Shift+Tab on TD"); 418 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 419 aDescription + "focus moved unexpectedly (Shift+Tab on TD)"); 420 421 // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress 422 // event should never be fired. 423 resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>"); 424 synthesizeKey("KEY_Tab", {ctrlKey: true}); 425 check(aDescription + "Ctrl+Tab on TD", false, false, false); 426 is(aElement.innerHTML, 427 "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>", 428 aDescription + "Ctrl+Tab on TD"); 429 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 430 aDescription + "focus moved unexpectedly (Ctrl+Tab on TD)"); 431 432 resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>"); 433 synthesizeKey("KEY_Tab", {altKey: true}); 434 check(aDescription + "Alt+Tab on TD", true, true, false); 435 is(aElement.innerHTML, 436 "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>", 437 aDescription + "Alt+Tab on TD"); 438 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 439 aDescription + "focus moved unexpectedly (Alt+Tab on TD)"); 440 441 resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>"); 442 synthesizeKey("KEY_Tab", {metaKey: true}); 443 check(aDescription + "Meta+Tab on TD", true, true, false); 444 is(aElement.innerHTML, 445 "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>", 446 aDescription + "Meta+Tab on TD"); 447 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 448 aDescription + "focus moved unexpectedly (Meta+Tab on TD)"); 449 450 // TH 451 resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>"); 452 synthesizeKey("KEY_Tab"); 453 check(aDescription + "Tab on TH", 454 true, true, !aIsTabbable && !aIsReadonly); 455 is(aElement.innerHTML, 456 aIsTabbable || aIsReadonly ? 457 "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" : 458 aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" : 459 "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>", 460 aDescription + "Tab on TH"); 461 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 462 aDescription + "focus moved unexpectedly (Tab on TH)"); 463 synthesizeKey("KEY_Tab", {shiftKey: true}); 464 check(aDescription + "Shift+Tab after Tab on TH", 465 true, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext); 466 is(aElement.innerHTML, 467 aIsTabbable || aIsReadonly ? 468 "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" : 469 aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" : 470 "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>", 471 aDescription + "Shift+Tab after Tab on TH"); 472 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 473 aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TH)"); 474 475 resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>"); 476 synthesizeKey("KEY_Tab", {shiftKey: true}); 477 check(aDescription + "Shift+Tab on TH", true, true, false); 478 is(aElement.innerHTML, 479 "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>", 480 aDescription + "Shift+Tab on TH"); 481 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 482 aDescription + "focus moved unexpectedly (Shift+Tab on TH)"); 483 484 // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress 485 // event should never be fired. 486 resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>"); 487 synthesizeKey("KEY_Tab", {ctrlKey: true}); 488 check(aDescription + "Ctrl+Tab on TH", false, false, false); 489 is(aElement.innerHTML, 490 "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>", 491 aDescription + "Ctrl+Tab on TH"); 492 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 493 aDescription + "focus moved unexpectedly (Ctrl+Tab on TH)"); 494 495 resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>"); 496 synthesizeKey("KEY_Tab", {altKey: true}); 497 check(aDescription + "Alt+Tab on TH", true, true, false); 498 is(aElement.innerHTML, 499 "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>", 500 aDescription + "Alt+Tab on TH"); 501 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 502 aDescription + "focus moved unexpectedly (Alt+Tab on TH)"); 503 504 resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>"); 505 synthesizeKey("KEY_Tab", {metaKey: true}); 506 check(aDescription + "Meta+Tab on TH", true, true, false); 507 is(aElement.innerHTML, 508 "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>", 509 aDescription + "Meta+Tab on TH"); 510 is(SpecialPowers.unwrap(fm.focusedElement), aElement, 511 aDescription + "focus moved unexpectedly (Meta+Tab on TH)"); 512 513 // Esc key: 514 // In all cases, esc key events are not consumed 515 reset("abc"); 516 synthesizeKey("KEY_Escape"); 517 check(aDescription + "Esc", true, true, false); 518 519 reset("abc"); 520 synthesizeKey("KEY_Escape", {shiftKey: true}); 521 check(aDescription + "Shift+Esc", true, true, false); 522 523 reset("abc"); 524 synthesizeKey("KEY_Escape", {ctrlKey: true}); 525 check(aDescription + "Ctrl+Esc", true, true, false); 526 527 reset("abc"); 528 synthesizeKey("KEY_Escape", {altKey: true}); 529 check(aDescription + "Alt+Esc", true, true, false); 530 531 reset("abc"); 532 synthesizeKey("KEY_Escape", {metaKey: true}); 533 check(aDescription + "Meta+Esc", true, true, false); 534 535 // typical typing tests: 536 reset(""); 537 sendString("M"); 538 check(aDescription + "M", true, true, !aIsReadonly); 539 sendString("o"); 540 check(aDescription + "o", true, true, !aIsReadonly); 541 sendString("z"); 542 check(aDescription + "z", true, true, !aIsReadonly); 543 sendString("i"); 544 check(aDescription + "i", true, true, !aIsReadonly); 545 sendString("l"); 546 check(aDescription + "l", true, true, !aIsReadonly); 547 sendString("l"); 548 check(aDescription + "l", true, true, !aIsReadonly); 549 sendString("a"); 550 check(aDescription + "a", true, true, !aIsReadonly); 551 sendString(" "); 552 check(aDescription + "' '", true, true, !aIsReadonly); 553 is(aElement.innerHTML, 554 (() => { 555 if (aIsReadonly) { 556 return ""; 557 } 558 if (aIsPlaintext) { 559 return "Mozilla "; 560 } 561 return "Mozilla "; 562 })(), 563 aDescription + "typed \"Mozilla \""); 564 565 // typing non-BMP character: 566 async function test_typing_surrogate_pair( 567 aTestPerSurrogateKeyPress, 568 aTestIllFormedUTF16KeyValue = false 569 ) { 570 await SpecialPowers.pushPrefEnv({ 571 set: [ 572 ["dom.event.keypress.dispatch_once_per_surrogate_pair", !aTestPerSurrogateKeyPress], 573 ["dom.event.keypress.key.allow_lone_surrogate", aTestIllFormedUTF16KeyValue], 574 ], 575 }); 576 reset(""); 577 let events = []; 578 function pushIntoEvents(aEvent) { 579 events.push(aEvent); 580 } 581 function getEventData(aKeyboardEventOrInputEvent) { 582 if (!aKeyboardEventOrInputEvent) { 583 return "{}"; 584 } 585 switch (aKeyboardEventOrInputEvent.type) { 586 case "keydown": 587 case "keypress": 588 case "keyup": 589 return `{ type: "${aKeyboardEventOrInputEvent.type}", key="${ 590 aKeyboardEventOrInputEvent.key 591 }", charCode=0x${ 592 aKeyboardEventOrInputEvent.charCode.toString(16).toUpperCase() 593 } }`; 594 default: 595 return `{ type: "${aKeyboardEventOrInputEvent.type}", inputType="${ 596 aKeyboardEventOrInputEvent.inputType 597 }", data="${aKeyboardEventOrInputEvent.data}" }`; 598 } 599 } 600 function getEventArrayData(aEvents) { 601 if (!aEvents.length) { 602 return "[]"; 603 } 604 let result = "[\n"; 605 for (const e of aEvents) { 606 result += ` ${getEventData(e)}\n`; 607 } 608 return result + "]"; 609 } 610 aElement.addEventListener("keydown", pushIntoEvents); 611 aElement.addEventListener("keypress", pushIntoEvents); 612 aElement.addEventListener("keyup", pushIntoEvents); 613 aElement.addEventListener("beforeinput", pushIntoEvents); 614 aElement.addEventListener("input", pushIntoEvents); 615 synthesizeKey("\uD842\uDFB7"); 616 aElement.removeEventListener("keydown", pushIntoEvents); 617 aElement.removeEventListener("keypress", pushIntoEvents); 618 aElement.removeEventListener("keyup", pushIntoEvents); 619 aElement.removeEventListener("beforeinput", pushIntoEvents); 620 aElement.removeEventListener("input", pushIntoEvents); 621 const settingDescription = 622 `aTestPerSurrogateKeyPress=${ 623 aTestPerSurrogateKeyPress 624 }, aTestIllFormedUTF16KeyValue=${aTestIllFormedUTF16KeyValue}`; 625 const allowIllFormedUTF16 = 626 aTestPerSurrogateKeyPress && aTestIllFormedUTF16KeyValue; 627 628 check(`${aDescription}, ${settingDescription}a surrogate pair`, true, true, !aIsReadonly); 629 is( 630 aElement.textContent, 631 !aIsReadonly ? "\uD842\uDFB7" : "", 632 `${aDescription}, ${settingDescription}, The typed surrogate pair should've been inserted` 633 ); 634 if (aIsReadonly) { 635 is( 636 getEventArrayData(events), 637 getEventArrayData( 638 aTestPerSurrogateKeyPress 639 ? ( 640 allowIllFormedUTF16 641 ? [ 642 { type: "keydown", key: "\uD842\uDFB7", charCode: 0 }, 643 { type: "keypress", key: "\uD842", charCode: 0xD842 }, 644 { type: "keypress", key: "\uDFB7", charCode: 0xDFB7 }, 645 { type: "keyup", key: "\uD842\uDFB7", charCode: 0 }, 646 ] 647 : [ 648 { type: "keydown", key: "\uD842\uDFB7", charCode: 0 }, 649 { type: "keypress", key: "\uD842\uDFB7", charCode: 0xD842 }, 650 { type: "keypress", key: "", charCode: 0xDFB7 }, 651 { type: "keyup", key: "\uD842\uDFB7", charCode: 0 }, 652 ] 653 ) 654 : [ 655 { type: "keydown", key: "\uD842\uDFB7", charCode: 0 }, 656 { type: "keypress", key: "\uD842\uDFB7", charCode: 0x20BB7 }, 657 { type: "keyup", key: "\uD842\uDFB7", charCode: 0 }, 658 ] 659 ), 660 `${aDescription}, ${ 661 settingDescription 662 }, Typing a surrogate pair in readonly editor should not cause input events` 663 ); 664 } else { 665 is( 666 getEventArrayData(events), 667 getEventArrayData( 668 aTestPerSurrogateKeyPress 669 ? ( 670 allowIllFormedUTF16 671 ? [ 672 { type: "keydown", key: "\uD842\uDFB7", charCode: 0 }, 673 { type: "keypress", key: "\uD842", charCode: 0xD842 }, 674 { type: "beforeinput", data: "\uD842", inputType: "insertText" }, 675 { type: "input", data: "\uD842", inputType: "insertText" }, 676 { type: "keypress", key: "\uDFB7", charCode: 0xDFB7 }, 677 { type: "beforeinput", data: "\uDFB7", inputType: "insertText" }, 678 { type: "input", data: "\uDFB7", inputType: "insertText" }, 679 { type: "keyup", key: "\uD842\uDFB7", charCode: 0 }, 680 ] 681 : [ 682 { type: "keydown", key: "\uD842\uDFB7", charCode: 0 }, 683 { type: "keypress", key: "\uD842\uDFB7", charCode: 0xD842 }, 684 { type: "beforeinput", data: "\uD842\uDFB7", inputType: "insertText" }, 685 { type: "input", data: "\uD842\uDFB7", inputType: "insertText" }, 686 { type: "keypress", key: "", charCode: 0xDFB7 }, 687 { type: "keyup", key: "\uD842\uDFB7", charCode: 0 }, 688 ] 689 ) 690 : [ 691 { type: "keydown", key: "\uD842\uDFB7", charCode: 0 }, 692 { type: "keypress", key: "\uD842\uDFB7", charCode: 0x20BB7 }, 693 { type: "beforeinput", data: "\uD842\uDFB7", inputType: "insertText" }, 694 { type: "input", data: "\uD842\uDFB7", inputType: "insertText" }, 695 { type: "keyup", key: "\uD842\uDFB7", charCode: 0 }, 696 ] 697 ), 698 `${aDescription}, ${ 699 settingDescription 700 }, Typing a surrogate pair in editor should cause input events` 701 ); 702 } 703 } 704 await test_typing_surrogate_pair(true, true); 705 await test_typing_surrogate_pair(true, false); 706 await test_typing_surrogate_pair(false); 707 } 708 709 await doTest(htmlEditor, "contenteditable=\"true\"", false, true, false); 710 711 const nsIEditor = SpecialPowers.Ci.nsIEditor; 712 var editor = SpecialPowers.wrap(window).docShell.editor; 713 var flags = editor.flags; 714 // readonly 715 editor.flags = flags | nsIEditor.eEditorReadonlyMask; 716 await doTest(htmlEditor, "readonly HTML editor", true, true, false); 717 718 // non-tabbable 719 editor.flags = flags & ~(nsIEditor.eEditorAllowInteraction); 720 await doTest(htmlEditor, "non-tabbable HTML editor", false, false, false); 721 722 // readonly and non-tabbable 723 editor.flags = 724 (flags | nsIEditor.eEditorReadonlyMask) & 725 ~(nsIEditor.eEditorAllowInteraction); 726 await doTest(htmlEditor, "readonly and non-tabbable HTML editor", 727 true, false, false); 728 729 // plaintext 730 editor.flags = flags | nsIEditor.eEditorPlaintextMask; 731 await doTest(htmlEditor, "HTML editor but plaintext mode", false, true, true); 732 733 // plaintext and non-tabbable 734 editor.flags = (flags | nsIEditor.eEditorPlaintextMask) & 735 ~(nsIEditor.eEditorAllowInteraction); 736 await doTest(htmlEditor, "non-tabbable HTML editor but plaintext mode", 737 false, false, true); 738 739 740 // readonly and plaintext 741 editor.flags = flags | nsIEditor.eEditorPlaintextMask | 742 nsIEditor.eEditorReadonlyMask; 743 await doTest(htmlEditor, "readonly HTML editor but plaintext mode", 744 true, true, true); 745 746 // readonly, plaintext and non-tabbable 747 editor.flags = (flags | nsIEditor.eEditorPlaintextMask | 748 nsIEditor.eEditorReadonlyMask) & 749 ~(nsIEditor.eEditorAllowInteraction); 750 await doTest(htmlEditor, "readonly and non-tabbable HTML editor but plaintext mode", 751 true, false, true); 752 753 SpecialPowers.wrap(window).removeEventListener("keypress", listener, { capture: true, mozSystemGroup: true }); 754 SpecialPowers.wrap(window).removeEventListener("keypress", listener, { capture: false, mozSystemGroup: true }); 755 756 SimpleTest.finish(); 757 } 758 759 </script> 760 </body> 761 762 </html>