Bug 1468460 - Fix and test extension menus and menus.overrideContext on options_ui pages. r=mixedpuppy,robwu
authorLuca Greco <lgreco@mozilla.com>
Tue, 08 Jan 2019 22:23:00 +0000
changeset 510215 dd5395e679a627a326a3a8b305c3f5f44329ddfa
parent 510214 d3f022310ff3c5864f62f979e85c7ff0a27de82b
child 510216 eee3227176bb9078ad72a8009fd8e6ec181d273c
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmixedpuppy, robwu
bugs1468460
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1468460 - Fix and test extension menus and menus.overrideContext on options_ui pages. r=mixedpuppy,robwu Depends on D9920 Differential Revision: https://phabricator.services.mozilla.com/D15225
browser/base/content/nsContextMenu.js
browser/components/extensions/test/browser/browser_ext_menus_viewType.js
browser/components/extensions/test/browser/browser_ext_optionsPage_popups.js
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -101,19 +101,22 @@ nsContextMenu.prototype = {
       if (this.isRemote) {
         this.hasPageMenu =
           PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
                                     this.browser, aXulMenu);
       } else {
         this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu);
       }
 
+      let tab = gBrowser && gBrowser.getTabForBrowser ?
+        gBrowser.getTabForBrowser(this.browser) : undefined;
+
       let subject = {
         menu: aXulMenu,
-        tab: gBrowser ? gBrowser.getTabForBrowser(this.browser) : undefined,
+        tab,
         timeStamp: this.timeStamp,
         isContentSelected: this.isContentSelected,
         inFrame: this.inFrame,
         isTextSelected: this.isTextSelected,
         onTextInput: this.onTextInput,
         onLink: this.onLink,
         onImage: this.onImage,
         onVideo: this.onVideo,
@@ -273,17 +276,17 @@ nsContextMenu.prototype = {
 
       let canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
       this.showItem("spell-check-enabled", canSpell);
       this.showItem("spell-separator", canSpell);
     }
   },  // setContext
 
   hiding: function CM_hiding() {
-    if (this.browser) {
+    if (this.browser && this.browser.messageManager) {
       this.browser.messageManager.sendAsyncMessage("ContextMenu:Hiding");
     }
 
     gContextMenuContentData = null;
     InlineSpellCheckerUI.clearSuggestionsFromMenu();
     InlineSpellCheckerUI.clearDictionaryListFromMenu();
     InlineSpellCheckerUI.uninit();
     if (Cu.isModuleLoaded("resource://gre/modules/LoginManagerContextMenu.jsm")) {
--- a/browser/components/extensions/test/browser/browser_ext_menus_viewType.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus_viewType.js
@@ -56,17 +56,17 @@ add_task(async function sidebar_panel_vi
     browser.menus.onShown.addListener(info => {
       browser.test.assertEq("sidebaronly", info.menuIds.join(","), "Expected menu items");
       browser.test.assertEq("sidebar", info.viewType, "Expected viewType");
       browser.test.sendMessage("shown");
     });
 
     // Create menus and change their viewTypes using menus.update.
     browser.menus.create({id: "sidebaronly", title: "sidebaronly", viewTypes: ["tab"]});
-    browser.menus.create({id: "tabonly", title: "sidebaronly", viewTypes: ["sidebar"]});
+    browser.menus.create({id: "tabonly", title: "tabonly", viewTypes: ["sidebar"]});
     await browser.menus.update("sidebaronly", {viewTypes: ["sidebar"]});
     await browser.menus.update("tabonly", {viewTypes: ["tab"]});
     browser.test.sendMessage("ready");
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary", // To automatically show sidebar on load.
     manifest: {
--- a/browser/components/extensions/test/browser/browser_ext_optionsPage_popups.js
+++ b/browser/components/extensions/test/browser/browser_ext_optionsPage_popups.js
@@ -1,26 +1,63 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+async function openContextMenuInOptionsPage(optionsBrowser) {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+
+  await BrowserTestUtils.waitForCondition(async () => {
+    BrowserTestUtils.synthesizeMouseAtCenter("a", {type: "contextmenu"}, optionsBrowser);
+
+    // It looks that syntesizeMouseAtCenter is sometimes able to trigger the mouse event on the
+    // HTML document instead of triggering it on the expected document element while running this
+    // test in --verify mode, and we are going to send the mouse event again if it didn't
+    // triggered the context menu yet.
+    return Promise.race([
+      popupShownPromise.then(() => true),
+      delay(500).then(() => false),
+    ]);
+  }, "Waiting the context menu to be shown");
+
+  return contentAreaContextMenu;
+}
+
+function contextMenuClosed(contextMenu) {
+  // Close the context menu and ensure that it is gone, otherwise there
+  // may be intermittent failures related to shutdown leaks.
+  return BrowserTestUtils.waitForCondition(async () => {
+    await closeContextMenu(contextMenu);
+    return contextMenu.state === "closed";
+  }, "Wait context menu popup to be closed");
+}
+
 add_task(async function test_tab_options_popups() {
-  function backgroundScript() {
+  async function backgroundScript() {
+    browser.menus.onShown.addListener(info => {
+      browser.test.sendMessage("extension-menus-onShown", info);
+    });
+
+    await browser.menus.create({id: "sidebaronly", title: "sidebaronly", viewTypes: ["sidebar"]});
+    await browser.menus.create({id: "tabonly", title: "tabonly", viewTypes: ["tab"]});
+    await browser.menus.create({id: "anypage", title: "anypage"});
+
     browser.runtime.openOptionsPage();
   }
 
   function optionsScript() {
-    browser.test.sendMessage("options-page:loaded");
+    browser.test.sendMessage("options-page:loaded", document.documentURI);
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     useAddonManager: "temporary",
 
     manifest: {
-      "permissions": ["tabs"],
+      "permissions": ["tabs", "menus"],
       "options_ui": {
         "page": "options.html",
       },
     },
     files: {
       "options.html": `<!DOCTYPE html>
         <html>
           <head>
@@ -36,55 +73,124 @@ add_task(async function test_tab_options
     },
     background: backgroundScript,
   });
 
   const aboutAddonsTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:addons");
 
   await extension.startup();
 
-  // Wait the options page to be loaded.
-  await extension.awaitMessage("options-page:loaded");
+  const pageUrl = await extension.awaitMessage("options-page:loaded");
 
   const optionsBrowser = gBrowser.selectedBrowser.contentDocument.getElementById("addon-options");
-  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
 
-  await BrowserTestUtils.waitForCondition(async () => {
-    BrowserTestUtils.synthesizeMouseAtCenter("a", {type: "contextmenu"}, optionsBrowser);
-
-    // It looks that syntesizeMouseAtCenter is sometimes able to trigger the mouse event on the
-    // HTML document instead of triggering it on the expected document element while running this
-    // test in --verify mode, and we are going to send the mouse event again if it didn't
-    // triggered the context menu yet.
-    return Promise.race([
-      popupShownPromise.then(() => true),
-      delay(500).then(() => false),
-    ]);
-  }, "Waiting the context menu to be shown");
+  const contentAreaContextMenu = await openContextMenuInOptionsPage(optionsBrowser);
 
   let contextMenuItemIds = [
     "context-openlinkintab",
     "context-openlinkprivate",
     "context-copylink",
     "context-openlinkinusercontext-menu",
   ];
 
   for (const itemID of contextMenuItemIds) {
     const item = contentAreaContextMenu.querySelector(`#${itemID}`);
 
     ok(!item.hidden, `${itemID} should not be hidden`);
     ok(!item.disabled, `${itemID} should not be disabled`);
   }
 
-  // Close the context menu and ensure that it is gone, otherwise there
-  // may be intermittent failures related to shutdown leaks.
-  await BrowserTestUtils.waitForCondition(async () => {
-    let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-    contentAreaContextMenu.hidePopup();
-    await popupHiddenPromise;
-    return contentAreaContextMenu.state === "closed";
-  }, "Wait context menu popup to be closed");
+  const menuDetails = await extension.awaitMessage("extension-menus-onShown");
+
+  isnot(menuDetails.targetElementId, undefined, "Got a targetElementId in the menu details");
+  delete menuDetails.targetElementId;
+
+
+  Assert.deepEqual(menuDetails, {
+    menuIds: ["anypage"],
+    contexts: ["link", "all"],
+    viewType: undefined,
+    frameId: 0,
+    editable: false,
+    linkText: "options page link",
+    linkUrl: "http://mochi.test:8888/",
+    pageUrl,
+  }, "Got the expected menu details from menus.onShown");
+
+  await contextMenuClosed(contentAreaContextMenu);
 
   BrowserTestUtils.removeTab(aboutAddonsTab);
 
   await extension.unload();
 });
+
+add_task(async function overrideContext_in_options_page() {
+  function optionsScript() {
+    document.addEventListener("contextmenu", () => {
+      browser.menus.overrideContext({});
+      browser.test.sendMessage("contextmenu-overridden");
+    }, {once: true});
+    browser.test.sendMessage("options-page:loaded");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "temporary",
+    manifest: {
+      "permissions": ["tabs", "menus", "menus.overrideContext"],
+      "options_ui": {
+        "page": "options.html",
+      },
+    },
+    files: {
+      "options.html": `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+            <script src="options.js" type="text/javascript"></script>
+          </head>
+          <body style="height: 100px;">
+            <h1>Extensions Options</h1>
+            <a href="http://mochi.test:8888/">options page link</a>
+          </body>
+        </html>`,
+      "options.js": optionsScript,
+    },
+    async background() {
+      // Expected to match and be shown.
+      await new Promise(resolve => {
+        browser.menus.create({id: "bg_1_1", title: "bg_1_1"});
+        browser.menus.create({id: "bg_1_2", title: "bg_1_2"});
+        // Expected to not match and be hidden.
+        browser.menus.create(
+          {id: "bg_1_3", title: "bg_1_3", targetUrlPatterns: ["*://nomatch/*"]},
+          // menus.create returns a number and gets a callback, the order
+          // is deterministic and so we just need to wait for the last one.
+          resolve);
+      });
+      browser.runtime.openOptionsPage();
+    },
+  });
+
+  const aboutAddonsTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:addons");
+
+  await extension.startup();
+  await extension.awaitMessage("options-page:loaded");
+
+  const optionsBrowser = gBrowser.selectedBrowser.contentDocument.getElementById("addon-options");
+  const contentAreaContextMenu = await openContextMenuInOptionsPage(optionsBrowser);
+
+  await extension.awaitMessage("contextmenu-overridden");
+
+  const allVisibleMenuItems = Array.from(contentAreaContextMenu.children).filter(elem => {
+    return !elem.hidden;
+  }).map(elem => elem.id);
+
+  Assert.deepEqual(allVisibleMenuItems, [
+    `${makeWidgetId(extension.id)}-menuitem-_bg_1_1`,
+    `${makeWidgetId(extension.id)}-menuitem-_bg_1_2`,
+  ], "Expected only extension menu items");
+
+  await contextMenuClosed(contentAreaContextMenu);
+
+  BrowserTestUtils.removeTab(aboutAddonsTab);
+
+  await extension.unload();
+});