Bug 1321544 - Support icons for context menu items in MozReview; r?zombie draft
authorSwapnesh Kumar Sahoo <swapneshks@gmail.com>
Wed, 07 Jun 2017 00:29:27 +0530
changeset 590338 e02ca5eecac13d6f904cd64a72446d318c429789
parent 585619 1fd39bb9452264d0ad576358905ec4592f7cd09e
child 632195 ec7f451d4c7823c2b57b6b7c37f1aae32b72fc0a
push id62710
push userswapneshks@gmail.com
push dateWed, 07 Jun 2017 17:35:20 +0000
reviewerszombie
bugs1321544
milestone55.0a1
Bug 1321544 - Support icons for context menu items in MozReview; r?zombie MozReview-Commit-ID: ETPvHYdKuIm
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/schemas/context_menus.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
browser/components/extensions/test/browser/browser_ext_icon_contextMenu.js
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -50,33 +50,17 @@ var gMenuBuilder = {
         // the root menu item itself either.
         continue;
       }
       rootElement.setAttribute("ext-type", "top-level-menu");
       rootElement = this.removeTopLevelMenuIfNeeded(rootElement);
 
       // Display the extension icon on the root element.
       if (root.extension.manifest.icons) {
-        let parentWindow = contextData.menu.ownerGlobal;
-        let extension = root.extension;
-
-        let {icon} = IconDetails.getPreferredIcon(extension.manifest.icons, extension,
-                                                  16 * parentWindow.devicePixelRatio);
-
-        // The extension icons in the manifest are not pre-resolved, since
-        // they're sometimes used by the add-on manager when the extension is
-        // not enabled, and its URLs are not resolvable.
-        let resolvedURL = root.extension.baseURI.resolve(icon);
-
-        if (rootElement.localName == "menu") {
-          rootElement.setAttribute("class", "menu-iconic");
-        } else if (rootElement.localName == "menuitem") {
-          rootElement.setAttribute("class", "menuitem-iconic");
-        }
-        rootElement.setAttribute("image", resolvedURL);
+        this.setMenuItemIcon(rootElement, root.extension, contextData, root.extension.manifest.icons);
       }
 
       if (firstItem) {
         firstItem = false;
         const separator = xulMenu.ownerDocument.createElement("menuseparator");
         this.itemsToCleanUp.add(separator);
         xulMenu.append(separator);
       }
@@ -194,27 +178,18 @@ var gMenuBuilder = {
       element.setAttribute("label", label);
     }
 
     if (item.id && item.extension && item.extension.id) {
       element.setAttribute("id",
         `${makeWidgetId(item.extension.id)}_${item.id}`);
     }
 
