--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -141,19 +141,35 @@ BrowserAction.prototype = {
if (badgeNode) {
let color = tabData.badgeBackgroundColor;
if (Array.isArray(color)) {
color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
}
badgeNode.style.backgroundColor = color || "";
}
- let iconURL = IconDetails.getURL(
- tabData.icon, node.ownerDocument.defaultView, this.extension);
- node.setAttribute("image", iconURL);
+ const LEGACY_CLASS = "toolbarbutton-legacy-addon";
+ node.classList.remove(LEGACY_CLASS);
+
+
+ let win = node.ownerDocument.defaultView;
+ let {icon, size} = IconDetails.getURL(tabData.icon, win, this.extension);
+
+ // If the best available icon size is not divisible by 16, check if we have
+ // an 18px icon to fall back to, and trim off the padding instead.
+ if (size % 16 && !icon.endsWith(".svg")) {
+ let result = IconDetails.getURL(tabData.icon, win, this.extension, 18);
+
+ if (result.size % 18 == 0) {
+ icon = result.icon;
+ node.classList.add(LEGACY_CLASS);
+ }
+ }
+
+ node.setAttribute("image", icon);
},
// Update the toolbar button for a given window.
updateWindow(window) {
let widget = this.widget.forWindow(window);
if (widget) {
let tab = window.gBrowser.selectedTab;
this.updateButton(widget.node, this.tabContext.get(tab));
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -86,17 +86,17 @@ PageAction.prototype = {
if (tabData.show) {
// Update the title and icon only if the button is visible.
let title = tabData.title || this.extension.name;
button.setAttribute("tooltiptext", title);
button.setAttribute("aria-label", title);
- let icon = IconDetails.getURL(tabData.icon, window, this.extension);
+ let {icon} = IconDetails.getURL(tabData.icon, window, this.extension);
button.setAttribute("src", icon);
}
button.hidden = !tabData.show;
},
// Create an |image| node and add it to the |urlbar-icons|
// container in the given window.
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -96,22 +96,44 @@ global.IconDetails = {
// as a manifest directive. Log a warning rather than
// raising an error.
extension.manifestError(`Invalid icon data: ${e}`);
}
return result;
},
+ getBestIcon: function(icons, window, size) {
+ },
+
// Returns the appropriate icon URL for the given icons object and the
// screen resolution of the given window.
- getURL(icons, window, extension, size = 18) {
+ getURL(icons, window, extension, size = 16) {
const DEFAULT = "chrome://browser/content/extension.svg";
- return AddonManager.getPreferredIconURL({icons: icons}, size, window) || DEFAULT;
+ size *= window.devicePixelRatio;
+
+ let bestSize = null;
+ if (icons[size]) {
+ bestSize = size;
+ } else if (icons[2 * size]) {
+ bestSize = 2 * size;
+ } else {
+ let sizes = Object.keys(icons)
+ .map(key => parseInt(key, 10))
+ .sort((a, b) => a - b);
+
+ bestSize = sizes.find(candidate => candidate > size) || sizes.pop();
+ }
+
+ if (bestSize) {
+ return {size: bestSize, icon: icons[bestSize]};
+ }
+
+ return {size, icon: DEFAULT};
},
convertImageDataToPNG(imageData, context) {
let document = context.contentWindow.document;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
canvas.getContext("2d").putImageData(imageData, 0, 0);
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -99,37 +99,66 @@ add_task(function* testDetailsObjects()
{details: {
"path": {"38": "a.png"},
"imageData": imageData.red.imageData},
resolutions: {
"1": imageData.red.url,
"2": browser.runtime.getURL("data/a.png")}},
// Various resolutions
- {details: {"path": {"18": "a.png", "32": "a-x2.png"}},
+ {details: {"path": {"18": "a.png", "36": "a-x2.png"}},
+ legacy: true,
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a-x2.png")}},
+ {details: {"path": {"16": "a.png", "30": "a-x2.png"}},
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a-x2.png")}},
{details: {"path": {"16": "16.png", "100": "100.png"}},
resolutions: {
- "1": browser.runtime.getURL("data/100.png"),
+ "1": browser.runtime.getURL("data/16.png"),
"2": browser.runtime.getURL("data/100.png")}},
{details: {"path": {"2": "2.png"}},
resolutions: {
"1": browser.runtime.getURL("data/2.png"),
"2": browser.runtime.getURL("data/2.png")}},
{details: {"path": {
+ "16": "16.svg",
+ "18": "18.svg"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/16.svg"),
+ "2": browser.runtime.getURL("data/18.svg")}},
+ {details: {"path": {
"6": "6.png",
"18": "18.png",
+ "36": "36.png",
+ "48": "48.png",
+ "128": "128.png"}},
+ legacy: true,
+ resolutions: {
+ "1": browser.runtime.getURL("data/18.png"),
+ "2": browser.runtime.getURL("data/36.png")}},
+ {details: {"path": {
+ "16": "16.png",
+ "18": "18.png",
"32": "32.png",
"48": "48.png",
"128": "128.png"}},
resolutions: {
- "1": browser.runtime.getURL("data/18.png"),
- "2": browser.runtime.getURL("data/48.png")}},
+ "1": browser.runtime.getURL("data/16.png"),
+ "2": browser.runtime.getURL("data/32.png")}},
+ {details: {"path": {
+ "18": "18.png",
+ "32": "32.png",
+ "48": "48.png",
+ "128": "128.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/32.png"),
+ "2": browser.runtime.getURL("data/32.png")}},
];
// Allow serializing ImageData objects for logging.
ImageData.prototype.toJSON = () => "<ImageData>";
let tabId;
browser.test.onMessage.addListener((msg, test) => {
@@ -141,17 +170,17 @@ add_task(function* testDetailsObjects()
let expectedURL = details.resolutions[test.resolution];
let detailString = JSON.stringify(details);
browser.test.log(`Setting browerAction/pageAction to ${detailString} expecting URL ${expectedURL}`);
browser.browserAction.setIcon(Object.assign({tabId}, details.details));
browser.pageAction.setIcon(Object.assign({tabId}, details.details));
- browser.test.sendMessage("imageURL", expectedURL);
+ browser.test.sendMessage("imageURL", [expectedURL, !!details.legacy]);
});
// Generate a list of tests and resolutions to send back to the test
// context.
//
// This process is a bit convoluted, because the outer test context needs
// to handle checking the button nodes and changing the screen resolution,
// but it can't pass us icon definitions with ImageData objects. This
@@ -204,21 +233,24 @@ add_task(function* testDetailsObjects()
let [, tests] = yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
for (let test of tests) {
SpecialPowers.setCharPref(RESOLUTION_PREF, String(test.resolution));
is(window.devicePixelRatio, test.resolution, "window has the required resolution");
extension.sendMessage("setIcon", test);
- let imageURL = yield extension.awaitMessage("imageURL");
+ let [imageURL, legacy] = yield extension.awaitMessage("imageURL");
let browserActionButton = document.getElementById(browserActionId);
is(browserActionButton.getAttribute("image"), imageURL, "browser action has the correct image");
+ let isLegacy = browserActionButton.classList.contains("toolbarbutton-legacy-addon");
+ is(isLegacy, legacy, "Legacy class should be present?");
+
let pageActionImage = document.getElementById(pageActionId);
is(pageActionImage.src, imageURL, "page action has the correct image");
}
yield extension.unload();
});
// Test that an error is thrown when providing invalid icon sizes
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -570,18 +570,18 @@ menuitem:not([type]):not(.menuitem-toolt
/* Primary toolbar buttons */
:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > .toolbarbutton-icon,
:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
max-width: 16px;
}
-:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > .toolbarbutton-icon,
-:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon,
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
max-width: 18px;
}
.findbar-button,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 {
-moz-appearance: none;
@@ -600,19 +600,19 @@ menuitem:not([type]):not(.menuitem-toolt
margin-inline-end: 0;
padding: 2px 6px;
border: 1px solid transparent;
border-radius: 2px;
transition-property: background-color, border-color;
transition-duration: 150ms;
}
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-stack,
-:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-badge-stack,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
padding: 3px 7px;
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
/* horizontal padding + border + actual icon width */
max-width: 32px !important /* bug 561154 */;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -596,18 +596,18 @@ toolbarpaletteitem[place="palette"] > #p
/* ----- PRIMARY TOOLBAR BUTTONS ----- */
:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > .toolbarbutton-icon,
:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
max-width: 16px;
margin: 1px;
}
-:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > .toolbarbutton-icon,
-:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
max-width: 18px;
margin: 0;
}
toolbar .toolbarbutton-1:not([type="menu-button"]),
.toolbarbutton-1 > .toolbarbutton-menubutton-button,
.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-box-orient: vertical;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -676,18 +676,18 @@ toolbar[brighttext] .toolbarbutton-1 > .
margin-inline-end: 0;
}
:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > .toolbarbutton-icon,
:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
max-width: 16px;
}
-:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > .toolbarbutton-icon,
-:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon,
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
max-width: 18px;
}
.findbar-button,
#nav-bar .toolbarbutton-1,
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
-moz-appearance: none;
@@ -788,19 +788,19 @@ toolbar[brighttext] .toolbarbutton-1 > .
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
border-inline-end-style: none;
}
-#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
-#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-stack,
-#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-badge-stack,
+#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
padding: calc(var(--toolbarbutton-vertical-inner-padding) + 1px) 7px;
}
#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-badge-stack,
#nav-bar .toolbarbutton-1[type=menu]:not(#PanelUI-menu-button):not(#back-button):not(#forward-button) > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1[type=menu]:not(#PanelUI-menu-button) > .toolbarbutton-badge-stack,
#nav-bar .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {