Index: ui/accessibility/extensions/caretbrowsing/accessibility_utils.js |
diff --git a/ui/accessibility/extensions/caretbrowsing/accessibility_utils.js b/ui/accessibility/extensions/caretbrowsing/accessibility_utils.js |
deleted file mode 100644 |
index ed0e084b7b915de3933c1186e068166d2dff94f7..0000000000000000000000000000000000000000 |
--- a/ui/accessibility/extensions/caretbrowsing/accessibility_utils.js |
+++ /dev/null |
@@ -1,1391 +0,0 @@ |
-// 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. |
- |
-axs = {}; |
-axs.utils = {}; |
- |
-/** |
- * @constant |
- * @type {string} |
- */ |
-axs.utils.FOCUSABLE_ELEMENTS_SELECTOR = |
- 'input:not([type=hidden]):not([disabled]),' + |
- 'select:not([disabled]),' + |
- 'textarea:not([disabled]),' + |
- 'button:not([disabled]),' + |
- 'a[href],' + |
- 'iframe,' + |
- '[tabindex]'; |
- |
-/** |
- * @constructor |
- * @param {number} red |
- * @param {number} green |
- * @param {number} blue |
- * @param {number} alpha |
- */ |
-axs.utils.Color = function(red, green, blue, alpha) { |
- /** @type {number} */ |
- this.red = red; |
- |
- /** @type {number} */ |
- this.green = green; |
- |
- /** @type {number} */ |
- this.blue = blue; |
- |
- /** @type {number} */ |
- this.alpha = alpha; |
-}; |
- |
-/** |
- * 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 |
- * function will return 21. |
- * @param {axs.utils.Color} fgColor |
- * @param {axs.utils.Color} bgColor |
- * @return {?number} |
- */ |
-axs.utils.calculateContrastRatio = function(fgColor, bgColor) { |
- if (!fgColor || !bgColor) |
- return null; |
- |
- if (fgColor.alpha < 1) |
- fgColor = axs.utils.flattenColors(fgColor, bgColor); |
- |
- var fgLuminance = axs.utils.calculateLuminance(fgColor); |
- var bgLuminance = axs.utils.calculateLuminance(bgColor); |
- var contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) / |
- (Math.min(fgLuminance, bgLuminance) + 0.05); |
- return contrastRatio; |
-}; |
- |
-axs.utils.luminanceRatio = function(luminance1, luminance2) { |
- return (Math.max(luminance1, luminance2) + 0.05) / |
- (Math.min(luminance1, luminance2) + 0.05); |
-} |
- |
-/** |
- * Returns the nearest ancestor which is an Element. |
- * @param {Node} node |
- * @return {Element} |
- */ |
-axs.utils.parentElement = function(node) { |
- if (!node) |
- return null; |
- if (node.nodeType == Node.DOCUMENT_FRAGMENT_NODE) |
- return node.host; |
- |
- var parentElement = node.parentElement; |
- if (parentElement) |
- return parentElement; |
- |
- var parentNode = node.parentNode; |
- if (!parentNode) |
- return null; |
- |
- switch (parentNode.nodeType) { |
- case Node.ELEMENT_NODE: |
- return /** @type {Element} */ (parentNode); |
- case Node.DOCUMENT_FRAGMENT_NODE: |
- return parentNode.host; |
- default: |
- return null; |
- } |
-}; |
- |
-/** |
- * Return the corresponding element for the given node. |
- * @param {Node} node |
- * @return {Element} |
- * @suppress {checkTypes} |
- */ |
-axs.utils.asElement = function(node) { |
- /** @type {Element} */ var element; |
- switch (node.nodeType) { |
- case Node.COMMENT_NODE: |
- return null; // Skip comments |
- case Node.ELEMENT_NODE: |
- element = /** (@type {Element}) */ node; |
- if (element.tagName.toLowerCase() == 'script') |
- return null; // Skip script elements |
- break; |
- case Node.TEXT_NODE: |
- element = axs.utils.parentElement(node); |
- break; |
- default: |
- console.warn('Unhandled node type: ', node.nodeType); |
- return null; |
- } |
- return element; |
-} |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.elementIsTransparent = function(element) { |
- return element.style.opacity == '0'; |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.elementHasZeroArea = function(element) { |
- var rect = element.getBoundingClientRect(); |
- var width = rect.right - rect.left; |
- var height = rect.top - rect.bottom; |
- if (!width || !height) |
- return true; |
- return false; |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.elementIsOutsideScrollArea = function(element) { |
- var parent = axs.utils.parentElement(element); |
- |
- var defaultView = element.ownerDocument.defaultView; |
- while (parent != defaultView.document.body) { |
- if (axs.utils.isClippedBy(element, parent)) |
- return true; |
- |
- if (axs.utils.canScrollTo(element, parent) && !axs.utils.elementIsOutsideScrollArea(parent)) |
- return false; |
- |
- parent = axs.utils.parentElement(parent); |
- } |
- |
- return !axs.utils.canScrollTo(element, defaultView.document.body); |
-}; |
- |
-/** |
- * Checks whether it's possible to scroll to the given element within the given container. |
- * Assumes that |container| is an ancestor of |element|. |
- * If |container| cannot be scrolled, returns True if the element is within its bounding client |
- * rect. |
- * @param {Element} element |
- * @param {Element} container |
- * @return {boolean} True iff it's possible to scroll to |element| within |container|. |
- */ |
-axs.utils.canScrollTo = function(element, container) { |
- var rect = element.getBoundingClientRect(); |
- var containerRect = container.getBoundingClientRect(); |
- var containerTop = containerRect.top; |
- var containerLeft = containerRect.left; |
- var containerScrollArea = |
- { top: containerTop - container.scrollTop, |
- bottom: containerTop - container.scrollTop + container.scrollHeight, |
- left: containerLeft - container.scrollLeft, |
- right: containerLeft - container.scrollLeft + container.scrollWidth }; |
- |
- if (rect.right < containerScrollArea.left || rect.bottom < containerScrollArea.top || |
- rect.left > containerScrollArea.right || rect.top > containerScrollArea.bottom) { |
- return false; |
- } |
- |
- var defaultView = element.ownerDocument.defaultView; |
- var style = defaultView.getComputedStyle(container); |
- |
- if (rect.left > containerRect.right || rect.top > containerRect.bottom) { |
- return (style.overflow == 'scroll' || style.overflow == 'auto' || |
- container instanceof defaultView.HTMLBodyElement); |
- } |
- |
- return true; |
-}; |
- |
-/** |
- * Checks whether the given element is clipped by the given container. |
- * Assumes that |container| is an ancestor of |element|. |
- * @param {Element} element |
- * @param {Element} container |
- * @return {boolean} True iff |element| is clipped by |container|. |
- */ |
-axs.utils.isClippedBy = function(element, container) { |
- var rect = element.getBoundingClientRect(); |
- var containerRect = container.getBoundingClientRect(); |
- var containerTop = containerRect.top; |
- var containerLeft = containerRect.left; |
- var containerScrollArea = |
- { top: containerTop - container.scrollTop, |
- bottom: containerTop - container.scrollTop + container.scrollHeight, |
- left: containerLeft - container.scrollLeft, |
- right: containerLeft - container.scrollLeft + container.scrollWidth }; |
- |
- var defaultView = element.ownerDocument.defaultView; |
- var style = defaultView.getComputedStyle(container); |
- |
- if ((rect.right < containerRect.left || rect.bottom < containerRect.top || |
- rect.left > containerRect.right || rect.top > containerRect.bottom) && |
- style.overflow == 'hidden') { |
- return true; |
- } |
- |
- if (rect.right < containerScrollArea.left || rect.bottom < containerScrollArea.top) |
- return (style.overflow != 'visible'); |
- |
- return false; |
-}; |
- |
-/** |
- * @param {Node} ancestor A potential ancestor of |node|. |
- * @param {Node} node |
- * @return {boolean} true if |ancestor| is an ancestor of |node| (including |
- * |ancestor| === |node|). |
- */ |
-axs.utils.isAncestor = function(ancestor, node) { |
- if (node == null) |
- return false; |
- if (node === ancestor) |
- return true; |
- |
- return axs.utils.isAncestor(ancestor, node.parentNode); |
-} |
- |
-/** |
- * @param {Element} element |
- * @return {Array.<Element>} An array of any non-transparent elements which |
- * overlap the given element. |
- */ |
-axs.utils.overlappingElements = function(element) { |
- if (axs.utils.elementHasZeroArea(element)) |
- return null; |
- |
- var overlappingElements = []; |
- var clientRects = element.getClientRects(); |
- for (var i = 0; i < clientRects.length; i++) { |
- var rect = clientRects[i]; |
- var center_x = (rect.left + rect.right) / 2; |
- var center_y = (rect.top + rect.bottom) / 2; |
- var elementAtPoint = document.elementFromPoint(center_x, center_y); |
- |
- if (elementAtPoint == null || elementAtPoint == element || |
- axs.utils.isAncestor(elementAtPoint, element) || |
- axs.utils.isAncestor(element, elementAtPoint)) { |
- continue; |
- } |
- |
- var overlappingElementStyle = window.getComputedStyle(elementAtPoint, null); |
- if (!overlappingElementStyle) |
- continue; |
- |
- var overlappingElementBg = axs.utils.getBgColor(overlappingElementStyle, |
- elementAtPoint); |
- if (overlappingElementBg && overlappingElementBg.alpha > 0 && |
- overlappingElements.indexOf(elementAtPoint) < 0) { |
- overlappingElements.push(elementAtPoint); |
- } |
- } |
- |
- return overlappingElements; |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return boolean |
- */ |
-axs.utils.elementIsHtmlControl = function(element) { |
- var defaultView = element.ownerDocument.defaultView; |
- |
- // HTML control |
- if (element instanceof defaultView.HTMLButtonElement) |
- return true; |
- if (element instanceof defaultView.HTMLInputElement) |
- return true; |
- if (element instanceof defaultView.HTMLSelectElement) |
- return true; |
- if (element instanceof defaultView.HTMLTextAreaElement) |
- return true; |
- |
- return false; |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return boolean |
- */ |
-axs.utils.elementIsAriaWidget = function(element) { |
- if (element.hasAttribute('role')) { |
- var roleValue = element.getAttribute('role'); |
- // TODO is this correct? |
- if (roleValue) { |
- var role = axs.constants.ARIA_ROLES[roleValue]; |
- if (role && 'widget' in role['allParentRolesSet']) |
- return true; |
- } |
- } |
- return false; |
-} |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.elementIsVisible = function(element) { |
- if (axs.utils.elementIsTransparent(element)) |
- return false; |
- if (axs.utils.elementHasZeroArea(element)) |
- return false; |
- if (axs.utils.elementIsOutsideScrollArea(element)) |
- return false; |
- |
- var overlappingElements = axs.utils.overlappingElements(element); |
- if (overlappingElements.length) |
- return false; |
- |
- return true; |
-}; |
- |
-/** |
- * @param {CSSStyleDeclaration} style |
- * @return {boolean} |
- */ |
-axs.utils.isLargeFont = function(style) { |
- var fontSize = style.fontSize; |
- var bold = style.fontWeight == 'bold'; |
- var matches = fontSize.match(/(\d+)px/); |
- if (matches) { |
- var fontSizePx = parseInt(matches[1], 10); |
- var bodyStyle = window.getComputedStyle(document.body, null); |
- var bodyFontSize = bodyStyle.fontSize; |
- matches = bodyFontSize.match(/(\d+)px/); |
- if (matches) { |
- var bodyFontSizePx = parseInt(matches[1], 10); |
- var boldLarge = bodyFontSizePx * 1.2; |
- var large = bodyFontSizePx * 1.5; |
- } else { |
- var boldLarge = 19.2; |
- var large = 24; |
- } |
- return (bold && fontSizePx >= boldLarge || fontSizePx >= large); |
- } |
- matches = fontSize.match(/(\d+)em/); |
- if (matches) { |
- var fontSizeEm = parseInt(matches[1], 10); |
- if (bold && fontSizeEm >= 1.2 || fontSizeEm >= 1.5) |
- return true; |
- return false; |
- } |
- matches = fontSize.match(/(\d+)%/); |
- if (matches) { |
- var fontSizePercent = parseInt(matches[1], 10); |
- if (bold && fontSizePercent >= 120 || fontSizePercent >= 150) |
- return true; |
- return false; |
- } |
- matches = fontSize.match(/(\d+)pt/); |
- if (matches) { |
- var fontSizePt = parseInt(matches[1], 10); |
- if (bold && fontSizePt >= 14 || fontSizePt >= 18) |
- return true; |
- return false; |
- } |
- return false; |
-}; |
- |
-/** |
- * @param {CSSStyleDeclaration} style |
- * @param {Element} element |
- * @return {?axs.utils.Color} |
- */ |
-axs.utils.getBgColor = function(style, element) { |
- var bgColorString = style.backgroundColor; |
- var bgColor = axs.utils.parseColor(bgColorString); |
- if (!bgColor) |
- return null; |
- |
- if (style.opacity < 1) |
- bgColor.alpha = bgColor.alpha * style.opacity |
- |
- if (bgColor.alpha < 1) { |
- var parentBg = axs.utils.getParentBgColor(element); |
- if (parentBg == null) |
- return null; |
- |
- bgColor = axs.utils.flattenColors(bgColor, parentBg); |
- } |
- return bgColor; |
-}; |
- |
-/** |
- * Gets the effective background color of the parent of |element|. |
- * @param {Element} element |
- * @return {?axs.utils.Color} |
- */ |
-axs.utils.getParentBgColor = function(element) { |
- /** @type {Element} */ var parent = element; |
- var bgStack = []; |
- var foundSolidColor = null; |
- while (parent = axs.utils.parentElement(parent)) { |
- var computedStyle = window.getComputedStyle(parent, null); |
- if (!computedStyle) |
- continue; |
- |
- var parentBg = axs.utils.parseColor(computedStyle.backgroundColor); |
- if (!parentBg) |
- continue; |
- |
- if (computedStyle.opacity < 1) |
- parentBg.alpha = parentBg.alpha * computedStyle.opacity; |
- |
- if (parentBg.alpha == 0) |
- continue; |
- |
- bgStack.push(parentBg); |
- |
- if (parentBg.alpha == 1) { |
- foundSolidColor = true; |
- break; |
- } |
- } |
- |
- if (!foundSolidColor) |
- bgStack.push(new axs.utils.Color(255, 255, 255, 1)); |
- |
- var bg = bgStack.pop(); |
- while (bgStack.length) { |
- var fg = bgStack.pop(); |
- bg = axs.utils.flattenColors(fg, bg); |
- } |
- return bg; |
-} |
- |
-/** |
- * @param {CSSStyleDeclaration} style |
- * @param {axs.utils.Color} bgColor The background color, which may come from |
- * another element (such as a parent element), for flattening into the |
- * foreground color. |
- * @return {?axs.utils.Color} |
- */ |
-axs.utils.getFgColor = function(style, element, bgColor) { |
- var fgColorString = style.color; |
- var fgColor = axs.utils.parseColor(fgColorString); |
- if (!fgColor) |
- return null; |
- |
- if (fgColor.alpha < 1) |
- fgColor = axs.utils.flattenColors(fgColor, bgColor); |
- |
- if (style.opacity < 1) { |
- var parentBg = axs.utils.getParentBgColor(element); |
- fgColor.alpha = fgColor.alpha * style.opacity; |
- fgColor = axs.utils.flattenColors(fgColor, parentBg); |
- } |
- |
- return fgColor; |
-}; |
- |
-/** |
- * @param {string} colorString The color string from CSS. |
- * @return {?axs.utils.Color} |
- */ |
-axs.utils.parseColor = function(colorString) { |
- var rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)$/; |
- var match = colorString.match(rgbRegex); |
- |
- if (match) { |
- var r = parseInt(match[1], 10); |
- var g = parseInt(match[2], 10); |
- var b = parseInt(match[3], 10); |
- var a = 1 |
- return new axs.utils.Color(r, g, b, a); |
- } |
- |
- var rgbaRegex = /^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/; |
- match = colorString.match(rgbaRegex); |
- if (match) { |
- var r = parseInt(match[1], 10); |
- var g = parseInt(match[2], 10); |
- var b = parseInt(match[3] ,10); |
- var a = parseFloat(match[4]); |
- return new axs.utils.Color(r, g, b, a); |
- } |
- |
- return null; |
-}; |
- |
-/** |
- * @param {number} value The value of a color channel, 0 <= value <= 0xFF |
- * @return {string} |
- */ |
-axs.utils.colorChannelToString = function(value) { |
- value = Math.round(value); |
- if (value <= 0xF) |
- return '0' + value.toString(16); |
- return value.toString(16); |
-}; |
- |
-/** |
- * @param {axs.utils.Color} color |
- * @return {string} |
- */ |
-axs.utils.colorToString = function(color) { |
- if (color.alpha == 1) { |
- return '#' + axs.utils.colorChannelToString(color.red) + |
- axs.utils.colorChannelToString(color.green) + axs.utils.colorChannelToString(color.blue); |
- } |
- else |
- return 'rgba(' + [color.red, color.green, color.blue, color.alpha].join(',') + ')'; |
-}; |
- |
-axs.utils.luminanceFromContrastRatio = function(luminance, contrast, higher) { |
- if (higher) { |
- var newLuminance = (luminance + 0.05) * contrast - 0.05; |
- return newLuminance; |
- } else { |
- var newLuminance = (luminance + 0.05) / contrast - 0.05; |
- return newLuminance; |
- } |
-}; |
- |
-axs.utils.translateColor = function(ycc, luminance) { |
- var oldLuminance = ycc[0]; |
- if (oldLuminance > luminance) |
- var endpoint = 0 |
- else |
- var endpoint = 1; |
- |
- var d = luminance - oldLuminance; |
- var scale = d / (endpoint - oldLuminance) |
- |
- /** @type {Array.<number>} */ var translatedColor = [ luminance, |
- ycc[1] - ycc[1] * scale, |
- ycc[2] - ycc[2] * scale ]; |
- var rgb = axs.utils.fromYCC(translatedColor); |
- return rgb; |
-}; |
- |
-/** |
- * @param {axs.utils.Color} fgColor |
- * @param {axs.utils.Color} bgColor |
- * @param {number} contrastRatio |
- * @param {CSSStyleDeclaration} style |
- * @return {Object} |
- */ |
-axs.utils.suggestColors = function(bgColor, fgColor, contrastRatio, style) { |
- if (!axs.utils.isLowContrast(contrastRatio, style, true)) |
- return null; |
- var colors = {}; |
- var bgLuminance = axs.utils.calculateLuminance(bgColor); |
- var fgLuminance = axs.utils.calculateLuminance(fgColor); |
- |
- var levelAAContrast = axs.utils.isLargeFont(style) ? 3.0 : 4.5; |
- var levelAAAContrast = axs.utils.isLargeFont(style) ? 4.5 : 7.0; |
- var fgLuminanceIsHigher = fgLuminance > bgLuminance; |
- var desiredFgLuminanceAA = axs.utils.luminanceFromContrastRatio(bgLuminance, levelAAContrast + 0.02, fgLuminanceIsHigher); |
- var desiredFgLuminanceAAA = axs.utils.luminanceFromContrastRatio(bgLuminance, levelAAAContrast + 0.02, fgLuminanceIsHigher); |
- var fgYCC = axs.utils.toYCC(fgColor); |
- |
- if (axs.utils.isLowContrast(contrastRatio, style, false) && |
- desiredFgLuminanceAA <= 1 && desiredFgLuminanceAA >= 0) { |
- var newFgColorAA = axs.utils.translateColor(fgYCC, desiredFgLuminanceAA); |
- var newContrastRatioAA = axs.utils.calculateContrastRatio(newFgColorAA, bgColor); |
- var newLuminance = axs.utils.calculateLuminance(newFgColorAA); |
- var suggestedColorsAA = {} |
- suggestedColorsAA['fg'] = axs.utils.colorToString(newFgColorAA); |
- suggestedColorsAA['bg'] = axs.utils.colorToString(bgColor); |
- suggestedColorsAA['contrast'] = newContrastRatioAA.toFixed(2); |
- colors['AA'] = suggestedColorsAA; |
- } |
- if (axs.utils.isLowContrast(contrastRatio, style, true) && |
- desiredFgLuminanceAAA <= 1 && desiredFgLuminanceAAA >= 0) { |
- var newFgColorAAA = axs.utils.translateColor(fgYCC, desiredFgLuminanceAAA); |
- var newContrastRatioAAA = axs.utils.calculateContrastRatio(newFgColorAAA, bgColor); |
- var suggestedColorsAAA = {}; |
- suggestedColorsAAA['fg'] = axs.utils.colorToString(newFgColorAAA); |
- suggestedColorsAAA['bg'] = axs.utils.colorToString(bgColor); |
- suggestedColorsAAA['contrast'] = newContrastRatioAAA.toFixed(2); |
- colors['AAA'] = suggestedColorsAAA; |
- } |
- var desiredBgLuminanceAA = axs.utils.luminanceFromContrastRatio(fgLuminance, levelAAContrast + 0.02, !fgLuminanceIsHigher); |
- var desiredBgLuminanceAAA = axs.utils.luminanceFromContrastRatio(fgLuminance, levelAAAContrast + 0.02, !fgLuminanceIsHigher); |
- var bgLuminanceBoundary = fgLuminanceIsHigher ? 0 : 1; |
- var bgYCC = axs.utils.toYCC(bgColor); |
- |
- if (!('AA' in colors) && axs.utils.isLowContrast(contrastRatio, style, false) && |
- desiredBgLuminanceAA <= 1 && desiredBgLuminanceAA >= 0) { |
- var newBgColorAA = axs.utils.translateColor(bgYCC, desiredBgLuminanceAA); |
- var newContrastRatioAA = axs.utils.calculateContrastRatio(fgColor, newBgColorAA); |
- var suggestedColorsAA = {}; |
- suggestedColorsAA['bg'] = axs.utils.colorToString(newBgColorAA); |
- suggestedColorsAA['fg'] = axs.utils.colorToString(fgColor); |
- suggestedColorsAA['contrast'] = newContrastRatioAA.toFixed(2); |
- colors['AA'] = suggestedColorsAA; |
- } |
- if (!('AAA' in colors) && axs.utils.isLowContrast(contrastRatio, style, true) && |
- desiredBgLuminanceAAA <= 1 && desiredBgLuminanceAAA >= 0) { |
- var newBgColorAAA = axs.utils.translateColor(bgYCC, desiredBgLuminanceAAA); |
- var newContrastRatioAAA = axs.utils.calculateContrastRatio(fgColor, newBgColorAAA); |
- var suggestedColorsAAA = {}; |
- suggestedColorsAAA['bg'] = axs.utils.colorToString(newBgColorAAA); |
- suggestedColorsAAA['fg'] = axs.utils.colorToString(fgColor); |
- suggestedColorsAAA['contrast'] = newContrastRatioAAA.toFixed(2); |
- colors['AAA'] = suggestedColorsAAA; |
- } |
- return colors; |
-} |
- |
-/** |
- * Combine the two given color according to alpha blending. |
- * @param {axs.utils.Color} fgColor |
- * @param {axs.utils.Color} bgColor |
- * @return {axs.utils.Color} |
- */ |
-axs.utils.flattenColors = function(fgColor, bgColor) { |
- var alpha = fgColor.alpha; |
- var r = ((1 - alpha) * bgColor.red) + (alpha * fgColor.red); |
- var g = ((1 - alpha) * bgColor.green) + (alpha * fgColor.green); |
- var b = ((1 - alpha) * bgColor.blue) + (alpha * fgColor.blue); |
- var a = fgColor.alpha + (bgColor.alpha * (1 - fgColor.alpha)); |
- |
- return new axs.utils.Color(r, g, b, a); |
-}; |
- |
-/** |
- * Calculate the luminance of the given color using the WCAG algorithm. |
- * @param {axs.utils.Color} color |
- * @return {number} |
- */ |
-axs.utils.calculateLuminance = function(color) { |
-/* var rSRGB = color.red / 255; |
- var gSRGB = color.green / 255; |
- var bSRGB = color.blue / 255; |
- |
- var r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055)/1.055), 2.4); |
- 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 0.2126 * r + 0.7152 * g + 0.0722 * b; */ |
- var ycc = axs.utils.toYCC(color); |
- return ycc[0]; |
-}; |
- |
-/** |
- * Returns an RGB to YCC conversion matrix for the given kR, kB constants. |
- * @param {number} kR |
- * @param {number} kB |
- * @return {Array.<Array.<number>>} |
- */ |
-axs.utils.RGBToYCCMatrix = function(kR, kB) { |
- return [ |
- [ |
- kR, |
- (1 - kR - kB), |
- kB |
- ], |
- [ |
- -kR/(2 - 2*kB), |
- (kR + kB - 1)/(2 - 2*kB), |
- (1 - kB)/(2 - 2*kB) |
- ], |
- [ |
- (1 - kR)/(2 - 2*kR), |
- (kR + kB - 1)/(2 - 2*kR), |
- -kB/(2 - 2*kR) |
- ] |
- ]; |
-} |
- |
-/** |
- * Return the inverse of the given 3x3 matrix. |
- * @param {Array.<Array.<number>>} matrix |
- * @return Array.<Array.<number>> The inverse of the given matrix. |
- */ |
-axs.utils.invert3x3Matrix = function(matrix) { |
- var a = matrix[0][0]; |
- var b = matrix[0][1]; |
- var c = matrix[0][2]; |
- var d = matrix[1][0]; |
- var e = matrix[1][1]; |
- var f = matrix[1][2]; |
- var g = matrix[2][0]; |
- var h = matrix[2][1]; |
- var k = matrix[2][2]; |
- |
- var A = (e*k - f*h); |
- var B = (f*g - d*k); |
- var C = (d*h - e*g); |
- var D = (c*h - b*k); |
- var E = (a*k - c*g); |
- var F = (g*b - a*h); |
- var G = (b*f - c*e); |
- var H = (c*d - a*f); |
- var K = (a*e - b*d); |
- |
- var det = a * (e*k - f*h) - b * (k*d - f*g) + c * (d*h - e*g); |
- var z = 1/det; |
- |
- return axs.utils.scalarMultiplyMatrix([ |
- [ A, D, G ], |
- [ B, E, H ], |
- [ C, F, K ] |
- ], z); |
-}; |
- |
-axs.utils.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; |
- } |
- } |
- |
- return result; |
-} |
- |
-axs.utils.kR = 0.2126; |
-axs.utils.kB = 0.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); |
- |
-/** |
- * Multiply the given color vector by the given transformation matrix. |
- * @param {Array.<Array.<number>>} matrix A 3x3 conversion matrix |
- * @param {Array.<number>} vector A 3-element color vector |
- * @return {Array.<number>} A 3-element color vector |
- */ |
-axs.utils.convertColor = function(matrix, vector) { |
- var a = matrix[0][0]; |
- var b = matrix[0][1]; |
- var c = matrix[0][2]; |
- var d = matrix[1][0]; |
- var e = matrix[1][1]; |
- var f = matrix[1][2]; |
- var g = matrix[2][0]; |
- var h = matrix[2][1]; |
- var k = matrix[2][2]; |
- |
- var x = vector[0]; |
- var y = vector[1]; |
- var z = vector[2]; |
- |
- return [ |
- a*x + b*y + c*z, |
- d*x + e*y + f*z, |
- g*x + h*y + k*z |
- ]; |
-}; |
- |
-axs.utils.multiplyMatrices = function(matrix1, matrix2) { |
- 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] = matrix1[i][0] * matrix2[0][j] + |
- matrix1[i][1] * matrix2[1][j] + |
- matrix1[i][2] * matrix2[2][j]; |
- } |
- } |
- return result; |
-} |
- |
-/** |
- * Convert a given RGB color to YCC. |
- */ |
-axs.utils.toYCC = function(color) { |
- var rSRGB = color.red / 255; |
- var gSRGB = color.green / 255; |
- var bSRGB = color.blue / 255; |
- |
- var r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055)/1.055), 2.4); |
- 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.utils.convertColor(axs.utils.YCC_MATRIX, [r, g, b]); |
-}; |
- |
-/** |
- * Convert a color from a YCC color (as a vector) to an RGB color |
- * @param {Array.<number>} yccColor |
- * @return {axs.utils.Color} |
- */ |
-axs.utils.fromYCC = function(yccColor) { |
- var rgb = axs.utils.convertColor(axs.utils.INVERTED_YCC_MATRIX, yccColor); |
- |
- var r = rgb[0]; |
- var g = rgb[1]; |
- var b = rgb[2]; |
- var rSRGB = r <= 0.00303949 ? (r * 12.92) : (Math.pow(r, (1/2.4)) * 1.055) - 0.055; |
- var gSRGB = g <= 0.00303949 ? (g * 12.92) : (Math.pow(g, (1/2.4)) * 1.055) - 0.055; |
- var bSRGB = b <= 0.00303949 ? (b * 12.92) : (Math.pow(b, (1/2.4)) * 1.055) - 0.055; |
- |
- var red = Math.min(Math.max(Math.round(rSRGB * 255), 0), 255); |
- var green = Math.min(Math.max(Math.round(gSRGB * 255), 0), 255); |
- var blue = Math.min(Math.max(Math.round(bSRGB * 255), 0), 255); |
- |
- return new axs.utils.Color(red, green, blue, 1); |
-}; |
- |
-axs.utils.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; |
- } |
- } |
- |
- return result; |
-} |
- |
-axs.utils.multiplyMatrices = function(matrix1, matrix2) { |
- 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] = matrix1[i][0] * matrix2[0][j] + |
- matrix1[i][1] * matrix2[1][j] + |
- matrix1[i][2] * matrix2[2][j]; |
- } |
- } |
- return result; |
-} |
- |
-/** |
- * @param {Element} element |
- * @return {?number} |
- */ |
-axs.utils.getContrastRatioForElement = function(element) { |
- var style = window.getComputedStyle(element, null); |
- return axs.utils.getContrastRatioForElementWithComputedStyle(style, element); |
-}; |
- |
-/** |
- * @param {CSSStyleDeclaration} style |
- * @param {Element} element |
- * @return {?number} |
- */ |
-axs.utils.getContrastRatioForElementWithComputedStyle = function(style, element) { |
- if (axs.utils.isElementHidden(element)) |
- return null; |
- |
- var bgColor = axs.utils.getBgColor(style, element); |
- if (!bgColor) |
- return null; |
- |
- var fgColor = axs.utils.getFgColor(style, element, bgColor); |
- if (!fgColor) |
- return null; |
- |
- return axs.utils.calculateContrastRatio(fgColor, bgColor); |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.isNativeTextElement = function(element) { |
- var tagName = element.tagName.toLowerCase(); |
- var type = element.type ? element.type.toLowerCase() : ''; |
- if (tagName == 'textarea') |
- return true; |
- if (tagName != 'input') |
- return false; |
- |
- switch (type) { |
- case 'email': |
- case 'number': |
- case 'password': |
- case 'search': |
- case 'text': |
- case 'tel': |
- case 'url': |
- case '': |
- return true; |
- default: |
- return false; |
- } |
-}; |
- |
-/** |
- * @param {number} contrastRatio |
- * @param {CSSStyleDeclaration} style |
- * @param {boolean=} opt_strict Whether to use AA (false) or AAA (true) level |
- * @return {boolean} |
- */ |
-axs.utils.isLowContrast = function(contrastRatio, style, opt_strict) { |
- // Round to nearest 0.1 |
- var roundedContrastRatio = (Math.round(contrastRatio * 10) / 10); |
- if (!opt_strict) { |
- return roundedContrastRatio < 3.0 || |
- (!axs.utils.isLargeFont(style) && roundedContrastRatio < 4.5); |
- } else { |
- return roundedContrastRatio < 4.5 || |
- (!axs.utils.isLargeFont(style) && roundedContrastRatio < 7.0); |
- } |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.hasLabel = function(element) { |
- var tagName = element.tagName.toLowerCase(); |
- var type = element.type ? element.type.toLowerCase() : ''; |
- |
- if (element.hasAttribute('aria-label')) |
- return true; |
- if (element.hasAttribute('title')) |
- return true; |
- if (tagName == 'img' && element.hasAttribute('alt')) |
- return true; |
- if (tagName == 'input' && type == 'image' && element.hasAttribute('alt')) |
- return true; |
- if (tagName == 'input' && (type == 'submit' || type == 'reset')) |
- return true; |
- |
- // There's a separate audit that makes sure this points to an actual element or elements. |
- if (element.hasAttribute('aria-labelledby')) |
- return true; |
- |
- if (axs.utils.isNativeTextElement(element) && element.hasAttribute('placeholder')) |
- return true; |
- |
- if (element.hasAttribute('id')) { |
- var labelsFor = document.querySelectorAll('label[for="' + element.id + '"]'); |
- if (labelsFor.length > 0) |
- return true; |
- } |
- |
- var parent = axs.utils.parentElement(element); |
- while (parent) { |
- if (parent.tagName.toLowerCase() == 'label') { |
- var parentLabel = /** HTMLLabelElement */ parent; |
- if (parentLabel.control == element) |
- return true; |
- } |
- parent = axs.utils.parentElement(parent); |
- } |
- return false; |
-}; |
- |
-/** |
- * @param {Element} element An element to check. |
- * @return {boolean} True if the element is hidden from accessibility. |
- */ |
-axs.utils.isElementHidden = function(element) { |
- if (!(element instanceof element.ownerDocument.defaultView.HTMLElement)) |
- return false; |
- |
- if (element.hasAttribute('chromevoxignoreariahidden')) |
- var chromevoxignoreariahidden = true; |
- |
- var style = window.getComputedStyle(element, null); |
- if (style.display == 'none' || style.visibility == 'hidden') |
- return true; |
- |
- if (element.hasAttribute('aria-hidden') && |
- element.getAttribute('aria-hidden').toLowerCase() == 'true') { |
- return !chromevoxignoreariahidden; |
- } |
- |
- return false; |
-}; |
- |
-/** |
- * @param {Element} element An element to check. |
- * @return {boolean} True if the element or one of its ancestors is |
- * hidden from accessibility. |
- */ |
-axs.utils.isElementOrAncestorHidden = function(element) { |
- if (axs.utils.isElementHidden(element)) |
- return true; |
- |
- if (axs.utils.parentElement(element)) |
- return axs.utils.isElementOrAncestorHidden(axs.utils.parentElement(element)); |
- else |
- return false; |
-}; |
- |
-/** |
- * @param {Element} element An element to check |
- * @return {boolean} True if the given element is an inline element, false |
- * otherwise. |
- */ |
-axs.utils.isInlineElement = function(element) { |
- var tagName = element.tagName.toUpperCase(); |
- return axs.constants.InlineElements[tagName]; |
-} |
- |
-/** |
- * @param {Element} element |
- * @return {Object|boolean} |
- */ |
-axs.utils.getRoles = function(element) { |
- if (!element.hasAttribute('role')) |
- return false; |
- var roleValue = element.getAttribute('role'); |
- var roleNames = roleValue.split(' '); |
- var roles = [] |
- var valid = true; |
- for (var i = 0; i < roleNames.length; i++) { |
- var role = roleNames[i]; |
- if (axs.constants.ARIA_ROLES[role]) |
- roles.push({'name': role, 'details': axs.constants.ARIA_ROLES[role], 'valid': true}); |
- else { |
- roles.push({'name': role, 'valid': false}); |
- valid = false; |
- } |
- } |
- |
- return { 'roles': roles, 'valid': valid }; |
-}; |
- |
-/** |
- * @param {!string} propertyName |
- * @param {!string} value |
- * @param {!Element} element |
- * @return {!Object} |
- */ |
-axs.utils.getAriaPropertyValue = function(propertyName, value, element) { |
- var propertyKey = propertyName.replace(/^aria-/, ''); |
- var property = axs.constants.ARIA_PROPERTIES[propertyKey]; |
- var result = { 'name': propertyName, 'rawValue': value }; |
- if (!property) { |
- result.valid = false; |
- result.reason = '"' + propertyName + '" is not a valid ARIA property'; |
- return result; |
- } |
- |
- var propertyType = property.valueType; |
- if (!propertyType) { |
- result.valid = false; |
- result.reason = '"' + propertyName + '" is not a valid ARIA property'; |
- return result; |
- } |
- |
- switch (propertyType) { |
- case "idref": |
- var isValid = axs.utils.isValidIDRefValue(value, element); |
- result.valid = isValid.valid; |
- result.reason = isValid.reason; |
- result.idref = isValid.idref; |
- case "idref_list": |
- var idrefValues = value.split(/\s+/); |
- result.valid = true; |
- for (var i = 0; i < idrefValues.length; i++) { |
- var refIsValid = axs.utils.isValidIDRefValue(idrefValues[i], element); |
- if (!refIsValid.valid) |
- result.valid = false; |
- if (result.values) |
- result.values.push(refIsValid); |
- else |
- result.values = [refIsValid]; |
- } |
- return result; |
- case "integer": |
- case "decimal": |
- var validNumber = axs.utils.isValidNumber(value); |
- if (!validNumber.valid) { |
- result.valid = false; |
- result.reason = validNumber.reason; |
- return result; |
- } |
- if (Math.floor(validNumber.value) != validNumber.value) { |
- result.valid = false; |
- result.reason = '' + value + ' is not a whole integer'; |
- } else { |
- result.valid = true; |
- result.value = validNumber.value; |
- } |
- return result; |
- case "number": |
- var validNumber = axs.utils.isValidNumber(value); |
- if (validNumber.valid) { |
- result.valid = true; |
- result.value = validNumber.value; |
- } |
- case "string": |
- result.valid = true; |
- result.value = value; |
- return result; |
- case "token": |
- var validTokenValue = axs.utils.isValidTokenValue(propertyName, value.toLowerCase()); |
- if (validTokenValue.valid) { |
- result.valid = true; |
- result.value = validTokenValue.value; |
- return result; |
- } else { |
- result.valid = false; |
- result.value = value; |
- result.reason = validTokenValue.reason; |
- return result; |
- } |
- case "token_list": |
- var tokenValues = value.split(/\s+/); |
- result.valid = true; |
- for (var i = 0; i < tokenValues.length; i++) { |
- var validTokenValue = axs.utils.isValidTokenValue(propertyName, tokenValues[i].toLowerCase()); |
- if (!validTokenValue.valid) { |
- result.valid = false; |
- if (result.reason) { |
- result.reason = [ result.reason ]; |
- result.reason.push(validTokenValue.reason); |
- } else { |
- result.reason = validTokenValue.reason; |
- result.possibleValues = validTokenValue.possibleValues; |
- } |
- } |
- // TODO (more structured result) |
- if (result.values) |
- result.values.push(validTokenValue.value); |
- else |
- result.values = [validTokenValue.value]; |
- } |
- return result; |
- case "tristate": |
- var validTristate = axs.utils.isPossibleValue(value.toLowerCase(), axs.constants.MIXED_VALUES, propertyName); |
- if (validTristate.valid) { |
- result.valid = true; |
- result.value = validTristate.value; |
- } else { |
- result.valid = false; |
- result.value = value; |
- result.reason = validTristate.reason; |
- } |
- return result; |
- case "boolean": |
- var validBoolean = axs.utils.isValidBoolean(value); |
- if (validBoolean.valid) { |
- result.valid = true; |
- result.value = validBoolean.value; |
- } else { |
- result.valid = false; |
- result.value = value; |
- result.reason = validBoolean.reason; |
- } |
- return result; |
- } |
- result.valid = false |
- result.reason = 'Not a valid ARIA property'; |
- return result; |
-}; |
- |
-/** |
- * @param {string} propertyName The name of the property. |
- * @param {string} value The value to check. |
- * @return {!Object} |
- */ |
-axs.utils.isValidTokenValue = function(propertyName, value) { |
- var propertyKey = propertyName.replace(/^aria-/, ''); |
- var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyKey]; |
- var possibleValues = propertyDetails.valuesSet; |
- return axs.utils.isPossibleValue(value, possibleValues, propertyName); |
-}; |
- |
-/** |
- * @param {string} value |
- * @param {Object.<string, boolean>} possibleValues |
- * @return {!Object} |
- */ |
-axs.utils.isPossibleValue = function(value, possibleValues, propertyName) { |
- if (!possibleValues[value]) |
- return { 'valid': false, |
- 'value': value, |
- 'reason': '"' + value + '" is not a valid value for ' + propertyName, |
- 'possibleValues': Object.keys(possibleValues) }; |
- return { 'valid': true, 'value': value }; |
-}; |
- |
-/** |
- * @param {string} value |
- * @return {!Object} |
- */ |
-axs.utils.isValidBoolean = function(value) { |
- try { |
- var parsedValue = JSON.parse(value); |
- } catch (e) { |
- parsedValue = ''; |
- } |
- if (typeof(parsedValue) != 'boolean') |
- return { 'valid': false, |
- 'value': value, |
- 'reason': '"' + value + '" is not a true/false value' }; |
- return { 'valid': true, 'value': parsedValue }; |
-}; |
- |
-/** |
- * @param {string} value |
- * @param {!Element} element |
- * @return {!Object} |
- */ |
-axs.utils.isValidIDRefValue = function(value, element) { |
- if (value.length == 0) |
- return { 'valid': true, 'idref': value }; |
- if (!element.ownerDocument.getElementById(value)) |
- return { 'valid': false, |
- 'idref': value, |
- 'reason': 'No element with ID "' + value + '"' }; |
- return { 'valid': true, 'idref': value }; |
-}; |
- |
-/** |
- * @param {string} value |
- * @return {!Object} |
- */ |
-axs.utils.isValidNumber = function(value) { |
- try { |
- var parsedValue = JSON.parse(value); |
- } catch (ex) { |
- return { 'valid': false, |
- 'value': value, |
- 'reason': '"' + value + '" is not a number' }; |
- } |
- if (typeof(parsedValue) != 'number') { |
- return { 'valid': false, |
- 'value': value, |
- 'reason': '"' + value + '" is not a number' }; |
- } |
- return { 'valid': true, 'value': parsedValue }; |
-}; |
- |
-/** |
- * @param {Element} element |
- * @return {boolean} |
- */ |
-axs.utils.isElementImplicitlyFocusable = function(element) { |
- var defaultView = element.ownerDocument.defaultView; |
- |
- if (element instanceof defaultView.HTMLAnchorElement || |
- element instanceof defaultView.HTMLAreaElement) |
- return element.hasAttribute('href'); |
- if (element instanceof defaultView.HTMLInputElement || |
- element instanceof defaultView.HTMLSelectElement || |
- element instanceof defaultView.HTMLTextAreaElement || |
- element instanceof defaultView.HTMLButtonElement || |
- element instanceof defaultView.HTMLIFrameElement) |
- return !element.disabled; |
- return false; |
-}; |
- |
-/** |
- * Returns an array containing the values of the given JSON-compatible object. |
- * (Simply ignores any function values.) |
- * @param {Object} obj |
- * @return {Array} |
- */ |
-axs.utils.values = function(obj) { |
- var values = []; |
- for (var key in obj) { |
- if (obj.hasOwnProperty(key) && typeof obj[key] != 'function') |
- values.push(obj[key]); |
- } |
- return values; |
-}; |
- |
-/** |
- * Returns an object containing the same keys and values as the given |
- * JSON-compatible object. (Simply ignores any function values.) |
- * @param {Object} obj |
- * @return {Object} |
- */ |
-axs.utils.namedValues = function(obj) { |
- var values = {}; |
- for (var key in obj) { |
- if (obj.hasOwnProperty(key) && typeof obj[key] != 'function') |
- values[key] = obj[key]; |
- } |
- return values |
-}; |
- |
-/** Gets a CSS selector text for a DOM object. |
- * @param {Node} obj The DOM object. |
- * @return {string} CSS selector text for the DOM object. |
- */ |
-axs.utils.getQuerySelectorText = function(obj) { |
- if (obj == null || obj.tagName == 'HTML') { |
- return 'html'; |
- } else if (obj.tagName == 'BODY') { |
- return 'body'; |
- } |
- |
- if (obj.hasAttribute) { |
- if (obj.id) { |
- return '#' + obj.id; |
- } |
- |
- if (obj.className) { |
- var selector = ''; |
- for (var i = 0; i < obj.classList.length; i++) |
- selector += '.' + obj.classList[i]; |
- |
- var total = 0; |
- if (obj.parentNode) { |
- for (i = 0; i < obj.parentNode.children.length; i++) { |
- var similar = obj.parentNode.children[i]; |
- if (axs.browserUtils.matchSelector(similar, selector)) |
- total++; |
- if (similar === obj) |
- break; |
- } |
- } else { |
- total = 1; |
- } |
- |
- if (total == 1) { |
- return axs.utils.getQuerySelectorText(obj.parentNode) + |
- ' > ' + selector; |
- } |
- } |
- |
- if (obj.parentNode) { |
- var similarTags = obj.parentNode.children; |
- var total = 1; |
- var i = 0; |
- while (similarTags[i] !== obj) { |
- if (similarTags[i].tagName == obj.tagName) { |
- total++; |
- } |
- i++; |
- } |
- |
- var next = ''; |
- if (obj.parentNode.tagName != 'BODY') { |
- next = axs.utils.getQuerySelectorText(obj.parentNode) + |
- ' > '; |
- } |
- |
- if (total == 1) { |
- return next + |
- obj.tagName; |
- } else { |
- return next + |
- obj.tagName + |
- ':nth-of-type(' + total + ')'; |
- } |
- } |
- |
- } else if (obj.selectorText) { |
- return obj.selectorText; |
- } |
- |
- return ''; |
-}; |