--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -322,16 +322,46 @@ toolbarpaletteitem > toolbaritem[sdkstyl
toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
display: -moz-box;
}
toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
display: -moz-box;
}
+@media not all and (min-resolution: 1.1dppx) {
+ .webextension-browser-action {
+ list-style-image: var(--webextension-toolbar-image, none);
+ }
+
+ .webextension-browser-action[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > .webextension-browser-action {
+ list-style-image: var(--webextension-menupanel-image, none);
+ }
+
+ .webextension-page-action {
+ list-style-image: var(--webextension-urlbar-image, none);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ .webextension-browser-action {
+ list-style-image: var(--webextension-toolbar-image-2x, none);
+ }
+
+ .webextension-browser-action[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > .webextension-browser-action {
+ list-style-image: var(--webextension-menupanel-image-2x, none);
+ }
+
+ .webextension-page-action {
+ list-style-image: var(--webextension-urlbar-image-2x, none);
+ }
+}
+
toolbarpaletteitem[removable="false"] {
opacity: 0.5;
cursor: default;
}
%ifndef XP_MACOSX
toolbarpaletteitem[place="palette"],
toolbarpaletteitem[place="panel"],
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -74,16 +74,17 @@ BrowserAction.prototype = {
let view = document.getElementById(this.viewId);
if (view) {
view.remove();
}
},
onCreated: node => {
node.classList.add("badged-button");
+ node.classList.add("webextension-browser-action");
node.setAttribute("constrain-size", "true");
this.updateButton(node, this.defaults);
},
onViewShowing: event => {
let document = event.target.ownerDocument;
let tabbrowser = document.defaultView.gBrowser;
@@ -145,32 +146,44 @@ BrowserAction.prototype = {
color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
}
badgeNode.style.backgroundColor = color || "";
}
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);
+ let baseSize = 16;
+ let {icon, size} = IconDetails.getPreferredIcon(tabData.icon, this.extension, baseSize);
// 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);
+ let result = IconDetails.getPreferredIcon(tabData.icon, this.extension, 18);
if (result.size % 18 == 0) {
+ baseSize = 18;
icon = result.icon;
node.classList.add(LEGACY_CLASS);
}
}
- node.setAttribute("image", icon);
+ // These URLs should already be properly escaped, but make doubly sure CSS
+ // string escape characters are escaped here, since they could lead to a
+ // sandbox break.
+ let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
+
+ let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+
+ node.setAttribute("style", `
+ --webextension-menupanel-image: url("${getIcon(32)}");
+ --webextension-menupanel-image-2x: url("${getIcon(64)}");
+ --webextension-toolbar-image: url("${escape(icon)}");
+ --webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}");
+ `);
},
// 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-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -49,17 +49,18 @@ var gMenuBuilder = {
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.ownerDocument.defaultView;
let extension = root.extension;
- let {icon} = IconDetails.getURL(extension.manifest.icons, parentWindow, extension, 16 /* size */);
+ let {icon} = IconDetails.getPreferredIcon(extension.manifest.icons, extension,
+ 16 * parentWindow.devicePixelRatio);
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);
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -87,31 +87,40 @@ 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);
- button.setAttribute("src", icon);
+ // These URLs should already be properly escaped, but make doubly sure CSS
+ // string escape characters are escaped here, since they could lead to a
+ // sandbox break.
+ let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
+
+ let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+
+ button.setAttribute("style", `
+ --webextension-urlbar-image: url("${getIcon(16)}");
+ --webextension-urlbar-image-2x: url("${getIcon(32)}");
+ `);
}
button.hidden = !tabData.show;
},
// Create an |image| node and add it to the |urlbar-icons|
// container in the given window.
addButton(window) {
let document = window.document;
let button = document.createElement("image");
button.id = this.id;
- button.setAttribute("class", "urlbar-icon");
+ button.setAttribute("class", "urlbar-icon webextension-page-action");
button.addEventListener("click", event => { // eslint-disable-line mozilla/balanced-listeners
if (event.button == 0) {
this.handleClick(window);
}
});
document.getElementById("urlbar-icons").appendChild(button);
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -92,17 +92,17 @@ function* runTests(options) {
function checkDetails(details) {
let button = document.getElementById(browserActionId);
ok(button, "button exists");
let title = details.title || options.manifest.name;
- is(button.getAttribute("image"), details.icon, "icon URL is correct");
+ is(getListStyleImage(button), details.icon, "icon URL is correct");
is(button.getAttribute("tooltiptext"), title, "image title is correct");
is(button.getAttribute("label"), title, "image label is correct");
is(button.getAttribute("badge"), details.badge, "badge text is correct");
is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
if (details.badge && details.badgeBackgroundColor) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, "class", "toolbarbutton-badge");
@@ -158,16 +158,21 @@ add_task(function* testTabSwitchContext(
"description": "Popup",
},
"title": {
"message": "Title",
"description": "Title",
},
},
+
+ "default.png": imageBuffer,
+ "default-2.png": imageBuffer,
+ "1.png": imageBuffer,
+ "2.png": imageBuffer,
},
getTests(tabs, expectDefaults) {
let details = [
{"icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title",
"badge": "",
@@ -317,16 +322,20 @@ add_task(function* testDefaultTitle() {
"browser_action": {
"default_icon": "icon.png",
},
"permissions": ["tabs"],
},
+ files: {
+ "icon.png": imageBuffer,
+ },
+
getTests(tabs, expectDefaults) {
let details = [
{"title": "Foo Extension",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png")},
{"title": "Foo Title",
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -47,16 +47,22 @@ add_task(function* testDetailsObjects()
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a.png")}},
{details: {"path": {"19": "a.png", "38": "a-x2.png"}},
resolutions: {
"1": browser.runtime.getURL("data/a.png"),
"2": browser.runtime.getURL("data/a-x2.png")}},
+ // Test that CSS strings are escaped properly.
+ {details: {"path": 'a.png#" \\'},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png#%22%20%5C"),
+ "2": browser.runtime.getURL("data/a.png#%22%20%5C")}},
+
// Only ImageData objects.
{details: {"imageData": imageData.red.imageData},
resolutions: {
"1": imageData.red.url,
"2": imageData.red.url}},
{details: {"imageData": {"19": imageData.red.imageData}},
resolutions: {
"1": imageData.red.url,
@@ -131,26 +137,33 @@ add_task(function* testDetailsObjects()
"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")}},
+ "2": browser.runtime.getURL("data/36.png")},
+ menuResolutions: {
+ "1": browser.runtime.getURL("data/36.png"),
+ "2": browser.runtime.getURL("data/128.png")}},
{details: {"path": {
"16": "16.png",
"18": "18.png",
"32": "32.png",
"48": "48.png",
+ "64": "64.png",
"128": "128.png"}},
resolutions: {
"1": browser.runtime.getURL("data/16.png"),
- "2": browser.runtime.getURL("data/32.png")}},
+ "2": browser.runtime.getURL("data/32.png")},
+ menuResolutions: {
+ "1": browser.runtime.getURL("data/32.png"),
+ "2": browser.runtime.getURL("data/64.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")}},
@@ -162,42 +175,44 @@ add_task(function* testDetailsObjects()
let tabId;
browser.test.onMessage.addListener((msg, test) => {
if (msg != "setIcon") {
browser.test.fail("expecting 'setIcon' message");
}
let details = iconDetails[test.index];
- let expectedURL = details.resolutions[test.resolution];
let detailString = JSON.stringify(details);
- browser.test.log(`Setting browerAction/pageAction to ${detailString} expecting URL ${expectedURL}`);
+ browser.test.log(`Setting browerAction/pageAction to ${detailString} expecting URLs ${JSON.stringify(details.resolutions)}`);
browser.browserAction.setIcon(Object.assign({tabId}, details.details));
browser.pageAction.setIcon(Object.assign({tabId}, details.details));
- browser.test.sendMessage("imageURL", [expectedURL, !!details.legacy]);
+ browser.test.sendMessage("iconSet");
});
// 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
// shouldn't be a problem, since structured clones should handle ImageData
// objects without issue. Unfortunately, |cloneInto| implements a slightly
// different algorithm than we use in web APIs, and does not handle them
// correctly.
let tests = [];
for (let [idx, icon] of iconDetails.entries()) {
- for (let res of Object.keys(icon.resolutions)) {
- tests.push({index: idx, resolution: Number(res)});
- }
+ tests.push({
+ index: idx,
+ legacy: !!icon.legacy,
+ menuResolutions: icon.menuResolutions,
+ resolutions: icon.resolutions,
+ });
}
// Sort by resolution, so we don't needlessly switch back and forth
// between each test.
tests.sort(test => test.resolution);
browser.tabs.query({active: true, currentWindow: true}, tabs => {
tabId = tabs[0].id;
@@ -214,45 +229,96 @@ add_task(function* testDetailsObjects()
"background": {
"page": "data/background.html",
}
},
files: {
"data/background.html": `<script src="background.js"></script>`,
"data/background.js": background,
+
+ "data/16.svg": imageBuffer,
+ "data/18.svg": imageBuffer,
+
+ "data/16.png": imageBuffer,
+ "data/18.png": imageBuffer,
+ "data/32.png": imageBuffer,
+ "data/36.png": imageBuffer,
+ "data/48.png": imageBuffer,
+ "data/64.png": imageBuffer,
+ "data/128.png": imageBuffer,
+
+ "a.png": imageBuffer,
+ "data/2.png": imageBuffer,
+ "data/100.png": imageBuffer,
+ "data/a.png": imageBuffer,
+ "data/a-x2.png": imageBuffer,
},
});
const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
- registerCleanupFunction(() => {
- SpecialPowers.clearUserPref(RESOLUTION_PREF);
- });
+
+ let pageActionId = makeWidgetId(extension.id) + "-page-action";
+ let browserActionWidget = getBrowserActionWidget(extension);
- let browserActionId = makeWidgetId(extension.id) + "-browser-action";
- let pageActionId = makeWidgetId(extension.id) + "-page-action";
- let [, tests] = yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
+ yield extension.startup();
+ let tests = yield 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);
+ yield extension.awaitMessage("iconSet");
+
+ let browserActionButton = browserActionWidget.forWindow(window).node;
+ let pageActionImage = document.getElementById(pageActionId);
+
+
+ // Test icon sizes in the toolbar/urlbar.
+ for (let resolution of Object.keys(test.resolutions)) {
+ yield SpecialPowers.pushPrefEnv({set: [[RESOLUTION_PREF, resolution]]});
- extension.sendMessage("setIcon", test);
+ is(window.devicePixelRatio, +resolution, "window has the required resolution");
+
+ let imageURL = test.resolutions[resolution];
+ is(getListStyleImage(browserActionButton), imageURL, `browser action has the correct image at ${resolution}x resolution`);
+ is(getListStyleImage(pageActionImage), imageURL, `page action has the correct image at ${resolution}x resolution`);
- let [imageURL, legacy] = yield extension.awaitMessage("imageURL");
+ let isLegacy = browserActionButton.classList.contains("toolbarbutton-legacy-addon");
+ is(isLegacy, test.legacy, "Legacy class should be present?");
+
+ yield SpecialPowers.popPrefEnv();
+ }
- let browserActionButton = document.getElementById(browserActionId);
- is(browserActionButton.getAttribute("image"), imageURL, "browser action has the correct image");
+ if (!test.menuResolutions) {
+ continue;
+ }
+
+
+ // Test icon sizes in the menu panel.
+ CustomizableUI.addWidgetToArea(browserActionWidget.id,
+ CustomizableUI.AREA_PANEL);
+
+ yield showBrowserAction(extension);
+ browserActionButton = browserActionWidget.forWindow(window).node;
- let isLegacy = browserActionButton.classList.contains("toolbarbutton-legacy-addon");
- is(isLegacy, legacy, "Legacy class should be present?");
+ for (let resolution of Object.keys(test.menuResolutions)) {
+ yield SpecialPowers.pushPrefEnv({set: [[RESOLUTION_PREF, resolution]]});
+
+ is(window.devicePixelRatio, +resolution, "window has the required resolution");
+
+ let imageURL = test.menuResolutions[resolution];
+ is(getListStyleImage(browserActionButton), imageURL, `browser action has the correct menu image at ${resolution}x resolution`);
- let pageActionImage = document.getElementById(pageActionId);
- is(pageActionImage.src, imageURL, "page action has the correct image");
+ yield SpecialPowers.popPrefEnv();
+ }
+
+ yield closeBrowserAction(extension);
+
+ CustomizableUI.addWidgetToArea(browserActionWidget.id,
+ CustomizableUI.AREA_NAVBAR);
}
yield extension.unload();
});
// Test that an error is thrown when providing invalid icon sizes
add_task(function* testInvalidIconSizes() {
let extension = ExtensionTestUtils.loadExtension({
@@ -336,31 +402,36 @@ add_task(function* testDefaultDetails()
background: function() {
browser.tabs.query({active: true, currentWindow: true}, tabs => {
let tabId = tabs[0].id;
browser.pageAction.show(tabId).then(() => {
browser.test.sendMessage("ready");
});
});
- }
+ },
+
+ files: {
+ "foo/bar.png": imageBuffer,
+ "baz/quux.png": imageBuffer,
+ },
});
yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let browserActionButton = document.getElementById(browserActionId);
- let image = browserActionButton.getAttribute("image");
+ let image = getListStyleImage(browserActionButton);
ok(expectedURL.test(image), `browser action image ${image} matches ${expectedURL}`);
let pageActionImage = document.getElementById(pageActionId);
- image = pageActionImage.src;
+ image = getListStyleImage(pageActionImage);
ok(expectedURL.test(image), `page action image ${image} matches ${expectedURL}`);
yield extension.unload();
let node = document.getElementById(pageActionId);
is(node, null, "pageAction image removed from document");
}
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -93,17 +93,17 @@ function* runTests(options) {
function checkDetails(details) {
let image = currentWindow.document.getElementById(pageActionId);
if (details == null) {
ok(image == null || image.hidden, "image is hidden");
} else {
ok(image, "image exists");
- is(image.src, details.icon, "icon URL is correct");
+ is(getListStyleImage(image), details.icon, "icon URL is correct");
let title = details.title || options.manifest.name;
is(image.getAttribute("tooltiptext"), title, "image title is correct");
is(image.getAttribute("aria-label"), title, "image aria-label is correct");
// TODO: Popup URL.
}
}
@@ -172,16 +172,20 @@ add_task(function* testTabSwitchContext(
"description": "Popup",
},
"title": {
"message": "Title",
"description": "Title",
},
},
+
+ "default.png": imageBuffer,
+ "1.png": imageBuffer,
+ "2.png": imageBuffer,
},
getTests(tabs) {
let details = [
{"icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title \u263a"},
{"icon": browser.runtime.getURL("1.png"),
@@ -304,16 +308,20 @@ add_task(function* testDefaultTitle() {
"page_action": {
"default_icon": "icon.png",
},
"permissions": ["tabs"],
},
+ files: {
+ "icon.png": imageBuffer,
+ },
+
getTests(tabs) {
let details = [
{"title": "Foo Extension",
"popup": "",
"icon": browser.runtime.getURL("icon.png")},
{"title": "Foo Title",
"popup": "",
"icon": browser.runtime.getURL("icon.png")},
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -5,16 +5,17 @@
/* exported CustomizableUI makeWidgetId focusWindow forceGC
* getBrowserActionWidget
* clickBrowserAction clickPageAction
* getBrowserActionPopup getPageActionPopup
* closeBrowserAction closePageAction
* promisePopupShown promisePopupHidden
* openContextMenu closeContextMenu
* openExtensionContextMenu closeExtensionContextMenu
+ * imageBuffer getListStyleImage
*/
var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
// Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
// times in debug builds, which results in intermittent timeouts. Until we have
// a better solution, we force a GC after certain strategic tests, which tend to
@@ -42,16 +43,27 @@ var focusWindow = Task.async(function* f
resolve();
}, true);
});
win.focus();
yield promise;
});
+let img = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==";
+var imageBuffer = Uint8Array.from(atob(img), byte => byte.charCodeAt(0)).buffer;
+
+function getListStyleImage(button) {
+ let style = button.ownerDocument.defaultView.getComputedStyle(button);
+
+ let match = /^url\("(.*)"\)$/.exec(style.listStyleImage);
+
+ return match && match[1];
+}
+
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.state == "open") {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
@@ -79,25 +91,31 @@ function getBrowserActionPopup(extension
let group = getBrowserActionWidget(extension);
if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
return win.document.getElementById("customizationui-widget-panel");
}
return win.PanelUI.panel;
}
-var clickBrowserAction = Task.async(function* (extension, win = window) {
+var showBrowserAction = Task.async(function* (extension, win = window) {
let group = getBrowserActionWidget(extension);
let widget = group.forWindow(win);
if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
ok(!widget.overflowed, "Expect widget not to be overflowed");
} else if (group.areaType == CustomizableUI.TYPE_MENU_PANEL) {
yield win.PanelUI.show();
}
+});
+
+var clickBrowserAction = Task.async(function* (extension, win = window) {
+ yield showBrowserAction(extension, win);
+
+ let widget = getBrowserActionWidget(extension).forWindow(win);
EventUtils.synthesizeMouseAtCenter(widget.node, {}, win);
});
function closeBrowserAction(extension, win = window) {
let group = getBrowserActionWidget(extension);
let node = win.document.getElementById(group.viewId);
--- a/mobile/android/components/extensions/ext-pageAction.js
+++ b/mobile/android/components/extensions/ext-pageAction.js
@@ -59,17 +59,18 @@ PageAction.prototype = {
if (this.options.icon) {
this.id = PageActions.add(this.options);
return Promise.resolve();
}
this.shouldShow = true;
- let {icon} = IconDetails.getURL(this.icons, context.contentWindow, this.extension, 18);
+ let {icon} = IconDetails.getPreferredIcon(this.icons, this.extension,
+ 18 * context.contentWindow.devicePixelRatio);
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
return IconDetails.convertImageURLToDataURL(icon, context, browserWindow).then(dataURI => {
if (this.shouldShow) {
this.options.icon = dataURI;
this.id = PageActions.add(this.options);
}
}).catch(() => {
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -469,21 +469,19 @@ let IconDetails = {
extension.manifestError(`Invalid icon data: ${e}`);
}
return result;
},
// Returns the appropriate icon URL for the given icons object and the
// screen resolution of the given window.
- getURL(icons, window, extension, size = 16) {
+ getPreferredIcon(icons, extension = null, size = 16) {
const DEFAULT = "chrome://browser/content/extension.svg";
- 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))