Bug 1268020 - Implement "main_menu" context draft 1268020-mainmenu
authorTomislav Jovanovic <tomica@gmail.com>
Sun, 22 Jan 2017 19:05:30 +0100
changeset 465624 b3bf36d6fb8d488a8f0b08db6337d3f6381a0e70
parent 464741 d5343b0f7e6a9619dfdf333d259cff6b4f773bff
child 543196 4c78b705529c14f56df30c72fdcef95ffb9cf7d2
push id42651
push userbmo:tomica@gmail.com
push dateTue, 24 Jan 2017 14:09:10 +0000
bugs1268020
milestone53.0a1
Bug 1268020 - Implement "main_menu" context MozReview-Commit-ID: JRJSIYDR0JM
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/schemas/context_menus.json
browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
browser/components/extensions/test/browser/head.js
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -298,23 +298,29 @@ function getContexts(contextData) {
   if (contextData.onPageAction) {
     contexts.add("page_action");
   }
 
   if (contextData.onBrowserAction) {
     contexts.add("browser_action");
   }
 
+  if (contextData.onTab) {
+    contexts.add("tab");
+  }
+
+  if (contextData.inMainMenu) {
+    contexts.add("main_menu");
+  }
+
   if (contexts.size === 0) {
     contexts.add("page");
   }
 
-  if (contextData.onTab) {
-    contexts.add("tab");
-  } else {
+  if (!contextData.onTab && !contextData.inMainMenu) {
     contexts.add("all");
   }
 
   return contexts;
 }
 
 function MenuItem(extension, createProperties, isRoot = false) {
   this.extension = extension;
@@ -529,70 +535,81 @@ MenuItem.prototype = {
       }
     }
 
     return true;
   },
 };
 
 // While any extensions are active, this Tracker registers to observe/listen
