Bug 1321544 - Support icons for context menu items in MozReview; r?zombie draft
authorSwapnesh Kumar Sahoo <swapneshks@gmail.com>
Sun, 04 Jun 2017 01:51:18 +0530
changeset 588682 1eb25e1ccd41eb56f51f7993df9e08868e01020c
parent 585619 1fd39bb9452264d0ad576358905ec4592f7cd09e
child 631643 b4affa1f32dc3e14eacc218fd42b6586cf16f6ac
push id62112
push userswapneshks@gmail.com
push dateSat, 03 Jun 2017 20:25:46 +0000
reviewerszombie
bugs1321544
milestone55.0a1
Bug 1321544 - Support icons for context menu items in MozReview; r?zombie MozReview-Commit-ID: 15aHKLVstVn
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,26 +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);
+        let resolvedURL = getResolvedIconURL(root.extension, contextData, root.extension.manifest.icons);
 
         if (rootElement.localName == "menu") {
           rootElement.setAttribute("class", "menu-iconic");
         } else if (rootElement.localName == "menuitem") {
           rootElement.setAttribute("class", "menuitem-iconic");
         }
         rootElement.setAttribute("image", resolvedURL);
       }
@@ -194,27 +185,26 @@ 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;
+    if(item.icons) {
+      let resolvedURL = getResolvedIconURL(item.extension, contextData, item.icons);
 
-          let {icon} = IconDetails.getPreferredIcon(item.children[0].icons, extension,
-                                                        16 * parentWindow.devicePixelRatio);
+      if (element.localName == "menu") {
+        element.setAttribute("class", "menu-iconic");
+      } else if (element.localName == "menuitem") {
+        element.setAttribute("class", "menuitem-iconic");
+      }
 
-          let resolvedURL = item.extension.baseURI.resolve(icon);
-          element.setAttribute("image", resolvedURL);
-      }
+      element.setAttribute("image", resolvedURL);
     }
 
     if (item.type == "checkbox") {
       element.setAttribute("type", "checkbox");
       if (item.checked) {
         element.setAttribute("checked", "true");
       }
     } else if (item.type == "radio") {
@@ -288,16 +278,28 @@ var gMenuBuilder = {
       item.remove();
     }
     this.itemsToCleanUp.clear();
   },
 
   itemsToCleanUp: new Set(),
 };
 
+function getResolvedIconURL(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.
+  return extension.baseURI.resolve(icon);
+}
+
 // Called from pageAction or browserAction popup.
 global.actionContextMenu = function(contextData) {
   gMenuBuilder.buildActionContextMenu(contextData);
 };
 
 function getContexts(contextData) {
   let contexts = new Set();
 
--- 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,85 @@ 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 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: {
+      "permissions": ["contextMenus"],
+      "icons": {
+        "18": "extension.png",
+      },
+    },
+
+    files: {
+      "extension.png": IMAGE_ARRAYBUFFER,
+    },
+
+    background: function() {
+      browser.contextMenus.create({
+        title: "child1",
+        id: "contextmenu-child1",
+        icons: {
+          18: "extension.png",
+        },
+      });
+
+      browser.contextMenus.create({
+        title: "child2",
+        id: "contextmenu-child2",
+        icons: {
+          18: "extension.png",
+        },
+      });
+
+      browser.contextMenus.create({
+        title: "child3",
+        id: "contextmenu-child3",
+        icons: {
+          18: "extension.png",
+        },
+      });
+
+      browser.test.notifyPass("contextmenus-icons");
+    },
+  });
+
+  let confirmContextMenuIcon = (childElement) => {
+    let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
+    let imageUrl = childElement.getAttribute("image");
+    ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the child element");
+  };
+
+  await extension.startup();
+  await extension.awaitFinish("contextmenus-icons");
+
+  let extensionMenu = await openExtensionContextMenu();
+
+  await openExtensionContextMenu();
+
+  let contextMenuChild1 = document.getElementById("contentmenu-child1");
+  confirmContextMenuIcon(contextMenuChild1);
+
+  let contextMenuChild2 = document.getElementById("contentmenu-child2");
+  confirmContextMenuIcon(contextMenuChild2);
+
+  let contextMenuChild3 = document.getElementById("contentmenu-child3");
+  confirmContextMenuIcon(contextMenuChild3);
+
+  await extension.unload();
+});
+
+function run_test() {
+  run_next_test();
+}
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();
-});
-