Bug 1321544 - Support icons for context menu items in MozReview; r?zombie
MozReview-Commit-ID: 15aHKLVstVn
--- 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();
-});
-