--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -274,32 +274,33 @@ this.pageAction = class extends Extensio
isShown(details) {
let tab = tabTracker.getTab(details.tabId);
return pageAction.getProperty(tab, "show");
},
setTitle(details) {
let tab = tabTracker.getTab(details.tabId);
-
- // Clear the tab-specific title when given a null string.
- pageAction.setProperty(tab, "title", details.title || null);
+ pageAction.setProperty(tab, "title", details.title);
},
getTitle(details) {
let tab = tabTracker.getTab(details.tabId);
let title = pageAction.getProperty(tab, "title");
return Promise.resolve(title);
},
setIcon(details) {
let tab = tabTracker.getTab(details.tabId);
let icon = IconDetails.normalize(details, extension, context);
+ if (!Object.keys(icon).length) {
+ icon = null;
+ }
pageAction.setProperty(tab, "icon", icon);
},
setPopup(details) {
let tab = tabTracker.getTab(details.tabId);
// Note: Chrome resolves arguments to setIcon relative to the calling
// context, but resolves arguments to setPopup relative to the extension
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -4,20 +4,16 @@
// The ext-* files are imported into the same scopes.
/* import-globals-from ext-browser.js */
/* globals WINDOW_ID_CURRENT */
Cu.import("resource://gre/modules/ExtensionParent.jsm");
var {
- ExtensionError,
-} = ExtensionUtils;
-
-var {
IconDetails,
} = ExtensionParent;
var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> SidebarAction]
let sidebarActionMap = new WeakMap();
@@ -51,23 +47,24 @@ this.sidebarAction = class extends Exten
this.browserStyle = options.browser_style || options.browser_style === null;
this.defaults = {
enabled: true,
title: options.default_title || extension.name,
icon: IconDetails.normalize({path: options.default_icon}, extension),
panel: options.default_panel || "",
};
+ this.globals = Object.create(this.defaults);
- this.tabContext = new TabContext(tab => Object.create(this.defaults),
+ this.tabContext = new TabContext(tab => Object.create(this.globals),
extension);
// We need to ensure our elements are available before session restore.
this.windowOpenListener = (window) => {
- this.createMenuItem(window, this.defaults);
+ this.createMenuItem(window, this.globals);
};
windowTracker.addOpenListener(this.windowOpenListener);
this.updateHeader = (event) => {
let window = event.target.ownerGlobal;
let details = this.tabContext.get(window.gBrowser.selectedTab);
let header = window.document.getElementById("sidebar-switcher-target");
if (window.SidebarUI.currentID === this.id) {
@@ -286,40 +283,44 @@ this.sidebarAction = class extends Exten
* @param {XULElement|null} nativeTab
* Webextension tab object, may be null.
* @param {string} prop
* String property to retrieve ["icon", "title", or "panel"].
* @param {string} value
* Value for property.
*/
setProperty(nativeTab, prop, value) {
+ let values;
if (nativeTab === null) {
- this.defaults[prop] = value;
- } else if (value !== null) {
- this.tabContext.get(nativeTab)[prop] = value;
+ values = this.globals;
} else {
- delete this.tabContext.get(nativeTab)[prop];
+ values = this.tabContext.get(nativeTab);
+ }
+ if (value === null) {
+ delete values[prop];
+ } else {
+ values[prop] = value;
}
this.updateOnChange(nativeTab);
}
/**
- * Retrieve a property from the tab or defaults if tab is null.
+ * Retrieve a property from the tab or globals if tab is null.
*
* @param {XULElement|null} nativeTab
* Browser tab object, may be null.
* @param {string} prop
* String property to retrieve ["icon", "title", or "panel"]
* @returns {string} value
* Value for prop.
*/
getProperty(nativeTab, prop) {
if (nativeTab === null) {
- return this.defaults[prop];
+ return this.globals[prop];
}
return this.tabContext.get(nativeTab)[prop];
}
/**
* Triggers this sidebar action for the given window, with the same effects as
* if it were toggled via menu or toolbarbutton by a user.
*
@@ -376,53 +377,48 @@ this.sidebarAction = class extends Exten
}
return null;
}
return {
sidebarAction: {
async setTitle(details) {
let nativeTab = getTab(details.tabId);
-
- let title = details.title;
- // Clear the tab-specific title when given a null string.
- if (nativeTab && title === "") {
- title = null;
- }
- sidebarAction.setProperty(nativeTab, "title", title);
+ sidebarAction.setProperty(nativeTab, "title", details.title);
},
getTitle(details) {
let nativeTab = getTab(details.tabId);
let title = sidebarAction.getProperty(nativeTab, "title");
return Promise.resolve(title);
},
async setIcon(details) {
let nativeTab = getTab(details.tabId);
let icon = IconDetails.normalize(details, extension, context);
+ if (!Object.keys(icon).length) {
+ icon = null;
+ }
sidebarAction.setProperty(nativeTab, "icon", icon);
},
async setPanel(details) {
let nativeTab = getTab(details.tabId);
let url;
- // Clear the tab-specific url when given a null string.
- if (nativeTab && details.panel === "") {
+ // Clear the url when given null or empty string.
+ if (!details.panel) {
url = null;
- } else if (details.panel !== "") {
+ } else {
url = context.uri.resolve(details.panel);
if (!context.checkLoadURL(url)) {
return Promise.reject({message: `Access denied for URL ${url}`});
}
- } else {
- throw new ExtensionError("Invalid url for sidebar panel.");
}
sidebarAction.setProperty(nativeTab, "panel", url);
},
getPanel(details) {
let nativeTab = getTab(details.tabId);
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -119,17 +119,23 @@
"type": "function",
"description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
- "title": {"type": "string", "description": "The tooltip string."}
+ "title": {
+ "choices": [
+ {"type": "string"},
+ {"type": "null"}
+ ],
+ "description": "The tooltip string."
+ }
}
}
]
},
{
"name": "getTitle",
"type": "function",
"description": "Gets the title of the page action.",
@@ -211,17 +217,20 @@
"description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
"popup": {
- "type": "string",
+ "choices": [
+ {"type": "string"},
+ {"type": "null"}
+ ],
"description": "The html file to show in a popup. If set to the empty string (''), no popup is shown."
}
}
}
]
},
{
"name": "getPopup",
--- a/browser/components/extensions/schemas/sidebar_action.json
+++ b/browser/components/extensions/schemas/sidebar_action.json
@@ -59,17 +59,20 @@
"description": "Sets the title of the sidebar action. This shows up in the tooltip.",
"async": true,
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"title": {
- "type": "string",
+ "choices": [
+ {"type": "string"},
+ {"type": "null"}
+ ],
"description": "The string the sidebar action should display when moused over."
},
"tabId": {
"type": "integer",
"optional": true,
"description": "Sets the sidebar title for the tab specified by tabId. Automatically resets when the tab is closed."
}
}
@@ -151,17 +154,20 @@
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Sets the sidebar url for the tab specified by tabId. Automatically resets when the tab is closed."
},
"panel": {
- "type": "string",
+ "choices": [
+ {"type": "string"},
+ {"type": "null"}
+ ],
"description": "The url to the html file to show in a sidebar. If set to the empty string (''), no sidebar is shown."
}
}
}
]
},
{
"name": "getPanel",
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -468,34 +468,34 @@ add_task(async function testPropertyRemo
"files": {
"default.png": imageBuffer,
"i1.png": imageBuffer,
"i2.png": imageBuffer,
"i3.png": imageBuffer,
},
getTests: function(tabs, expectGlobals) {
- let contextUri = browser.runtime.getURL("_generated_background_page.html");
+ let defaultIcon = "chrome://browser/content/extension.svg";
let details = [
{"icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title",
"badge": "",
"badgeBackgroundColor": [0xd9, 0x00, 0x00, 0xFF]},
{"icon": browser.runtime.getURL("i1.png"),
"popup": browser.runtime.getURL("p1.html"),
"title": "t1",
"badge": "b1",
"badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF]},
{"icon": browser.runtime.getURL("i2.png"),
"popup": browser.runtime.getURL("p2.html"),
"title": "t2",
"badge": "b2",
"badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
- {"icon": contextUri,
+ {"icon": defaultIcon,
"popup": "",
"title": "",
"badge": "",
"badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
{"icon": browser.runtime.getURL("i3.png"),
"popup": browser.runtime.getURL("p3.html"),
"title": "t3",
"badge": "b3",
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -47,29 +47,30 @@ add_task(async function testTabSwitchCon
},
"default.png": imageBuffer,
"1.png": imageBuffer,
"2.png": imageBuffer,
},
getTests: function(tabs) {
+ let defaultIcon = "chrome://browser/content/extension.svg";
let details = [
{"icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default T\u00edtulo \u263a"},
{"icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default T\u00edtulo \u263a"},
{"icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2"},
- {"icon": browser.runtime.getURL("2.png"),
- "popup": browser.runtime.getURL("2.html"),
- "title": "Default T\u00edtulo \u263a"},
+ {"icon": defaultIcon,
+ "popup": "",
+ "title": ""},
];
let promiseTabLoad = details => {
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
if (tabId == details.id && changed.url == details.url) {
browser.tabs.onUpdated.removeListener(listener);
resolve();
@@ -119,21 +120,31 @@ add_task(async function testTabSwitchCon
let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
await promise;
expect(details[2]);
},
expect => {
- browser.test.log("Clear the title. Expect default title.");
+ browser.test.log("Set empty string values. Expect empty strings but default icon.");
+ browser.pageAction.setIcon({tabId: tabs[1], path: ""});
+ browser.pageAction.setPopup({tabId: tabs[1], popup: ""});
browser.pageAction.setTitle({tabId: tabs[1], title: ""});
expect(details[3]);
},
+ expect => {
+ browser.test.log("Clear the values. Expect default ones.");
+ browser.pageAction.setIcon({tabId: tabs[1], path: null});
+ browser.pageAction.setPopup({tabId: tabs[1], popup: null});
+ browser.pageAction.setTitle({tabId: tabs[1], title: null});
+
+ expect(details[0]);
+ },
async expect => {
browser.test.log("Navigate to a new page. Expect icon hidden.");
// TODO: This listener should not be necessary, but the |tabs.update|
// callback currently fires too early in e10s windows.
let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
browser.tabs.update(tabs[1], {url: "about:blank?1"});
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
@@ -59,16 +59,19 @@ add_task(async function testTabSwitchCon
{"icon": browser.runtime.getURL("1.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default T\u00edtulo \u263a"},
{"icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2"},
{"icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
+ "title": ""},
+ {"icon": browser.runtime.getURL("2.png"),
+ "popup": browser.runtime.getURL("2.html"),
"title": "Default T\u00edtulo \u263a"},
];
let promiseTabLoad = details => {
return new Promise(resolve => {
browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
if (tabId == details.id && changed.url == details.url) {
browser.tabs.onUpdated.removeListener(listener);
@@ -119,21 +122,27 @@ add_task(async function testTabSwitchCon
let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
await promise;
expect(details[2]);
},
expect => {
- browser.test.log("Clear the title. Expect default title.");
+ browser.test.log("Set empty title. Expect empty title.");
browser.pageAction.setTitle({tabId: tabs[1], title: ""});
expect(details[3]);
},
+ expect => {
+ browser.test.log("Clear the title. Expect default title.");
+ browser.pageAction.setTitle({tabId: tabs[1], title: null});
+
+ expect(details[4]);
+ },
async expect => {
browser.test.log("Navigate to a new page. Expect icon hidden.");
// TODO: This listener should not be necessary, but the |tabs.update|
// callback currently fires too early in e10s windows.
let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
browser.tabs.update(tabs[1], {url: "about:blank?1"});
@@ -191,16 +200,19 @@ add_task(async function testDefaultTitle
getTests: function(tabs) {
let details = [
{"title": "Foo Extension",
"popup": "",
"icon": browser.runtime.getURL("icon.png")},
{"title": "Foo Title",
"popup": "",
"icon": browser.runtime.getURL("icon.png")},
+ {"title": "",
+ "popup": "",
+ "icon": browser.runtime.getURL("icon.png")},
];
return [
expect => {
browser.test.log("Initial state. No icon visible.");
expect(null);
},
async expect => {
@@ -209,16 +221,21 @@ add_task(async function testDefaultTitle
expect(details[0]);
},
expect => {
browser.test.log("Change the title. Expect new title.");
browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"});
expect(details[1]);
},
expect => {
+ browser.test.log("Set empty title. Expect empty title.");
+ browser.pageAction.setTitle({tabId: tabs[0], title: ""});
+ expect(details[2]);
+ },
+ expect => {
browser.test.log("Clear the title. Expect extension title.");
- browser.pageAction.setTitle({tabId: tabs[0], title: ""});
+ browser.pageAction.setTitle({tabId: tabs[0], title: null});
expect(details[0]);
},
];
},
});
});
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
@@ -29,21 +29,22 @@ let extData = {
browser.test.sendMessage("sidebar");
};
},
},
background: function() {
browser.test.onMessage.addListener(async ({msg, data}) => {
if (msg === "set-panel") {
- await browser.sidebarAction.setPanel({panel: ""}).then(() => {
- browser.test.notifyFail("empty panel settable");
- }).catch(() => {
- browser.test.notifyPass("unable to set empty panel");
- });
+ await browser.sidebarAction.setPanel({panel: null});
+ browser.test.assertEq(
+ await browser.sidebarAction.getPanel({}),
+ browser.runtime.getURL("sidebar.html"),
+ "Global panel can be reverted to the default."
+ );
} else if (msg === "isOpen") {
let {arg = {}, result} = data;
let isOpen = await browser.sidebarAction.isOpen(arg);
browser.test.assertEq(result, isOpen, "expected value from isOpen");
}
browser.test.sendMessage("done");
});
},
@@ -91,17 +92,16 @@ add_task(async function sidebar_two_side
add_task(async function sidebar_empty_panel() {
let extension = ExtensionTestUtils.loadExtension(extData);
await extension.startup();
// Test sidebar is opened on install
await extension.awaitMessage("sidebar");
ok(!document.getElementById("sidebar-box").hidden, "sidebar box is visible in first window");
await sendMessage(extension, "set-panel");
- await extension.awaitFinish();
await extension.unload();
});
add_task(async function sidebar_isOpen() {
info("Load extension1");
let extension1 = ExtensionTestUtils.loadExtension(extData);
await extension1.startup();
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
@@ -293,17 +293,17 @@ add_task(async function testTabSwitchCon
browser.test.log("Change tab panel.");
let tabId = tabs[0];
await browser.sidebarAction.setPanel({tabId, panel: "2.html"});
expect(details[6]);
},
async expect => {
browser.test.log("Revert tab panel.");
let tabId = tabs[0];
- await browser.sidebarAction.setPanel({tabId, panel: ""});
+ await browser.sidebarAction.setPanel({tabId, panel: null});
expect(details[4]);
},
];
},
});
});
add_task(async function testDefaultTitle() {
@@ -319,72 +319,176 @@ add_task(async function testDefaultTitle
"permissions": ["tabs"],
},
files: {
"sidebar.html": sidebar,
"icon.png": imageBuffer,
},
- getTests: function(tabs, expectDefaults) {
+ getTests: function(tabs, expectGlobals) {
let details = [
{"title": "Foo Extension",
"panel": browser.runtime.getURL("sidebar.html"),
"icon": browser.runtime.getURL("icon.png")},
{"title": "Foo Title",
"panel": browser.runtime.getURL("sidebar.html"),
"icon": browser.runtime.getURL("icon.png")},
{"title": "Bar Title",
"panel": browser.runtime.getURL("sidebar.html"),
"icon": browser.runtime.getURL("icon.png")},
- {"title": "",
- "panel": browser.runtime.getURL("sidebar.html"),
- "icon": browser.runtime.getURL("icon.png")},
];
return [
async expect => {
- browser.test.log("Initial state. Expect extension title as default title.");
+ browser.test.log("Initial state. Expect default extension title.");
- await expectDefaults(details[0]);
+ await expectGlobals(details[0]);
expect(details[0]);
},
async expect => {
- browser.test.log("Change the title. Expect new title.");
+ browser.test.log("Change the tab title. Expect new title.");
browser.sidebarAction.setTitle({tabId: tabs[0], title: "Foo Title"});
- await expectDefaults(details[0]);
+ await expectGlobals(details[0]);
expect(details[1]);
},
async expect => {
- browser.test.log("Change the default. Expect same properties.");
+ browser.test.log("Change the global title. Expect same properties.");
browser.sidebarAction.setTitle({title: "Bar Title"});
- await expectDefaults(details[2]);
+ await expectGlobals(details[2]);
expect(details[1]);
},
async expect => {
- browser.test.log("Clear the title. Expect new default title.");
- browser.sidebarAction.setTitle({tabId: tabs[0], title: ""});
+ browser.test.log("Clear the tab title. Expect new global title.");
+ browser.sidebarAction.setTitle({tabId: tabs[0], title: null});
- await expectDefaults(details[2]);
+ await expectGlobals(details[2]);
expect(details[2]);
},
async expect => {
- browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
- browser.sidebarAction.setTitle({title: ""});
+ browser.test.log("Clear the global title. Expect default title.");
+ browser.sidebarAction.setTitle({title: null});
- await expectDefaults(details[3]);
- expect(details[3]);
+ await expectGlobals(details[0]);
+ expect(details[0]);
},
async expect => {
browser.test.assertRejects(
browser.sidebarAction.setPanel({panel: "about:addons"}),
/Access denied for URL about:addons/,
"unable to set panel to about:addons");
- await expectDefaults(details[3]);
- expect(details[3]);
+ await expectGlobals(details[0]);
+ expect(details[0]);
},
];
},
});
});
+
+add_task(async function testPropertyRemoval() {
+ await runTests({
+ manifest: {
+ "name": "Foo Extension",
+
+ "sidebar_action": {
+ "default_icon": "default.png",
+ "default_panel": "default.html",
+ "default_title": "Default Title",
+ },
+
+ "permissions": ["tabs"],
+ },
+
+ files: {
+ "default.html": sidebar,
+ "p1.html": sidebar,
+ "p2.html": sidebar,
+ "p3.html": sidebar,
+ "default.png": imageBuffer,
+ "i1.png": imageBuffer,
+ "i2.png": imageBuffer,
+ "i3.png": imageBuffer,
+ },
+
+ getTests: function(tabs, expectGlobals) {
+ let defaultIcon = "chrome://browser/content/extension.svg";
+ let details = [
+ {"icon": browser.runtime.getURL("default.png"),
+ "panel": browser.runtime.getURL("default.html"),
+ "title": "Default Title"},
+ {"icon": browser.runtime.getURL("i1.png"),
+ "panel": browser.runtime.getURL("p1.html"),
+ "title": "t1"},
+ {"icon": browser.runtime.getURL("i2.png"),
+ "panel": browser.runtime.getURL("p2.html"),
+ "title": "t2"},
+ {"icon": defaultIcon,
+ "panel": browser.runtime.getURL("p1.html"),
+ "title": ""},
+ {"icon": browser.runtime.getURL("i3.png"),
+ "panel": browser.runtime.getURL("p3.html"),
+ "title": "t3"},
+ ];
+
+ return [
+ async expect => {
+ browser.test.log("Initial state, expect default properties.");
+ await expectGlobals(details[0]);
+ expect(details[0]);
+ },
+ async expect => {
+ browser.test.log("Set global values, expect the new values.");
+ browser.sidebarAction.setIcon({path: "i1.png"});
+ browser.sidebarAction.setPanel({panel: "p1.html"});
+ browser.sidebarAction.setTitle({title: "t1"});
+ await expectGlobals(details[1]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Set tab values, expect the new values.");
+ let tabId = tabs[0];
+ browser.sidebarAction.setIcon({tabId, path: "i2.png"});
+ browser.sidebarAction.setPanel({tabId, panel: "p2.html"});
+ browser.sidebarAction.setTitle({tabId, title: "t2"});
+ await expectGlobals(details[1]);
+ expect(details[2]);
+ },
+ async expect => {
+ browser.test.log("Set empty tab values.");
+ let tabId = tabs[0];
+ browser.sidebarAction.setIcon({tabId, path: ""});
+ browser.sidebarAction.setPanel({tabId, panel: ""});
+ browser.sidebarAction.setTitle({tabId, title: ""});
+ await expectGlobals(details[1]);
+ expect(details[3]);
+ },
+ async expect => {
+ browser.test.log("Remove tab values, expect global values.");
+ let tabId = tabs[0];
+ browser.sidebarAction.setIcon({tabId, path: null});
+ browser.sidebarAction.setPanel({tabId, panel: null});
+ browser.sidebarAction.setTitle({tabId, title: null});
+ await expectGlobals(details[1]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Change global values, expect the new values.");
+ browser.sidebarAction.setIcon({path: "i3.png"});
+ browser.sidebarAction.setPanel({panel: "p3.html"});
+ browser.sidebarAction.setTitle({title: "t3"});
+ await expectGlobals(details[4]);
+ expect(details[4]);
+ },
+ async expect => {
+ browser.test.log("Remove global values, expect defaults.");
+ browser.sidebarAction.setIcon({path: null});
+ browser.sidebarAction.setPanel({panel: null});
+ browser.sidebarAction.setTitle({title: null});
+ await expectGlobals(details[0]);
+ expect(details[0]);
+ },
+ ];
+ },
+ });
+});
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -1294,24 +1294,26 @@ let IconDetails = {
let baseURI = context ? context.uri : extension.baseURI;
if (path != null) {
if (typeof path != "object") {
path = {"19": path};
}
for (let size of Object.keys(path)) {
- let url = baseURI.resolve(path[size]);
+ let url = path[size];
+ if (url) {
+ url = baseURI.resolve(path[size]);
- // The Chrome documentation specifies these parameters as
- // relative paths. We currently accept absolute URLs as well,
- // which means we need to check that the extension is allowed
- // to load them. This will throw an error if it's not allowed.
- this._checkURL(url, extension);
-
+ // The Chrome documentation specifies these parameters as
+ // relative paths. We currently accept absolute URLs as well,
+ // which means we need to check that the extension is allowed
+ // to load them. This will throw an error if it's not allowed.
+ this._checkURL(url, extension);
+ }
result[size] = url;
}
}
if (themeIcons) {
themeIcons.forEach(({size, light, dark}) => {
let lightURL = baseURI.resolve(light);
let darkURL = baseURI.resolve(dark);
@@ -1363,17 +1365,17 @@ let IconDetails = {
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: bestSize, icon: icons[bestSize] || DEFAULT};
}
return {size, icon: DEFAULT};
},
convertImageURLToDataURL(imageURL, contentWindow, browserWindow, size = 18) {
return new Promise((resolve, reject) => {
let image = new contentWindow.Image();