Commit 45dbd64d authored by Oriol Brufau's avatar Oriol Brufau
Browse files

Bug 1474110 - Choose browserAction text color among white and black,...

Bug 1474110 - Choose browserAction text color among white and black, maximizing contrast r=mixedpuppy

MozReview-Commit-ID: CykmiUc0BsO

--HG--
extra : rebase_source : 23f74ba575d7f6c2ed6e379ea762080331540399
parent 061b467f
Loading
Loading
Loading
Loading
+70 −23
Original line number Diff line number Diff line
@@ -72,7 +72,8 @@ this.browserAction = class extends ExtensionAPI {
      enabled: true,
      title: options.default_title || extension.name,
      badgeText: "",
      badgeBackgroundColor: null,
      badgeBackgroundColor: [0xd9, 0, 0, 255],
      badgeDefaultColor: [255, 255, 255, 255],
      badgeTextColor: null,
      popup: options.default_popup || "",
      area: browserAreas[options.default_area || "navbar"],
@@ -442,21 +443,11 @@ this.browserAction = class extends ExtensionAPI {
        node.setAttribute("disabled", "true");
      }

      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 serializeColor = ([r, g, b, a]) => `rgba(${r}, ${g}, ${b}, ${a / 255})`;
      node.setAttribute("badgeStyle", [
        `background-color: ${serializeColor(tabData.badgeBackgroundColor)}`,
        `color: ${serializeColor(this.getTextColor(tabData))}`,
      ].join("; "));

      let style = this.iconData.get(tabData.icon);
      node.setAttribute("style", style);
@@ -569,10 +560,12 @@ this.browserAction = class extends ExtensionAPI {
   * @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"
   *        String property to set. Should should be one of "icon", "title", "badgeText",
   *        "popup", "badgeBackgroundColor", "badgeTextColor" or "enabled".
   * @param {string} value
   *        Value for prop.
   * @returns {Object}
   *        The object to which the property has been set.
   */
  setProperty(details, prop, value) {
    let {target, values} = this.getContextData(details);
@@ -583,6 +576,54 @@ this.browserAction = class extends ExtensionAPI {
    }

    this.updateOnChange(target);
    return values;
  }

  /**
   * Determines the text badge color to be used in a tab, window, or globally.
   *
   * @param {Object} values
   *        The values associated with the tab or window, or global values.
   * @returns {ColorArray}
   */
  getTextColor(values) {
    // If a text color has been explicitly provided, use it.
    let {badgeTextColor} = values;
    if (badgeTextColor) {
      return badgeTextColor;
    }

    // Otherwise, check if the default color to be used has been cached previously.
    let {badgeDefaultColor} = values;
    if (badgeDefaultColor) {
      return badgeDefaultColor;
    }

    // Choose a color among white and black, maximizing contrast with background
    // according to https://www.w3.org/TR/WCAG20-TECHS/G18.html#G18-procedure
    let [r, g, b] = values.badgeBackgroundColor.slice(0, 3).map(function(channel) {
      channel /= 255;
      if (channel <= 0.03928) {
        return channel / 12.92;
      }
      return ((channel + 0.055) / 1.055) ** 2.4;
    });
    let lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    // The luminance is 0 for black, 1 for white, and `lum` for the background color.
    // Since `0 <= lum`, the contrast ratio for black is `c0 = (lum + 0.05) / 0.05`.
    // Since `lum <= 1`, the contrast ratio for white is `c1 = 1.05 / (lum + 0.05)`.
    // We want to maximize contrast, so black is chosen if `c1 < c0`, that is, if
    // `1.05 * 0.05 < (L + 0.05) ** 2`. Otherwise white is chosen.
    let channel = 1.05 * 0.05 < (lum + 0.05) ** 2 ? 0 : 255;
    let result = [channel, channel, channel, 255];

    // Cache the result as high as possible in the prototype chain
    while (!Object.getOwnPropertyDescriptor(values, "badgeDefaultColor")) {
      values = Object.getPrototypeOf(values);
    }
    values.badgeDefaultColor = result;
    return result;
  }

  /**
@@ -692,12 +733,18 @@ this.browserAction = class extends ExtensionAPI {

        setBadgeBackgroundColor: function(details) {
          let color = parseColor(details.color, "background");
          browserAction.setProperty(details, "badgeBackgroundColor", color);
          let values = browserAction.setProperty(details, "badgeBackgroundColor", color);
          if (color === null) {
            // Let the default text color inherit after removing background color
            delete values.badgeDefaultColor;
          } else {
            // Invalidate a cached default color calculated with the old background
            values.badgeDefaultColor = null;
          }
        },

        getBadgeBackgroundColor: function(details, callback) {
          let color = browserAction.getProperty(details, "badgeBackgroundColor");
          return color || [0xd9, 0, 0, 255];
          return browserAction.getProperty(details, "badgeBackgroundColor");
        },

        setBadgeTextColor: function(details) {
@@ -705,9 +752,9 @@ this.browserAction = class extends ExtensionAPI {
          browserAction.setProperty(details, "badgeTextColor", color);
        },

        getBadgeTextColor: function(details, callback) {
          let color = browserAction.getProperty(details, "badgeTextColor");
          return color || [255, 255, 255, 255];
        getBadgeTextColor: function(details) {
          let {values} = browserAction.getContextData(details);
          return browserAction.getTextColor(values);
        },

        openPopup: function() {
+83 −0
Original line number Diff line number Diff line
@@ -760,6 +760,89 @@ add_task(async function testMultipleWindows() {
  });
});

add_task(async function testDefaultBadgeTextColor() {
  await runTests({
    manifest: {
      "browser_action": {
        "default_icon": "default.png",
        "default_popup": "default.html",
        "default_title": "Default Title",
      },
    },

    "files": {
      "default.png": imageBuffer,
      "window1.png": imageBuffer,
      "window2.png": imageBuffer,
    },

    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},
        {"badgeBackgroundColor": [0xff, 0xff, 0x00, 0xFF],
         "badgeTextColor": [0x00, 0x00, 0x00, 0xff]},
        {"badgeBackgroundColor": [0x00, 0x00, 0xff, 0xFF],
         "badgeTextColor": [0xff, 0xff, 0xff, 0xff]},
        {"badgeBackgroundColor": [0xff, 0xff, 0xff, 0x00],
         "badgeTextColor": [0x00, 0x00, 0x00, 0xff]},
        {"badgeBackgroundColor": [0x00, 0x00, 0xff, 0xFF],
         "badgeTextColor": [0xff, 0x00, 0xff, 0xff]},
        {"badgeBackgroundColor": [0xff, 0xff, 0xff, 0x00]},
        {"badgeBackgroundColor": [0x00, 0x00, 0x00, 0x00],
         "badgeTextColor": [0xff, 0xff, 0xff, 0xff]},
      ];

      return [
        async expect => {
          browser.test.log("Initial state, expect default properties.");
          expect(null, null, null, details[0]);
        },
        async expect => {
          browser.test.log("Set a global light bgcolor, expect black text.");
          browser.browserAction.setBadgeBackgroundColor({color: "#ff0"});
          expect(null, null, details[1], details[0]);
        },
        async expect => {
          browser.test.log("Set a window-specific dark bgcolor, expect white text.");
          let windowId = windows[0];
          browser.browserAction.setBadgeBackgroundColor({windowId, color: "#00f"});
          expect(null, details[2], details[1], details[0]);
        },
        async expect => {
          browser.test.log("Set a tab-specific transparent-white bgcolor, expect black text.");
          let tabId = tabs[0];
          browser.browserAction.setBadgeBackgroundColor({tabId, color: "#fff0"});
          expect(details[3], details[2], details[1], details[0]);
        },
        async expect => {
          browser.test.log("Set a window-specific text color, expect it in the tab.");
          let windowId = windows[0];
          browser.browserAction.setBadgeTextColor({windowId, color: "#f0f"});
          expect(details[5], details[4], details[1], details[0]);
        },
        async expect => {
          browser.test.log("Remove the window-specific text color, expect black again.");
          let windowId = windows[0];
          browser.browserAction.setBadgeTextColor({windowId, color: null});
          expect(details[3], details[2], details[1], details[0]);
        },
        async expect => {
          browser.test.log("Set a tab-specific transparent-black bgcolor, expect white text.");
          let tabId = tabs[0];
          browser.browserAction.setBadgeBackgroundColor({tabId, color: "#0000"});
          expect(details[6], details[2], details[1], details[0]);
        },
      ];
    },
  });
});

add_task(async function testNavigationClearsData() {
  let url = "http://example.com/";
  let default_title = "Default title";