Bug 1279717 - introduce a Color.jsm module that implements common color math operations in a single place. r?jaws
MozReview-Commit-ID: LvDoQgUcbqh
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -39,16 +39,18 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
"resource:///modules/AboutHome.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log",
"resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Color",
+ "resource://gre/modules/Color.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
"@mozilla.org/browser/favicon-service;1",
"mozIAsyncFavicons");
XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
"@mozilla.org/network/dns-service;1",
"nsIDNSService");
XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
@@ -1005,33 +1007,20 @@ var gBrowserInit = {
// Misc. inits.
TabletModeUpdater.init();
CombinedStopReload.init();
gPrivateBrowsingUI.init();
if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
window.matchMedia("(-moz-windows-default-theme)").matches) {
- let windowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
- .Windows8WindowFrameColor.get();
-
- // Formula from W3C's WCAG 2.0 spec's color ratio and relative luminance,
- // section 1.3.4, http://www.w3.org/TR/WCAG20/ .
- windowFrameColor = windowFrameColor.map((color) => {
- if (color <= 10) {
- return color / 255 / 12.92;
- }
- return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
- });
- let backgroundLuminance = windowFrameColor[0] * 0.2126 +
- windowFrameColor[1] * 0.7152 +
- windowFrameColor[2] * 0.0722;
- let foregroundLuminance = 0; // Default to black for foreground text.
- let contrastRatio = (backgroundLuminance + 0.05) / (foregroundLuminance + 0.05);
- if (contrastRatio < 3) {
+ let windowFrameColor = new Color(...Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
+ .Windows8WindowFrameColor.get());
+ // Default to black for foreground text.
+ if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
document.documentElement.setAttribute("darkwindowframe", "true");
}
}
ToolbarIconColor.init();
// Wait until chrome is painted before executing code not critical to making the window visible
this._boundDelayedStartup = this._delayedStartup.bind(this);
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/Color.jsm
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Color"];
+
+/**
+ * Color class, which describes a color.
+ * In the future, this object may be extended to allow for conversions between
+ * different color formats and notations, support transparency.
+ *
+ * @param {Number} r Red color component
+ * @param {Number} g Green color component
+ * @param {Number} b Blue color component
+ */
+function Color(r, g, b) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+}
+
+Color.prototype = {
+ /**
+ * Formula from W3C's WCAG 2.0 spec's relative luminance, section 1.4.1,
+ * http://www.w3.org/TR/WCAG20/.
+ *
+ * @return {Number} Relative luminance, represented as number between 0 and 1.
+ */
+ get relativeLuminance() {
+ let colorArr = [this.r, this.b, this.g].map(color => {
+ color = parseInt(color, 10);
+ if (color <= 10)
+ return color / 255 / 12.92;
+ return Math.pow(((color / 255) + 0.055) / 1.055, 2.4);
+ });
+ return colorArr[0] * 0.2126 +
+ colorArr[1] * 0.7152 +
+ colorArr[2] * 0.0722;
+ },
+
+ /**
+ * @return {Boolean} TRUE if the color value can be considered bright.
+ */
+ get isBright() {
+ return this.relativeLuminance > 0.7;
+ },
+
+ /**
+ * Get the contrast ratio between the current color and a second other color.
+ * A common use case is to express the difference between a foreground and a
+ * background color in numbers.
+ * Formula from W3C's WCAG 2.0 spec's contrast ratio, section 1.4.1,
+ * http://www.w3.org/TR/WCAG20/.
+ *
+ * @param {Color} otherColor Color instance to calculate the contrast with
+ * @return {Number} Contrast ratios can range from 1 to 21, commonly written
+ * as 1:1 to 21:1.
+ */
+ contrastRatio(otherColor) {
+ if (!(otherColor instanceof Color))
+ throw new TypeError("The first argument should be an instance of Color");
+
+ let luminance = this.relativeLuminance;
+ let otherLuminance = otherColor.relativeLuminance;
+ return (Math.max(luminance, otherLuminance) + 0.05) /
+ (Math.min(luminance, otherLuminance) + 0.05);
+ },
+
+ /**
+ * Biased method to check if the contrast ratio between two colors is high
+ * enough to be discernable.
+ *
+ * @param {Color} otherColor Color instance to calculate the contrast with
+ * @return {Boolean}
+ */
+ isContrastRatioAcceptable(otherColor) {
+ return this.contrastRatio(otherColor) > 3;
+ }
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -27,16 +27,17 @@ EXTRA_JS_MODULES += [
'AsyncPrefs.jsm',
'Battery.jsm',
'BinarySearch.jsm',
'BrowserUtils.jsm',
'CanonicalJSON.jsm',
'CertUtils.jsm',
'CharsetMenu.jsm',
'ClientID.jsm',
+ 'Color.jsm',
'Console.jsm',
'debug.js',
'DeferredTask.jsm',
'Deprecated.jsm',
'FileUtils.jsm',
'Finder.jsm',
'FinderHighlighter.jsm',
'Geometry.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_Color.js
@@ -0,0 +1,53 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/Color.jsm");
+
+function run_test() {
+ testRelativeLuminance();
+ testIsBright();
+ testContrastRatio();
+ testIsContrastRatioAcceptable();
+}
+
+function testRelativeLuminance() {
+ let c = new Color(0, 0, 0);
+ Assert.equal(c.relativeLuminance, 0, "Black is not illuminating");
+
+ c = new Color(255, 255, 255);
+ Assert.equal(c.relativeLuminance, 1, "White is quite the luminant one");
+
+ c = new Color(142, 42, 142);
+ Assert.equal(c.relativeLuminance, 0.25263952353998204,
+ "This purple is not that luminant");
+}
+
+function testIsBright() {
+ let c = new Color(0, 0, 0);
+ Assert.equal(c.isBright, 0, "Black is bright");
+
+ c = new Color(255, 255, 255);
+ Assert.equal(c.isBright, 1, "White is bright");
+}
+
+function testContrastRatio() {
+ let c = new Color(0, 0, 0);
+ let c2 = new Color(255, 255, 255);
+ Assert.equal(c.contrastRatio(c2), 21, "Contrast between black and white is max");
+ Assert.equal(c.contrastRatio(c), 1, "Contrast between equals is min");
+
+ let c3 = new Color(142, 42, 142);
+ Assert.equal(c.contrastRatio(c3), 6.05279047079964, "Contrast between black and purple");
+ Assert.equal(c2.contrastRatio(c3), 3.469474137806338, "Contrast between white and purple");
+}
+
+function testIsContrastRatioAcceptable() {
+ // Let's assert what browser.js is doing for window frames.
+ let c = new Color(...[55, 156, 152]);
+ let c2 = new Color(0, 0, 0);
+ Assert.equal(c.r, 55, "Reds should match");
+ Assert.equal(c.g, 156, "Greens should match");
+ Assert.equal(c.b, 152, "Blues should match");
+ Assert.ok(c.isContrastRatioAcceptable(c2), "The blue is high contrast enough");
+ c = new Color(...[35, 65, 100]);
+ Assert.ok(!c.isContrastRatioAcceptable(c2), "The blue is not high contrast enough");
+}
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -9,16 +9,17 @@ support-files =
chromeappsstore.sqlite
zips/zen.zip
[test_BinarySearch.js]
skip-if = toolkit == 'android'
[test_CanonicalJSON.js]
[test_client_id.js]
skip-if = toolkit == 'android'
+[test_Color.js]
[test_DeferredTask.js]
skip-if = toolkit == 'android'
[test_FileUtils.js]
skip-if = toolkit == 'android'
[test_GMPInstallManager.js]
skip-if = toolkit == 'android'
[test_Http.js]
skip-if = toolkit == 'android'