From 88731e16c8639738a1375f8c2de1ec2a23fcb631 Mon Sep 17 00:00:00 2001 From: David Vieira Date: Sat, 2 May 2015 02:07:35 -0300 Subject: [PATCH 01/10] #149 Be able to evaluate page with iframes; disabling webSecurity in phantomJs -> so we can get the document root from frames. --- dist/js/axs_testing.js | 688 ++++++++++++++++++++++++++++++----------- src/js/AuditRule.js | 8 + tools/runner/audit.js | 3 + 3 files changed, 527 insertions(+), 172 deletions(-) diff --git a/dist/js/axs_testing.js b/dist/js/axs_testing.js index f0e6f4a3..ceefb47c 100644 --- a/dist/js/axs_testing.js +++ b/dist/js/axs_testing.js @@ -1,5 +1,5 @@ /* - * Copyright 2014 Google Inc. + * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/3d4893b4ecd0eb8f4765e04479213d04b240f3e0 + * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/f61fd0447d7ad90256597e6b1203e6495f430694 * * See project README for build steps. */ @@ -62,7 +62,7 @@ goog.setTestOnly = function(a) { goog.forwardDeclare = function(a) { }; COMPILED || (goog.isProvided_ = function(a) { - return!goog.implicitNamespaces_[a] && goog.isDefAndNotNull(goog.getObjectByName(a)); + return !goog.implicitNamespaces_[a] && goog.isDefAndNotNull(goog.getObjectByName(a)); }, goog.implicitNamespaces_ = {}); goog.getObjectByName = function(a, b) { for (var c = a.split("."), d = b || goog.global, e;e = c.shift();) { @@ -153,14 +153,14 @@ goog.DEPENDENCIES_ENABLED && (goog.included_ = {}, goog.dependencies_ = {pathToN var b = goog.global.document; if ("complete" == b.readyState) { if (/\bdeps.js$/.test(a)) { - return!1; + return !1; } throw Error('Cannot write "' + a + '" after document load'); } b.write(' + diff --git a/test/js/color-test.js b/test/js/color-test.js new file mode 100644 index 00000000..bcb5b444 --- /dev/null +++ b/test/js/color-test.js @@ -0,0 +1,55 @@ +// Copyright 2012 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module("Contrast Ratio", { + setup: function () { + var fixture = document.createElement('div'); + document.getElementById('qunit-fixture').appendChild(fixture); + this.fixture_ = fixture; + this.black_ = {"red": 0, "green": 0, "blue": 0, "alpha": 1}; + this.white_ = {"red": 255, "green": 255, "blue": 255, "alpha": 1}; + } +}); +test("Black and white.", function () { + equal(axs.color.calculateContrastRatio(this.white_, this.black_), 21); + equal(axs.color.calculateContrastRatio(this.black_, this.white_), 21); +}); +test("Same color === no contrast.", function () { + equal(axs.color.calculateContrastRatio(this.white_, this.white_), 1); + equal(axs.color.calculateContrastRatio(this.black_, this.black_), 1); +}); +test("Transparent foreground === no contrast.", function () { + equal(axs.color.calculateContrastRatio({"red": 0, "green": 0, "blue": 0, "alpha": 0}, this.white_), 1); +}); + + +module("parseColor"); +test("parses alpha values correctly", function() { + var colorString = 'rgba(255, 255, 255, .47)'; + var color = axs.color.parseColor(colorString); + equal(color.red, 255); + equal(color.blue, 255); + equal(color.green, 255); + equal(color.alpha, .47); +}); + +module("suggestColors"); +test("suggests correct grey values", function() { + var white = new axs.color.Color(255, 255, 255, 1) + var desiredContrastRatios = { AA: 4.5, AAA: 7.0 }; + var suggestions = axs.color.suggestColors(white, white, desiredContrastRatios); + deepEqual(suggestions, { AA: { bg: "#ffffff", contrast: "4.54", fg: "#767676" }, + AAA: { bg: "#ffffff", contrast: "7.00", fg: "#595959" } }); +}); + diff --git a/test/js/utils-test.js b/test/js/utils-test.js index 93c0a185..574dbff8 100644 --- a/test/js/utils-test.js +++ b/test/js/utils-test.js @@ -12,27 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -module("Contrast Ratio", { - setup: function () { - var fixture = document.createElement('div'); - document.getElementById('qunit-fixture').appendChild(fixture); - this.fixture_ = fixture; - this.black_ = {"red": 0, "green": 0, "blue": 0, "alpha": 1}; - this.white_ = {"red": 255, "green": 255, "blue": 255, "alpha": 1}; - } -}); -test("Black and white.", function () { - equal(axs.utils.calculateContrastRatio(this.white_, this.black_), 21); - equal(axs.utils.calculateContrastRatio(this.black_, this.white_), 21); -}); -test("Same color === no contrast.", function () { - equal(axs.utils.calculateContrastRatio(this.white_, this.white_), 1); - equal(axs.utils.calculateContrastRatio(this.black_, this.black_), 1); -}); -test("Transparent foreground === no contrast.", function () { - equal(axs.utils.calculateContrastRatio({"red": 0, "green": 0, "blue": 0, "alpha": 0}, this.white_), 1); -}); - module("Zero Area", { setup: function () { var fixture = document.createElement('div'); @@ -143,16 +122,6 @@ test("nth-of-type does not refer to a selector but a tagName", function() { 'selector "' + selector + '" does not match element'); }); -module("parseColor"); -test("parses alpha values correctly", function() { - var colorString = 'rgba(255, 255, 255, .47)'; - var color = axs.utils.parseColor(colorString); - equal(color.red, 255); - equal(color.blue, 255); - equal(color.green, 255); - equal(color.alpha, .47); -}); - module("getIdReferrers", { setup: function () { this.fixture_ = document.getElementById('qunit-fixture'); From 1b9bad3dc5dbca168e76417fdf2569e494b6154c Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Sun, 7 Jun 2015 21:04:59 -0700 Subject: [PATCH 07/10] New color suggestion algorithm. 1. Let L be the line in the coordinate space (Cb, Cr, luma) keeping (Cb, Cr) constant. 2. Let Corner be either the black or the white corner of the color cube, depending on whether the desired luma is lower (black) or higher (white) than the original luma. 3. Find the intersection between L and one of the three planes of the cube containing Corner, let this be point I. 4. If the luma at I is closer to the luma at Corner than the desired luma, then desired luma is on L point within the cube, so return the color with original Cb/Cr values and desired luma. 5. Otherwise, the desired luma is on L outside the cube, so find the point on the line (I, Corner) which has the desired luma value. --- Changelog.md | 2 + src/js/AuditRules.js | 5 +- src/js/Color.js | 238 +++++++++++++++++++++++++++++++++--------- test/index.html | 1 + test/js/color-test.js | 2 - 5 files changed, 197 insertions(+), 51 deletions(-) diff --git a/Changelog.md b/Changelog.md index b6c34920..d6af9944 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,8 @@ ### Enhancements: * Rework findTextAlternatives not to return non-exposed text alternatives. * Add Bower config (#157) +* Pull color code into separate file. +* Improve color suggestion algorithm. ### Bug fixes: * Check for any text alternatives when assessing unlabeled images (#154). diff --git a/src/js/AuditRules.js b/src/js/AuditRules.js index 4c8f904d..fe9d497c 100644 --- a/src/js/AuditRules.js +++ b/src/js/AuditRules.js @@ -20,6 +20,9 @@ goog.provide('axs.AuditRules'); var auditRulesByName = {}; var auditRulesByCode = {}; + /** @type {Object.} */ + axs.AuditRules.specs = {}; + /** * Instantiates and registers an audit rule. * If a conflicting rule is already registered then the new rule will not be added. @@ -27,7 +30,6 @@ goog.provide('axs.AuditRules'); * @throws {Error} If the rule duplicates properties that must be unique. */ axs.AuditRules.addRule = function(spec) { - // axs.AuditRule.specs[spec.name] = spec; // This would add backwards compatibility // create the auditRule before checking props as we can expect the constructor to perform the // first layer of sanity checking. var auditRule = new axs.AuditRule(spec); @@ -36,6 +38,7 @@ goog.provide('axs.AuditRules'); if (auditRule.name in auditRulesByName) throw new Error('Can not add audit rule with same name: "' + auditRule.name + '"'); auditRulesByName[auditRule.name] = auditRulesByCode[auditRule.code] = auditRule; + axs.AuditRules.specs[spec.name] = spec; }; /** diff --git a/src/js/Color.js b/src/js/Color.js index 6e91e55e..e7e77955 100644 --- a/src/js/Color.js +++ b/src/js/Color.js @@ -36,6 +36,54 @@ axs.color.Color = function(red, green, blue, alpha) { this.alpha = alpha; }; +/** + * @constructor + * See https://en.wikipedia.org/wiki/YCbCr for more information. + * @param {Array.} coords The YCbCr values as a 3 element array, in the order [luma, Cb, Cr]. + * All numbers are in the range [0, 1]. + */ +axs.color.YCbCr = function(coords) { + /** @type {number} */ + this.luma = this.z = coords[0]; + + /** @type {number} */ + this.Cb = this.x = coords[1]; + + /** @type {number} */ + this.Cr = this.y = coords[2]; +}; + +axs.color.YCbCr.prototype = { + /** + * @param {number} scalar + * @return {axs.color.YCbCr} This color multiplied by the given scalar + */ + multiply: function(scalar) { + var result = [ this.luma * scalar, this.Cb * scalar, this.Cr * scalar ]; + return new axs.color.YCbCr(result); + }, + + /** + * @param {axs.color.YCbCr} other + * @return {axs.color.YCbCr} This plus other + */ + add: function(other) { + var result = [ this.luma + other.luma, this.Cb + other.Cb, this.Cr + other.Cr ]; + return new axs.color.YCbCr(result); + }, + + /** + * @param {axs.color.YCbCr} other + * @return {axs.color.YCbCr} This minus other + */ + subtract: function(other) { + var result = [ this.luma - other.luma, this.Cb - other.Cb, this.Cr - other.Cr ]; + return new axs.color.YCbCr(result); + } + +}; + + /** * Calculate the contrast ratio between the two given colors. Returns the ratio * to 1, for example for two two colors with a contrast ratio of 21:1, this @@ -70,8 +118,8 @@ axs.color.calculateLuminance = function(color) { var b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055)/1.055), 2.4); return 0.2126 * r + 0.7152 * g + 0.0722 * b; */ - var ycc = axs.color.toYCC(color); - return ycc[0]; + var ycc = axs.color.toYCbCr(color); + return ycc.luma; }; /** @@ -155,27 +203,53 @@ axs.color.luminanceFromContrastRatio = function(luminance, contrast, higher) { }; /** - * Given a color in YCC format and a desired luminance, pick a new color with the desired luminance which is - * a translation towards black or white of the old color. - * @param {Array.} ycc A color in YCC as an array with three elements. - * @param {number} luminance The desired luminance - * @return {axs.color.Color} A new color in RGB. + * Given a color in YCbCr format and a desired luminance, pick a new color with the desired luminance which is + * as close as possible to the original color. + * @param {axs.color.YCbCr} ycc The original color in YCbCr form. + * @param {number} luma The desired luminance + * @return {!axs.color.Color} A new color in RGB. */ -axs.color.translateColor = function(ycc, luminance) { - var oldLuminance = ycc[0]; - if (oldLuminance > luminance) - var endpoint = 0; - else - var endpoint = 1; +axs.color.translateColor = function(ycc, luma) { + var endpoint = (luma > ycc.luma) ? axs.color.WHITE_YCC : axs.color.BLACK_YCC; + var cubeFaces = (endpoint == axs.color.WHITE_YCC) ? axs.color.YCC_CUBE_FACES_WHITE + : axs.color.YCC_CUBE_FACES_BLACK; + + var a = new axs.color.YCbCr([0, ycc.Cb, ycc.Cr]); + var b = new axs.color.YCbCr([1, ycc.Cb, ycc.Cr]); + var line = { a: a, b: b }; + + var intersection = null; + for (var i = 0; i < cubeFaces.length; i++) { + var cubeFace = cubeFaces[i]; + intersection = axs.color.findIntersection(line, cubeFace); + // If intersection within [0, 1] in Z axis, it is within the cube. + if (intersection.z >= 0 && intersection.z <= 1) + break; + } + if (!intersection) { + // Should never happen + throw "Couldn't find intersection with YCbCr color cube for Cb=" + ycc.Cb + ", Cr=" + ycc.Cr + "."; + } + if (intersection.x != ycc.x || intersection.y != ycc.y) { + // Should never happen + throw "Intersection has wrong Cb/Cr values."; + } + + // If intersection.luma is closer to endpoint than desired luma, then luma is inside cube + // and we can immediately return new value. + if (Math.abs(endpoint.luma - intersection.luma) < Math.abs(endpoint.luma - luma)) { + var translatedColor = [luma, ycc.Cb, ycc.Cr]; + return axs.color.fromYCbCrArray(translatedColor); + } - var d = luminance - oldLuminance; - var scale = 0; // d / (endpoint - oldLuminance); + // Otherwise, translate from intersection towards white/black such that luma is correct. + var dLuma = luma - intersection.luma; + var scale = dLuma / (endpoint.luma - intersection.luma); + var translatedColor = [ luma, + intersection.Cb - (intersection.Cb * scale), + intersection.Cr - (intersection.Cr * scale) ]; - /** @type {Array.} */ var translatedColor = [ luminance, - ycc[1] - ycc[1] * scale, - ycc[2] - ycc[2] * scale ]; - var rgb = axs.color.fromYCC(translatedColor); - return rgb; + return axs.color.fromYCbCrArray(translatedColor); }; /** @typedef {{fg: string, bg: string, contrast: string}} */ @@ -193,14 +267,14 @@ axs.color.suggestColors = function(bgColor, fgColor, desiredContrastRatios) { var fgLuminance = axs.color.calculateLuminance(fgColor); var fgLuminanceIsHigher = fgLuminance > bgLuminance; - var fgYCC = axs.color.toYCC(fgColor); - var bgYCC = axs.color.toYCC(bgColor); + var fgYCbCr = axs.color.toYCbCr(fgColor); + var bgYCbCr = axs.color.toYCbCr(bgColor); for (var desiredLabel in desiredContrastRatios) { var desiredContrast = desiredContrastRatios[desiredLabel]; var desiredFgLuminance = axs.color.luminanceFromContrastRatio(bgLuminance, desiredContrast + 0.02, fgLuminanceIsHigher); if (desiredFgLuminance <= 1 && desiredFgLuminance >= 0) { - var newFgColor = axs.color.translateColor(fgYCC, desiredFgLuminance); + var newFgColor = axs.color.translateColor(fgYCbCr, desiredFgLuminance); var newContrastRatio = axs.color.calculateContrastRatio(newFgColor, bgColor); var suggestedColors = {}; suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(newFgColor)); @@ -212,7 +286,7 @@ axs.color.suggestColors = function(bgColor, fgColor, desiredContrastRatios) { var desiredBgLuminance = axs.color.luminanceFromContrastRatio(fgLuminance, desiredContrast + 0.02, !fgLuminanceIsHigher); if (desiredBgLuminance <= 1 && desiredBgLuminance >= 0) { - var newBgColor = axs.color.translateColor(bgYCC, desiredBgLuminance); + var newBgColor = axs.color.translateColor(bgYCbCr, desiredBgLuminance); var newContrastRatio = axs.color.calculateContrastRatio(fgColor, newBgColor); var suggestedColors = {}; suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(newBgColor)); @@ -241,12 +315,12 @@ axs.color.flattenColors = function(fgColor, bgColor) { }; /** - * Multiply the given color vector by the given transformation matrix. - * @param {Array.>} matrix A 3x3 conversion matrix - * @param {Array.} vector A 3-element color vector - * @return {Array.} A 3-element color vector + * Multiply the given vector by the given matrix. + * @param {Array.>} matrix A 3x3 matrix + * @param {Array.} vector A 3-element vector + * @return {Array.} A 3-element vector */ -axs.color.convertColor = function(matrix, vector) { +axs.color.multiplyMatrixVector = function(matrix, vector) { var a = matrix[0][0]; var b = matrix[0][1]; var c = matrix[0][2]; @@ -269,10 +343,11 @@ axs.color.convertColor = function(matrix, vector) { }; /** - * Convert a given RGB color to YCC. + * Convert a given RGB color to YCbCr. * @param {axs.color.Color} color + * @return {axs.color.YCbCr} */ -axs.color.toYCC = function(color) { +axs.color.toYCbCr = function(color) { var rSRGB = color.red / 255; var gSRGB = color.green / 255; var bSRGB = color.blue / 255; @@ -281,16 +356,24 @@ axs.color.toYCC = function(color) { var g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow(((gSRGB + 0.055)/1.055), 2.4); var b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055)/1.055), 2.4); - return axs.color.convertColor(axs.color.YCC_MATRIX, [r, g, b]); + return new axs.color.YCbCr(axs.color.multiplyMatrixVector(axs.color.YCC_MATRIX, [r, g, b])); }; /** - * Convert a color from a YCC color (as a vector) to an RGB color - * @param {Array.} yccColor - * @return {axs.color.Color} + * @param {axs.color.YCbCr} ycc + * @return {!axs.color.Color} */ -axs.color.fromYCC = function(yccColor) { - var rgb = axs.color.convertColor(axs.color.INVERTED_YCC_MATRIX, yccColor); +axs.color.fromYCbCr = function(ycc) { + return axs.color.fromYCbCrArray([ycc.luma, ycc.Cb, ycc.Cr]); +}; + +/** + * Convert a color from a YCbCr color (as a vector) to an RGB color + * @param {Array.} yccArray + * @return {!axs.color.Color} + */ +axs.color.fromYCbCrArray = function(yccArray) { + var rgb = axs.color.multiplyMatrixVector(axs.color.INVERTED_YCC_MATRIX, yccArray); var r = rgb[0]; var g = rgb[1]; @@ -307,12 +390,12 @@ axs.color.fromYCC = function(yccColor) { }; /** - * Returns an RGB to YCC conversion matrix for the given kR, kB constants. + * Returns an RGB to YCbCr conversion matrix for the given kR, kB constants. * @param {number} kR * @param {number} kB * @return {Array.>} */ -axs.color.RGBToYCCMatrix = function(kR, kB) { +axs.color.RGBToYCbCrMatrix = function(kR, kB) { return [ [ kR, @@ -368,6 +451,34 @@ axs.color.invert3x3Matrix = function(matrix) { ], z); }; +/** @typedef {{ a: axs.color.YCbCr, b: axs.color.YCbCr }} */ +axs.color.Line; + +/** @typedef {{ p0: axs.color.YCbCr, p1: axs.color.YCbCr, p2: axs.color.YCbCr }} */ +axs.color.Plane; + +/** + * Find the intersection between a line and a plane using + * http://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection#Parametric_form + * @param {axs.color.Line} l + * @param {axs.color.Plane} p + * @return {axs.color.YCbCr} + */ +axs.color.findIntersection = function(l, p) { + var lhs = [ l.a.x - p.p0.x, l.a.y - p.p0.y, l.a.z - p.p0.z ]; + + var matrix = [ [ l.a.x - l.b.x, p.p1.x - p.p0.x, p.p2.x - p.p0.x ], + [ l.a.y - l.b.y, p.p1.y - p.p0.y, p.p2.y - p.p0.y ], + [ l.a.z - l.b.z, p.p1.z - p.p0.z, p.p2.z - p.p0.z ] ]; + var invertedMatrix = axs.color.invert3x3Matrix(matrix); + + var tuv = axs.color.multiplyMatrixVector(invertedMatrix, lhs); + var t = tuv[0]; + + var result = l.a.add(l.b.subtract(l.a).multiply(t)); + return result; +}; + /** * Multiply a matrix by a scalar. * @param {Array.>} matrix A 3x3 matrix. @@ -376,20 +487,51 @@ axs.color.invert3x3Matrix = function(matrix) { */ axs.color.scalarMultiplyMatrix = function(matrix, scalar) { var result = []; - result[0] = []; - result[1] = []; - result[2] = []; - for (var i = 0; i < 3; i++) { - for (var j = 0; j < 3; j++) { - result[i][j] = matrix[i][j] * scalar; - } - } + for (var i = 0; i < 3; i++) + result[i] = axs.color.scalarMultiplyVector(matrix[i], scalar); return result; }; +/** + * Multiply a vector by a scalar. + * @param {Array.} vector + * @param {number} scalar + * @return {Array.} vector + */ +axs.color.scalarMultiplyVector = function(vector, scalar) { + var result = [] + for (var i = 0; i < vector.length; i++) + result[i] = vector[i] * scalar; + return result; +}; + axs.color.kR = 0.2126; axs.color.kB = 0.0722; -axs.color.YCC_MATRIX = axs.color.RGBToYCCMatrix(axs.color.kR, axs.color.kB); +axs.color.YCC_MATRIX = axs.color.RGBToYCbCrMatrix(axs.color.kR, axs.color.kB); axs.color.INVERTED_YCC_MATRIX = axs.color.invert3x3Matrix(axs.color.YCC_MATRIX); + +axs.color.BLACK = new axs.color.Color(0, 0, 0, 1.0); +axs.color.BLACK_YCC = axs.color.toYCbCr(axs.color.BLACK); +axs.color.WHITE = new axs.color.Color(255, 255, 255, 1.0); +axs.color.WHITE_YCC = axs.color.toYCbCr(axs.color.WHITE); +axs.color.RED = new axs.color.Color(255, 0, 0, 1.0); +axs.color.RED_YCC = axs.color.toYCbCr(axs.color.RED); +axs.color.GREEN = new axs.color.Color(0, 255, 0, 1.0); +axs.color.GREEN_YCC = axs.color.toYCbCr(axs.color.GREEN); +axs.color.BLUE = new axs.color.Color(0, 0, 255, 1.0); +axs.color.BLUE_YCC = axs.color.toYCbCr(axs.color.BLUE); +axs.color.CYAN = new axs.color.Color(0, 255, 255, 1.0); +axs.color.CYAN_YCC = axs.color.toYCbCr(axs.color.CYAN); +axs.color.MAGENTA = new axs.color.Color(255, 0, 255, 1.0); +axs.color.MAGENTA_YCC = axs.color.toYCbCr(axs.color.MAGENTA); +axs.color.YELLOW = new axs.color.Color(255, 255, 0, 1.0); +axs.color.YELLOW_YCC = axs.color.toYCbCr(axs.color.YELLOW); + +axs.color.YCC_CUBE_FACES_BLACK = [ { p0: axs.color.BLACK_YCC, p1: axs.color.RED_YCC, p2: axs.color.GREEN_YCC }, + { p0: axs.color.BLACK_YCC, p1: axs.color.GREEN_YCC, p2: axs.color.BLUE_YCC }, + { p0: axs.color.BLACK_YCC, p1: axs.color.BLUE_YCC, p2: axs.color.RED_YCC } ]; +axs.color.YCC_CUBE_FACES_WHITE = [ { p0: axs.color.WHITE_YCC, p1: axs.color.CYAN_YCC, p2: axs.color.MAGENTA_YCC }, + { p0: axs.color.WHITE_YCC, p1: axs.color.MAGENTA_YCC, p2: axs.color.YELLOW_YCC }, + { p0: axs.color.WHITE_YCC, p1: axs.color.YELLOW_YCC, p2: axs.color.CYAN_YCC } ]; diff --git a/test/index.html b/test/index.html index ad5bfa4b..b16fc580 100644 --- a/test/index.html +++ b/test/index.html @@ -47,6 +47,7 @@ + diff --git a/test/js/color-test.js b/test/js/color-test.js index bcb5b444..72f897df 100644 --- a/test/js/color-test.js +++ b/test/js/color-test.js @@ -33,7 +33,6 @@ test("Transparent foreground === no contrast.", function () { equal(axs.color.calculateContrastRatio({"red": 0, "green": 0, "blue": 0, "alpha": 0}, this.white_), 1); }); - module("parseColor"); test("parses alpha values correctly", function() { var colorString = 'rgba(255, 255, 255, .47)'; @@ -52,4 +51,3 @@ test("suggests correct grey values", function() { deepEqual(suggestions, { AA: { bg: "#ffffff", contrast: "4.54", fg: "#767676" }, AAA: { bg: "#ffffff", contrast: "7.00", fg: "#595959" } }); }); - From e569020bedec1451312c7f041c529944ade8c9ac Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Tue, 23 Jun 2015 14:50:59 +0100 Subject: [PATCH 08/10] Add Color.js to Gruntfile --- Gruntfile.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Gruntfile.js b/Gruntfile.js index bb877489..c64db54a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -18,6 +18,7 @@ module.exports = function(grunt) { './src/js/axs.js', './src/js/BrowserUtils.js', './src/js/Constants.js', + './src/js/Color.js', './src/js/AccessibilityUtils.js', './src/js/Properties.js', './src/js/AuditRule.js', From 407f318f682c802645c3ef867ed97854de4b655b Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Fri, 10 Jul 2015 13:10:33 -0700 Subject: [PATCH 09/10] Bring Changelog up to date --- Changelog.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 7cb921e7..f3b2072b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,8 @@ +# Enhancements: +* Pull color code into separate file. +* Improve color suggestion algorithm. +* Descend into iframes when collecting matching elements. + ## 2.7.1 - 2015-06-30 ## 2.7.1-rc.1 - 2015-06-23 @@ -11,8 +16,6 @@ ### Enhancements: * Rework findTextAlternatives not to return non-exposed text alternatives. * Add Bower config (#157) -* Pull color code into separate file. -* Improve color suggestion algorithm. ### Bug fixes: * Check for any text alternatives when assessing unlabeled images (#154). From 48b283ed84ab8544095b31c4b2052d523f9ffd8a Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Fri, 10 Jul 2015 13:20:59 -0700 Subject: [PATCH 10/10] Release v2.8.0-rc.0 --- Changelog.md | 2 + bower.json | 2 +- dist/js/axs_testing.js | 348 ++++++++++++++++++++++------------------- package.json | 2 +- 4 files changed, 194 insertions(+), 160 deletions(-) diff --git a/Changelog.md b/Changelog.md index f3b2072b..94712386 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,5 @@ +## 2.8.0-rc.0 - 2015-07-10 + # Enhancements: * Pull color code into separate file. * Improve color suggestion algorithm. diff --git a/bower.json b/bower.json index 603827af..e14e95cc 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "accessibility-developer-tools", - "version": "2.7.1", + "version": "2.8.0-rc.0", "homepage": "https://github.com/GoogleChrome/accessibility-developer-tools", "authors": [ "Google" diff --git a/dist/js/axs_testing.js b/dist/js/axs_testing.js index 371db550..fb1671e2 100644 --- a/dist/js/axs_testing.js +++ b/dist/js/axs_testing.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/ef4fec4549616cd69a0f0f732d43c838d8ab17eb + * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/407f318f682c802645c3ef867ed97854de4b655b * * See project README for build steps. */ @@ -523,25 +523,166 @@ reserved:!0}], MATH:[{role:"", allowed:["presentation"]}], MENU:[{role:"toolbar" allowed:["application", "document", "img", "presentation"]}], OL:[{role:"list", allowed:"directory group listbox menu menubar tablist toolbar tree presentation".split(" ")}], OPTGROUP:[{role:"", allowed:["presentation"]}], OPTION:[{role:"option"}], OUTPUT:[{role:"status", allowed:["*"]}], PARAM:[{role:"", reserved:!0}], PICTURE:[{role:"", reserved:!0}], PROGRESS:[{role:"progressbar", allowed:["presentation"]}], SCRIPT:[{role:"", reserved:!0}], SECTION:[{role:"region", allowed:"alert alertdialog application contentinfo dialog document log marquee search status presentation".split(" ")}], SELECT:[{role:"listbox"}], SOURCE:[{role:"", reserved:!0}], SPAN:[{role:"", allowed:["*"]}], STYLE:[{role:"", reserved:!0}], SVG:[{role:"", allowed:["application", "document", "img", "presentation"]}], SUMMARY:[{role:"", allowed:["presentation"]}], TABLE:[{role:"", allowed:["*"]}], TEMPLATE:[{role:"", reserved:!0}], TEXTAREA:[{role:"textbox"}], TBODY:[{role:"rowgroup", allowed:["*"]}], THEAD:[{role:"rowgroup", allowed:["*"]}], TFOOT:[{role:"rowgroup", allowed:["*"]}], TITLE:[{role:"", reserved:!0}], TD:[{role:"", allowed:["*"]}], TH:[{role:"", allowed:["*"]}], TR:[{role:"", allowed:["*"]}], TRACK:[{role:"", reserved:!0}], UL:[{role:"list", allowed:"directory group listbox menu menubar tablist toolbar tree presentation".split(" ")}], VIDEO:[{role:"", allowed:["application", "presentation"]}]}; -axs.utils = {}; -axs.utils.FOCUSABLE_ELEMENTS_SELECTOR = "input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),a[href],iframe,[tabindex]"; -axs.utils.Color = function(a, b, c, d) { +axs.color = {}; +axs.color.Color = function(a, b, c, d) { this.red = a; this.green = b; this.blue = c; this.alpha = d; }; -axs.utils.calculateContrastRatio = function(a, b) { - if (!a || !b) { - return null; - } - 1 > a.alpha && (a = axs.utils.flattenColors(a, b)); - var c = axs.utils.calculateLuminance(a), d = axs.utils.calculateLuminance(b); +axs.color.YCbCr = function(a) { + this.luma = this.z = a[0]; + this.Cb = this.x = a[1]; + this.Cr = this.y = a[2]; +}; +axs.color.YCbCr.prototype = {multiply:function(a) { + return new axs.color.YCbCr([this.luma * a, this.Cb * a, this.Cr * a]); +}, add:function(a) { + return new axs.color.YCbCr([this.luma + a.luma, this.Cb + a.Cb, this.Cr + a.Cr]); +}, subtract:function(a) { + return new axs.color.YCbCr([this.luma - a.luma, this.Cb - a.Cb, this.Cr - a.Cr]); +}}; +axs.color.calculateContrastRatio = function(a, b) { + 1 > a.alpha && (a = axs.color.flattenColors(a, b)); + var c = axs.color.calculateLuminance(a), d = axs.color.calculateLuminance(b); return (Math.max(c, d) + .05) / (Math.min(c, d) + .05); }; -axs.utils.luminanceRatio = function(a, b) { +axs.color.calculateLuminance = function(a) { + return axs.color.toYCbCr(a).luma; +}; +axs.color.luminanceRatio = function(a, b) { return (Math.max(a, b) + .05) / (Math.min(a, b) + .05); }; +axs.color.parseColor = function(a) { + var b = a.match(/^rgb\((\d+), (\d+), (\d+)\)$/); + if (b) { + a = parseInt(b[1], 10); + var c = parseInt(b[2], 10), d = parseInt(b[3], 10); + return new axs.color.Color(a, c, d, 1); + } + return (b = a.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/)) ? (a = parseInt(b[1], 10), c = parseInt(b[2], 10), d = parseInt(b[3], 10), b = parseFloat(b[4]), new axs.color.Color(a, c, d, b)) : null; +}; +axs.color.colorChannelToString = function(a) { + a = Math.round(a); + return 15 >= a ? "0" + a.toString(16) : a.toString(16); +}; +axs.color.colorToString = function(a) { + return 1 == a.alpha ? "#" + axs.color.colorChannelToString(a.red) + axs.color.colorChannelToString(a.green) + axs.color.colorChannelToString(a.blue) : "rgba(" + [a.red, a.green, a.blue, a.alpha].join() + ")"; +}; +axs.color.luminanceFromContrastRatio = function(a, b, c) { + return c ? (a + .05) * b - .05 : (a + .05) / b - .05; +}; +axs.color.translateColor = function(a, b) { + for (var c = b > a.luma ? axs.color.WHITE_YCC : axs.color.BLACK_YCC, d = c == axs.color.WHITE_YCC ? axs.color.YCC_CUBE_FACES_WHITE : axs.color.YCC_CUBE_FACES_BLACK, e = new axs.color.YCbCr([0, a.Cb, a.Cr]), f = new axs.color.YCbCr([1, a.Cb, a.Cr]), f = {a:e, b:f}, e = null, g = 0;g < d.length && !(e = axs.color.findIntersection(f, d[g]), 0 <= e.z && 1 >= e.z);g++) { + } + if (!e) { + throw "Couldn't find intersection with YCbCr color cube for Cb=" + a.Cb + ", Cr=" + a.Cr + "."; + } + if (e.x != a.x || e.y != a.y) { + throw "Intersection has wrong Cb/Cr values."; + } + if (Math.abs(c.luma - e.luma) < Math.abs(c.luma - b)) { + return c = [b, a.Cb, a.Cr], axs.color.fromYCbCrArray(c); + } + c = (b - e.luma) / (c.luma - e.luma); + c = [b, e.Cb - e.Cb * c, e.Cr - e.Cr * c]; + return axs.color.fromYCbCrArray(c); +}; +axs.color.suggestColors = function(a, b, c) { + var d = {}, e = axs.color.calculateLuminance(a), f = axs.color.calculateLuminance(b), g = f > e, h = axs.color.toYCbCr(b), k = axs.color.toYCbCr(a), m; + for (m in c) { + var l = c[m], n = axs.color.luminanceFromContrastRatio(e, l + .02, g); + if (1 >= n && 0 <= n) { + var p = axs.color.translateColor(h, n), l = axs.color.calculateContrastRatio(p, a), n = {}; + n.fg = axs.color.colorToString(p); + n.bg = axs.color.colorToString(a); + n.contrast = l.toFixed(2); + d[m] = n; + } else { + l = axs.color.luminanceFromContrastRatio(f, l + .02, !g), 1 >= l && 0 <= l && (p = axs.color.translateColor(k, l), l = axs.color.calculateContrastRatio(b, p), n = {}, n.bg = axs.color.colorToString(p), n.fg = axs.color.colorToString(b), n.contrast = l.toFixed(2), d[m] = n); + } + } + return d; +}; +axs.color.flattenColors = function(a, b) { + var c = a.alpha; + return new axs.color.Color((1 - c) * b.red + c * a.red, (1 - c) * b.green + c * a.green, (1 - c) * b.blue + c * a.blue, a.alpha + b.alpha * (1 - a.alpha)); +}; +axs.color.multiplyMatrixVector = function(a, b) { + var c = b[0], d = b[1], e = b[2]; + return [a[0][0] * c + a[0][1] * d + a[0][2] * e, a[1][0] * c + a[1][1] * d + a[1][2] * e, a[2][0] * c + a[2][1] * d + a[2][2] * e]; +}; +axs.color.toYCbCr = function(a) { + var b = a.red / 255, c = a.green / 255; + a = a.blue / 255; + b = .03928 >= b ? b / 12.92 : Math.pow((b + .055) / 1.055, 2.4); + c = .03928 >= c ? c / 12.92 : Math.pow((c + .055) / 1.055, 2.4); + a = .03928 >= a ? a / 12.92 : Math.pow((a + .055) / 1.055, 2.4); + return new axs.color.YCbCr(axs.color.multiplyMatrixVector(axs.color.YCC_MATRIX, [b, c, a])); +}; +axs.color.fromYCbCr = function(a) { + return axs.color.fromYCbCrArray([a.luma, a.Cb, a.Cr]); +}; +axs.color.fromYCbCrArray = function(a) { + var b = axs.color.multiplyMatrixVector(axs.color.INVERTED_YCC_MATRIX, a), c = b[0]; + a = b[1]; + b = b[2]; + c = .00303949 >= c ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - .055; + a = .00303949 >= a ? 12.92 * a : 1.055 * Math.pow(a, 1 / 2.4) - .055; + b = .00303949 >= b ? 12.92 * b : 1.055 * Math.pow(b, 1 / 2.4) - .055; + c = Math.min(Math.max(Math.round(255 * c), 0), 255); + a = Math.min(Math.max(Math.round(255 * a), 0), 255); + b = Math.min(Math.max(Math.round(255 * b), 0), 255); + return new axs.color.Color(c, a, b, 1); +}; +axs.color.RGBToYCbCrMatrix = function(a, b) { + return [[a, 1 - a - b, b], [-a / (2 - 2 * b), (a + b - 1) / (2 - 2 * b), (1 - b) / (2 - 2 * b)], [(1 - a) / (2 - 2 * a), (a + b - 1) / (2 - 2 * a), -b / (2 - 2 * a)]]; +}; +axs.color.invert3x3Matrix = function(a) { + var b = a[0][0], c = a[0][1], d = a[0][2], e = a[1][0], f = a[1][1], g = a[1][2], h = a[2][0], k = a[2][1]; + a = a[2][2]; + return axs.color.scalarMultiplyMatrix([[f * a - g * k, d * k - c * a, c * g - d * f], [g * h - e * a, b * a - d * h, d * e - b * g], [e * k - f * h, h * c - b * k, b * f - c * e]], 1 / (b * (f * a - g * k) - c * (a * e - g * h) + d * (e * k - f * h))); +}; +axs.color.findIntersection = function(a, b) { + var c = [a.a.x - b.p0.x, a.a.y - b.p0.y, a.a.z - b.p0.z], d = axs.color.invert3x3Matrix([[a.a.x - a.b.x, b.p1.x - b.p0.x, b.p2.x - b.p0.x], [a.a.y - a.b.y, b.p1.y - b.p0.y, b.p2.y - b.p0.y], [a.a.z - a.b.z, b.p1.z - b.p0.z, b.p2.z - b.p0.z]]), c = axs.color.multiplyMatrixVector(d, c)[0]; + return a.a.add(a.b.subtract(a.a).multiply(c)); +}; +axs.color.scalarMultiplyMatrix = function(a, b) { + for (var c = [], d = 0;3 > d;d++) { + c[d] = axs.color.scalarMultiplyVector(a[d], b); + } + return c; +}; +axs.color.scalarMultiplyVector = function(a, b) { + for (var c = [], d = 0;d < a.length;d++) { + c[d] = a[d] * b; + } + return c; +}; +axs.color.kR = .2126; +axs.color.kB = .0722; +axs.color.YCC_MATRIX = axs.color.RGBToYCbCrMatrix(axs.color.kR, axs.color.kB); +axs.color.INVERTED_YCC_MATRIX = axs.color.invert3x3Matrix(axs.color.YCC_MATRIX); +axs.color.BLACK = new axs.color.Color(0, 0, 0, 1); +axs.color.BLACK_YCC = axs.color.toYCbCr(axs.color.BLACK); +axs.color.WHITE = new axs.color.Color(255, 255, 255, 1); +axs.color.WHITE_YCC = axs.color.toYCbCr(axs.color.WHITE); +axs.color.RED = new axs.color.Color(255, 0, 0, 1); +axs.color.RED_YCC = axs.color.toYCbCr(axs.color.RED); +axs.color.GREEN = new axs.color.Color(0, 255, 0, 1); +axs.color.GREEN_YCC = axs.color.toYCbCr(axs.color.GREEN); +axs.color.BLUE = new axs.color.Color(0, 0, 255, 1); +axs.color.BLUE_YCC = axs.color.toYCbCr(axs.color.BLUE); +axs.color.CYAN = new axs.color.Color(0, 255, 255, 1); +axs.color.CYAN_YCC = axs.color.toYCbCr(axs.color.CYAN); +axs.color.MAGENTA = new axs.color.Color(255, 0, 255, 1); +axs.color.MAGENTA_YCC = axs.color.toYCbCr(axs.color.MAGENTA); +axs.color.YELLOW = new axs.color.Color(255, 255, 0, 1); +axs.color.YELLOW_YCC = axs.color.toYCbCr(axs.color.YELLOW); +axs.color.YCC_CUBE_FACES_BLACK = [{p0:axs.color.BLACK_YCC, p1:axs.color.RED_YCC, p2:axs.color.GREEN_YCC}, {p0:axs.color.BLACK_YCC, p1:axs.color.GREEN_YCC, p2:axs.color.BLUE_YCC}, {p0:axs.color.BLACK_YCC, p1:axs.color.BLUE_YCC, p2:axs.color.RED_YCC}]; +axs.color.YCC_CUBE_FACES_WHITE = [{p0:axs.color.WHITE_YCC, p1:axs.color.CYAN_YCC, p2:axs.color.MAGENTA_YCC}, {p0:axs.color.WHITE_YCC, p1:axs.color.MAGENTA_YCC, p2:axs.color.YELLOW_YCC}, {p0:axs.color.WHITE_YCC, p1:axs.color.YELLOW_YCC, p2:axs.color.CYAN_YCC}]; +axs.utils = {}; +axs.utils.FOCUSABLE_ELEMENTS_SELECTOR = "input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),a[href],iframe,[tabindex]"; axs.utils.parentElement = function(a) { if (!a) { return null; @@ -669,7 +810,7 @@ axs.utils.isLargeFont = function(a) { return !1; }; axs.utils.getBgColor = function(a, b) { - var c = axs.utils.parseColor(a.backgroundColor); + var c = axs.color.parseColor(a.backgroundColor); if (!c) { return null; } @@ -679,7 +820,7 @@ axs.utils.getBgColor = function(a, b) { if (null == d) { return null; } - c = axs.utils.flattenColors(c, d); + c = axs.color.flattenColors(c, d); } return c; }; @@ -689,146 +830,28 @@ axs.utils.getParentBgColor = function(a) { for (var c = null;b = axs.utils.parentElement(b);) { var d = window.getComputedStyle(b, null); if (d) { - var e = axs.utils.parseColor(d.backgroundColor); + var e = axs.color.parseColor(d.backgroundColor); if (e && (1 > d.opacity && (e.alpha *= d.opacity), 0 != e.alpha && (a.push(e), 1 == e.alpha))) { c = !0; break; } } } - c || a.push(new axs.utils.Color(255, 255, 255, 1)); + c || a.push(new axs.color.Color(255, 255, 255, 1)); for (b = a.pop();a.length;) { - c = a.pop(), b = axs.utils.flattenColors(c, b); + c = a.pop(), b = axs.color.flattenColors(c, b); } return b; }; axs.utils.getFgColor = function(a, b, c) { - var d = axs.utils.parseColor(a.color); + var d = axs.color.parseColor(a.color); if (!d) { return null; } - 1 > d.alpha && (d = axs.utils.flattenColors(d, c)); - 1 > a.opacity && (b = axs.utils.getParentBgColor(b), d.alpha *= a.opacity, d = axs.utils.flattenColors(d, b)); + 1 > d.alpha && (d = axs.color.flattenColors(d, c)); + 1 > a.opacity && (b = axs.utils.getParentBgColor(b), d.alpha *= a.opacity, d = axs.color.flattenColors(d, b)); return d; }; -axs.utils.parseColor = function(a) { - var b = a.match(/^rgb\((\d+), (\d+), (\d+)\)$/); - if (b) { - a = parseInt(b[1], 10); - var c = parseInt(b[2], 10), d = parseInt(b[3], 10); - return new axs.utils.Color(a, c, d, 1); - } - return (b = a.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/)) ? (a = parseInt(b[1], 10), c = parseInt(b[2], 10), d = parseInt(b[3], 10), b = parseFloat(b[4]), new axs.utils.Color(a, c, d, b)) : null; -}; -axs.utils.colorChannelToString = function(a) { - a = Math.round(a); - return 15 >= a ? "0" + a.toString(16) : a.toString(16); -}; -axs.utils.colorToString = function(a) { - return 1 == a.alpha ? "#" + axs.utils.colorChannelToString(a.red) + axs.utils.colorChannelToString(a.green) + axs.utils.colorChannelToString(a.blue) : "rgba(" + [a.red, a.green, a.blue, a.alpha].join() + ")"; -}; -axs.utils.luminanceFromContrastRatio = function(a, b, c) { - return c ? (a + .05) * b - .05 : (a + .05) / b - .05; -}; -axs.utils.translateColor = function(a, b) { - var c = a[0], c = (b - c) / ((c > b ? 0 : 1) - c); - return axs.utils.fromYCC([b, a[1] - a[1] * c, a[2] - a[2] * c]); -}; -axs.utils.suggestColors = function(a, b, c, d) { - if (!axs.utils.isLowContrast(c, d, !0)) { - return null; - } - var e = {}, f = axs.utils.calculateLuminance(a), g = axs.utils.calculateLuminance(b), h = axs.utils.isLargeFont(d) ? 3 : 4.5, k = axs.utils.isLargeFont(d) ? 4.5 : 7, m = g > f, l = axs.utils.luminanceFromContrastRatio(f, h + .02, m), n = axs.utils.luminanceFromContrastRatio(f, k + .02, m), p = axs.utils.toYCC(b); - if (axs.utils.isLowContrast(c, d, !1) && 1 >= l && 0 <= l) { - var q = axs.utils.translateColor(p, l), l = axs.utils.calculateContrastRatio(q, a), f = {}; - f.fg = axs.utils.colorToString(q); - f.bg = axs.utils.colorToString(a); - f.contrast = l.toFixed(2); - e.AA = f; - } - axs.utils.isLowContrast(c, d, !0) && 1 >= n && 0 <= n && (n = axs.utils.translateColor(p, n), l = axs.utils.calculateContrastRatio(n, a), f = {}, f.fg = axs.utils.colorToString(n), f.bg = axs.utils.colorToString(a), f.contrast = l.toFixed(2), e.AAA = f); - h = axs.utils.luminanceFromContrastRatio(g, h + .02, !m); - g = axs.utils.luminanceFromContrastRatio(g, k + .02, !m); - a = axs.utils.toYCC(a); - !("AA" in e) && axs.utils.isLowContrast(c, d, !1) && 1 >= h && 0 <= h && (k = axs.utils.translateColor(a, h), l = axs.utils.calculateContrastRatio(b, k), f = {}, f.bg = axs.utils.colorToString(k), f.fg = axs.utils.colorToString(b), f.contrast = l.toFixed(2), e.AA = f); - !("AAA" in e) && axs.utils.isLowContrast(c, d, !0) && 1 >= g && 0 <= g && (c = axs.utils.translateColor(a, g), l = axs.utils.calculateContrastRatio(b, c), f = {}, f.bg = axs.utils.colorToString(c), f.fg = axs.utils.colorToString(b), f.contrast = l.toFixed(2), e.AAA = f); - return e; -}; -axs.utils.flattenColors = function(a, b) { - var c = a.alpha; - return new axs.utils.Color((1 - c) * b.red + c * a.red, (1 - c) * b.green + c * a.green, (1 - c) * b.blue + c * a.blue, a.alpha + b.alpha * (1 - a.alpha)); -}; -axs.utils.calculateLuminance = function(a) { - return axs.utils.toYCC(a)[0]; -}; -axs.utils.RGBToYCCMatrix = function(a, b) { - return [[a, 1 - a - b, b], [-a / (2 - 2 * b), (a + b - 1) / (2 - 2 * b), (1 - b) / (2 - 2 * b)], [(1 - a) / (2 - 2 * a), (a + b - 1) / (2 - 2 * a), -b / (2 - 2 * a)]]; -}; -axs.utils.invert3x3Matrix = function(a) { - var b = a[0][0], c = a[0][1], d = a[0][2], e = a[1][0], f = a[1][1], g = a[1][2], h = a[2][0], k = a[2][1]; - a = a[2][2]; - return axs.utils.scalarMultiplyMatrix([[f * a - g * k, d * k - c * a, c * g - d * f], [g * h - e * a, b * a - d * h, d * e - b * g], [e * k - f * h, h * c - b * k, b * f - c * e]], 1 / (b * (f * a - g * k) - c * (a * e - g * h) + d * (e * k - f * h))); -}; -axs.utils.scalarMultiplyMatrix = function(a, b) { - for (var c = [[], [], []], d = 0;3 > d;d++) { - for (var e = 0;3 > e;e++) { - c[d][e] = a[d][e] * b; - } - } - return c; -}; -axs.utils.kR = .2126; -axs.utils.kB = .0722; -axs.utils.YCC_MATRIX = axs.utils.RGBToYCCMatrix(axs.utils.kR, axs.utils.kB); -axs.utils.INVERTED_YCC_MATRIX = axs.utils.invert3x3Matrix(axs.utils.YCC_MATRIX); -axs.utils.convertColor = function(a, b) { - var c = b[0], d = b[1], e = b[2]; - return [a[0][0] * c + a[0][1] * d + a[0][2] * e, a[1][0] * c + a[1][1] * d + a[1][2] * e, a[2][0] * c + a[2][1] * d + a[2][2] * e]; -}; -axs.utils.multiplyMatrices = function(a, b) { - for (var c = [[], [], []], d = 0;3 > d;d++) { - for (var e = 0;3 > e;e++) { - c[d][e] = a[d][0] * b[0][e] + a[d][1] * b[1][e] + a[d][2] * b[2][e]; - } - } - return c; -}; -axs.utils.toYCC = function(a) { - var b = a.red / 255, c = a.green / 255; - a = a.blue / 255; - b = .03928 >= b ? b / 12.92 : Math.pow((b + .055) / 1.055, 2.4); - c = .03928 >= c ? c / 12.92 : Math.pow((c + .055) / 1.055, 2.4); - a = .03928 >= a ? a / 12.92 : Math.pow((a + .055) / 1.055, 2.4); - return axs.utils.convertColor(axs.utils.YCC_MATRIX, [b, c, a]); -}; -axs.utils.fromYCC = function(a) { - var b = axs.utils.convertColor(axs.utils.INVERTED_YCC_MATRIX, a), c = b[0]; - a = b[1]; - b = b[2]; - c = .00303949 >= c ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - .055; - a = .00303949 >= a ? 12.92 * a : 1.055 * Math.pow(a, 1 / 2.4) - .055; - b = .00303949 >= b ? 12.92 * b : 1.055 * Math.pow(b, 1 / 2.4) - .055; - c = Math.min(Math.max(Math.round(255 * c), 0), 255); - a = Math.min(Math.max(Math.round(255 * a), 0), 255); - b = Math.min(Math.max(Math.round(255 * b), 0), 255); - return new axs.utils.Color(c, a, b, 1); -}; -axs.utils.scalarMultiplyMatrix = function(a, b) { - for (var c = [[], [], []], d = 0;3 > d;d++) { - for (var e = 0;3 > e;e++) { - c[d][e] = a[d][e] * b; - } - } - return c; -}; -axs.utils.multiplyMatrices = function(a, b) { - for (var c = [[], [], []], d = 0;3 > d;d++) { - for (var e = 0;3 > e;e++) { - c[d][e] = a[d][0] * b[0][e] + a[d][1] * b[1][e] + a[d][2] * b[2][e]; - } - } - return c; -}; axs.utils.getContrastRatioForElement = function(a) { var b = window.getComputedStyle(a, null); return axs.utils.getContrastRatioForElementWithComputedStyle(b, a); @@ -842,7 +865,7 @@ axs.utils.getContrastRatioForElementWithComputedStyle = function(a, b) { return null; } var d = axs.utils.getFgColor(a, b, c); - return d ? axs.utils.calculateContrastRatio(d, c) : null; + return d ? axs.color.calculateContrastRatio(d, c) : null; }; axs.utils.isNativeTextElement = function(a) { var b = a.tagName.toLowerCase(); @@ -1189,16 +1212,22 @@ axs.properties.getContrastRatioProperties = function(a) { if (!d) { return null; } - b.backgroundColor = axs.utils.colorToString(d); + b.backgroundColor = axs.color.colorToString(d); var e = axs.utils.getFgColor(c, a, d); - b.foregroundColor = axs.utils.colorToString(e); + b.foregroundColor = axs.color.colorToString(e); a = axs.utils.getContrastRatioForElementWithComputedStyle(c, a); if (!a) { return null; } b.value = a.toFixed(2); axs.utils.isLowContrast(a, c) && (b.alert = !0); - (c = axs.utils.suggestColors(d, e, a, c)) && Object.keys(c).length && (b.suggestedColors = c); + var f = axs.utils.isLargeFont(c) ? 3 : 4.5, c = axs.utils.isLargeFont(c) ? 4.5 : 7, g = {}; + f > a && (g.AA = f); + c > a && (g.AAA = c); + if (!Object.keys(g).length) { + return b; + } + (d = axs.color.suggestColors(d, e, g)) && Object.keys(d).length && (b.suggestedColors = d); return b; }; axs.properties.findTextAlternatives = function(a, b, c, d) { @@ -1288,11 +1317,11 @@ axs.properties.getTextFromAriaLabelledby = function(a, b) { }; axs.properties.getTextFromHostLanguageAttributes = function(a, b, c, d) { if (axs.browserUtils.matchSelector(a, "img") && a.hasAttribute("alt")) { - var e = {type:"string", valid:!0}; - e.text = a.getAttribute("alt"); - c ? e.unused = !0 : c = e.text; - b.alt = e; - } + var e = {type:"string", valid:!0}; + e.text = a.getAttribute("alt"); + c ? e.unused = !0 : c = e.text; + b.alt = e; + } if (axs.browserUtils.matchSelector(a, 'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), video:not([disabled])') && !d) { if (a.hasAttribute("id")) { d = document.querySelectorAll('label[for="' + a.id + '"]'); @@ -1330,8 +1359,8 @@ axs.properties.getLastWord = function(a) { axs.properties.getTextProperties = function(a) { var b = {}, c = axs.properties.findTextAlternatives(a, b, !1, !0); if (0 == Object.keys(b).length && ((a = axs.utils.asElement(a)) && axs.browserUtils.matchSelector(a, "img") && (b.alt = {valid:!1, errorMessage:"No alt value provided"}, a = a.src, "string" == typeof a && (c = a.split("/").pop(), b.filename = {text:c})), !c)) { - return null; - } + return null; + } b.hasProperties = Boolean(Object.keys(b).length); b.computedText = c; b.lastWord = axs.properties.getLastWord(c); @@ -1527,19 +1556,20 @@ axs.AuditRule.collectMatchingElements = function(a, b, c, d) { } } if (e && "content" == e.localName) { - for (e = e.getDistributedNodes(), f = 0;f < e.length;f++) { - axs.AuditRule.collectMatchingElements(e[f], b, c, d); + for (var f = e.getDistributedNodes(), g = 0;g < f.length;g++) { + axs.AuditRule.collectMatchingElements(f[g], b, c, d); } } else { if (e && "shadow" == e.localName) { if (f = e, d) { - for (e = f.getDistributedNodes(), f = 0;f < e.length;f++) { - axs.AuditRule.collectMatchingElements(e[f], b, c, d); + for (f = f.getDistributedNodes(), g = 0;g < f.length;g++) { + axs.AuditRule.collectMatchingElements(f[g], b, c, d); } } else { console.warn("ShadowRoot not provided for", e); } } + e && "iframe" == e.localName && e.contentDocument && axs.AuditRule.collectMatchingElements(e.contentDocument, b, c, d); for (a = a.firstChild;null != a;) { axs.AuditRule.collectMatchingElements(a, b, c, d), a = a.nextSibling; } @@ -1574,15 +1604,17 @@ axs.AuditRule.prototype.run = function(a) { axs.AuditRules = {}; (function() { var a = {}, b = {}; + axs.AuditRules.specs = {}; axs.AuditRules.addRule = function(c) { - c = new axs.AuditRule(c); - if (c.code in b) { - throw Error('Can not add audit rule with same code: "' + c.code + '"'); + var d = new axs.AuditRule(c); + if (d.code in b) { + throw Error('Can not add audit rule with same code: "' + d.code + '"'); } - if (c.name in a) { - throw Error('Can not add audit rule with same name: "' + c.name + '"'); + if (d.name in a) { + throw Error('Can not add audit rule with same name: "' + d.name + '"'); } - a[c.name] = b[c.code] = c; + a[d.name] = b[d.code] = d; + axs.AuditRules.specs[c.name] = c; }; axs.AuditRules.getRule = function(c) { return a[c] || b[c] || null; diff --git a/package.json b/package.json index 121ce66f..8f072e16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "accessibility-developer-tools", - "version": "2.7.1", + "version": "2.8.0-rc.0", "repository": { "type": "git", "url": "http://github.com/GoogleChrome/accessibility-developer-tools"