--- a/browser/components/extensions/parent/ext-browserAction.js
+++ b/browser/components/extensions/parent/ext-browserAction.js
@@ -68,16 +68,17 @@ this.browserAction = class extends Exten
this.tabManager = extension.tabManager;
this.defaults = {
enabled: true,
title: options.default_title || extension.name,
badgeText: "",
badgeBackgroundColor: null,
+ badgeTextColor: null,
popup: options.default_popup || "",
area: browserAreas[options.default_area || "navbar"],
};
this.globals = Object.create(this.defaults);
this.browserStyle = options.browser_style;
browserActionMap.set(extension, this);
@@ -436,20 +437,28 @@ this.browserAction = class extends Exten
}
if (tabData.enabled) {
node.removeAttribute("disabled");
} else {
node.setAttribute("disabled", "true");
}
- let color = tabData.badgeBackgroundColor;
- if (color) {
- color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
- node.setAttribute("badgeStyle", `background-color: ${color};`);
+ let {badgeBackgroundColor, badgeTextColor} = tabData;
+ let badgeStyle = [];
+ if (badgeBackgroundColor) {
+ let [r, g, b, a] = badgeBackgroundColor;
+ badgeStyle.push(`background-color: rgba(${r}, ${g}, ${b}, ${a / 255})`);
+ }
+ if (badgeTextColor) {
+ let [r, g, b, a] = badgeTextColor;
+ badgeStyle.push(`color: rgba(${r}, ${g}, ${b}, ${a / 255})`);
+ }
+ if (badgeStyle.length) {
+ node.setAttribute("badgeStyle", badgeStyle.join("; "));
} else {
node.removeAttribute("badgeStyle");
}
let {style, legacy} = this.iconData.get(tabData.icon);
const LEGACY_CLASS = "toolbarbutton-legacy-addon";
if (legacy) {
node.classList.add(LEGACY_CLASS);
@@ -580,18 +589,18 @@ this.browserAction = class extends Exten
}
/**
* Set a global, window specific or tab specific property.
*
* @param {Object} details
* An object with optional `tabId` or `windowId` properties.
* @param {string} prop
- * String property to set. Should should be one of "icon", "title",
- * "badgeText", "popup", "badgeBackgroundColor" or "enabled".
+ * String property to set. Should should be one of "icon", "title", "badgeText"
+ * "popup", "badgeBackgroundColor", "badgeTextColor" or "enabled".
* @param {string} value
* Value for prop.
*/
setProperty(details, prop, value) {
let {target, values} = this.getContextData(details);
if (value === null) {
delete values[prop];
} else {
@@ -617,16 +626,27 @@ this.browserAction = class extends Exten
}
getAPI(context) {
let {extension} = context;
let {tabManager} = extension;
let browserAction = this;
+ function parseColor(color, kind) {
+ if (typeof color == "string") {
+ let rgba = InspectorUtils.colorToRGBA(color);
+ if (!rgba) {
+ throw new ExtensionError(`Invalid badge ${kind} color: "${color}"`);
+ }
+ color = [rgba.r, rgba.g, rgba.b, Math.round(rgba.a * 255)];
+ }
+ return color;
+ }
+
return {
browserAction: {
onClicked: new EventManager({
context,
name: "browserAction.onClicked",
inputHandling: true,
register: fire => {
let listener = (event, browser) => {
@@ -691,32 +711,35 @@ this.browserAction = class extends Exten
browserAction.setProperty(details, "popup", url);
},
getPopup: function(details) {
return browserAction.getProperty(details, "popup");
},
setBadgeBackgroundColor: function(details) {
- let color = details.color;
- if (typeof color == "string") {
- let col = InspectorUtils.colorToRGBA(color);
- if (!col) {
- throw new ExtensionError(`Invalid badge background color: "${color}"`);
- }
- color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
- }
+ let color = parseColor(details.color, "background");
browserAction.setProperty(details, "badgeBackgroundColor", color);
},
getBadgeBackgroundColor: function(details, callback) {
let color = browserAction.getProperty(details, "badgeBackgroundColor");
return color || [0xd9, 0, 0, 255];
},
+ setBadgeTextColor: function(details) {
+ let color = parseColor(details.color, "text");
+ browserAction.setProperty(details, "badgeTextColor", color);
+ },
+
+ getBadgeTextColor: function(details, callback) {
+ let color = browserAction.getProperty(details, "badgeTextColor");
+ return color || [255, 255, 255, 255];
+ },
+
openPopup: function() {
let window = windowTracker.topWindow;
browserAction.triggerAction(window);
},
},
};
}
};
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -17,16 +17,21 @@ async function runTests(options) {
browser.test.assertEq(expecting.badge, badge,
"expected value from getBadge");
let badgeBackgroundColor = await browser.browserAction.getBadgeBackgroundColor(details);
browser.test.assertEq(String(expecting.badgeBackgroundColor),
String(badgeBackgroundColor),
"expected value from getBadgeBackgroundColor");
+ let badgeTextColor = await browser.browserAction.getBadgeTextColor(details);
+ browser.test.assertEq(String(expecting.badgeTextColor),
+ String(badgeTextColor),
+ "expected value from getBadgeTextColor");
+
let enabled = await browser.browserAction.isEnabled(details);
browser.test.assertEq(expecting.enabled, enabled,
"expected value from isEnabled");
}
let tabs = [];
let windows = [];
let tests = getTests(tabs, windows);
@@ -36,16 +41,17 @@ async function runTests(options) {
let calls = [
() => browser.browserAction.enable(tabId),
() => browser.browserAction.disable(tabId),
() => browser.browserAction.setTitle({tabId, title: "foo"}),
() => browser.browserAction.setIcon({tabId, path: "foo.png"}),
() => browser.browserAction.setPopup({tabId, popup: "foo.html"}),
() => browser.browserAction.setBadgeText({tabId, text: "foo"}),
() => browser.browserAction.setBadgeBackgroundColor({tabId, color: [0xff, 0, 0, 0xff]}),
+ () => browser.browserAction.setBadgeTextColor({tabId, color: [0, 0xff, 0xff, 0xff]}),
];
for (let call of calls) {
await browser.test.assertRejects(
new Promise(resolve => resolve(call())),
RegExp(`Invalid tab ID: ${tabId}`),
"Expected invalid tab ID error");
}
@@ -91,16 +97,23 @@ async function runTests(options) {
let extension = ExtensionTestUtils.loadExtension({
manifest: options.manifest,
files: options.files || {},
background: `(${background})(${options.getTests})`,
});
+ function serializeColor([r, g, b, a]) {
+ if (a === 255) {
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+ return `rgba(${r}, ${g}, ${b}, ${a / 255})`;
+ }
+
let browserActionId;
function checkDetails(details, windowId) {
let {document} = Services.wm.getOuterWindowWithId(windowId);
if (!browserActionId) {
browserActionId = `${makeWidgetId(extension.id)}-browser-action`;
}
let button = document.getElementById(browserActionId);
@@ -110,28 +123,29 @@ async function runTests(options) {
let title = details.title || options.manifest.name;
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", !details.enabled, "disabled state is correct");
- if (details.badge && details.badgeBackgroundColor) {
+ if (details.badge) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, "class", "toolbarbutton-badge");
-
- let badgeColor = window.getComputedStyle(badge).backgroundColor;
- let color = details.badgeBackgroundColor;
- let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
-
- is(badgeColor, expectedColor, "badge color is correct");
+ let style = window.getComputedStyle(badge);
+ let expected = {
+ backgroundColor: serializeColor(details.badgeBackgroundColor),
+ color: serializeColor(details.badgeTextColor),
+ };
+ for (let [prop, value] of Object.entries(expected)) {
+ is(style[prop], value, `${prop} is correct`);
+ }
}
-
// TODO: Popup URL.
}
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", async (expecting, windowId, testsRemaining) => {
await promiseAnimationFrame();
checkDetails(expecting, windowId);
@@ -186,35 +200,39 @@ add_task(async function testTabSwitchCon
getTests: function(tabs, windows) {
let details = [
{"icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title",
"badge": "",
"badgeBackgroundColor": [0xd9, 0, 0, 255],
+ "badgeTextColor": [0xff, 0xff, 0xff, 0xff],
"enabled": true},
{"icon": browser.runtime.getURL("1.png")},
{"icon": browser.runtime.getURL("2.png"),
"popup": browser.runtime.getURL("2.html"),
"title": "Title 2",
"badge": "2",
"badgeBackgroundColor": [0xff, 0, 0, 0xff],
+ "badgeTextColor": [0, 0xff, 0xff, 0xff],
"enabled": false},
{"icon": browser.runtime.getURL("global.png"),
"popup": browser.runtime.getURL("global.html"),
"title": "Global Title",
"badge": "g",
"badgeBackgroundColor": [0, 0xff, 0, 0xff],
+ "badgeTextColor": [0xff, 0, 0xff, 0xff],
"enabled": false},
{"icon": browser.runtime.getURL("global.png"),
"popup": browser.runtime.getURL("global.html"),
"title": "Global Title",
"badge": "g",
- "badgeBackgroundColor": [0, 0xff, 0, 0xff]},
+ "badgeBackgroundColor": [0, 0xff, 0, 0xff],
+ "badgeTextColor": [0xff, 0, 0xff, 0xff]},
];
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();
@@ -252,32 +270,34 @@ add_task(async function testTabSwitchCon
async expect => {
browser.test.log("Change properties. Expect new properties.");
let tabId = tabs[1];
browser.browserAction.setIcon({tabId, path: "2.png"});
browser.browserAction.setPopup({tabId, popup: "2.html"});
browser.browserAction.setTitle({tabId, title: "Title 2"});
browser.browserAction.setBadgeText({tabId, text: "2"});
browser.browserAction.setBadgeBackgroundColor({tabId, color: "#ff0000"});
+ browser.browserAction.setBadgeTextColor({tabId, color: "#00ffff"});
browser.browserAction.disable(tabId);
expect(details[2], null, null, details[0]);
},
async expect => {
browser.test.log("Switch back to the first tab. Expect previously set properties.");
await browser.tabs.update(tabs[0], {active: true});
expect(details[1], null, null, details[0]);
},
async expect => {
browser.test.log("Change global values, expect those changes reflected.");
browser.browserAction.setIcon({path: "global.png"});
browser.browserAction.setPopup({popup: "global.html"});
browser.browserAction.setTitle({title: "Global Title"});
browser.browserAction.setBadgeText({text: "g"});
browser.browserAction.setBadgeBackgroundColor({color: [0, 0xff, 0, 0xff]});
+ browser.browserAction.setBadgeTextColor({color: [0xff, 0, 0xff, 0xff]});
browser.browserAction.disable();
expect(details[1], null, details[3], details[0]);
},
async expect => {
browser.test.log("Re-enable globally. Expect enabled.");
browser.browserAction.enable();
@@ -336,16 +356,17 @@ add_task(async function testDefaultTitle
},
getTests: function(tabs, windows) {
let details = [
{"title": "Foo Extension",
"popup": "",
"badge": "",
"badgeBackgroundColor": [0xd9, 0, 0, 255],
+ "badgeTextColor": [0xff, 0xff, 0xff, 0xff],
"icon": browser.runtime.getURL("icon.png"),
"enabled": true},
{"title": "Foo Title"},
{"title": "Bar Title"},
];
return [
async expect => {
@@ -455,128 +476,146 @@ add_task(async function testPropertyRemo
getTests: function(tabs, windows) {
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],
+ "badgeTextColor": [0xff, 0xff, 0xff, 0xff],
"enabled": true},
{"icon": browser.runtime.getURL("global.png"),
"popup": browser.runtime.getURL("global.html"),
"title": "global",
"badge": "global",
- "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF]},
+ "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF],
+ "badgeTextColor": [0x99, 0x99, 0x99, 0xff]},
{"icon": browser.runtime.getURL("window.png"),
"popup": browser.runtime.getURL("window.html"),
"title": "window",
"badge": "window",
- "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
+ "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF],
+ "badgeTextColor": [0x88, 0x88, 0x88, 0xff]},
{"icon": browser.runtime.getURL("tab.png"),
"popup": browser.runtime.getURL("tab.html"),
"title": "tab",
"badge": "tab",
- "badgeBackgroundColor": [0x33, 0x33, 0x33, 0xFF]},
+ "badgeBackgroundColor": [0x33, 0x33, 0x33, 0xFF],
+ "badgeTextColor": [0x77, 0x77, 0x77, 0xff]},
{"icon": defaultIcon,
"popup": "",
"title": "",
"badge": "",
- "badgeBackgroundColor": [0x33, 0x33, 0x33, 0xFF]},
+ "badgeBackgroundColor": [0x33, 0x33, 0x33, 0xFF],
+ "badgeTextColor": [0x77, 0x77, 0x77, 0xff]},
{"icon": browser.runtime.getURL("global2.png"),
"popup": browser.runtime.getURL("global2.html"),
"title": "global2",
"badge": "global2",
- "badgeBackgroundColor": [0x44, 0x44, 0x44, 0xFF]},
+ "badgeBackgroundColor": [0x44, 0x44, 0x44, 0xFF],
+ "badgeTextColor": [0x66, 0x66, 0x66, 0xff]},
];
return [
async expect => {
browser.test.log("Initial state, expect default properties.");
expect(null, null, null, details[0]);
},
async expect => {
browser.test.log("Set global values, expect the new values.");
browser.browserAction.setIcon({path: "global.png"});
browser.browserAction.setPopup({popup: "global.html"});
browser.browserAction.setTitle({title: "global"});
browser.browserAction.setBadgeText({text: "global"});
browser.browserAction.setBadgeBackgroundColor({color: "#111"});
+ browser.browserAction.setBadgeTextColor({color: "#999"});
expect(null, null, details[1], details[0]);
},
async expect => {
browser.test.log("Set window values, expect the new values.");
let windowId = windows[0];
browser.browserAction.setIcon({windowId, path: "window.png"});
browser.browserAction.setPopup({windowId, popup: "window.html"});
browser.browserAction.setTitle({windowId, title: "window"});
browser.browserAction.setBadgeText({windowId, text: "window"});
browser.browserAction.setBadgeBackgroundColor({windowId, color: "#222"});
+ browser.browserAction.setBadgeTextColor({windowId, color: "#888"});
expect(null, details[2], details[1], details[0]);
},
async expect => {
browser.test.log("Set tab values, expect the new values.");
let tabId = tabs[0];
browser.browserAction.setIcon({tabId, path: "tab.png"});
browser.browserAction.setPopup({tabId, popup: "tab.html"});
browser.browserAction.setTitle({tabId, title: "tab"});
browser.browserAction.setBadgeText({tabId, text: "tab"});
browser.browserAction.setBadgeBackgroundColor({tabId, color: "#333"});
+ browser.browserAction.setBadgeTextColor({tabId, color: "#777"});
expect(details[3], details[2], details[1], details[0]);
},
async expect => {
- browser.test.log("Set empty tab values, expect empty values except for bgcolor.");
+ browser.test.log("Set empty tab values, expect empty values except for colors.");
let tabId = tabs[0];
browser.browserAction.setIcon({tabId, path: ""});
browser.browserAction.setPopup({tabId, popup: ""});
browser.browserAction.setTitle({tabId, title: ""});
browser.browserAction.setBadgeText({tabId, text: ""});
await browser.test.assertRejects(
browser.browserAction.setBadgeBackgroundColor({tabId, color: ""}),
/^Invalid badge background color: ""$/,
"Expected invalid badge background color error"
);
+ await browser.test.assertRejects(
+ browser.browserAction.setBadgeTextColor({tabId, color: ""}),
+ /^Invalid badge text color: ""$/,
+ "Expected invalid badge text color error"
+ );
expect(details[4], details[2], details[1], details[0]);
},
async expect => {
browser.test.log("Remove tab values, expect window values.");
let tabId = tabs[0];
browser.browserAction.setIcon({tabId, path: null});
browser.browserAction.setPopup({tabId, popup: null});
browser.browserAction.setTitle({tabId, title: null});
browser.browserAction.setBadgeText({tabId, text: null});
browser.browserAction.setBadgeBackgroundColor({tabId, color: null});
+ browser.browserAction.setBadgeTextColor({tabId, color: null});
expect(null, details[2], details[1], details[0]);
},
async expect => {
browser.test.log("Remove window values, expect global values.");
let windowId = windows[0];
browser.browserAction.setIcon({windowId, path: null});
browser.browserAction.setPopup({windowId, popup: null});
browser.browserAction.setTitle({windowId, title: null});
browser.browserAction.setBadgeText({windowId, text: null});
browser.browserAction.setBadgeBackgroundColor({windowId, color: null});
+ browser.browserAction.setBadgeTextColor({windowId, color: null});
expect(null, null, details[1], details[0]);
},
async expect => {
browser.test.log("Change global values, expect the new values.");
browser.browserAction.setIcon({path: "global2.png"});
browser.browserAction.setPopup({popup: "global2.html"});
browser.browserAction.setTitle({title: "global2"});
browser.browserAction.setBadgeText({text: "global2"});
browser.browserAction.setBadgeBackgroundColor({color: "#444"});
+ browser.browserAction.setBadgeTextColor({color: "#666"});
expect(null, null, details[5], details[0]);
},
async expect => {
browser.test.log("Remove global values, expect defaults.");
browser.browserAction.setIcon({path: null});
browser.browserAction.setPopup({popup: null});
browser.browserAction.setBadgeText({text: null});
browser.browserAction.setTitle({title: null});
browser.browserAction.setBadgeBackgroundColor({color: null});
+ browser.browserAction.setBadgeTextColor({color: null});
expect(null, null, null, details[0]);
},
];
},
});
});
add_task(async function testMultipleWindows() {
@@ -597,43 +636,47 @@ add_task(async function testMultipleWind
getTests: function(tabs, windows) {
let details = [
{"icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
"title": "Default Title",
"badge": "",
"badgeBackgroundColor": [0xd9, 0x00, 0x00, 0xFF],
+ "badgeTextColor": [0xff, 0xff, 0xff, 0xff],
"enabled": true},
{"icon": browser.runtime.getURL("window1.png"),
"popup": browser.runtime.getURL("window1.html"),
"title": "window1",
"badge": "w1",
- "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF]},
+ "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF],
+ "badgeTextColor": [0x99, 0x99, 0x99, 0xff]},
{"icon": browser.runtime.getURL("window2.png"),
"popup": browser.runtime.getURL("window2.html"),
"title": "window2",
"badge": "w2",
- "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
+ "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF],
+ "badgeTextColor": [0x88, 0x88, 0x88, 0xff]},
{"title": "tab"},
];
return [
async expect => {
browser.test.log("Initial state, expect default properties.");
expect(null, null, null, details[0]);
},
async expect => {
browser.test.log("Set window values, expect the new values.");
let windowId = windows[0];
browser.browserAction.setIcon({windowId, path: "window1.png"});
browser.browserAction.setPopup({windowId, popup: "window1.html"});
browser.browserAction.setTitle({windowId, title: "window1"});
browser.browserAction.setBadgeText({windowId, text: "w1"});
browser.browserAction.setBadgeBackgroundColor({windowId, color: "#111"});
+ browser.browserAction.setBadgeTextColor({windowId, color: "#999"});
expect(null, details[1], null, details[0]);
},
async expect => {
browser.test.log("Create a new tab, expect window values.");
let tab = await browser.tabs.create({active: true});
tabs.push(tab.id);
expect(null, details[1], null, details[0]);
},
@@ -651,16 +694,17 @@ add_task(async function testMultipleWind
async expect => {
browser.test.log("Set window values, expect the new values.");
let windowId = windows[1];
browser.browserAction.setIcon({windowId, path: "window2.png"});
browser.browserAction.setPopup({windowId, popup: "window2.html"});
browser.browserAction.setTitle({windowId, title: "window2"});
browser.browserAction.setBadgeText({windowId, text: "w2"});
browser.browserAction.setBadgeBackgroundColor({windowId, color: "#222"});
+ browser.browserAction.setBadgeTextColor({windowId, color: "#888"});
expect(null, details[2], null, details[0]);
},
async expect => {
browser.test.log("Move tab from old window to the new one. Tab-specific data"
+ " is preserved but inheritance is from the new window");
await browser.tabs.move(tabs[1], {windowId: windows[1], index: -1});
await browser.tabs.update(tabs[1], {active: true});
expect(details[3], details[2], null, details[0]);
@@ -685,16 +729,17 @@ add_task(async function testMultipleWind
browser.test.log("Assert failures for bad parameters. Expect no change");
let calls = {
setIcon: {path: "default.png"},
setPopup: {popup: "default.html"},
setTitle: {title: "Default Title"},
setBadgeText: {text: ""},
setBadgeBackgroundColor: {color: [0xd9, 0x00, 0x00, 0xFF]},
+ setBadgeTextColor: {color: [0xff, 0xff, 0xff, 0xff]},
getPopup: {},
getTitle: {},
getBadgeText: {},
getBadgeBackgroundColor: {},
};
for (let [method, arg] of Object.entries(calls)) {
browser.test.assertThrows(
() => browser.browserAction[method]({...arg, windowId: -3}),