tor-browser

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

browser_key_shortcuts.js (12881B)


      1 /* Any copyright is dedicated to the Public Domain.
      2  http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 var isOSX = Services.appinfo.OS === "Darwin";
      7 
      8 add_task(async function () {
      9  const shortcuts = new KeyShortcuts({
     10    window,
     11  });
     12 
     13  await testSimple(shortcuts);
     14  await testNonLetterCharacter(shortcuts);
     15  await testPlusCharacter(shortcuts);
     16  await testFunctionKey(shortcuts);
     17  await testMixup(shortcuts);
     18  await testLooseDigits(shortcuts);
     19  await testExactModifiers(shortcuts);
     20  await testLooseShiftModifier(shortcuts);
     21  await testStrictLetterShiftModifier(shortcuts);
     22  await testAltModifier(shortcuts);
     23  await testCommandOrControlModifier(shortcuts);
     24  await testCtrlModifier(shortcuts);
     25  await testInvalidShortcutString(shortcuts);
     26  await testNullShortcut(shortcuts);
     27  await testCmdShiftShortcut(shortcuts);
     28  await testTabCharacterShortcut(shortcuts);
     29  shortcuts.destroy();
     30 
     31  await testTarget();
     32 });
     33 
     34 // Test helper to listen to the next key press for a given key,
     35 // returning a promise to help using Tasks.
     36 function once(shortcuts, key, listener) {
     37  let called = false;
     38  return new Promise(done => {
     39    const onShortcut = event => {
     40      shortcuts.off(key, onShortcut);
     41      ok(!called, "once listener called only once (i.e. off() works)");
     42      called = true;
     43      listener(event);
     44      done();
     45    };
     46    shortcuts.on(key, onShortcut);
     47  });
     48 }
     49 
     50 async function testSimple(shortcuts) {
     51  info("Test simple key shortcuts");
     52 
     53  const onKey = once(shortcuts, "0", event => {
     54    is(event.key, "0");
     55 
     56    // Display another key press to ensure that once() correctly stop listening
     57    EventUtils.synthesizeKey("0", {}, window);
     58  });
     59 
     60  EventUtils.synthesizeKey("0", {}, window);
     61  await onKey;
     62 }
     63 
     64 async function testNonLetterCharacter(shortcuts) {
     65  info("Test non-naive character key shortcuts");
     66 
     67  const onKey = once(shortcuts, "[", event => {
     68    is(event.key, "[");
     69  });
     70 
     71  EventUtils.synthesizeKey("[", {}, window);
     72  await onKey;
     73 }
     74 
     75 async function testFunctionKey(shortcuts) {
     76  info("Test function key shortcuts");
     77 
     78  const onKey = once(shortcuts, "F12", event => {
     79    is(event.key, "F12");
     80  });
     81 
     82  EventUtils.synthesizeKey("F12", { keyCode: 123 }, window);
     83  await onKey;
     84 }
     85 
     86 // Plus is special. It's keycode is the one for "=". That's because it requires
     87 // shift to be pressed and is behind "=" key. So it should be considered as a
     88 // character key
     89 async function testPlusCharacter(shortcuts) {
     90  info("Test 'Plus' key shortcuts");
     91 
     92  const onKey = once(shortcuts, "Plus", event => {
     93    is(event.key, "+");
     94  });
     95 
     96  EventUtils.synthesizeKey("+", { keyCode: 61, shiftKey: true }, window);
     97  await onKey;
     98 }
     99 
    100 // Test they listeners are not mixed up between shortcuts
    101 async function testMixup(shortcuts) {
    102  info("Test possible listener mixup");
    103 
    104  let hitFirst = false,
    105    hitSecond = false;
    106  const onFirstKey = once(shortcuts, "0", event => {
    107    is(event.key, "0");
    108    hitFirst = true;
    109  });
    110  const onSecondKey = once(shortcuts, "Alt+A", event => {
    111    is(event.key, "a");
    112    ok(event.altKey);
    113    hitSecond = true;
    114  });
    115 
    116  // Dispatch the first shortcut and expect only this one to be notified
    117  ok(!hitFirst, "First shortcut isn't notified before firing the key event");
    118  EventUtils.synthesizeKey("0", {}, window);
    119  await onFirstKey;
    120  ok(hitFirst, "Got the first shortcut notified");
    121  ok(!hitSecond, "No mixup, second shortcut is still not notified (1/2)");
    122 
    123  // Wait an extra time, just to be sure this isn't racy
    124  await new Promise(done => {
    125    window.setTimeout(done, 0);
    126  });
    127  ok(!hitSecond, "No mixup, second shortcut is still not notified (2/2)");
    128 
    129  // Finally dispatch the second shortcut
    130  EventUtils.synthesizeKey("a", { altKey: true }, window);
    131  await onSecondKey;
    132  ok(hitSecond, "Got the second shortcut notified once it is actually fired");
    133 }
    134 
    135 // On azerty keyboard, digits are only available by pressing Shift/Capslock,
    136 // but we accept them even if we omit doing that.
    137 async function testLooseDigits(shortcuts) {
    138  info("Test Loose digits");
    139  let onKey = once(shortcuts, "0", event => {
    140    is(event.key, "à");
    141    ok(!event.altKey);
    142    ok(!event.ctrlKey);
    143    ok(!event.metaKey);
    144    ok(!event.shiftKey);
    145  });
    146  // Simulate a press on the "0" key, without shift pressed on a french
    147  // keyboard
    148  EventUtils.synthesizeKey("à", { keyCode: 48 }, window);
    149  await onKey;
    150 
    151  onKey = once(shortcuts, "0", event => {
    152    is(event.key, "0");
    153    ok(!event.altKey);
    154    ok(!event.ctrlKey);
    155    ok(!event.metaKey);
    156    ok(event.shiftKey);
    157  });
    158  // Simulate the same press with shift pressed
    159  EventUtils.synthesizeKey("0", { keyCode: 48, shiftKey: true }, window);
    160  await onKey;
    161 }
    162 
    163 // Test that shortcuts is notified only when the modifiers match exactly
    164 async function testExactModifiers(shortcuts) {
    165  info("Test exact modifiers match");
    166 
    167  let hit = false;
    168  const onKey = once(shortcuts, "Alt+A", event => {
    169    is(event.key, "a");
    170    ok(event.altKey);
    171    ok(!event.ctrlKey);
    172    ok(!event.metaKey);
    173    ok(!event.shiftKey);
    174    hit = true;
    175  });
    176 
    177  // Dispatch with unexpected set of modifiers
    178  ok(!hit, "Shortcut isn't notified before firing the key event");
    179  EventUtils.synthesizeKey(
    180    "a",
    181    { accelKey: true, altKey: true, shiftKey: true },
    182    window
    183  );
    184  EventUtils.synthesizeKey(
    185    "a",
    186    { accelKey: true, altKey: false, shiftKey: false },
    187    window
    188  );
    189  EventUtils.synthesizeKey(
    190    "a",
    191    { accelKey: false, altKey: false, shiftKey: true },
    192    window
    193  );
    194  EventUtils.synthesizeKey(
    195    "a",
    196    { accelKey: false, altKey: false, shiftKey: false },
    197    window
    198  );
    199 
    200  // Wait an extra time to let a chance to call the listener
    201  await new Promise(done => {
    202    window.setTimeout(done, 0);
    203  });
    204  ok(!hit, "Listener isn't called when modifiers aren't exactly matching");
    205 
    206  // Dispatch the expected modifiers
    207  EventUtils.synthesizeKey(
    208    "a",
    209    { accelKey: false, altKey: true, shiftKey: false },
    210    window
    211  );
    212  await onKey;
    213  ok(hit, "Got shortcut notified once it is actually fired");
    214 }
    215 
    216 // Some keys are only accessible via shift and listener should also be called
    217 // even if the key didn't explicitely requested Shift modifier.
    218 // For example, `%` on french keyboards is only accessible via Shift.
    219 // Same thing for `@` on US keybords.
    220 async function testLooseShiftModifier(shortcuts) {
    221  info("Test Loose shift modifier");
    222  let onKey = once(shortcuts, "%", event => {
    223    is(event.key, "%");
    224    ok(!event.altKey);
    225    ok(!event.ctrlKey);
    226    ok(!event.metaKey);
    227    ok(event.shiftKey);
    228  });
    229  EventUtils.synthesizeKey(
    230    "%",
    231    { accelKey: false, altKey: false, ctrlKey: false, shiftKey: true },
    232    window
    233  );
    234  await onKey;
    235 
    236  onKey = once(shortcuts, "@", event => {
    237    is(event.key, "@");
    238    ok(!event.altKey);
    239    ok(!event.ctrlKey);
    240    ok(!event.metaKey);
    241    ok(event.shiftKey);
    242  });
    243  EventUtils.synthesizeKey(
    244    "@",
    245    { accelKey: false, altKey: false, ctrlKey: false, shiftKey: true },
    246    window
    247  );
    248  await onKey;
    249 }
    250 
    251 // But Shift modifier is strict on all letter characters (a to Z)
    252 async function testStrictLetterShiftModifier(shortcuts) {
    253  info("Test strict shift modifier on letters");
    254  let hitFirst = false;
    255  const onKey = once(shortcuts, "a", event => {
    256    is(event.key, "a");
    257    ok(!event.altKey);
    258    ok(!event.ctrlKey);
    259    ok(!event.metaKey);
    260    ok(!event.shiftKey);
    261    hitFirst = true;
    262  });
    263  const onShiftKey = once(shortcuts, "Shift+a", event => {
    264    is(event.key, "a");
    265    ok(!event.altKey);
    266    ok(!event.ctrlKey);
    267    ok(!event.metaKey);
    268    ok(event.shiftKey);
    269  });
    270  EventUtils.synthesizeKey("a", { shiftKey: true }, window);
    271  await onShiftKey;
    272  ok(!hitFirst, "Didn't fire the explicit shift+a");
    273 
    274  EventUtils.synthesizeKey("a", { shiftKey: false }, window);
    275  await onKey;
    276 }
    277 
    278 async function testAltModifier(shortcuts) {
    279  info("Test Alt modifier");
    280  const onKey = once(shortcuts, "Alt+F1", event => {
    281    is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
    282    ok(event.altKey);
    283    ok(!event.ctrlKey);
    284    ok(!event.metaKey);
    285    ok(!event.shiftKey);
    286  });
    287  EventUtils.synthesizeKey("VK_F1", { altKey: true }, window);
    288  await onKey;
    289 }
    290 
    291 async function testCommandOrControlModifier(shortcuts) {
    292  info("Test CommandOrControl modifier");
    293  const onKey = once(shortcuts, "CommandOrControl+F1", event => {
    294    is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
    295    ok(!event.altKey);
    296    if (isOSX) {
    297      ok(!event.ctrlKey);
    298      ok(event.metaKey);
    299    } else {
    300      ok(event.ctrlKey);
    301      ok(!event.metaKey);
    302    }
    303    ok(!event.shiftKey);
    304  });
    305  const onKeyAlias = once(shortcuts, "CmdOrCtrl+F1", event => {
    306    is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
    307    ok(!event.altKey);
    308    if (isOSX) {
    309      ok(!event.ctrlKey);
    310      ok(event.metaKey);
    311    } else {
    312      ok(event.ctrlKey);
    313      ok(!event.metaKey);
    314    }
    315    ok(!event.shiftKey);
    316  });
    317  if (isOSX) {
    318    EventUtils.synthesizeKey("VK_F1", { metaKey: true }, window);
    319  } else {
    320    EventUtils.synthesizeKey("VK_F1", { ctrlKey: true }, window);
    321  }
    322  await onKey;
    323  await onKeyAlias;
    324 }
    325 
    326 async function testCtrlModifier(shortcuts) {
    327  info("Test Ctrl modifier");
    328  const onKey = once(shortcuts, "Ctrl+F1", event => {
    329    is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
    330    ok(!event.altKey);
    331    ok(event.ctrlKey);
    332    ok(!event.metaKey);
    333    ok(!event.shiftKey);
    334  });
    335  const onKeyAlias = once(shortcuts, "Control+F1", event => {
    336    is(event.keyCode, window.KeyboardEvent.DOM_VK_F1);
    337    ok(!event.altKey);
    338    ok(event.ctrlKey);
    339    ok(!event.metaKey);
    340    ok(!event.shiftKey);
    341  });
    342  EventUtils.synthesizeKey("VK_F1", { ctrlKey: true }, window);
    343  await onKey;
    344  await onKeyAlias;
    345 }
    346 
    347 async function testCmdShiftShortcut(shortcuts) {
    348  if (!isOSX) {
    349    // This test is OSX only (Bug 1300458).
    350    return;
    351  }
    352 
    353  const onCmdKey = once(shortcuts, "CmdOrCtrl+[", event => {
    354    is(event.key, "[");
    355    ok(!event.altKey);
    356    ok(!event.ctrlKey);
    357    ok(event.metaKey);
    358    ok(!event.shiftKey);
    359  });
    360  const onCmdShiftKey = once(shortcuts, "CmdOrCtrl+Shift+[", event => {
    361    is(event.key, "[");
    362    ok(!event.altKey);
    363    ok(!event.ctrlKey);
    364    ok(event.metaKey);
    365    ok(event.shiftKey);
    366  });
    367 
    368  EventUtils.synthesizeKey("[", { metaKey: true, shiftKey: true }, window);
    369  EventUtils.synthesizeKey("[", { metaKey: true }, window);
    370 
    371  await onCmdKey;
    372  await onCmdShiftKey;
    373 }
    374 
    375 async function testTarget() {
    376  info("Test KeyShortcuts with target argument");
    377 
    378  const target = document.createElementNS(
    379    "http://www.w3.org/1999/xhtml",
    380    "input"
    381  );
    382  document.documentElement.appendChild(target);
    383  target.focus();
    384 
    385  const shortcuts = new KeyShortcuts({
    386    window,
    387    target,
    388  });
    389  const onKey = once(shortcuts, "0", event => {
    390    is(event.key, "0");
    391    is(event.target, target);
    392  });
    393  EventUtils.synthesizeKey("0", {}, window);
    394  await onKey;
    395 
    396  target.remove();
    397 
    398  shortcuts.destroy();
    399 }
    400 
    401 function testInvalidShortcutString(shortcuts) {
    402  info("Test wrong shortcut string");
    403 
    404  const shortcut = KeyShortcuts.parseElectronKey("Cmmd+F");
    405  ok(
    406    !shortcut,
    407    "Passing a invalid shortcut string should return a null object"
    408  );
    409 
    410  shortcuts.on("Cmmd+F", function () {});
    411  ok(true, "on() shouldn't throw when passing invalid shortcut string");
    412 }
    413 
    414 // Can happen on localized builds where the value of the localized string is
    415 // empty, eg `toolbox.elementPicker.key=`. See Bug 1569572.
    416 function testNullShortcut(shortcuts) {
    417  info("Test null shortcut");
    418 
    419  const shortcut = KeyShortcuts.parseElectronKey(null);
    420  ok(!shortcut, "Passing a null object should return a null object");
    421 
    422  const stringified = KeyShortcuts.stringifyShortcut(shortcut);
    423  is(stringified, "", "A null object should be stringified as an empty string");
    424 
    425  shortcuts.on(null, function () {});
    426  ok(true, "on() shouldn't throw when passing a null object");
    427 }
    428 
    429 /**
    430 * Shift+Alt+I generates ^ key (`event.key`) on OSX and KeyShortcuts module
    431 * must ensure that this doesn't interfere with shortcuts CmdOrCtrl+Alt+Shift+I
    432 * for opening the Browser Toolbox and CmdOrCtrl+Alt+I for toggling the Toolbox.
    433 */
    434 async function testTabCharacterShortcut(shortcuts) {
    435  if (!isOSX) {
    436    return;
    437  }
    438 
    439  info("Test tab character shortcut");
    440 
    441  once(shortcuts, "CmdOrCtrl+Alt+I", () => {
    442    ok(false, "This handler must not be executed");
    443  });
    444 
    445  const onKey = once(shortcuts, "CmdOrCtrl+Alt+Shift+I", event => {
    446    info("Test for CmdOrCtrl+Alt+Shift+I");
    447    is(event.key, "^");
    448    is(event.keyCode, 73);
    449  });
    450 
    451  // Simulate `CmdOrCtrl+Alt+Shift+I` shortcut. Note that EventUtils doesn't
    452  // generate `^` like real keyboard, so we need to pass it explicitly
    453  // and use proper keyCode for `I` character.
    454  EventUtils.synthesizeKey(
    455    "^",
    456    {
    457      code: "KeyI",
    458      key: "^",
    459      keyCode: 73,
    460      shiftKey: true,
    461      altKey: true,
    462      metaKey: true,
    463    },
    464    window
    465  );
    466 
    467  await onKey;
    468 }