browser_key_shortcuts.js 11 KB

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