Bug 1424620 - Add setBadgeTextColor and getBadgeTextColor methods to browserAction draft
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Thu, 05 Jul 2018 19:37:08 +0200
changeset 815411 89dbc2ab0d390df19ee836b85da9b58578e5a98b
parent 813360 7d20e7fae1039720f92db1a3a72bc2c7424b5f98
push id115513
push userbmo:oriol-bugzilla@hotmail.com
push dateSun, 08 Jul 2018 11:29:55 +0000
bugs1424620
milestone63.0a1
Bug 1424620 - Add setBadgeTextColor and getBadgeTextColor methods to browserAction MozReview-Commit-ID: ALYerCv3YJ7
browser/components/extensions/parent/ext-browserAction.js
browser/components/extensions/schemas/browser_action.json
browser/components/extensions/test/browser/browser_ext_browserAction_context.js
--- 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/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -90,16 +90,25 @@
       },
       {
         "id": "ImageDataType",
         "type": "object",
         "isInstanceOf": "ImageData",
         "additionalProperties": { "type": "any" },
         "postprocess": "convertImageDataToURL",
         "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
+      },
+      {
+        "id": "ColorValue",
+        "description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
+        "choices": [
+          {"type": "string"},
+          {"$ref": "ColorArray"},
+          {"type": "null"}
+        ]
       }
     ],
     "functions": [
       {
         "name": "setTitle",
         "type": "function",
         "description": "Sets the title of the browser action. This shows up in the tooltip.",
         "async": "callback",
@@ -301,38 +310,31 @@
         "description": "Sets the background color for the badge.",
         "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "$import": "Details",
             "properties": {
-              "color": {
-                "description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
-                "choices": [
-                  {"type": "string"},
-                  {"$ref": "ColorArray"},
-                  {"type": "null"}
-                ]
-              }
+              "color": { "$ref": "ColorValue" }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": []
           }
         ]
       },
       {
         "name": "getBadgeBackgroundColor",
         "type": "function",
-        "description": "Gets the background color of the browser action.",
+        "description": "Gets the background color of the browser action badge.",
         "async": "callback",
         "parameters": [
           {
             "name": "details",
             "$ref": "Details"
           },
           {
             "type": "function",
@@ -342,16 +344,44 @@
                 "name": "result",
                 "$ref": "ColorArray"
               }
             ]
           }
         ]
       },
       {
+        "name": "setBadgeTextColor",
+        "type": "function",
+        "description": "Sets the text color for the badge.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "$import": "Details",
+            "properties": {
+              "color": { "$ref": "ColorValue" }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getBadgeTextColor",
+        "type": "function",
+        "description": "Gets the text color of the browser action badge.",
+        "async": true,
+        "parameters": [
+          {
+            "name": "details",
+            "$ref": "Details"
+          }
+        ]
+      },
+      {
         "name": "enable",
         "type": "function",
         "description": "Enables the browser action for a tab. By default, browser actions are enabled.",
         "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "optional": true,
--- 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}),