-    for(let child of item.children) {
-      if(child.icon) {
-          let parentWindow = contextData.menu.ownerGlobal;
-          let extension = item.extension;
-
-          let {icon} = IconDetails.getPreferredIcon(item.children[0].icons, extension,
-                                                        16 * parentWindow.devicePixelRatio);
-
-          let resolvedURL = item.extension.baseURI.resolve(icon);
-          element.setAttribute("image", resolvedURL);
-      }
+    if (item.icons) {
+      this.setMenuItemIcon(element, item.extension, contextData, item.icons);
     }
 
     if (item.type == "checkbox") {
       element.setAttribute("type", "checkbox");
       if (item.checked) {
         element.setAttribute("checked", "true");
       }
     } else if (item.type == "radio") {
@@ -285,16 +260,36 @@ var gMenuBuilder = {
     let target = event.target;
     target.removeEventListener("popuphidden", this);
     for (let item of this.itemsToCleanUp) {
       item.remove();
     }
     this.itemsToCleanUp.clear();
   },
 
+  setMenuItemIcon(element, extension, contextData, icons) {
+    let parentWindow = contextData.menu.ownerGlobal;
+
+    let {icon} = IconDetails.getPreferredIcon(icons, extension,
+                                              16 * parentWindow.devicePixelRatio);
+
+    // The extension icons in the manifest are not pre-resolved, since
+    // they're sometimes used by the add-on manager when the extension is
+    // not enabled, and its URLs are not resolvable.
+    let resolvedURL = extension.baseURI.resolve(icon);
+
+    if (element.localName == "menu") {
+      element.setAttribute("class", "menu-iconic");
+    } else if (element.localName == "menuitem") {
+      element.setAttribute("class", "menuitem-iconic");
+    }
+
+    element.setAttribute("image", resolvedURL);
+  },
+
   itemsToCleanUp: new Set(),
 };
 
 // Called from pageAction or browserAction popup.
 global.actionContextMenu = function(contextData) {
   gMenuBuilder.buildActionContextMenu(contextData);
 };
 
--- a/browser/components/extensions/schemas/context_menus.json
+++ b/browser/components/extensions/schemas/context_menus.json
@@ -140,47 +140,18 @@
               "id": {
                 "type": "string",
                 "optional": true,
                 "description": "The unique ID to assign to this item. Mandatory for event pages. Cannot be the same as another ID for this extension."
               },
               "icons": {
                 "type": "object",
                 "optional" : true,
-                "properties" : {
-                  "16" : {
-                    "type": "string",
-                    "optional": true,
-                    "description": "Icon (size 16x16) to be displayed for context-menu. "
-                  },
-                  "32" : {
-                    "type": "string",
-                    "optional": true,
-                    "description": "Icon (size 32x32) to be displayed for context-menu. "
-                  },
-                  "48" : {
-                    "type": "string",
-                    "optional": true,
-                    "description": "Icon (size 48x48) to be displayed for context-menu. "
-                  },
-                  "64" : {
-                    "type": "string",
-                    "optional": true,
-                    "description": "Icon (size 64x64) to be displayed for context-menu. "
-                  },
-                  "128" : {
-                    "type": "string",
-                    "optional": true,
-                    "description": "Icon (size 128x128) to be displayed for context-menu. "
-                  },
-                  "256" : {
-                    "type": "string",
-                    "optional": true,
-                    "description": "Icon (size 256x256) to be displayed for context-menu. "
-                  }
+                "patternProperties" : {
+                  "^[1-9]\\d*$": { "type": "string" }
                 }
               },
               "title": {
                 "type": "string",
                 "optional": true,
                 "description": "The text to be displayed in the item; this is <em>required</em> unless <code>type</code> is 'separator'. When the context is 'selection', you can use <code>%s</code> within the string to show the selected text. For example, if this parameter's value is \"Translate '%s' to Pig Latin\" and the user selects the word \"cool\", the context menu item for the selection is \"Translate 'cool' to Pig Latin\"."
               },
               "checked": {
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -61,17 +61,16 @@ support-files =
 [browser_ext_currentWindow.js]
 [browser_ext_devtools_inspectedWindow.js]
 [browser_ext_devtools_inspectedWindow_reload.js]
 [browser_ext_devtools_network.js]
 [browser_ext_devtools_page.js]
 [browser_ext_devtools_panel.js]
 [browser_ext_geckoProfiler_symbolicate.js]
 [browser_ext_getViews.js]
-[browser_ext_icon_contextMenu.js]
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_omnibox.js]
 skip-if = debug || asan # Bug 1354681
 [browser_ext_optionsPage_browser_style.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -1,13 +1,13 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-add_task(async function() {
+add_task(async function test_root_icon() {
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser,
     "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html?test=icons");
 
   let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
   const IMAGE_ARRAYBUFFER = imageBufferFromDataURI(encodedImageData);
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -63,8 +63,93 @@ add_task(async function() {
   topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
 
   confirmContextMenuIcon(topLevelMenuItem);
   await closeContextMenu();
 
   await extension.unload();
   await BrowserTestUtils.removeTab(tab1);
 });
+
+add_task(async function test_child_icon() {
+  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html?test=icons");
+
+  let blackIconData = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QYGEhkO2P07+gAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAARSURBVCjPY2AYBaNgFAxPAAAD3gABo0ohTgAAAABJRU5ErkJggg==";
+  const IMAGE_ARRAYBUFFER_BLACK = imageBufferFromDataURI(blackIconData);
+
+  let redIconData = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QYGEgw1XkM0ygAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAYSURBVCjPY/zPQA5gYhjVNqptVNsg1wYAItkBI/GNR3YAAAAASUVORK5CYII=";
+  const IMAGE_ARRAYBUFFER_RED = imageBufferFromDataURI(redIconData);
+
+  let blueIconData = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QYGEg0QDFzRzAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAbSURBVCjPY2SQ+89AOmBiIAuMahvVNqqNftoAlKMBQZXKX9kAAAAASUVORK5CYII=";
+  const IMAGE_ARRAYBUFFER_BLUE = imageBufferFromDataURI(blueIconData);
+
+  let greenIconData = "iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QYGEg0rvVc46AAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAaSURBVCjPY+Q8xkAGYGJgGNU2qm1U2+DWBgBolADz1beTnwAAAABJRU5ErkJggg==";
+  const IMAGE_ARRAYBUFFER_GREEN = imageBufferFromDataURI(greenIconData);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+      "icons": {
+        "18": "black_icon.png",
+      },
+    },
+
+    files: {
+      "black_icon.png": IMAGE_ARRAYBUFFER_BLACK,
+      "red_icon.png": IMAGE_ARRAYBUFFER_RED,
+      "blue_icon.png": IMAGE_ARRAYBUFFER_BLUE,
+      "green_icon.png": IMAGE_ARRAYBUFFER_GREEN,
+    },
+
+    background: function() {
+      browser.contextMenus.create({
+        title: "child1",
+        id: "contextmenu-child1",
+        icons: {
+          18: "red_icon.png",
+        },
+      });
+
+      browser.contextMenus.create({
+        title: "child2",
+        id: "contextmenu-child2",
+        icons: {
+          18: "blue_icon.png",
+        },
+      });
+
+      browser.contextMenus.create({
+        title: "child3",
+        id: "contextmenu-child3",
+        icons: {
+          18: "green_icon.png",
+        },
+      });
+
+      browser.test.notifyPass("contextmenus-icons");
+    },
+  });
+
+  let confirmContextMenuIcon = (element, imageName) => {
+    let imageURL = element.getAttribute("image");
+    ok(imageURL.endsWith(imageName), "The context menu should display the extension icon next to the child element");
+  };
+
+  await extension.startup();
+  await extension.awaitFinish("contextmenus-icons");
+
+  await openExtensionContextMenu();
+
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  let contextMenuChild1 = contextMenu.getElementsByAttribute("label", "child1")[0];
+  confirmContextMenuIcon(contextMenuChild1, "red_icon.png");
+
+  let contextMenuChild2 = contextMenu.getElementsByAttribute("label", "child2")[0];
+  confirmContextMenuIcon(contextMenuChild2, "blue_icon.png");
+
+  let contextMenuChild3 = contextMenu.getElementsByAttribute("label", "child3")[0];
+  confirmContextMenuIcon(contextMenuChild3, "green_icon.png");
+
+  await extension.unload();
+  await BrowserTestUtils.removeTab(tab1);
+});
+
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_icon_contextMenu.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-"use strict";
-
-let extData = {
-  manifest: {
-    "permissions": ["contextMenus"],
-    "browser_action": {
-      "default_popup": "popup.html",
-    },
-  },
-  useAddonManager: "temporary",
-
-  files: {
-    "popup.html": `
-      <!DOCTYPE html>
-      <html>
-      <head><meta charset="utf-8"/>
-      </head>
-      <body>
-      <span id="text">A Test Popup</span>
-      <img id="testimg" src="data:image/svg+xml,<svg></svg>" height="10" width="10">
-      </body></html>
-    `,
-  },
-
-  background: function() {
-    browser.contextMenus.create({
-      id: "clickme-page1",
-      title: "Click me!",
-      icons: {
-        48 : "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon.dc6635050bf5.ico",
-      },
-      contexts: ["all"],
-    });
-//    browser.contextMenus.create({
-//      id: "clickme-page2",
-//      title: "Click me!222",
-//      icon: "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon.dc6635050bf5.ico",
-//      contexts: ["all"],
-//    });
-  },
-};
-
-let contextMenuItems = {
-  "context-navigation": "hidden",
-  "context-sep-navigation": "hidden",
-  "context-viewsource": "",
-  "context-viewinfo": "disabled",
-  "inspect-separator": "hidden",
-  "context-inspect": "hidden",
-  "context-bookmarkpage": "hidden",
-  "context-sharepage": "hidden",
-};
-
-add_task(async function browseraction_popup_contextmenu() {
-  let extension = ExtensionTestUtils.loadExtension(extData);
-  await extension.startup();
-
-  await clickBrowserAction(extension, window);
-
-  let contentAreaContextMenu = await openContextMenuInPopup(extension);
-  let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
-  is(item.length, 1, "contextMenu item for page was found");
-  await closeContextMenu(contentAreaContextMenu);
-
-  await extension.unload();
-});
-