tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_texteditor_keyevent_handling.html (18123B)


      1 <html>
      2 <head>
      3  <title>Test for key event handler of text 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  <input type="text" id="inputField">
     12  <input type="password" id="passwordField">
     13  <textarea id="textarea"></textarea>
     14 </div>
     15 <div id="content" style="display: none">
     16 
     17 </div>
     18 <pre id="test">
     19 </pre>
     20 
     21 <script class="testbody" type="application/javascript">
     22 
     23 SimpleTest.waitForExplicitFinish();
     24 SimpleTest.waitForFocus(runTests, window);
     25 
     26 var inputField = document.getElementById("inputField");
     27 var passwordField = document.getElementById("passwordField");
     28 var textarea = document.getElementById("textarea");
     29 
     30 const kIsMac = navigator.platform.includes("Mac");
     31 const kIsWin = navigator.platform.includes("Win");
     32 const kIsLinux = navigator.platform.includes("Linux");
     33 
     34 async function runTests() {
     35  var fm = SpecialPowers.Services.focus;
     36 
     37  var capturingPhase = { fired: false, prevented: false };
     38  var bubblingPhase = { fired: false, prevented: false };
     39 
     40  var listener = {
     41    handleEvent: function _hv(aEvent) {
     42      is(aEvent.type, "keypress", "unexpected event is handled");
     43      switch (aEvent.eventPhase) {
     44      case aEvent.CAPTURING_PHASE:
     45        capturingPhase.fired = true;
     46        capturingPhase.prevented = aEvent.defaultPrevented;
     47        break;
     48      case aEvent.BUBBLING_PHASE:
     49        bubblingPhase.fired = true;
     50        bubblingPhase.prevented = aEvent.defaultPrevented;
     51        aEvent.preventDefault(); // prevent the browser default behavior
     52        break;
     53      default:
     54        ok(false, "event is handled in unexpected phase");
     55      }
     56    },
     57  };
     58 
     59  function check(aDescription,
     60                 aFiredOnCapture, aFiredOnBubbling, aPreventedOnBubbling) {
     61    function getDesciption(aExpected) {
     62      return aDescription + (aExpected ? " wasn't " : " was ");
     63    }
     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  var parentElement = document.getElementById("display");
     83  SpecialPowers.wrap(parentElement).addEventListener("keypress", listener, { capture: true, mozSystemGroup: true });
     84  SpecialPowers.wrap(parentElement).addEventListener("keypress", listener, { capture: false, mozSystemGroup: true });
     85 
     86  async function doTest(aElement, aDescription, aIsSingleLine, aIsReadonly) {
     87    function reset(aText) {
     88      capturingPhase.fired = false;
     89      capturingPhase.prevented = false;
     90      bubblingPhase.fired = false;
     91      bubblingPhase.prevented = false;
     92      aElement.value = aText;
     93    }
     94 
     95    if (document.activeElement) {
     96      document.activeElement.blur();
     97    }
     98 
     99    aDescription += ": ";
    100 
    101    aElement.focus();
    102    is(SpecialPowers.unwrap(fm.focusedElement), aElement, aDescription + "failed to move focus");
    103 
    104    // Backspace key:
    105    //   If native key bindings map the key combination to something, it's consumed.
    106    //   If editor is readonly, it doesn't consume.
    107    //   If editor is editable, it consumes backspace and shift+backspace.
    108    //   Otherwise, editor doesn't consume the event but the native key
    109    //   bindings on nsTextControlFrame may consume it.
    110    reset("");
    111    synthesizeKey("KEY_Backspace");
    112    check(aDescription + "Backspace", true, true, true);
    113 
    114    reset("");
    115    synthesizeKey("KEY_Backspace", {shiftKey: true});
    116    check(aDescription + "Shift+Backspace", true, true, true);
    117 
    118    reset("");
    119    synthesizeKey("KEY_Backspace", {ctrlKey: true});
    120    // Win: cmd_deleteWordBackward
    121    check(aDescription + "Ctrl+Backspace",
    122          true, true, aIsReadonly || kIsWin);
    123 
    124    reset("");
    125    synthesizeKey("KEY_Backspace", {altKey: true});
    126    // Win: cmd_undo
    127    // Mac: cmd_deleteWordBackward
    128    check(aDescription + "Alt+Backspace",
    129          true, true, aIsReadonly || kIsWin || kIsMac);
    130 
    131    reset("");
    132    synthesizeKey("KEY_Backspace", {metaKey: true});
    133    check(aDescription + "Meta+Backspace", true, true, aIsReadonly || kIsMac);
    134 
    135    // Delete key:
    136    //   If native key bindings map the key combination to something, it's consumed.
    137    //   If editor is readonly, it doesn't consume.
    138    //   If editor is editable, delete is consumed.
    139    //   Otherwise, editor doesn't consume the event but the native key
    140    //   bindings on nsTextControlFrame may consume it.
    141    reset("");
    142    synthesizeKey("KEY_Delete");
    143    // Linux: native handler
    144    // Mac: cmd_deleteCharForward
    145    check(aDescription + "Delete",
    146          true, true, !aIsReadonly || kIsLinux || kIsMac);
    147 
    148    reset("");
    149    // Win: cmd_cutOrDelete
    150    // Linux: cmd_cut
    151    // Mac: cmd_deleteCharForward
    152    synthesizeKey("KEY_Delete", {shiftKey: true});
    153    check(aDescription + "Shift+Delete",
    154          true, true, true);
    155 
    156    reset("");
    157    synthesizeKey("KEY_Delete", {ctrlKey: true});
    158    // Win: cmd_deleteWordForward
    159    // Linux: cmd_copy
    160    check(aDescription + "Ctrl+Delete",
    161          true, true, kIsWin || kIsLinux);
    162 
    163    reset("");
    164    synthesizeKey("KEY_Delete", {altKey: true});
    165    // Mac: cmd_deleteWordForward
    166    check(aDescription + "Alt+Delete",
    167          true, true, kIsMac);
    168 
    169    reset("");
    170    synthesizeKey("KEY_Delete", {metaKey: true});
    171    // Linux: native handler consumed.
    172    check(aDescription + "Meta+Delete",
    173          true, true, kIsLinux);
    174 
    175    // XXX input.value returns "\n" when it's empty, so, we should use dummy
    176    // value ("a") for the following tests.
    177 
    178    // Return key:
    179    //   If editor is readonly, it doesn't consume.
    180    //   If editor is editable and not single line editor, it consumes Return
    181    //   and Shift+Return.
    182    //   Otherwise, editor doesn't consume the event.
    183    reset("a");
    184    synthesizeKey("KEY_Enter");
    185    check(aDescription + "Return",
    186          true, true, !aIsSingleLine && !aIsReadonly);
    187    is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
    188       aDescription + "Return");
    189 
    190    reset("a");
    191    synthesizeKey("KEY_Enter", {shiftKey: true});
    192    check(aDescription + "Shift+Return",
    193          true, true, !aIsSingleLine && !aIsReadonly);
    194    is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
    195       aDescription + "Shift+Return");
    196 
    197    reset("a");
    198    synthesizeKey("KEY_Enter", {ctrlKey: true});
    199    check(aDescription + "Ctrl+Return", true, true, false);
    200    is(aElement.value, "a", aDescription + "Ctrl+Return");
    201 
    202    reset("a");
    203    synthesizeKey("KEY_Enter", {altKey: true});
    204    check(aDescription + "Alt+Return", true, true, false);
    205    is(aElement.value, "a", aDescription + "Alt+Return");
    206 
    207    reset("a");
    208    synthesizeKey("KEY_Enter", {metaKey: true});
    209    check(aDescription + "Meta+Return", true, true, false);
    210    is(aElement.value, "a", aDescription + "Meta+Return");
    211 
    212    // Tab key:
    213    //   Editor consumes tab key event unless any modifier keys are pressed.
    214    reset("a");
    215    synthesizeKey("KEY_Tab");
    216    check(aDescription + "Tab", true, true, false);
    217    is(aElement.value, "a", aDescription + "Tab");
    218    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    219       aDescription + "focus moved unexpectedly (Tab)");
    220    aElement.focus();
    221 
    222    reset("a");
    223    synthesizeKey("KEY_Tab", {shiftKey: true});
    224    check(aDescription + "Shift+Tab", true, true, false);
    225    is(aElement.value, "a", aDescription + "Shift+Tab");
    226    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    227       aDescription + "focus moved unexpectedly (Shift+Tab)");
    228 
    229    // Ctrl+Tab should be consumed by tabbrowser at keydown, so, keypress
    230    // event should never be fired.
    231    reset("a");
    232    synthesizeKey("KEY_Tab", {ctrlKey: true});
    233    check(aDescription + "Ctrl+Tab", false, false, false);
    234    is(aElement.value, "a", aDescription + "Ctrl+Tab");
    235    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    236       aDescription + "focus moved unexpectedly (Ctrl+Tab)");
    237 
    238    reset("a");
    239    synthesizeKey("KEY_Tab", {altKey: true});
    240    check(aDescription + "Alt+Tab", true, true, false);
    241    is(aElement.value, "a", aDescription + "Alt+Tab");
    242    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    243       aDescription + "focus moved unexpectedly (Alt+Tab)");
    244 
    245    reset("a");
    246    synthesizeKey("KEY_Tab", {metaKey: true});
    247    check(aDescription + "Meta+Tab", true, true, false);
    248    is(aElement.value, "a", aDescription + "Meta+Tab");
    249    is(SpecialPowers.unwrap(fm.focusedElement), aElement,
    250       aDescription + "focus moved unexpectedly (Meta+Tab)");
    251 
    252    // Esc key:
    253    //   In all cases, esc key events are not consumed
    254    reset("abc");
    255    synthesizeKey("KEY_Escape");
    256    check(aDescription + "Esc", true, true, false);
    257 
    258    reset("abc");
    259    synthesizeKey("KEY_Escape", {shiftKey: true});
    260    check(aDescription + "Shift+Esc", true, true, false);
    261 
    262    reset("abc");
    263    synthesizeKey("KEY_Escape", {ctrlKey: true});
    264    check(aDescription + "Ctrl+Esc", true, true, false);
    265 
    266    reset("abc");
    267    synthesizeKey("KEY_Escape", {altKey: true});
    268    check(aDescription + "Alt+Esc", true, true, false);
    269 
    270    reset("abc");
    271    synthesizeKey("KEY_Escape", {metaKey: true});
    272    check(aDescription + "Meta+Esc", true, true, false);
    273 
    274    // typical typing tests:
    275    reset("");
    276    sendString("M");
    277    check(aDescription + "M", true, true, !aIsReadonly);
    278    sendString("o");
    279    check(aDescription + "o", true, true, !aIsReadonly);
    280    sendString("z");
    281    check(aDescription + "z", true, true, !aIsReadonly);
    282    sendString("i");
    283    check(aDescription + "i", true, true, !aIsReadonly);
    284    sendString("l");
    285    check(aDescription + "l", true, true, !aIsReadonly);
    286    sendString("l");
    287    check(aDescription + "l", true, true, !aIsReadonly);
    288    sendString("a");
    289    check(aDescription + "a", true, true, !aIsReadonly);
    290    sendString(" ");
    291    check(aDescription + "' '", true, true, !aIsReadonly);
    292    is(aElement.value, !aIsReadonly ? "Mozilla " : "",
    293       aDescription + "typed \"Mozilla \"");
    294 
    295    // typing non-BMP character:
    296    async function test_typing_surrogate_pair(
    297      aTestPerSurrogateKeyPress,
    298      aTestIllFormedUTF16KeyValue = false
    299    ) {
    300      await SpecialPowers.pushPrefEnv({
    301        set: [
    302          ["dom.event.keypress.dispatch_once_per_surrogate_pair", !aTestPerSurrogateKeyPress],
    303          ["dom.event.keypress.key.allow_lone_surrogate", aTestIllFormedUTF16KeyValue],
    304        ],
    305      });
    306      reset("");
    307      let events = [];
    308      function pushIntoEvents(aEvent) {
    309        events.push(aEvent);
    310      }
    311      function getEventData(aKeyboardEventOrInputEvent) {
    312        if (!aKeyboardEventOrInputEvent) {
    313          return "{}";
    314        }
    315        switch (aKeyboardEventOrInputEvent.type) {
    316          case "keydown":
    317          case "keypress":
    318          case "keyup":
    319            return `{ type: "${aKeyboardEventOrInputEvent.type}", key="${
    320              aKeyboardEventOrInputEvent.key
    321            }", charCode=0x${
    322              aKeyboardEventOrInputEvent.charCode.toString(16).toUpperCase()
    323            } }`;
    324          default:
    325            return `{ type: "${aKeyboardEventOrInputEvent.type}", inputType="${
    326              aKeyboardEventOrInputEvent.inputType
    327            }", data="${aKeyboardEventOrInputEvent.data}" }`;
    328        }
    329      }
    330      function getEventArrayData(aEvents) {
    331        if (!aEvents.length) {
    332          return "[]";
    333        }
    334        let result = "[\n";
    335        for (const e of aEvents) {
    336          result += `  ${getEventData(e)}\n`;
    337        }
    338        return result + "]";
    339      }
    340      aElement.addEventListener("keydown", pushIntoEvents);
    341      aElement.addEventListener("keypress", pushIntoEvents);
    342      aElement.addEventListener("keyup", pushIntoEvents);
    343      aElement.addEventListener("beforeinput", pushIntoEvents);
    344      aElement.addEventListener("input", pushIntoEvents);
    345      synthesizeKey("\uD842\uDFB7");
    346      aElement.removeEventListener("keydown", pushIntoEvents);
    347      aElement.removeEventListener("keypress", pushIntoEvents);
    348      aElement.removeEventListener("keyup", pushIntoEvents);
    349      aElement.removeEventListener("beforeinput", pushIntoEvents);
    350      aElement.removeEventListener("input", pushIntoEvents);
    351      const settingDescription =
    352        `aTestPerSurrogateKeyPress=${
    353          aTestPerSurrogateKeyPress
    354        }, aTestIllFormedUTF16KeyValue=${aTestIllFormedUTF16KeyValue}`;
    355      const allowIllFormedUTF16 =
    356        aTestPerSurrogateKeyPress && aTestIllFormedUTF16KeyValue;
    357 
    358      check(`${aDescription}, ${settingDescription}a surrogate pair`, true, true, !aIsReadonly);
    359      is(
    360        aElement.value,
    361        !aIsReadonly ? "\uD842\uDFB7" : "",
    362        `${aDescription}, ${
    363          settingDescription
    364        }, The typed surrogate pair should've been inserted`
    365      );
    366      if (aIsReadonly) {
    367        is(
    368          getEventArrayData(events),
    369          getEventArrayData(
    370            // eslint-disable-next-line no-nested-ternary
    371            aTestPerSurrogateKeyPress
    372              ? (
    373                allowIllFormedUTF16
    374                  ? [
    375                      { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    376                      { type: "keypress",    key:  "\uD842",       charCode: 0xD842 },
    377                      { type: "keypress",    key:  "\uDFB7",       charCode: 0xDFB7 },
    378                      { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    379                    ]
    380                  : [
    381                      { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    382                      { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0xD842 },
    383                      { type: "keypress",    key:  "",             charCode: 0xDFB7 },
    384                      { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    385                    ]
    386                 )
    387              : [
    388                  { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    389                  { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0x20BB7 },
    390                  { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    391                ]
    392          ),
    393          `${aDescription}, ${
    394            settingDescription
    395          }, Typing a surrogate pair in readonly editor should not cause input events`
    396        );
    397      } else {
    398        is(
    399          getEventArrayData(events),
    400          getEventArrayData(
    401            // eslint-disable-next-line no-nested-ternary
    402            aTestPerSurrogateKeyPress
    403              ? (
    404                allowIllFormedUTF16
    405                  ? [
    406                      { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    407                      { type: "keypress",    key:  "\uD842",       charCode: 0xD842 },
    408                      { type: "beforeinput", data: "\uD842",       inputType: "insertText" },
    409                      { type: "input",       data: "\uD842",       inputType: "insertText" },
    410                      { type: "keypress",    key:  "\uDFB7",       charCode: 0xDFB7 },
    411                      { type: "beforeinput", data: "\uDFB7",       inputType: "insertText" },
    412                      { type: "input",       data: "\uDFB7",       inputType: "insertText" },
    413                      { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    414                    ]
    415                  : [
    416                      { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    417                      { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0xD842 },
    418                      { type: "beforeinput", data: "\uD842\uDFB7", inputType: "insertText" },
    419                      { type: "input",       data: "\uD842\uDFB7", inputType: "insertText" },
    420                      { type: "keypress",    key:  "",             charCode: 0xDFB7 },
    421                      { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    422                    ]
    423                )
    424              : [
    425                  { type: "keydown",     key:  "\uD842\uDFB7", charCode: 0 },
    426                  { type: "keypress",    key:  "\uD842\uDFB7", charCode: 0x20BB7 },
    427                  { type: "beforeinput", data: "\uD842\uDFB7", inputType: "insertText" },
    428                  { type: "input",       data: "\uD842\uDFB7", inputType: "insertText" },
    429                  { type: "keyup",       key:  "\uD842\uDFB7", charCode: 0 },
    430                ]
    431          ),
    432          `${aDescription}, ${
    433            settingDescription
    434          }, Typing a surrogate pair in editor should cause input events`
    435        );
    436      }
    437    }
    438    await test_typing_surrogate_pair(true, true);
    439    await test_typing_surrogate_pair(true, false);
    440    await test_typing_surrogate_pair(false);
    441  }
    442 
    443  await doTest(inputField, "<input type=\"text\">", true, false);
    444 
    445  inputField.setAttribute("readonly", "readonly");
    446  await doTest(inputField, "<input type=\"text\" readonly>", true, true);
    447 
    448  await doTest(passwordField, "<input type=\"password\">", true, false);
    449 
    450  passwordField.setAttribute("readonly", "readonly");
    451  await doTest(passwordField, "<input type=\"password\" readonly>", true, true);
    452 
    453  await doTest(textarea, "<textarea>", false, false);
    454 
    455  textarea.setAttribute("readonly", "readonly");
    456  await doTest(textarea, "<textarea readonly>", false, true);
    457 
    458  SpecialPowers.wrap(parentElement).removeEventListener("keypress", listener, { capture: true, mozSystemGroup: true });
    459  SpecialPowers.wrap(parentElement).removeEventListener("keypress", listener, { capture: false, mozSystemGroup: true });
    460 
    461  SimpleTest.finish();
    462 }
    463 
    464 </script>
    465 </body>
    466 
    467 </html>