diff options
Diffstat (limited to 'devtools/client/shared/test/browser_key_shortcuts.js')
-rw-r--r-- | devtools/client/shared/test/browser_key_shortcuts.js | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/devtools/client/shared/test/browser_key_shortcuts.js b/devtools/client/shared/test/browser_key_shortcuts.js new file mode 100644 index 0000000000..d48666ddca --- /dev/null +++ b/devtools/client/shared/test/browser_key_shortcuts.js @@ -0,0 +1,468 @@ +/* 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(async function () { + const shortcuts = new KeyShortcuts({ + window, + }); + + await testSimple(shortcuts); + await testNonLetterCharacter(shortcuts); + await testPlusCharacter(shortcuts); + await testFunctionKey(shortcuts); + await testMixup(shortcuts); + await testLooseDigits(shortcuts); + await testExactModifiers(shortcuts); + await testLooseShiftModifier(shortcuts); + await testStrictLetterShiftModifier(shortcuts); + await testAltModifier(shortcuts); + await testCommandOrControlModifier(shortcuts); + await testCtrlModifier(shortcuts); + await testInvalidShortcutString(shortcuts); + await testNullShortcut(shortcuts); + await testCmdShiftShortcut(shortcuts); + await testTabCharacterShortcut(shortcuts); + shortcuts.destroy(); + + await 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 => { + const onShortcut = event => { + shortcuts.off(key, onShortcut); + ok(!called, "once listener called only once (i.e. off() works)"); + called = true; + listener(event); + done(); + }; + shortcuts.on(key, onShortcut); + }); +} + +async function testSimple(shortcuts) { + info("Test simple key shortcuts"); + + const onKey = once(shortcuts, "0", event => { + is(event.key, "0"); + + // Display another key press to ensure that once() correctly stop listening + EventUtils.synthesizeKey("0", {}, window); + }); + + EventUtils.synthesizeKey("0", {}, window); + await onKey; +} + +async function testNonLetterCharacter(shortcuts) { + info("Test non-naive character key shortcuts"); + + const onKey = once(shortcuts, "[", event => { + is(event.key, "["); + }); + + EventUtils.synthesizeKey("[", {}, window); + await onKey; +} + +async function testFunctionKey(shortcuts) { + info("Test function key shortcuts"); + + const onKey = once(shortcuts, "F12", event => { + is(event.key, "F12"); + }); + + EventUtils.synthesizeKey("F12", { keyCode: 123 }, window); + await 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 +async function testPlusCharacter(shortcuts) { + info("Test 'Plus' key shortcuts"); + + const onKey = once(shortcuts, "Plus", event => { + is(event.key, "+"); + }); + + EventUtils.synthesizeKey("+", { keyCode: 61, shiftKey: true }, window); + await onKey; +} + +// Test they listeners are not mixed up between shortcuts +async function testMixup(shortcuts) { + info("Test possible listener mixup"); + + let hitFirst = false, + hitSecond = false; + const onFirstKey = once(shortcuts, "0", event => { + is(event.key, "0"); + hitFirst = true; + }); + const onSecondKey = once(shortcuts, "Alt+A", 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); + await 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 + await 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); + await 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. +async function testLooseDigits(shortcuts) { + info("Test Loose digits"); + let onKey = once(shortcuts, "0", 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); + await onKey; + + onKey = once(shortcuts, "0", 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); + await onKey; +} + +// Test that shortcuts is notified only when the modifiers match exactly +async function testExactModifiers(shortcuts) { + info("Test exact modifiers match"); + + let hit = false; + const onKey = once(shortcuts, "Alt+A", 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 + await 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 + ); + await 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. +async function testLooseShiftModifier(shortcuts) { + info("Test Loose shift modifier"); + let onKey = once(shortcuts, "%", 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 + ); + await onKey; + + onKey = once(shortcuts, "@", 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 + ); + await onKey; +} + +// But Shift modifier is strict on all letter characters (a to Z) +async function testStrictLetterShiftModifier(shortcuts) { + info("Test strict shift modifier on letters"); + let hitFirst = false; + const onKey = once(shortcuts, "a", event => { + is(event.key, "a"); + ok(!event.altKey); + ok(!event.ctrlKey); + ok(!event.metaKey); + ok(!event.shiftKey); + hitFirst = true; + }); + const onShiftKey = once(shortcuts, "Shift+a", event => { + is(event.key, "a"); + ok(!event.altKey); + ok(!event.ctrlKey); + ok(!event.metaKey); + ok(event.shiftKey); + }); + EventUtils.synthesizeKey("a", { shiftKey: true }, window); + await onShiftKey; + ok(!hitFirst, "Didn't fire the explicit shift+a"); + + EventUtils.synthesizeKey("a", { shiftKey: false }, window); + await onKey; +} + +async function testAltModifier(shortcuts) { + info("Test Alt modifier"); + const onKey = once(shortcuts, "Alt+F1", 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); + await onKey; +} + +async function testCommandOrControlModifier(shortcuts) { + info("Test CommandOrControl modifier"); + const onKey = once(shortcuts, "CommandOrControl+F1", 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); + }); + const onKeyAlias = once(shortcuts, "CmdOrCtrl+F1", 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); + } + await onKey; + await onKeyAlias; +} + +async function testCtrlModifier(shortcuts) { + info("Test Ctrl modifier"); + const onKey = once(shortcuts, "Ctrl+F1", event => { + is(event.keyCode, window.KeyboardEvent.DOM_VK_F1); + ok(!event.altKey); + ok(event.ctrlKey); + ok(!event.metaKey); + ok(!event.shiftKey); + }); + const onKeyAlias = once(shortcuts, "Control+F1", 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); + await onKey; + await onKeyAlias; +} + +async function testCmdShiftShortcut(shortcuts) { + if (!isOSX) { + // This test is OSX only (Bug 1300458). + return; + } + + const onCmdKey = once(shortcuts, "CmdOrCtrl+[", event => { + is(event.key, "["); + ok(!event.altKey); + ok(!event.ctrlKey); + ok(event.metaKey); + ok(!event.shiftKey); + }); + const onCmdShiftKey = once(shortcuts, "CmdOrCtrl+Shift+[", 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); + + await onCmdKey; + await onCmdShiftKey; +} + +async function testTarget() { + info("Test KeyShortcuts with target argument"); + + const target = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "input" + ); + document.documentElement.appendChild(target); + target.focus(); + + const shortcuts = new KeyShortcuts({ + window, + target, + }); + const onKey = once(shortcuts, "0", event => { + is(event.key, "0"); + is(event.target, target); + }); + EventUtils.synthesizeKey("0", {}, window); + await onKey; + + target.remove(); + + shortcuts.destroy(); +} + +function testInvalidShortcutString(shortcuts) { + info("Test wrong shortcut string"); + + const 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"); +} + +// Can happen on localized builds where the value of the localized string is +// empty, eg `toolbox.elementPicker.key=`. See Bug 1569572. +function testNullShortcut(shortcuts) { + info("Test null shortcut"); + + const shortcut = KeyShortcuts.parseElectronKey(window, null); + ok(!shortcut, "Passing a null object should return a null object"); + + const stringified = KeyShortcuts.stringify(shortcut); + is(stringified, "", "A null object should be stringified as an empty string"); + + shortcuts.on(null, function () {}); + ok(true, "on() shouldn't throw when passing a null object"); +} + +/** + * Shift+Alt+I generates ^ key (`event.key`) on OSX and KeyShortcuts module + * must ensure that this doesn't interfere with shortcuts CmdOrCtrl+Alt+Shift+I + * for opening the Browser Toolbox and CmdOrCtrl+Alt+I for toggling the Toolbox. + */ +async function testTabCharacterShortcut(shortcuts) { + if (!isOSX) { + return; + } + + info("Test tab character shortcut"); + + once(shortcuts, "CmdOrCtrl+Alt+I", event => { + ok(false, "This handler must not be executed"); + }); + + const onKey = once(shortcuts, "CmdOrCtrl+Alt+Shift+I", event => { + info("Test for CmdOrCtrl+Alt+Shift+I"); + is(event.key, "^"); + is(event.keyCode, 73); + }); + + // Simulate `CmdOrCtrl+Alt+Shift+I` shortcut. Note that EventUtils doesn't + // generate `^` like real keyboard, so we need to pass it explicitly + // and use proper keyCode for `I` character. + EventUtils.synthesizeKey( + "^", + { + code: "KeyI", + key: "^", + keyCode: 73, + shiftKey: true, + altKey: true, + metaKey: true, + }, + window + ); + + await onKey; +} |