Bug 1302742 - Add keyboard modifiers to contextMenus ClickInfo draft 1302742-context-modifiers
authorTomislav Jovanovic <tomica@gmail.com>
Fri, 27 Jan 2017 21:10:58 +0100
changeset 468161 24af01b0b0596a83e16d67a206433eafe4dafefa
parent 467833 77af15f468e84d9a5ff659641737343ffa0444cf
child 543870 10dba810f2bad9c751c4ebee5a74b8ba5561808d
push id43376
push userbmo:tomica@gmail.com
push dateMon, 30 Jan 2017 20:57:25 +0000
bugs1302742
milestone54.0a1
Bug 1302742 - Add keyboard modifiers to contextMenus ClickInfo MozReview-Commit-ID: K4YnQdH5uOO
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
browser/components/extensions/test/browser/head.js
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -1,16 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/MatchPattern.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 var {
   ExtensionError,
   IconDetails,
   SingletonEventManager,
 } = ExtensionUtils;
 
 const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
@@ -227,16 +228,23 @@ var gMenuBuilder = {
         // Select the clicked radio item.
         item.checked = true;
       }
 
       item.tabManager.addActiveTabPermission();
 
       let tab = item.tabManager.convert(contextData.tab);
       let info = item.getClickInfo(contextData, wasChecked);
+
+      const map = {shiftKey: "Shift", altKey: "Alt", metaKey: "Command", ctrlKey: "Ctrl"};
+      info.modifiers = Object.keys(map).filter(key => event[key]).map(key => map[key]);
+      if (event.ctrlKey && AppConstants.platform === "macosx") {
+        info.modifiers.push("MacCtrl");
+      }
+
       item.extension.emit("webext-contextmenu-menuitem-click", info, tab);
     });
 
     return element;
   },
 
   handleEvent(event) {
     if (this.xulMenu != event.target || event.type != "popuphidden") {
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
@@ -1,12 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+const PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
+
 // Loaded both as a background script and a tab page.
 function testScript() {
   let page = location.pathname.includes("tab.html") ? "tab" : "background";
   let clickCounts = {
     old: 0,
     new: 0,
   };
   browser.contextMenus.onClicked.addListener(() => {
@@ -71,18 +73,17 @@ function testScript() {
     pages = pages.filter(w => w !== window);
     browser.test.assertEq(pages[0], browser.extension.getBackgroundPage(),
         "Expected the other page to be a background page");
     browser.test.sendMessage("tab.html ready");
   }
 }
 
 add_task(function* () {
-  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
-    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
 
   gBrowser.selectedTab = tab1;
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["contextMenus"],
     },
     background: testScript,
@@ -189,8 +190,57 @@ add_task(function* () {
       // more coverage, let's use removeAll instead of remove.
       extension.sendMessage(pageOne, "removeAll");
       yield extension.awaitMessage("next");
     }
   }
   yield extension.unload();
   yield BrowserTestUtils.removeTab(tab1);
 });
+
+add_task(function* test_onclick_modifiers() {
+  const manifest = {
+    permissions: ["contextMenus"],
+  };
+
+  function background() {
+    function onclick(info) {
+      browser.test.sendMessage("click", info);
+    }
+    browser.contextMenus.create({contexts: ["all"], title: "modify", onclick});
+    browser.test.sendMessage("ready");
+  }
+
+  const extension = ExtensionTestUtils.loadExtension({manifest, background});
+  const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+  yield extension.startup();
+  yield extension.awaitMessage("ready");
+
+  function* click(modifiers = {}) {
+    const menu = yield openContextMenu();
+    const items = menu.getElementsByAttribute("label", "modify");
+    yield closeExtensionContextMenu(items[0], modifiers);
+    return extension.awaitMessage("click");
+  }
+
+  const plain = yield click();
+  is(plain.modifiers.length, 0, "modifiers array empty with a plain click");
+
+  const shift = yield click({shiftKey: true});
+  is(shift.modifiers.join(), "Shift", "Correct modifier: Shift");
+
+  const ctrl = yield click({ctrlKey: true});
+  if (AppConstants.platform !== "macosx") {
+    is(ctrl.modifiers.join(), "Ctrl", "Correct modifier: Ctrl");
+  } else {
+    is(ctrl.modifiers.sort().join(), "Ctrl,MacCtrl", "Correct modifier: Ctrl (and MacCtrl)");
+
+    const meta = yield click({metaKey: true});
+    is(meta.modifiers.join(), "Command", "Correct modifier: Command");
+  }
+
+  const altShift = yield click({altKey: true, shiftKey: true});
+  is(altShift.modifiers.sort().join(), "Alt,Shift", "Correct modifiers: Shift+Alt");
+
+  yield BrowserTestUtils.removeTab(tab);
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -252,20 +252,20 @@ function* openExtensionContextMenu(selec
 
   let extensionMenu = topLevelMenu[0].childNodes[0];
   let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
   yield popupShownPromise;
   return extensionMenu;
 }
 
-function* closeExtensionContextMenu(itemToSelect) {
+function* closeExtensionContextMenu(itemToSelect, modifiers = {}) {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-  EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
+  EventUtils.synthesizeMouseAtCenter(itemToSelect, modifiers);
   yield popupHiddenPromise;
 }
 
 function* openChromeContextMenu(menuId, target, win = window) {
   const node = win.document.querySelector(target);
   const menu = win.document.getElementById(menuId);
   const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
   EventUtils.synthesizeMouseAtCenter(node, {type: "contextmenu"}, win);