/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; var isOSX = Services.appinfo.OS === "Darwin"; add_task(function* () { let shortcuts = new KeyShortcuts({ window }); yield testSimple(shortcuts); yield testNonLetterCharacter(shortcuts); yield testPlusCharacter(shortcuts); yield testFunctionKey(shortcuts); yield testMixup(shortcuts); yield testLooseDigits(shortcuts); yield testExactModifiers(shortcuts); yield testLooseShiftModifier(shortcuts); yield testStrictLetterShiftModifier(shortcuts); yield testAltModifier(shortcuts); yield testCommandOrControlModifier(shortcuts); yield testCtrlModifier(shortcuts); yield testInvalidShortcutString(shortcuts); yield testCmdShiftShortcut(shortcuts); shortcuts.destroy(); yield testTarget(); }); // Test helper to listen to the next key press for a given key, // returning a promise to help using Tasks. function once(shortcuts, key, listener) { let called = false; return new Promise(done => { let onShortcut = (key2, event) => { shortcuts.off(key, onShortcut); ok(!called, "once listener called only once (i.e. off() works)"); is(key, key2, "listener first argument match the key we listen"); called = true; listener(key2, event); done(); }; shortcuts.on(key, onShortcut); }); } function* testSimple(shortcuts) { info("Test simple key shortcuts"); let onKey = once(shortcuts, "0", (key, event) => { is(event.key, "0"); // Display another key press to ensure that once() correctly stop listening EventUtils.synthesizeKey("0", {}, window); }); EventUtils.synthesizeKey("0", {}, window); yield onKey; } function* testNonLetterCharacter(shortcuts) { info("Test non-naive character key shortcuts"); let onKey = once(shortcuts, "[", (key, event) => { is(event.key, "["); }); EventUtils.synthesizeKey("[", {}, window); yield onKey; } function* testFunctionKey(shortcuts) { info("Test function key shortcuts"); let onKey = once(shortcuts, "F12", (key, event) => { is(event.key, "F12"); }); EventUtils.synthesizeKey("F12", { keyCode: 123 }, window); yield onKey; } // Plus is special. It's keycode is the one for "=". That's because it requires // shift to be pressed and is behind "=" key. So it should be considered as a // character key function* testPlusCharacter(shortcuts) { info("Test 'Plus' key shortcuts"); let onKey = once(shortcuts, "Plus", (key, event) => { is(event.key, "+"); }); EventUtils.synthesizeKey("+", { keyCode: 61, shiftKey: true }, window); yield onKey; } // Test they listeners are not mixed up between shortcuts function* testMixup(shortcuts) { info("Test possible listener mixup"); let hitFirst = false, hitSecond = false; let onFirstKey = once(shortcuts, "0", (key, event) => { is(event.key, "0"); hitFirst = true; }); let onSecondKey = once(shortcuts, "Alt+A", (key, event) => { is(event.key, "a"); ok(event.altKey); hitSecond = true; }); // Dispatch the first shortcut and expect only this one to be notified ok(!hitFirst, "First shortcut isn't notified before firing the key event"); EventUtils.synthesizeKey("0", {}, window); yield onFirstKey; ok(hitFirst, "Got the first shortcut notified"); ok(!hitSecond, "No mixup, second shortcut is still not notified (1/2)"); // Wait an extra time, just to be sure this isn't racy yield new Promise(done => { window.setTimeout(done, 0); }); ok(!hitSecond, "No mixup, second shortcut is still not notified (2/2)"); // Finally dispatch the second shortcut EventUtils.synthesizeKey("a", { altKey: true }, window); yield onSecondKey; ok(hitSecond, "Got the second shortcut notified once it is actually fired"); } // On azerty keyboard, digits are only available by pressing Shift/Capslock, // but we accept them even if we omit doing that. function* testLooseDigits(shortcuts) { info("Test Loose digits"); let onKey = once(shortcuts, "0", (key, event) => { is(event.key, "à"); ok(!event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(!event.shiftKey); }); // Simulate a press on the "0" key, without shift pressed on a french // keyboard EventUtils.synthesizeKey( "à", { keyCode: 48 }, window); yield onKey; onKey = once(shortcuts, "0", (key, event) => { is(event.key, "0"); ok(!event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(event.shiftKey); }); // Simulate the same press with shift pressed EventUtils.synthesizeKey( "0", { keyCode: 48, shiftKey: true }, window); yield onKey; } // Test that shortcuts is notified only when the modifiers match exactly function* testExactModifiers(shortcuts) { info("Test exact modifiers match"); let hit = false; let onKey = once(shortcuts, "Alt+A", (key, event) => { is(event.key, "a"); ok(event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(!event.shiftKey); hit = true; }); // Dispatch with unexpected set of modifiers ok(!hit, "Shortcut isn't notified before firing the key event"); EventUtils.synthesizeKey("a", { accelKey: true, altKey: true, shiftKey: true }, window); EventUtils.synthesizeKey( "a", { accelKey: true, altKey: false, shiftKey: false }, window); EventUtils.synthesizeKey( "a", { accelKey: false, altKey: false, shiftKey: true }, window); EventUtils.synthesizeKey( "a", { accelKey: false, altKey: false, shiftKey: false }, window); // Wait an extra time to let a chance to call the listener yield new Promise(done => { window.setTimeout(done, 0); }); ok(!hit, "Listener isn't called when modifiers aren't exactly matching"); // Dispatch the expected modifiers EventUtils.synthesizeKey("a", { accelKey: false, altKey: true, shiftKey: false}, window); yield onKey; ok(hit, "Got shortcut notified once it is actually fired"); } // Some keys are only accessible via shift and listener should also be called // even if the key didn't explicitely requested Shift modifier. // For example, `%` on french keyboards is only accessible via Shift. // Same thing for `@` on US keybords. function* testLooseShiftModifier(shortcuts) { info("Test Loose shift modifier"); let onKey = once(shortcuts, "%", (key, event) => { is(event.key, "%"); ok(!event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(event.shiftKey); }); EventUtils.synthesizeKey( "%", { accelKey: false, altKey: false, ctrlKey: false, shiftKey: true}, window); yield onKey; onKey = once(shortcuts, "@", (key, event) => { is(event.key, "@"); ok(!event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(event.shiftKey); }); EventUtils.synthesizeKey( "@", { accelKey: false, altKey: false, ctrlKey: false, shiftKey: true}, window); yield onKey; } // But Shift modifier is strict on all letter characters (a to Z) function* testStrictLetterShiftModifier(shortcuts) { info("Test strict shift modifier on letters"); let hitFirst = false; let onKey = once(shortcuts, "a", (key, event) => { is(event.key, "a"); ok(!event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(!event.shiftKey); hitFirst = true; }); let onShiftKey = once(shortcuts, "Shift+a", (key, event) => { is(event.key, "a"); ok(!event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(event.shiftKey); }); EventUtils.synthesizeKey( "a", { shiftKey: true}, window); yield onShiftKey; ok(!hitFirst, "Didn't fire the explicit shift+a"); EventUtils.synthesizeKey( "a", { shiftKey: false}, window); yield onKey; } function* testAltModifier(shortcuts) { info("Test Alt modifier"); let onKey = once(shortcuts, "Alt+F1", (key, event) => { is(event.keyCode, window.KeyboardEvent.DOM_VK_F1); ok(event.altKey); ok(!event.ctrlKey); ok(!event.metaKey); ok(!event.shiftKey); }); EventUtils.synthesizeKey( "VK_F1", { altKey: true }, window); yield onKey; } function* testCommandOrControlModifier(shortcuts) { info("Test CommandOrControl modifier"); let onKey = once(shortcuts, "CommandOrControl+F1", (key, event) => { is(event.keyCode, window.KeyboardEvent.DOM_VK_F1); ok(!event.altKey); if (isOSX) { ok(!event.ctrlKey); ok(event.metaKey); } else { ok(event.ctrlKey); ok(!event.metaKey); } ok(!event.shiftKey); }); let onKeyAlias = once(shortcuts, "CmdOrCtrl+F1", (key, event) => { is(event.keyCode, window.KeyboardEvent.DOM_VK_F1); ok(!event.altKey); if (isOSX) { ok(!event.ctrlKey); ok(event.metaKey); } else { ok(event.ctrlKey); ok(!event.metaKey); } ok(!event.shiftKey); }); if (isOSX) { EventUtils.synthesizeKey( "VK_F1", { metaKey: true }, window); } else { EventUtils.synthesizeKey( "VK_F1", { ctrlKey: true }, window); } yield onKey; yield onKeyAlias; } function* testCtrlModifier(shortcuts) { info("Test Ctrl modifier"); let onKey = once(shortcuts, "Ctrl+F1", (key, event) => { is(event.keyCode, window.KeyboardEvent.DOM_VK_F1); ok(!event.altKey); ok(event.ctrlKey); ok(!event.metaKey); ok(!event.shiftKey); }); let onKeyAlias = once(shortcuts, "Control+F1", (key, event) => { is(event.keyCode, window.KeyboardEvent.DOM_VK_F1); ok(!event.altKey); ok(event.ctrlKey); ok(!event.metaKey); ok(!event.shiftKey); }); EventUtils.synthesizeKey( "VK_F1", { ctrlKey: true }, window); yield onKey; yield onKeyAlias; } function* testCmdShiftShortcut(shortcuts) { if (!isOSX) { // This test is OSX only (Bug 1300458). return; } let onCmdKey = once(shortcuts, "CmdOrCtrl+[", (key, event) => { is(event.key, "["); ok(!event.altKey); ok(!event.ctrlKey); ok(event.metaKey); ok(!event.shiftKey); }); let onCmdShiftKey = once(shortcuts, "CmdOrCtrl+Shift+[", (key, event) => { is(event.key, "["); ok(!event.altKey); ok(!event.ctrlKey); ok(event.metaKey); ok(event.shiftKey); }); EventUtils.synthesizeKey( "[", { metaKey: true, shiftKey: true }, window); EventUtils.synthesizeKey( "[", { metaKey: true }, window); yield onCmdKey; yield onCmdShiftKey; } function* testTarget() { info("Test KeyShortcuts with target argument"); let target = document.createElementNS("http://www.w3.org/1999/xhtml", "input"); document.documentElement.appendChild(target); target.focus(); let shortcuts = new KeyShortcuts({ window, target }); let onKey = once(shortcuts, "0", (key, event) => { is(event.key, "0"); is(event.target, target); }); EventUtils.synthesizeKey("0", {}, window); yield onKey; target.remove(); shortcuts.destroy(); } function testInvalidShortcutString(shortcuts) { info("Test wrong shortcut string"); let shortcut = KeyShortcuts.parseElectronKey(window, "Cmmd+F"); ok(!shortcut, "Passing a invalid shortcut string should return a null object"); shortcuts.on("Cmmd+F", function () {}); ok(true, "on() shouldn't throw when passing invalid shortcut string"); }