-// for contex-menu events from both content and chrome.
-const contextMenuTracker = {
+// for menu events from both main and context-menus, both content and chrome.
+const menuTracker = {
+  menuIds: ["menu_ToolsPopup", "tabContextMenu"],
+
   register() {
     Services.obs.addObserver(this, "on-build-contextmenu", false);
     for (const window of WindowListManager.browserWindows()) {
       this.onWindowOpen(window);
     }
     WindowListManager.addOpenListener(this.onWindowOpen);
   },
 
   unregister() {
     Services.obs.removeObserver(this, "on-build-contextmenu");
     for (const window of WindowListManager.browserWindows()) {
-      const menu = window.document.getElementById("tabContextMenu");
-      menu.removeEventListener("popupshowing", this);
+      for (const id of this.menuIds) {
+        const menu = window.document.getElementById(id);
+        menu.removeEventListener("popupshowing", this);
+      }
     }
     WindowListManager.removeOpenListener(this.onWindowOpen);
   },
 
   observe(subject, topic, data) {
     subject = subject.wrappedJSObject;
     gMenuBuilder.build(subject);
   },
 
   onWindowOpen(window) {
-    const menu = window.document.getElementById("tabContextMenu");
-    menu.addEventListener("popupshowing", contextMenuTracker);
+    for (const id of menuTracker.menuIds) {
+      const menu = window.document.getElementById(id);
+      menu.addEventListener("popupshowing", menuTracker);
+    }
   },
 
   handleEvent(event) {
     const menu = event.target;
+    if (menu.id === "menu_ToolsPopup") {
+      const tab = TabManager.activeTab;
+      const pageUrl = tab.linkedBrowser.currentURI.spec;
+      gMenuBuilder.build({menu, tab, pageUrl, inMainMenu: true});
+    }
     if (menu.id === "tabContextMenu") {
       const trigger = menu.triggerNode;
       const tab = trigger.localName === "tab" ? trigger : TabManager.activeTab;
       const pageUrl = tab.linkedBrowser.currentURI.spec;
       gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
     }
   },
 };
 
 var gExtensionCount = 0;
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("startup", (type, extension) => {
   gContextMenuMap.set(extension, new Map());
   if (++gExtensionCount == 1) {
-    contextMenuTracker.register();
+    menuTracker.register();
   }
 });
 
 extensions.on("shutdown", (type, extension) => {
   gContextMenuMap.delete(extension);
   gRootItems.delete(extension);
   if (--gExtensionCount == 0) {
-    contextMenuTracker.unregister();
+    menuTracker.unregister();
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("contextMenus", "addon_parent", context => {
   let {extension} = context;
   return {
     contextMenus: {
--- a/browser/components/extensions/schemas/context_menus.json
+++ b/browser/components/extensions/schemas/context_menus.json
@@ -26,18 +26,18 @@
         "value": 6,
         "description": "The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored."
       }
     },
     "types": [
       {
         "id": "ContextType",
         "type": "string",
-        "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"],
-        "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab'."
+        "enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab", "main_menu"],
+        "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'main_menu'."
       },
       {
         "id": "ItemType",
         "type": "string",
         "enum": ["normal", "checkbox", "radio", "separator"],
         "description": "The type of menu item."
       },
       {
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
@@ -125,8 +125,65 @@ add_task(function* test_tabContextMenu()
   const click = yield first.awaitMessage("click");
   is(click.info.pageUrl, "http://example.com/", "Click info pageUrl is correct");
   is(click.tab.id, tabId, "Click event tab ID is correct");
 
   yield BrowserTestUtils.removeTab(tab);
   yield first.unload();
   yield second.unload();
 });
+
+add_task(function* test_main_menu() {
+  const first = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["contextMenus"],
+    },
+    async background() {
+      await browser.contextMenus.create({title: "alpha", contexts: ["main_menu"]});
+      await browser.contextMenus.create({title: "beta", contexts: ["main_menu"]});
+      browser.test.sendMessage("ready");
+    },
+  });
+
+  const second = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["contextMenus"],
+    },
+    async background() {
+      await browser.contextMenus.create({title: "gamma", contexts: ["main_menu"]});
+      browser.contextMenus.onClicked.addListener((info, tab) => {
+        browser.test.sendMessage("click", {info, tab});
+      });
+
+      const [tab] = await browser.tabs.query({active: true});
+      browser.test.sendMessage("ready", tab.id);
+    },
+  });
+
+  const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+  yield first.startup();
+  yield second.startup();
+
+  yield first.awaitMessage("ready");
+  const tabId = yield second.awaitMessage("ready");
+  const menu = yield openToolsMenu();
+
+  const [separator, submenu, gamma] = Array.from(menu.children).slice(-3);
+  is(separator.tagName, "menuseparator", "Separator before first extension item");
+
+  is(submenu.tagName, "menu", "Correct submenu type");
+  is(submenu.getAttribute("label"), "Generated extension", "Correct submenu title");
+  is(submenu.firstChild.children.length, 2, "Correct number of submenu items");
+
+  is(gamma.tagName, "menuitem", "Third menu item type is correct");
+  is(gamma.getAttribute("label"), "gamma", "Third menu item label is correct");
+
+  // doCommand instead of "clicking" on menuitem since we are mocking on OSX.
+  gamma.doCommand();
+
+  const click = yield second.awaitMessage("click");
+  is(click.info.pageUrl, "http://example.com/", "Click info pageUrl is correct");
+  is(click.tab.id, tabId, "Click event tab ID is correct");
+
+  yield BrowserTestUtils.removeTab(tab);
+  yield first.unload();
+  yield second.unload();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -6,17 +6,17 @@
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
- *          openTabContextMenu closeTabContextMenu
+ *          openTabContextMenu closeTabContextMenu openToolsMenu
  *          imageBuffer getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  */
 
 const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
 const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
@@ -260,16 +260,30 @@ function* openExtensionContextMenu(selec
 
 function* closeExtensionContextMenu(itemToSelect) {
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
   EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
   yield popupHiddenPromise;
 }
 
+function* openToolsMenu(win = window) {
+  const node = win.document.getElementById("tools-menu");
+  const menu = win.document.getElementById("menu_ToolsPopup");
+  const shown = BrowserTestUtils.waitForEvent(menu, "popupshowing");
+  if (AppConstants.platform === "macosx") {
+    // We can't open the main menu on OSX, so mocking instead.
+    menu.dispatchEvent(new MouseEvent("popupshowing"));
+  } else {
+    node.open = true;
+  }
+  yield shown;
+  return menu;
+}
+
 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);
   yield shown;
   return menu;
 }