- commit
- 137aee0f416f98840dd08b9ef1fef7160a9aead7
- parent
- b00dfb587103535e651db71e753e19de153265af
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2018-02-09 06:55
update accdc
Diffstat
| M | babel.js | 5505 | ++++++++++++++++++++++++++++++++----------------------------- |
1 files changed, 2884 insertions, 2621 deletions
diff --git a/babel.js b/babel.js
@@ -1,2291 +1,1641 @@ 1 1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){2 -1 var query = require('./lib/query.js');3 -1 var name = require('./lib/name.js');-1 2 // Copyright 2012 Google Inc. -1 3 // -1 4 // Licensed under the Apache License, Version 2.0 (the "License"); -1 5 // you may not use this file except in compliance with the License. -1 6 // You may obtain a copy of the License at -1 7 // -1 8 // http://www.apache.org/licenses/LICENSE-2.0 -1 9 // -1 10 // Unless required by applicable law or agreed to in writing, software -1 11 // distributed under the License is distributed on an "AS IS" BASIS, -1 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -1 13 // See the License for the specific language governing permissions and -1 14 // limitations under the License. 4 155 -1 module.exports = {6 -1 getRole: query.getRole,7 -1 getAttribute: query.getAttribute,8 -1 getName: name.getName,9 -1 getDescription: name.getDescription,-1 16 goog.require('axs.browserUtils'); -1 17 goog.require('axs.color'); -1 18 goog.require('axs.color.Color'); -1 19 goog.require('axs.constants'); -1 20 goog.require('axs.dom'); 10 2111 -1 matches: query.matches,12 -1 querySelector: query.querySelector,13 -1 querySelectorAll: query.querySelectorAll,14 -1 closest: query.closest,15 -1 };-1 22 goog.provide('axs.utils'); 16 2317 -1 },{"./lib/name.js":3,"./lib/query.js":4}],2:[function(require,module,exports){18 -1 exports.attributes = {19 -1 // widget20 -1 'autocomplete': 'token',21 -1 'checked': 'tristate',22 -1 'current': 'token',23 -1 'disabled': 'bool',24 -1 'expanded': 'bool-undefined',25 -1 'haspopup': 'token',26 -1 'hidden': 'bool', // !27 -1 'invalid': 'token',28 -1 'keyshortcuts': 'string',29 -1 'label': 'string',30 -1 'level': 'int',31 -1 'modal': 'bool',32 -1 'multiline': 'bool',33 -1 'multiselectable': 'bool',34 -1 'orientation': 'token',35 -1 'placeholder': 'string',36 -1 'pressed': 'tristate',37 -1 'readonly': 'bool',38 -1 'required': 'bool',39 -1 'roledescription': 'string',40 -1 'selected': 'bool-undefined',41 -1 'valuemax': 'number',42 -1 'valuemin': 'number',43 -1 'valuenow': 'number',44 -1 'valuetext': 'string',-1 24 /** -1 25 * @const -1 26 * @type {string} -1 27 */ -1 28 axs.utils.FOCUSABLE_ELEMENTS_SELECTOR = -1 29 'input:not([type=hidden]):not([disabled]),' + -1 30 'select:not([disabled]),' + -1 31 'textarea:not([disabled]),' + -1 32 'button:not([disabled]),' + -1 33 'a[href],' + -1 34 'iframe,' + -1 35 '[tabindex]'; 45 3646 -1 // live47 -1 'atomic': 'bool',48 -1 'busy': 'bool',49 -1 'live': 'token',50 -1 'relevant': 'token-list',-1 37 /** -1 38 * Elements that can have labels: https://html.spec.whatwg.org/multipage/forms.html#category-label -1 39 * @const -1 40 * @type {string} -1 41 */ -1 42 axs.utils.LABELABLE_ELEMENTS_SELECTOR = -1 43 'button,' + -1 44 'input:not([type=hidden]),' + -1 45 'keygen,' + -1 46 'meter,' + -1 47 'output,' + -1 48 'progress,' + -1 49 'select,' + -1 50 'textarea'; 51 5152 -1 // dragndrop53 -1 'dropeffect': 'token-list',54 -1 'grabbed': 'bool-undefined',55 5256 -1 // relationship57 -1 'activedescendant': 'id',58 -1 'colcount': 'int',59 -1 'colindex': 'int',60 -1 'colspan': 'int',61 -1 'controls': 'id-list',62 -1 'describedby': 'id-list',63 -1 'details': 'id',64 -1 'errormessage': 'id',65 -1 'flowto': 'id-list',66 -1 'labelledby': 'id-list',67 -1 'owns': 'id-list',68 -1 'posinset': 'int',69 -1 'rowcount': 'int',70 -1 'rowindex': 'int',71 -1 'rowspan': 'int',72 -1 'setsize': 'int',73 -1 'sort': 'token',-1 53 /** -1 54 * @param {Element} element -1 55 * @return {boolean} -1 56 */ -1 57 axs.utils.elementIsTransparent = function(element) { -1 58 return element.style.opacity == '0'; 74 59 }; 75 6076 -1 // https://www.w3.org/TR/html-aria/#docconformance77 -1 exports.extraSelectors = {78 -1 article: ['article'],79 -1 button: [80 -1 'button',81 -1 'input[type="button"]',82 -1 'input[type="image"]',83 -1 'input[type="reset"]',84 -1 'input[type="submit"]',85 -1 'summary',86 -1 ],87 -1 cell: ['td'],88 -1 checkbox: ['input[type="checkbox"]'],89 -1 combobox: [90 -1 'input:not([type])[list]',91 -1 'input[type="email"][list]',92 -1 'input[type="search"][list]',93 -1 'input[type="tel"][list]',94 -1 'input[type="text"][list]',95 -1 'input[type="url"][list]',96 -1 'select:not([multiple])',97 -1 ],98 -1 complementary: ['aside'],99 -1 definition: ['dd'],100 -1 dialog: ['dialog'],101 -1 document: ['body'],102 -1 figure: ['figure'],103 -1 form: ['form[aria-label]', 'form[aria-labelledby]'],104 -1 group: ['details', 'optgroup'],105 -1 heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],106 -1 img: ['img:not([alt=""])'],107 -1 link: ['a[href]', 'area[href]', 'link[href]'],108 -1 list: ['dl', 'ol', 'ul'],109 -1 listbox: ['select[multiple]'],110 -1 listitem: ['dt', 'ul > li', 'ol > li'],111 -1 main: ['main'],112 -1 math: ['math'],113 -1 menuitemcheckbox: ['menuitem[type="checkbox"]'],114 -1 menuitem: ['menuitem[type="command"]'],115 -1 menuitemradio: ['menuitem[type="radio"]'],116 -1 menu: ['menu[type="context"]'],117 -1 navigation: ['nav'],118 -1 option: ['option'],119 -1 progressbar: ['progress'],120 -1 radio: ['input[type="radio"]'],121 -1 region: ['section'],122 -1 rowgroup: ['tbody', 'thead', 'tfoot'],123 -1 row: ['tr'],124 -1 searchbox: ['input[type="search"]:not([list])'],125 -1 separator: ['hr'],126 -1 slider: ['input[type="range"]'],127 -1 spinbutton: ['input[type="number"]'],128 -1 status: ['output'],129 -1 table: ['table'],130 -1 textbox: [131 -1 'input:not([type]):not([list])',132 -1 'input[type="email"]:not([list])',133 -1 'input[type="tel"]:not([list])',134 -1 'input[type="text"]:not([list])',135 -1 'input[type="url"]:not([list])',136 -1 'textarea',137 -1 ],-1 61 /** -1 62 * @param {Element} element -1 63 * @return {boolean} -1 64 */ -1 65 axs.utils.elementHasZeroArea = function(element) { -1 66 var rect = element.getBoundingClientRect(); -1 67 var width = rect.right - rect.left; -1 68 var height = rect.top - rect.bottom; -1 69 if (!width || !height) -1 70 return true; -1 71 return false; -1 72 }; 138 73139 -1 // if scope is missing, it is calculated automatically140 -1 rowheader: ['th[scope="row"]'],141 -1 columnheader: ['th[scope="col"]'],-1 74 /** -1 75 * @param {Element} element -1 76 * @return {boolean} -1 77 */ -1 78 axs.utils.elementIsOutsideScrollArea = function(element) { -1 79 var parent = axs.dom.parentElement(element); -1 80 -1 81 var defaultView = element.ownerDocument.defaultView; -1 82 while (parent != defaultView.document.body) { -1 83 if (axs.utils.isClippedBy(element, parent)) -1 84 return true; -1 85 -1 86 if (axs.utils.canScrollTo(element, parent) && !axs.utils.elementIsOutsideScrollArea(parent)) -1 87 return false; -1 88 -1 89 parent = axs.dom.parentElement(parent); -1 90 } -1 91 -1 92 return !axs.utils.canScrollTo(element, defaultView.document.body); 142 93 }; 143 94144 -1 exports.scoped = [145 -1 'article *', 'aside *', 'main *', 'nav *', 'section *',146 -1 ].join(',');-1 95 /** -1 96 * Checks whether it's possible to scroll to the given element within the given container. -1 97 * Assumes that |container| is an ancestor of |element|. -1 98 * If |container| cannot be scrolled, returns True if the element is within its bounding client -1 99 * rect. -1 100 * @param {Element} element -1 101 * @param {Element} container -1 102 * @return {boolean} True iff it's possible to scroll to |element| within |container|. -1 103 */ -1 104 axs.utils.canScrollTo = function(element, container) { -1 105 var rect = element.getBoundingClientRect(); -1 106 var containerRect = container.getBoundingClientRect(); -1 107 if (container == container.ownerDocument.body) { -1 108 var absoluteTop = containerRect.top; -1 109 var absoluteLeft = containerRect.left; -1 110 } else { -1 111 var absoluteTop = containerRect.top - container.scrollTop; -1 112 var absoluteLeft = containerRect.left - container.scrollLeft; -1 113 } -1 114 var containerScrollArea = -1 115 { top: absoluteTop, -1 116 bottom: absoluteTop + container.scrollHeight, -1 117 left: absoluteLeft, -1 118 right: absoluteLeft + container.scrollWidth }; 147 119148 -1 // https://www.w3.org/TR/wai-aria/roles149 -1 var subRoles = {150 -1 cell: ['gridcell', 'rowheader'],151 -1 command: ['button', 'link', 'menuitem'],152 -1 composite: ['grid', 'select', 'spinbutton', 'tablist'],153 -1 img: ['doc-cover'],154 -1 input: ['checkbox', 'option', 'radio', 'slider', 'spinbutton', 'textbox'],155 -1 landmark: [156 -1 'banner',157 -1 'complementary',158 -1 'contentinfo',159 -1 'doc-acknowledgments',160 -1 'doc-afterword',161 -1 'doc-appendix',162 -1 'doc-bibliography',163 -1 'doc-chapter',164 -1 'doc-conclusion',165 -1 'doc-credits',166 -1 'doc-endnotes',167 -1 'doc-epilogue',168 -1 'doc-errata',169 -1 'doc-foreword',170 -1 'doc-glossary',171 -1 'doc-introduction',172 -1 'doc-part',173 -1 'doc-preface',174 -1 'doc-prologue',175 -1 'form',176 -1 'main',177 -1 'navigation',178 -1 'region',179 -1 'search',180 -1 ],181 -1 range: ['progressbar', 'scrollbar', 'slider', 'spinbutton'],182 -1 roletype: ['structure', 'widget', 'window'],183 -1 section: [184 -1 'alert',185 -1 'cell',186 -1 'definition',187 -1 'doc-abstract',188 -1 'doc-colophon',189 -1 'doc-credit',190 -1 'doc-dedication',191 -1 'doc-epigraph',192 -1 'doc-example',193 -1 'doc-footnote',194 -1 'doc-qna',195 -1 'figure',196 -1 'group',197 -1 'img',198 -1 'landmark',199 -1 'list',200 -1 'listitem',201 -1 'log',202 -1 'marquee',203 -1 'math',204 -1 'note',205 -1 'status',206 -1 'table',207 -1 'tabpanel',208 -1 'term',209 -1 'tooltip',210 -1 ],211 -1 sectionhead: [212 -1 'columnheader',213 -1 'doc-subtitle',214 -1 'heading',215 -1 'rowheader',216 -1 'tab',217 -1 ],218 -1 select: ['combobox', 'listbox', 'menu', 'radiogroup', 'tree'],219 -1 separator: ['doc-pagebreak'],220 -1 structure: [221 -1 'application',222 -1 'document',223 -1 'none',224 -1 'presentation',225 -1 'rowgroup',226 -1 'section',227 -1 'sectionhead',228 -1 'separator',229 -1 ],230 -1 table: ['grid'],231 -1 textbox: ['searchbox'],232 -1 widget: [233 -1 'command',234 -1 'composite',235 -1 'gridcell',236 -1 'input',237 -1 'range',238 -1 'row',239 -1 'separator',240 -1 'tab',241 -1 ],242 -1 window: ['dialog'],243 -1 alert: ['alertdialog'],244 -1 checkbox: ['menuitemcheckbox', 'switch'],245 -1 dialog: ['alertdialog'],246 -1 gridcell: ['columnheader', 'rowheader'],247 -1 menuitem: ['menuitemcheckbox'],248 -1 menuitemcheckbox: ['menuitemradio'],249 -1 option: ['treeitem'],250 -1 radio: ['menuitemradio'],251 -1 status: ['timer'],252 -1 grid: ['treegrid'],253 -1 menu: ['menubar'],254 -1 tree: ['treegrid'],255 -1 document: ['article'],256 -1 group: ['row', 'select', 'toolbar'],257 -1 link: ['doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref'],258 -1 list: ['directory', 'feed'],259 -1 listitem: ['doc-biblioentry', 'doc-endnote', 'treeitem'],260 -1 navigation: ['doc-index', 'doc-pagelist', 'doc-toc'],261 -1 note: ['doc-notice', 'doc-tip'],262 -1 };263 -1264 -1 var getSubRoles = function(role) {265 -1 var children = subRoles[role] || [];266 -1 var descendents = children.map(getSubRoles);-1 120 if (rect.right < containerScrollArea.left || rect.bottom < containerScrollArea.top || -1 121 rect.left > containerScrollArea.right || rect.top > containerScrollArea.bottom) { -1 122 return false; -1 123 } 267 124268 -1 var result = [role];-1 125 var defaultView = element.ownerDocument.defaultView; -1 126 var style = defaultView.getComputedStyle(container); 269 127270 -1 descendents.forEach(function(list) {271 -1 list.forEach(function(r) {272 -1 if (result.indexOf(r) === -1) {273 -1 result.push(r);274 -1 }275 -1 });276 -1 });-1 128 if (rect.left > containerRect.right || rect.top > containerRect.bottom) { -1 129 return (style.overflow == 'scroll' || style.overflow == 'auto' || -1 130 container instanceof defaultView.HTMLBodyElement); -1 131 } 277 132278 -1 return result;-1 133 return true; 279 134 }; 280 135281 -1 exports.subRoles = {};282 -1 for (var role in subRoles) {283 -1 exports.subRoles[role] = getSubRoles(role);284 -1 }285 -1 exports.subRoles['none'] = ['none', 'presentation'];286 -1 exports.subRoles['presentation'] = ['presentation', 'none'];-1 136 /** -1 137 * Checks whether the given element is clipped by the given container. -1 138 * Assumes that |container| is an ancestor of |element|. -1 139 * @param {Element} element -1 140 * @param {Element} container -1 141 * @return {boolean} True iff |element| is clipped by |container|. -1 142 */ -1 143 axs.utils.isClippedBy = function(element, container) { -1 144 var rect = element.getBoundingClientRect(); -1 145 var containerRect = container.getBoundingClientRect(); -1 146 var containerTop = containerRect.top; -1 147 var containerLeft = containerRect.left; -1 148 var containerScrollArea = -1 149 { top: containerTop - container.scrollTop, -1 150 bottom: containerTop - container.scrollTop + container.scrollHeight, -1 151 left: containerLeft - container.scrollLeft, -1 152 right: containerLeft - container.scrollLeft + container.scrollWidth }; 287 153288 -1 exports.nameFromContents = [289 -1 'button',290 -1 'checkbox',291 -1 'columnheader',292 -1 'doc-backlink',293 -1 'doc-biblioref',294 -1 'doc-glossref',295 -1 'doc-noteref',296 -1 'gridcell',297 -1 'heading',298 -1 'link',299 -1 'menuitem',300 -1 'menuitemcheckbox',301 -1 'menuitemradio',302 -1 'option',303 -1 'radio',304 -1 'row',305 -1 'rowgroup',306 -1 'rowheader',307 -1 'sectionhead',308 -1 'tab',309 -1 'tooltip',310 -1 'treeitem',311 -1 'switch',312 -1 ];-1 154 var defaultView = element.ownerDocument.defaultView; -1 155 var style = defaultView.getComputedStyle(container); 313 156314 -1 exports.labelable = [315 -1 'button',316 -1 'input:not([type="hidden"])',317 -1 'keygen',318 -1 'meter',319 -1 'output',320 -1 'progress',321 -1 'select',322 -1 'textarea',323 -1 ];-1 157 if ((rect.right < containerRect.left || rect.bottom < containerRect.top || -1 158 rect.left > containerRect.right || rect.top > containerRect.bottom) && -1 159 style.overflow == 'hidden') { -1 160 return true; -1 161 } 324 162325 -1 },{}],3:[function(require,module,exports){326 -1 var constants = require('./constants.js');327 -1 var query = require('./query.js');328 -1 var util = require('./util.js');-1 163 if (rect.right < containerScrollArea.left || rect.bottom < containerScrollArea.top) -1 164 return (style.overflow != 'visible'); 329 165330 -1 var getPseudoContent = function(node, selector) {331 -1 var styles = window.getComputedStyle(node, selector);332 -1 var ret = styles.getPropertyValue('content');333 -1 if (ret === 'none' || ret.substr(0, 4) === '-moz') {334 -1 return '';335 -1 } else {336 -1 return ret337 -1 .replace(/^["']/, '')338 -1 .replace(/["']$/, '');339 -1 }-1 166 return false; 340 167 }; 341 168342 -1 var getContent = function(root, referenced) {343 -1 var ret = getPseudoContent(root, ':before');344 -1 var node = root.firstChild;345 -1 while (node) {346 -1 if (node.nodeType === node.TEXT_NODE) {347 -1 ret += node.textContent;348 -1 } else if (node.nodeType === node.ELEMENT_NODE) {349 -1 ret += getName(node, true, referenced);350 -1 }351 -1 node = node.nextSibling;352 -1 }353 -1 ret += getPseudoContent(root, ':after');354 -1 return ret;355 -1 };-1 169 /** -1 170 * @param {Node} ancestor A potential ancestor of |node|. -1 171 * @param {Node} node -1 172 * @return {boolean} true if |ancestor| is an ancestor of |node| (including -1 173 * |ancestor| === |node|). -1 174 */ -1 175 axs.utils.isAncestor = function(ancestor, node) { -1 176 if (node == null) -1 177 return false; -1 178 if (node === ancestor) -1 179 return true; 356 180357 -1 var allowNameFromContent = function(el) {358 -1 var role = query.getRole(el);359 -1 return !role || constants.nameFromContents.indexOf(role) !== -1;-1 181 var parentNode = axs.dom.composedParentNode(node); -1 182 return axs.utils.isAncestor(ancestor, parentNode); 360 183 }; 361 184362 -1 var isLabelable = function(el) {363 -1 var selector = constants.labelable.join(',');364 -1 return el.matches(selector);365 -1 };-1 185 /** -1 186 * @param {Element} element -1 187 * @return {Array.<Element>} An array of any non-transparent elements which -1 188 * overlap the given element. -1 189 */ -1 190 axs.utils.overlappingElements = function(element) { -1 191 if (axs.utils.elementHasZeroArea(element)) -1 192 return null; 366 193367 -1 // Control.labels is part of the standard, but not supported in most browsers368 -1 var getLabelNodes = function(element) {369 -1 var labels = [];370 -1 var labelable = constants.labelable.join(',');371 -1 util.walkDOM(document.body, function(node) {372 -1 if (node.tagName && node.tagName.toLowerCase() === 'label') {373 -1 if (node.getAttribute('for')) {374 -1 if (element.id && node.getAttribute('for') === element.id) {375 -1 labels.push(node);376 -1 }377 -1 } else if (node.querySelector(labelable) === element) {378 -1 labels.push(node);379 -1 }380 -1 }381 -1 });382 -1 return labels;383 -1 };-1 194 var overlappingElements = []; -1 195 var clientRects = element.getClientRects(); -1 196 for (var i = 0; i < clientRects.length; i++) { -1 197 var rect = clientRects[i]; -1 198 var center_x = (rect.left + rect.right) / 2; -1 199 var center_y = (rect.top + rect.bottom) / 2; -1 200 var elementAtPoint = document.elementFromPoint(center_x, center_y); 384 201385 -1 // http://www.ssbbartgroup.com/blog/how-the-w3c-text-alternative-computation-works/386 -1 // https://www.w3.org/TR/accname-aam-1.1/#h-mapping_additional_nd_te387 -1 var getName = function(el, recursive, referenced) {388 -1 var ret;-1 202 if (elementAtPoint == null || elementAtPoint == element || -1 203 axs.utils.isAncestor(elementAtPoint, element) || -1 204 axs.utils.isAncestor(element, elementAtPoint)) { -1 205 continue; -1 206 } 389 207390 -1 if (query.getAttribute(el, 'hidden', referenced)) {391 -1 return '';392 -1 }393 -1 if (query.matches(el, 'presentation')) {394 -1 return getContent(el, referenced);395 -1 }396 -1 if (!recursive && el.matches('[aria-labelledby]')) {397 -1 var ids = el.getAttribute('aria-labelledby').split(/\s+/);398 -1 var strings = ids.map(function(id) {399 -1 var label = document.getElementById(id);400 -1 return getName(label, true, label);401 -1 });402 -1 ret = strings.join(' ');403 -1 }404 -1 if (!ret && el.matches('[aria-label]')) {405 -1 ret = el.getAttribute('aria-label');406 -1 }407 -1 if (!query.matches(el, 'presentation')) {408 -1 if (!ret && !recursive && isLabelable(el)) {409 -1 var strings = getLabelNodes(el).map(function(label) {410 -1 return getName(label, true, label);411 -1 });412 -1 ret = strings.join(' ');413 -1 }414 -1 if (!ret) {415 -1 ret = el.getAttribute('placeholder');416 -1 }417 -1 if (!ret) {418 -1 ret = el.getAttribute('alt');419 -1 }420 -1 if (!ret && el.matches('abbr,acronym') && el.title) {421 -1 ret = el.title;422 -1 }423 -1 // figcaption424 -1 // caption425 -1 // table426 -1 }427 -1 // FIXME only if this is embedded in a label428 -1 if (!ret && query.matches(el, 'textbox,button,combobox,range')) {429 -1 if (query.matches(el, 'textbox,button')) {430 -1 ret = el.value || el.textContent;431 -1 } else if (query.matches(el, 'combobox')) {432 -1 var selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');433 -1 if (selected) {434 -1 ret = getName(selected, recursive, referenced);435 -1 }436 -1 } else if (query.matches(el, 'range')) {437 -1 ret = '' + (query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value);438 -1 }439 -1 }440 -1 if (!ret && (recursive || allowNameFromContent(el))) {441 -1 ret = getContent(el, referenced);442 -1 }443 -1 if (!ret) {444 -1 ret = el.getAttribute('title');445 -1 }-1 208 var overlappingElementStyle = window.getComputedStyle(elementAtPoint, null); -1 209 if (!overlappingElementStyle) -1 210 continue; 446 211447 -1 return (ret || '').trim().replace(/\s+/g, ' ');-1 212 var overlappingElementBg = axs.utils.getBgColor(overlappingElementStyle, -1 213 elementAtPoint); -1 214 if (overlappingElementBg && overlappingElementBg.alpha > 0 && -1 215 overlappingElements.indexOf(elementAtPoint) < 0) { -1 216 overlappingElements.push(elementAtPoint); -1 217 } -1 218 } -1 219 -1 220 return overlappingElements; 448 221 }; 449 222450 -1 var getDescription = function(el) {451 -1 var ret = '';-1 223 /** -1 224 * @param {Element} element -1 225 * @return {boolean} -1 226 */ -1 227 axs.utils.elementIsHtmlControl = function(element) { -1 228 var defaultView = element.ownerDocument.defaultView; 452 229453 -1 if (el.matches('[aria-describedby]')) {454 -1 var ids = el.getAttribute('aria-describedby').split(/\s+/);455 -1 var strings = ids.map(function(id) {456 -1 var label = document.getElementById(id);457 -1 return getName(label, true, label);458 -1 });459 -1 ret = strings.join(' ');460 -1 } else if (el.title) {461 -1 ret = el.title;462 -1 } else if (el.placeholder) {463 -1 ret = el.placeholder;464 -1 }-1 230 // HTML control -1 231 if (element instanceof defaultView.HTMLButtonElement) -1 232 return true; -1 233 if (element instanceof defaultView.HTMLInputElement) -1 234 return true; -1 235 if (element instanceof defaultView.HTMLSelectElement) -1 236 return true; -1 237 if (element instanceof defaultView.HTMLTextAreaElement) -1 238 return true; 465 239466 -1 return (ret || '').trim().replace(/\s+/g, ' ');-1 240 return false; 467 241 }; 468 242469 -1 module.exports = {470 -1 getName: getName,471 -1 getDescription: getDescription,-1 243 /** -1 244 * @param {Element} element -1 245 * @return {boolean} -1 246 */ -1 247 axs.utils.elementIsAriaWidget = function(element) { -1 248 if (element.hasAttribute('role')) { -1 249 var roleValue = element.getAttribute('role'); -1 250 // TODO is this correct? -1 251 if (roleValue) { -1 252 var role = axs.constants.ARIA_ROLES[roleValue]; -1 253 if (role && 'widget' in role['allParentRolesSet']) -1 254 return true; -1 255 } -1 256 } -1 257 return false; 472 258 }; 473 259474 -1 },{"./constants.js":2,"./query.js":4,"./util.js":5}],4:[function(require,module,exports){475 -1 var constants = require('./constants.js');476 -1 var util = require('./util.js');-1 260 /** -1 261 * @param {Element} element -1 262 * @return {boolean} -1 263 */ -1 264 axs.utils.elementIsVisible = function(element) { -1 265 if (axs.utils.elementIsTransparent(element)) -1 266 return false; -1 267 if (axs.utils.elementHasZeroArea(element)) -1 268 return false; -1 269 if (axs.utils.elementIsOutsideScrollArea(element)) -1 270 return false; 477 271478 -1 var getSubRoles = function(roles) {479 -1 return [].concat.apply([], roles.map(function(role) {480 -1 return constants.subRoles[role] || [role];481 -1 }));-1 272 var overlappingElements = axs.utils.overlappingElements(element); -1 273 if (overlappingElements.length) -1 274 return false; -1 275 -1 276 return true; 482 277 }; 483 278484 -1 // candidates can be passed for performance optimization485 -1 var _getRole = function(el, candidates) {486 -1 if (el.hasAttribute('role')) {487 -1 return el.getAttribute('role');488 -1 }489 -1 for (var role in constants.extraSelectors) {490 -1 var selector = constants.extraSelectors[role].join(',');491 -1 if ((!candidates || candidates.indexOf(role) !== -1) && el.matches(selector)) {492 -1 return role;493 -1 }494 -1 }-1 279 /** -1 280 * @param {CSSStyleDeclaration} style -1 281 * @return {boolean} -1 282 */ -1 283 axs.utils.isLargeFont = function(style) { -1 284 var fontSize = style.fontSize; -1 285 var bold = style.fontWeight == 'bold'; -1 286 var matches = fontSize.match(/(\d+)px/); -1 287 if (matches) { -1 288 var fontSizePx = parseInt(matches[1], 10); -1 289 var bodyStyle = window.getComputedStyle(document.body, null); -1 290 var bodyFontSize = bodyStyle.fontSize; -1 291 matches = bodyFontSize.match(/(\d+)px/); -1 292 if (matches) { -1 293 var bodyFontSizePx = parseInt(matches[1], 10); -1 294 var boldLarge = bodyFontSizePx * 1.2; -1 295 var large = bodyFontSizePx * 1.5; -1 296 } else { -1 297 var boldLarge = 19.2; -1 298 var large = 24; -1 299 } -1 300 return (bold && fontSizePx >= boldLarge || fontSizePx >= large); -1 301 } -1 302 matches = fontSize.match(/(\d+)em/); -1 303 if (matches) { -1 304 var fontSizeEm = parseInt(matches[1], 10); -1 305 if (bold && fontSizeEm >= 1.2 || fontSizeEm >= 1.5) -1 306 return true; -1 307 return false; -1 308 } -1 309 matches = fontSize.match(/(\d+)%/); -1 310 if (matches) { -1 311 var fontSizePercent = parseInt(matches[1], 10); -1 312 if (bold && fontSizePercent >= 120 || fontSizePercent >= 150) -1 313 return true; -1 314 return false; -1 315 } -1 316 matches = fontSize.match(/(\d+)pt/); -1 317 if (matches) { -1 318 var fontSizePt = parseInt(matches[1], 10); -1 319 if (bold && fontSizePt >= 14 || fontSizePt >= 18) -1 320 return true; -1 321 return false; -1 322 } -1 323 return false; -1 324 }; 495 325496 -1 if (!candidates ||497 -1 candidates.indexOf('banner') !== -1 ||498 -1 candidates.indexOf('contentinfo') !== -1) {499 -1 var scoped = el.matches(constants.scoped);-1 326 /** -1 327 * @param {CSSStyleDeclaration} style -1 328 * @param {Element} element -1 329 * @return {?axs.color.Color} -1 330 */ -1 331 axs.utils.getBgColor = function(style, element) { -1 332 var bgColorString = style.backgroundColor; -1 333 var bgColor = axs.color.parseColor(bgColorString); -1 334 if (!bgColor) -1 335 return null; 500 336501 -1 if (el.matches('header') && !scoped) {502 -1 return 'banner';503 -1 }504 -1 if (el.matches('footer') && !scoped) {505 -1 return 'contentinfo';506 -1 }507 -1 }-1 337 if (style.opacity < 1) -1 338 bgColor.alpha = bgColor.alpha * style.opacity; -1 339 -1 340 if (bgColor.alpha < 1) { -1 341 var parentBg = axs.utils.getParentBgColor(element); -1 342 if (parentBg == null) -1 343 return null; -1 344 -1 345 bgColor = axs.color.flattenColors(bgColor, parentBg); -1 346 } -1 347 return bgColor; 508 348 }; 509 349510 -1 var getAttribute = function(el, key, _hiddenRoot) {511 -1 if (key === 'hidden' && el === _hiddenRoot) { // used for name calculation512 -1 return false;513 -1 }-1 350 /** -1 351 * Gets the effective background color of the parent of |element|. -1 352 * @param {Element} element -1 353 * @return {?axs.color.Color} -1 354 */ -1 355 axs.utils.getParentBgColor = function(element) { -1 356 /** @type {Element} */ var parent = element; -1 357 var bgStack = []; -1 358 var foundSolidColor = null; -1 359 while ((parent = axs.dom.parentElement(parent))) { -1 360 var computedStyle = window.getComputedStyle(parent, null); -1 361 if (!computedStyle) -1 362 continue; 514 363515 -1 var type = constants.attributes[key];516 -1 var raw = el.getAttribute('aria-' + key);-1 364 var parentBg = axs.color.parseColor(computedStyle.backgroundColor); -1 365 if (!parentBg) -1 366 continue; 517 367518 -1 if (raw) {519 -1 if (type === 'bool') {520 -1 return raw === 'true';521 -1 } else if (type === 'tristate') {522 -1 return raw === 'true' ? true : raw === 'false' ? false : 'mixed';523 -1 } else if (type === 'bool-undefined') {524 -1 return raw === 'true' ? true : raw === 'false' ? false : undefined;525 -1 } else if (type === 'id-list') {526 -1 return raw.split(/\s+/);527 -1 } else if (type === 'integer') {528 -1 return parseInt(raw);529 -1 } else if (type === 'number') {530 -1 return parseFloat(raw);531 -1 } else if (type === 'token-list') {532 -1 return raw.split(/\s+/);533 -1 } else {534 -1 return raw;535 -1 }536 -1 }537 -1538 -1 if (key === 'level') {539 -1 for (var i = 1; i <= 6; i++) {540 -1 if (el.tagName.toLowerCase() === 'h' + i) {541 -1 return i;542 -1 }543 -1 }544 -1 } else if (key === 'disabled') {545 -1 return el.disabled;546 -1 } else if (key === 'placeholder') {547 -1 return el.placeholder;548 -1 } else if (key === 'required') {549 -1 return el.required;550 -1 } else if (key === 'readonly') {551 -1 return el.readOnly && !el.isContentEditable;552 -1 } else if (key === 'hidden') {553 -1 var style = window.getComputedStyle(el);554 -1 if (el.hidden || style.display === 'none' || style.visibility === 'hidden') {555 -1 return true;556 -1 } else if (el.clientHeight === 0) { // rough check for performance557 -1 return el.parentNode && getAttribute(el.parentNode, 'hidden', _hiddenRoot);558 -1 }559 -1 } else if (key === 'invalid' && el.checkValidity) {560 -1 return el.checkValidity();561 -1 }-1 368 if (computedStyle.opacity < 1) -1 369 parentBg.alpha = parentBg.alpha * computedStyle.opacity; 562 370563 -1 if (type === 'bool' || type === 'tristate') {564 -1 return false;565 -1 }566 -1 };-1 371 if (parentBg.alpha == 0) -1 372 continue; 567 373568 -1 var matches = function(el, selector) {569 -1 var actual;-1 374 bgStack.push(parentBg); 570 375571 -1 if (selector.substr(0, 1) === ':') {572 -1 var attr = selector.substr(1);573 -1 return getAttribute(el, attr);574 -1 } else if (selector.substr(0, 1) === '[') {575 -1 var match = /\[([a-z]+)="(.*)"\]/.exec(selector);576 -1 actual = getAttribute(el, match[1]);577 -1 var rawValue = match[2];578 -1 return actual.toString() == rawValue;579 -1 } else {580 -1 var candidates = getSubRoles(selector.split(','));581 -1 actual = _getRole(el, candidates);582 -1 return candidates.indexOf(actual) !== -1;583 -1 }584 -1 };-1 376 if (parentBg.alpha == 1) { -1 377 foundSolidColor = true; -1 378 break; -1 379 } -1 380 } 585 381586 -1 var _querySelector = function(all) {587 -1 return function(root, role) {588 -1 var results = [];589 -1 util.walkDOM(root, function(node) {590 -1 if (node.nodeType === node.ELEMENT_NODE) {591 -1 // FIXME: skip hidden elements592 -1 if (matches(node, role)) {593 -1 results.push(node);594 -1 if (!all) {595 -1 return false;596 -1 }597 -1 }598 -1 }599 -1 });600 -1 return all ? results : results[0];601 -1 };602 -1 };-1 382 if (!foundSolidColor) -1 383 bgStack.push(new axs.color.Color(255, 255, 255, 1)); 603 384604 -1 var closest = function(el, selector) {605 -1 return util.searchUp(el, function(candidate) {606 -1 return matches(candidate, selector);607 -1 });-1 385 var bg = bgStack.pop(); -1 386 while (bgStack.length) { -1 387 var fg = bgStack.pop(); -1 388 bg = axs.color.flattenColors(fg, bg); -1 389 } -1 390 return bg; 608 391 }; 609 392610 -1 module.exports = {611 -1 getRole: function(el) {612 -1 return _getRole(el);613 -1 },614 -1 getAttribute: getAttribute,615 -1 matches: matches,616 -1 querySelector: _querySelector(),617 -1 querySelectorAll: _querySelector(true),618 -1 closest: closest,619 -1 };-1 393 /** -1 394 * @param {CSSStyleDeclaration} style -1 395 * @param {Element} element -1 396 * @param {axs.color.Color} bgColor The background color, which may come from -1 397 * another element (such as a parent element), for flattening into the -1 398 * foreground color. -1 399 * @return {?axs.color.Color} -1 400 */ -1 401 axs.utils.getFgColor = function(style, element, bgColor) { -1 402 var fgColorString = style.color; -1 403 var fgColor = axs.color.parseColor(fgColorString); -1 404 if (!fgColor) -1 405 return null; 620 406621 -1 },{"./constants.js":2,"./util.js":5}],5:[function(require,module,exports){622 -1 var walkDOM = function(root, fn) {623 -1 if (fn(root) === false) {624 -1 return false;625 -1 }626 -1 var node = root.firstChild;627 -1 while (node) {628 -1 if (walkDOM(node, fn) === false) {629 -1 return false;630 -1 }631 -1 node = node.nextSibling;632 -1 }633 -1 };-1 407 if (fgColor.alpha < 1) -1 408 fgColor = axs.color.flattenColors(fgColor, bgColor); 634 409635 -1 var searchUp = function(el, test) {636 -1 var candidate = el.parentElement;637 -1 if (candidate) {638 -1 if (test(candidate)) {639 -1 return candidate;640 -1 } else {641 -1 return searchUp(candidate, test);642 -1 }643 -1 }644 -1 };-1 410 if (style.opacity < 1) { -1 411 var parentBg = axs.utils.getParentBgColor(element); -1 412 fgColor.alpha = fgColor.alpha * style.opacity; -1 413 fgColor = axs.color.flattenColors(fgColor, parentBg); -1 414 } 645 415646 -1 module.exports = {647 -1 walkDOM: walkDOM,648 -1 searchUp: searchUp,-1 416 return fgColor; 649 417 }; 650 418651 -1 },{}],6:[function(require,module,exports){652 -1 // Copyright 2012 Google Inc.653 -1 //654 -1 // Licensed under the Apache License, Version 2.0 (the "License");655 -1 // you may not use this file except in compliance with the License.656 -1 // You may obtain a copy of the License at657 -1 //658 -1 // http://www.apache.org/licenses/LICENSE-2.0659 -1 //660 -1 // Unless required by applicable law or agreed to in writing, software661 -1 // distributed under the License is distributed on an "AS IS" BASIS,662 -1 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.663 -1 // See the License for the specific language governing permissions and664 -1 // limitations under the License.665 -1666 -1 goog.require('axs.browserUtils');667 -1 goog.require('axs.color');668 -1 goog.require('axs.color.Color');669 -1 goog.require('axs.constants');670 -1 goog.require('axs.dom');671 -1672 -1 goog.provide('axs.utils');673 -1674 419 /**675 -1 * @const676 -1 * @type {string}-1 420 * @param {Element} element -1 421 * @return {?number} 677 422 */678 -1 axs.utils.FOCUSABLE_ELEMENTS_SELECTOR =679 -1 'input:not([type=hidden]):not([disabled]),' +680 -1 'select:not([disabled]),' +681 -1 'textarea:not([disabled]),' +682 -1 'button:not([disabled]),' +683 -1 'a[href],' +684 -1 'iframe,' +685 -1 '[tabindex]';-1 423 axs.utils.getContrastRatioForElement = function(element) { -1 424 var style = window.getComputedStyle(element, null); -1 425 return axs.utils.getContrastRatioForElementWithComputedStyle(style, element); -1 426 }; 686 427 687 428 /**688 -1 * Elements that can have labels: https://html.spec.whatwg.org/multipage/forms.html#category-label689 -1 * @const690 -1 * @type {string}-1 429 * @param {CSSStyleDeclaration} style -1 430 * @param {Element} element -1 431 * @return {?number} 691 432 */692 -1 axs.utils.LABELABLE_ELEMENTS_SELECTOR =693 -1 'button,' +694 -1 'input:not([type=hidden]),' +695 -1 'keygen,' +696 -1 'meter,' +697 -1 'output,' +698 -1 'progress,' +699 -1 'select,' +700 -1 'textarea';-1 433 axs.utils.getContrastRatioForElementWithComputedStyle = function(style, element) { -1 434 if (axs.utils.isElementHidden(element)) -1 435 return null; 701 436 -1 437 var bgColor = axs.utils.getBgColor(style, element); -1 438 if (!bgColor) -1 439 return null; 702 440703 -1 /**704 -1 * @param {Element} element705 -1 * @return {boolean}706 -1 */707 -1 axs.utils.elementIsTransparent = function(element) {708 -1 return element.style.opacity == '0';-1 441 var fgColor = axs.utils.getFgColor(style, element, bgColor); -1 442 if (!fgColor) -1 443 return null; -1 444 -1 445 return axs.color.calculateContrastRatio(fgColor, bgColor); 709 446 }; 710 447 711 448 /** 712 449 * @param {Element} element 713 450 * @return {boolean} 714 451 */715 -1 axs.utils.elementHasZeroArea = function(element) {716 -1 var rect = element.getBoundingClientRect();717 -1 var width = rect.right - rect.left;718 -1 var height = rect.top - rect.bottom;719 -1 if (!width || !height)-1 452 axs.utils.isNativeTextElement = function(element) { -1 453 var tagName = element.tagName.toLowerCase(); -1 454 var type = element.type ? element.type.toLowerCase() : ''; -1 455 if (tagName == 'textarea') 720 456 return true;721 -1 return false;-1 457 if (tagName != 'input') -1 458 return false; -1 459 -1 460 switch (type) { -1 461 case 'email': -1 462 case 'number': -1 463 case 'password': -1 464 case 'search': -1 465 case 'text': -1 466 case 'tel': -1 467 case 'url': -1 468 case '': -1 469 return true; -1 470 default: -1 471 return false; -1 472 } 722 473 }; 723 474 724 475 /**725 -1 * @param {Element} element-1 476 * @param {number} contrastRatio -1 477 * @param {CSSStyleDeclaration} style -1 478 * @param {boolean=} opt_strict Whether to use AA (false) or AAA (true) level 726 479 * @return {boolean} 727 480 */728 -1 axs.utils.elementIsOutsideScrollArea = function(element) {729 -1 var parent = axs.dom.parentElement(element);730 -1731 -1 var defaultView = element.ownerDocument.defaultView;732 -1 while (parent != defaultView.document.body) {733 -1 if (axs.utils.isClippedBy(element, parent))734 -1 return true;735 -1736 -1 if (axs.utils.canScrollTo(element, parent) && !axs.utils.elementIsOutsideScrollArea(parent))737 -1 return false;738 -1739 -1 parent = axs.dom.parentElement(parent);740 -1 }741 -1742 -1 return !axs.utils.canScrollTo(element, defaultView.document.body);743 -1 };744 -1745 -1 /**746 -1 * Checks whether it's possible to scroll to the given element within the given container.747 -1 * Assumes that |container| is an ancestor of |element|.748 -1 * If |container| cannot be scrolled, returns True if the element is within its bounding client749 -1 * rect.750 -1 * @param {Element} element751 -1 * @param {Element} container752 -1 * @return {boolean} True iff it's possible to scroll to |element| within |container|.753 -1 */754 -1 axs.utils.canScrollTo = function(element, container) {755 -1 var rect = element.getBoundingClientRect();756 -1 var containerRect = container.getBoundingClientRect();757 -1 if (container == container.ownerDocument.body) {758 -1 var absoluteTop = containerRect.top;759 -1 var absoluteLeft = containerRect.left;-1 481 axs.utils.isLowContrast = function(contrastRatio, style, opt_strict) { -1 482 // Round to nearest 0.1 -1 483 var roundedContrastRatio = (Math.round(contrastRatio * 10) / 10); -1 484 if (!opt_strict) { -1 485 return roundedContrastRatio < 3.0 || -1 486 (!axs.utils.isLargeFont(style) && roundedContrastRatio < 4.5); 760 487 } else {761 -1 var absoluteTop = containerRect.top - container.scrollTop;762 -1 var absoluteLeft = containerRect.left - container.scrollLeft;763 -1 }764 -1 var containerScrollArea =765 -1 { top: absoluteTop,766 -1 bottom: absoluteTop + container.scrollHeight,767 -1 left: absoluteLeft,768 -1 right: absoluteLeft + container.scrollWidth };769 -1770 -1 if (rect.right < containerScrollArea.left || rect.bottom < containerScrollArea.top ||771 -1 rect.left > containerScrollArea.right || rect.top > containerScrollArea.bottom) {772 -1 return false;773 -1 }774 -1775 -1 var defaultView = element.ownerDocument.defaultView;776 -1 var style = defaultView.getComputedStyle(container);777 -1778 -1 if (rect.left > containerRect.right || rect.top > containerRect.bottom) {779 -1 return (style.overflow == 'scroll' || style.overflow == 'auto' ||780 -1 container instanceof defaultView.HTMLBodyElement);-1 488 return roundedContrastRatio < 4.5 || -1 489 (!axs.utils.isLargeFont(style) && roundedContrastRatio < 7.0); 781 490 }782 -1783 -1 return true;784 491 }; 785 492 786 493 /**787 -1 * Checks whether the given element is clipped by the given container.788 -1 * Assumes that |container| is an ancestor of |element|.789 494 * @param {Element} element790 -1 * @param {Element} container791 -1 * @return {boolean} True iff |element| is clipped by |container|.-1 495 * @return {boolean} 792 496 */793 -1 axs.utils.isClippedBy = function(element, container) {794 -1 var rect = element.getBoundingClientRect();795 -1 var containerRect = container.getBoundingClientRect();796 -1 var containerTop = containerRect.top;797 -1 var containerLeft = containerRect.left;798 -1 var containerScrollArea =799 -1 { top: containerTop - container.scrollTop,800 -1 bottom: containerTop - container.scrollTop + container.scrollHeight,801 -1 left: containerLeft - container.scrollLeft,802 -1 right: containerLeft - container.scrollLeft + container.scrollWidth };-1 497 axs.utils.hasLabel = function(element) { -1 498 var tagName = element.tagName.toLowerCase(); -1 499 var type = element.type ? element.type.toLowerCase() : ''; 803 500804 -1 var defaultView = element.ownerDocument.defaultView;805 -1 var style = defaultView.getComputedStyle(container);-1 501 if (element.hasAttribute('aria-label')) -1 502 return true; -1 503 if (element.hasAttribute('title')) -1 504 return true; -1 505 if (tagName == 'img' && element.hasAttribute('alt')) -1 506 return true; -1 507 if (tagName == 'input' && type == 'image' && element.hasAttribute('alt')) -1 508 return true; -1 509 if (tagName == 'input' && (type == 'submit' || type == 'reset')) -1 510 return true; 806 511807 -1 if ((rect.right < containerRect.left || rect.bottom < containerRect.top ||808 -1 rect.left > containerRect.right || rect.top > containerRect.bottom) &&809 -1 style.overflow == 'hidden') {-1 512 // There's a separate audit that makes sure this points to an actual element or elements. -1 513 if (element.hasAttribute('aria-labelledby')) 810 514 return true;811 -1 }812 515813 -1 if (rect.right < containerScrollArea.left || rect.bottom < containerScrollArea.top)814 -1 return (style.overflow != 'visible');-1 516 if (element.hasAttribute('id')) { -1 517 var labelsFor = document.querySelectorAll('label[for="' + element.id + '"]'); -1 518 if (labelsFor.length > 0) -1 519 return true; -1 520 } 815 521 -1 522 var parent = axs.dom.parentElement(element); -1 523 while (parent) { -1 524 if (parent.tagName.toLowerCase() == 'label') { -1 525 var parentLabel = /** HTMLLabelElement */ parent; -1 526 if (parentLabel.control == element) -1 527 return true; -1 528 } -1 529 parent = axs.dom.parentElement(parent); -1 530 } 816 531 return false; 817 532 }; 818 533 819 534 /**820 -1 * @param {Node} ancestor A potential ancestor of |node|.821 -1 * @param {Node} node822 -1 * @return {boolean} true if |ancestor| is an ancestor of |node| (including823 -1 * |ancestor| === |node|).-1 535 * Determine if this element natively supports being disabled (i.e. via the `disabled` attribute. -1 536 * Disabled here means that the element should be considered disabled according to specification. -1 537 * This element may or may not be effectively disabled in practice as this is dependent on implementation. -1 538 * -1 539 * @param {Element} element An element to check. -1 540 * @return {boolean} true If the element supports being natively disabled. 824 541 */825 -1 axs.utils.isAncestor = function(ancestor, node) {826 -1 if (node == null)827 -1 return false;828 -1 if (node === ancestor)829 -1 return true;830 -1831 -1 var parentNode = axs.dom.composedParentNode(node);832 -1 return axs.utils.isAncestor(ancestor, parentNode);-1 542 axs.utils.isNativelyDisableable = function(element) { -1 543 var tagName = element.tagName.toUpperCase(); -1 544 return (tagName in axs.constants.NATIVELY_DISABLEABLE); 833 545 }; 834 546 835 547 /**836 -1 * @param {Element} element837 -1 * @return {Array.<Element>} An array of any non-transparent elements which838 -1 * overlap the given element.-1 548 * Determine if this element is disabled directly or indirectly by a disabled ancestor. -1 549 * Disabled here means that the element should be considered disabled according to specification. -1 550 * This element may or may not be effectively disabled in practice as this is dependent on implementation. -1 551 * -1 552 * @param {Element} element An element to check. -1 553 * @param {boolean=} ignoreAncestors If true do not check for disabled ancestors. -1 554 * @return {boolean} true if the element or one of its ancestors is disabled. 839 555 */840 -1 axs.utils.overlappingElements = function(element) {841 -1 if (axs.utils.elementHasZeroArea(element))842 -1 return null;843 -1844 -1 var overlappingElements = [];845 -1 var clientRects = element.getClientRects();846 -1 for (var i = 0; i < clientRects.length; i++) {847 -1 var rect = clientRects[i];848 -1 var center_x = (rect.left + rect.right) / 2;849 -1 var center_y = (rect.top + rect.bottom) / 2;850 -1 var elementAtPoint = document.elementFromPoint(center_x, center_y);851 -1852 -1 if (elementAtPoint == null || elementAtPoint == element ||853 -1 axs.utils.isAncestor(elementAtPoint, element) ||854 -1 axs.utils.isAncestor(element, elementAtPoint)) {855 -1 continue;-1 556 axs.utils.isElementDisabled = function(element, ignoreAncestors) { -1 557 var selector = ignoreAncestors ? '[aria-disabled=true]' : '[aria-disabled=true], [aria-disabled=true] *'; -1 558 if (axs.browserUtils.matchSelector(element, selector)) { -1 559 return true; -1 560 } -1 561 if (!axs.utils.isNativelyDisableable(element) || -1 562 axs.browserUtils.matchSelector(element, 'fieldset>legend:first-of-type *')) { -1 563 return false; -1 564 } -1 565 for (var next = element; next !== null; next = axs.dom.parentElement(next)) { -1 566 if (next.hasAttribute('disabled')) { -1 567 return true; 856 568 }857 -1858 -1 var overlappingElementStyle = window.getComputedStyle(elementAtPoint, null);859 -1 if (!overlappingElementStyle)860 -1 continue;861 -1862 -1 var overlappingElementBg = axs.utils.getBgColor(overlappingElementStyle,863 -1 elementAtPoint);864 -1 if (overlappingElementBg && overlappingElementBg.alpha > 0 &&865 -1 overlappingElements.indexOf(elementAtPoint) < 0) {866 -1 overlappingElements.push(elementAtPoint);-1 569 if (ignoreAncestors) { -1 570 return false; 867 571 } 868 572 }869 -1870 -1 return overlappingElements;-1 573 return false; 871 574 }; 872 575 873 576 /**874 -1 * @param {Element} element875 -1 * @return {boolean}-1 577 * @param {Element} element An element to check. -1 578 * @return {boolean} True if the element is hidden from accessibility. 876 579 */877 -1 axs.utils.elementIsHtmlControl = function(element) {878 -1 var defaultView = element.ownerDocument.defaultView;-1 580 axs.utils.isElementHidden = function(element) { -1 581 if (!(element instanceof element.ownerDocument.defaultView.HTMLElement)) -1 582 return false; 879 583880 -1 // HTML control881 -1 if (element instanceof defaultView.HTMLButtonElement)882 -1 return true;883 -1 if (element instanceof defaultView.HTMLInputElement)884 -1 return true;885 -1 if (element instanceof defaultView.HTMLSelectElement)886 -1 return true;887 -1 if (element instanceof defaultView.HTMLTextAreaElement)888 -1 return true;-1 584 if (element.hasAttribute('chromevoxignoreariahidden')) -1 585 var chromevoxignoreariahidden = true; 889 586890 -1 return false;891 -1 };-1 587 var style = window.getComputedStyle(element, null); -1 588 if (style.display == 'none' || style.visibility == 'hidden') -1 589 return true; 892 590893 -1 /**894 -1 * @param {Element} element895 -1 * @return {boolean}896 -1 */897 -1 axs.utils.elementIsAriaWidget = function(element) {898 -1 if (element.hasAttribute('role')) {899 -1 var roleValue = element.getAttribute('role');900 -1 // TODO is this correct?901 -1 if (roleValue) {902 -1 var role = axs.constants.ARIA_ROLES[roleValue];903 -1 if (role && 'widget' in role['allParentRolesSet'])904 -1 return true;905 -1 }-1 591 if (element.hasAttribute('aria-hidden') && -1 592 element.getAttribute('aria-hidden').toLowerCase() == 'true') { -1 593 return !chromevoxignoreariahidden; 906 594 } -1 595 907 596 return false; 908 597 }; 909 598 910 599 /**911 -1 * @param {Element} element912 -1 * @return {boolean}-1 600 * @param {Element} element An element to check. -1 601 * @return {boolean} True if the element or one of its ancestors is -1 602 * hidden from accessibility. 913 603 */914 -1 axs.utils.elementIsVisible = function(element) {915 -1 if (axs.utils.elementIsTransparent(element))916 -1 return false;917 -1 if (axs.utils.elementHasZeroArea(element))918 -1 return false;919 -1 if (axs.utils.elementIsOutsideScrollArea(element))920 -1 return false;-1 604 axs.utils.isElementOrAncestorHidden = function(element) { -1 605 if (axs.utils.isElementHidden(element)) -1 606 return true; 921 607922 -1 var overlappingElements = axs.utils.overlappingElements(element);923 -1 if (overlappingElements.length)-1 608 if (axs.dom.parentElement(element)) -1 609 return axs.utils.isElementOrAncestorHidden(axs.dom.parentElement(element)); -1 610 else 924 611 return false;925 -1926 -1 return true;927 612 }; 928 613 929 614 /**930 -1 * @param {CSSStyleDeclaration} style931 -1 * @return {boolean}-1 615 * @param {Element} element An element to check -1 616 * @return {boolean} True if the given element is an inline element, false -1 617 * otherwise. 932 618 */933 -1 axs.utils.isLargeFont = function(style) {934 -1 var fontSize = style.fontSize;935 -1 var bold = style.fontWeight == 'bold';936 -1 var matches = fontSize.match(/(\d+)px/);937 -1 if (matches) {938 -1 var fontSizePx = parseInt(matches[1], 10);939 -1 var bodyStyle = window.getComputedStyle(document.body, null);940 -1 var bodyFontSize = bodyStyle.fontSize;941 -1 matches = bodyFontSize.match(/(\d+)px/);942 -1 if (matches) {943 -1 var bodyFontSizePx = parseInt(matches[1], 10);944 -1 var boldLarge = bodyFontSizePx * 1.2;945 -1 var large = bodyFontSizePx * 1.5;946 -1 } else {947 -1 var boldLarge = 19.2;948 -1 var large = 24;949 -1 }950 -1 return (bold && fontSizePx >= boldLarge || fontSizePx >= large);951 -1 }952 -1 matches = fontSize.match(/(\d+)em/);953 -1 if (matches) {954 -1 var fontSizeEm = parseInt(matches[1], 10);955 -1 if (bold && fontSizeEm >= 1.2 || fontSizeEm >= 1.5)956 -1 return true;957 -1 return false;958 -1 }959 -1 matches = fontSize.match(/(\d+)%/);960 -1 if (matches) {961 -1 var fontSizePercent = parseInt(matches[1], 10);962 -1 if (bold && fontSizePercent >= 120 || fontSizePercent >= 150)963 -1 return true;964 -1 return false;965 -1 }966 -1 matches = fontSize.match(/(\d+)pt/);967 -1 if (matches) {968 -1 var fontSizePt = parseInt(matches[1], 10);969 -1 if (bold && fontSizePt >= 14 || fontSizePt >= 18)970 -1 return true;971 -1 return false;972 -1 }973 -1 return false;-1 619 axs.utils.isInlineElement = function(element) { -1 620 var tagName = element.tagName.toUpperCase(); -1 621 return axs.constants.InlineElements[tagName]; 974 622 }; 975 623 976 624 /**977 -1 * @param {CSSStyleDeclaration} style978 -1 * @param {Element} element979 -1 * @return {?axs.color.Color}-1 625 * -1 626 * Gets role details from an element. -1 627 * @param {Element} element The DOM element whose role we want. -1 628 * @param {boolean=} implicit if true then implicit semantics will be considered if there is no role attribute. -1 629 * -1 630 * @return {Object} 980 631 */981 -1 axs.utils.getBgColor = function(style, element) {982 -1 var bgColorString = style.backgroundColor;983 -1 var bgColor = axs.color.parseColor(bgColorString);984 -1 if (!bgColor)-1 632 axs.utils.getRoles = function(element, implicit) { -1 633 if (!element || element.nodeType !== Node.ELEMENT_NODE || (!element.hasAttribute('role') && !implicit)) 985 634 return null;986 -1987 -1 if (style.opacity < 1)988 -1 bgColor.alpha = bgColor.alpha * style.opacity;989 -1990 -1 if (bgColor.alpha < 1) {991 -1 var parentBg = axs.utils.getParentBgColor(element);992 -1 if (parentBg == null)993 -1 return null;994 -1995 -1 bgColor = axs.color.flattenColors(bgColor, parentBg);-1 635 var roleValue = element.getAttribute('role'); -1 636 if (!roleValue && implicit) -1 637 roleValue = axs.properties.getImplicitRole(element); -1 638 if (!roleValue) // role='' or implicit role came up empty -1 639 return null; -1 640 var roleNames = roleValue.split(' '); -1 641 var result = { roles: [], valid: false }; -1 642 for (var i = 0; i < roleNames.length; i++) { -1 643 var role = roleNames[i]; -1 644 var ariaRole = axs.constants.ARIA_ROLES[role]; -1 645 var roleObject = { 'name': role }; -1 646 if (ariaRole && !ariaRole.abstract) { -1 647 roleObject.details = ariaRole; -1 648 if (!result.applied) { -1 649 result.applied = roleObject; -1 650 } -1 651 roleObject.valid = result.valid = true; -1 652 } else { -1 653 roleObject.valid = false; -1 654 } -1 655 result.roles.push(roleObject); 996 656 }997 -1 return bgColor;-1 657 -1 658 return result; 998 659 }; 999 660 1000 661 /**1001 -1 * Gets the effective background color of the parent of |element|.1002 -1 * @param {Element} element1003 -1 * @return {?axs.color.Color}-1 662 * @param {!string} propertyName -1 663 * @param {!string} value -1 664 * @param {!Element} element -1 665 * @return {!Object} 1004 666 */1005 -1 axs.utils.getParentBgColor = function(element) {1006 -1 /** @type {Element} */ var parent = element;1007 -1 var bgStack = [];1008 -1 var foundSolidColor = null;1009 -1 while ((parent = axs.dom.parentElement(parent))) {1010 -1 var computedStyle = window.getComputedStyle(parent, null);1011 -1 if (!computedStyle)1012 -1 continue;1013 -11014 -1 var parentBg = axs.color.parseColor(computedStyle.backgroundColor);1015 -1 if (!parentBg)1016 -1 continue;1017 -11018 -1 if (computedStyle.opacity < 1)1019 -1 parentBg.alpha = parentBg.alpha * computedStyle.opacity;1020 -11021 -1 if (parentBg.alpha == 0)1022 -1 continue;1023 -11024 -1 bgStack.push(parentBg);1025 -11026 -1 if (parentBg.alpha == 1) {1027 -1 foundSolidColor = true;1028 -1 break;1029 -1 }-1 667 axs.utils.getAriaPropertyValue = function(propertyName, value, element) { -1 668 var propertyKey = propertyName.replace(/^aria-/, ''); -1 669 var property = axs.constants.ARIA_PROPERTIES[propertyKey]; -1 670 var result = { 'name': propertyName, 'rawValue': value }; -1 671 if (!property) { -1 672 result.valid = false; -1 673 result.reason = '"' + propertyName + '" is not a valid ARIA property'; -1 674 return result; 1030 675 } 1031 6761032 -1 if (!foundSolidColor)1033 -1 bgStack.push(new axs.color.Color(255, 255, 255, 1));-1 677 var propertyType = property.valueType; -1 678 if (!propertyType) { -1 679 result.valid = false; -1 680 result.reason = '"' + propertyName + '" is not a valid ARIA property'; -1 681 return result; -1 682 } 1034 6831035 -1 var bg = bgStack.pop();1036 -1 while (bgStack.length) {1037 -1 var fg = bgStack.pop();1038 -1 bg = axs.color.flattenColors(fg, bg);-1 684 switch (propertyType) { -1 685 case "idref": -1 686 var isValid = axs.utils.isValidIDRefValue(value, element); -1 687 result.valid = isValid.valid; -1 688 result.reason = isValid.reason; -1 689 result.idref = isValid.idref; -1 690 // falls through -1 691 case "idref_list": -1 692 var idrefValues = value.split(/\s+/); -1 693 result.valid = true; -1 694 for (var i = 0; i < idrefValues.length; i++) { -1 695 var refIsValid = axs.utils.isValidIDRefValue(idrefValues[i], element); -1 696 if (!refIsValid.valid) -1 697 result.valid = false; -1 698 if (result.values) -1 699 result.values.push(refIsValid); -1 700 else -1 701 result.values = [refIsValid]; -1 702 } -1 703 return result; -1 704 case "integer": -1 705 var validNumber = axs.utils.isValidNumber(value); -1 706 if (!validNumber.valid) { -1 707 result.valid = false; -1 708 result.reason = validNumber.reason; -1 709 return result; -1 710 } -1 711 if (Math.floor(validNumber.value) !== validNumber.value) { -1 712 result.valid = false; -1 713 result.reason = '' + value + ' is not a whole integer'; -1 714 } else { -1 715 result.valid = true; -1 716 result.value = validNumber.value; -1 717 } -1 718 return result; -1 719 case "decimal": -1 720 case "number": -1 721 var validNumber = axs.utils.isValidNumber(value); -1 722 result.valid = validNumber.valid; -1 723 if (!validNumber.valid) { -1 724 result.reason = validNumber.reason; -1 725 return result; -1 726 } -1 727 result.value = validNumber.value; -1 728 return result; -1 729 case "string": -1 730 result.valid = true; -1 731 result.value = value; -1 732 return result; -1 733 case "token": -1 734 var validTokenValue = axs.utils.isValidTokenValue(propertyName, value.toLowerCase()); -1 735 if (validTokenValue.valid) { -1 736 result.valid = true; -1 737 result.value = validTokenValue.value; -1 738 return result; -1 739 } else { -1 740 result.valid = false; -1 741 result.value = value; -1 742 result.reason = validTokenValue.reason; -1 743 return result; -1 744 } -1 745 // falls through -1 746 case "token_list": -1 747 var tokenValues = value.split(/\s+/); -1 748 result.valid = true; -1 749 for (var i = 0; i < tokenValues.length; i++) { -1 750 var validTokenValue = axs.utils.isValidTokenValue(propertyName, tokenValues[i].toLowerCase()); -1 751 if (!validTokenValue.valid) { -1 752 result.valid = false; -1 753 if (result.reason) { -1 754 result.reason = [ result.reason ]; -1 755 result.reason.push(validTokenValue.reason); -1 756 } else { -1 757 result.reason = validTokenValue.reason; -1 758 result.possibleValues = validTokenValue.possibleValues; -1 759 } -1 760 } -1 761 // TODO (more structured result) -1 762 if (result.values) -1 763 result.values.push(validTokenValue.value); -1 764 else -1 765 result.values = [validTokenValue.value]; -1 766 } -1 767 return result; -1 768 case "tristate": -1 769 var validTristate = axs.utils.isPossibleValue(value.toLowerCase(), axs.constants.MIXED_VALUES, propertyName); -1 770 if (validTristate.valid) { -1 771 result.valid = true; -1 772 result.value = validTristate.value; -1 773 } else { -1 774 result.valid = false; -1 775 result.value = value; -1 776 result.reason = validTristate.reason; -1 777 } -1 778 return result; -1 779 case "boolean": -1 780 var validBoolean = axs.utils.isValidBoolean(value); -1 781 if (validBoolean.valid) { -1 782 result.valid = true; -1 783 result.value = validBoolean.value; -1 784 } else { -1 785 result.valid = false; -1 786 result.value = value; -1 787 result.reason = validBoolean.reason; -1 788 } -1 789 return result; 1039 790 }1040 -1 return bg;-1 791 result.valid = false; -1 792 result.reason = 'Not a valid ARIA property'; -1 793 return result; 1041 794 }; 1042 795 1043 796 /**1044 -1 * @param {CSSStyleDeclaration} style1045 -1 * @param {Element} element1046 -1 * @param {axs.color.Color} bgColor The background color, which may come from1047 -1 * another element (such as a parent element), for flattening into the1048 -1 * foreground color.1049 -1 * @return {?axs.color.Color}-1 797 * @param {string} propertyName The name of the property. -1 798 * @param {string} value The value to check. -1 799 * @return {!Object} 1050 800 */1051 -1 axs.utils.getFgColor = function(style, element, bgColor) {1052 -1 var fgColorString = style.color;1053 -1 var fgColor = axs.color.parseColor(fgColorString);1054 -1 if (!fgColor)1055 -1 return null;1056 -11057 -1 if (fgColor.alpha < 1)1058 -1 fgColor = axs.color.flattenColors(fgColor, bgColor);1059 -11060 -1 if (style.opacity < 1) {1061 -1 var parentBg = axs.utils.getParentBgColor(element);1062 -1 fgColor.alpha = fgColor.alpha * style.opacity;1063 -1 fgColor = axs.color.flattenColors(fgColor, parentBg);1064 -1 }1065 -11066 -1 return fgColor;-1 801 axs.utils.isValidTokenValue = function(propertyName, value) { -1 802 var propertyKey = propertyName.replace(/^aria-/, ''); -1 803 var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyKey]; -1 804 var possibleValues = propertyDetails.valuesSet; -1 805 return axs.utils.isPossibleValue(value, possibleValues, propertyName); 1067 806 }; 1068 807 1069 808 /**1070 -1 * @param {Element} element1071 -1 * @return {?number}-1 809 * @param {string} value -1 810 * @param {Object.<string, boolean>} possibleValues -1 811 * @param {string} propertyName The name of the property. -1 812 * @return {!Object} 1072 813 */1073 -1 axs.utils.getContrastRatioForElement = function(element) {1074 -1 var style = window.getComputedStyle(element, null);1075 -1 return axs.utils.getContrastRatioForElementWithComputedStyle(style, element);-1 814 axs.utils.isPossibleValue = function(value, possibleValues, propertyName) { -1 815 if (!possibleValues[value]) -1 816 return { 'valid': false, -1 817 'value': value, -1 818 'reason': '"' + value + '" is not a valid value for ' + propertyName, -1 819 'possibleValues': Object.keys(possibleValues) }; -1 820 return { 'valid': true, 'value': value }; 1076 821 }; 1077 822 1078 823 /**1079 -1 * @param {CSSStyleDeclaration} style1080 -1 * @param {Element} element1081 -1 * @return {?number}-1 824 * @param {string} value -1 825 * @return {!Object} 1082 826 */1083 -1 axs.utils.getContrastRatioForElementWithComputedStyle = function(style, element) {1084 -1 if (axs.utils.isElementHidden(element))1085 -1 return null;1086 -11087 -1 var bgColor = axs.utils.getBgColor(style, element);1088 -1 if (!bgColor)1089 -1 return null;1090 -11091 -1 var fgColor = axs.utils.getFgColor(style, element, bgColor);1092 -1 if (!fgColor)1093 -1 return null;1094 -11095 -1 return axs.color.calculateContrastRatio(fgColor, bgColor);-1 827 axs.utils.isValidBoolean = function(value) { -1 828 try { -1 829 var parsedValue = JSON.parse(value); -1 830 } catch (e) { -1 831 parsedValue = ''; -1 832 } -1 833 if (typeof(parsedValue) != 'boolean') -1 834 return { 'valid': false, -1 835 'value': value, -1 836 'reason': '"' + value + '" is not a true/false value' }; -1 837 return { 'valid': true, 'value': parsedValue }; 1096 838 }; 1097 839 1098 840 /**1099 -1 * @param {Element} element1100 -1 * @return {boolean}-1 841 * @param {string} value -1 842 * @param {!Element} element -1 843 * @return {!Object} 1101 844 */1102 -1 axs.utils.isNativeTextElement = function(element) {1103 -1 var tagName = element.tagName.toLowerCase();1104 -1 var type = element.type ? element.type.toLowerCase() : '';1105 -1 if (tagName == 'textarea')1106 -1 return true;1107 -1 if (tagName != 'input')1108 -1 return false;1109 -11110 -1 switch (type) {1111 -1 case 'email':1112 -1 case 'number':1113 -1 case 'password':1114 -1 case 'search':1115 -1 case 'text':1116 -1 case 'tel':1117 -1 case 'url':1118 -1 case '':1119 -1 return true;1120 -1 default:1121 -1 return false;1122 -1 }-1 845 axs.utils.isValidIDRefValue = function(value, element) { -1 846 if (value.length == 0) -1 847 return { 'valid': true, 'idref': value }; -1 848 if (!element.ownerDocument.getElementById(value)) -1 849 return { 'valid': false, -1 850 'idref': value, -1 851 'reason': 'No element with ID "' + value + '"' }; -1 852 return { 'valid': true, 'idref': value }; 1123 853 }; 1124 854 1125 855 /**1126 -1 * @param {number} contrastRatio1127 -1 * @param {CSSStyleDeclaration} style1128 -1 * @param {boolean=} opt_strict Whether to use AA (false) or AAA (true) level1129 -1 * @return {boolean}-1 856 * Tests if a number is real number for a11y purposes. -1 857 * Must be a real, numerical, decimal value; heavily inspired by -1 858 * http://www.w3.org/TR/wai-aria/states_and_properties#valuetype_number -1 859 * @param {string} value -1 860 * @return {!Object} 1130 861 */1131 -1 axs.utils.isLowContrast = function(contrastRatio, style, opt_strict) {1132 -1 // Round to nearest 0.11133 -1 var roundedContrastRatio = (Math.round(contrastRatio * 10) / 10);1134 -1 if (!opt_strict) {1135 -1 return roundedContrastRatio < 3.0 ||1136 -1 (!axs.utils.isLargeFont(style) && roundedContrastRatio < 4.5);1137 -1 } else {1138 -1 return roundedContrastRatio < 4.5 ||1139 -1 (!axs.utils.isLargeFont(style) && roundedContrastRatio < 7.0);-1 862 axs.utils.isValidNumber = function(value) { -1 863 var failResult = { -1 864 'valid': false, -1 865 'value': value, -1 866 'reason': '"' + value + '" is not a number' -1 867 }; -1 868 if (!value) { -1 869 return failResult; -1 870 } -1 871 if (/^0x/i.test(value)) { -1 872 failResult.reason = '"' + value + '" is not a decimal number'; // hex is not accepted -1 873 return failResult; -1 874 } -1 875 var parsedValue = value * 1; -1 876 if (!isFinite(parsedValue)) { -1 877 return failResult; 1140 878 } -1 879 return { 'valid': true, 'value': parsedValue }; 1141 880 }; 1142 881 1143 882 /** 1144 883 * @param {Element} element 1145 884 * @return {boolean} 1146 885 */1147 -1 axs.utils.hasLabel = function(element) {1148 -1 var tagName = element.tagName.toLowerCase();1149 -1 var type = element.type ? element.type.toLowerCase() : '';1150 -11151 -1 if (element.hasAttribute('aria-label'))1152 -1 return true;1153 -1 if (element.hasAttribute('title'))1154 -1 return true;1155 -1 if (tagName == 'img' && element.hasAttribute('alt'))1156 -1 return true;1157 -1 if (tagName == 'input' && type == 'image' && element.hasAttribute('alt'))1158 -1 return true;1159 -1 if (tagName == 'input' && (type == 'submit' || type == 'reset'))1160 -1 return true;1161 -11162 -1 // There's a separate audit that makes sure this points to an actual element or elements.1163 -1 if (element.hasAttribute('aria-labelledby'))1164 -1 return true;1165 -11166 -1 if (element.hasAttribute('id')) {1167 -1 var labelsFor = document.querySelectorAll('label[for="' + element.id + '"]');1168 -1 if (labelsFor.length > 0)1169 -1 return true;1170 -1 }-1 886 axs.utils.isElementImplicitlyFocusable = function(element) { -1 887 var defaultView = element.ownerDocument.defaultView; 1171 8881172 -1 var parent = axs.dom.parentElement(element);1173 -1 while (parent) {1174 -1 if (parent.tagName.toLowerCase() == 'label') {1175 -1 var parentLabel = /** HTMLLabelElement */ parent;1176 -1 if (parentLabel.control == element)1177 -1 return true;1178 -1 }1179 -1 parent = axs.dom.parentElement(parent);1180 -1 }-1 889 if (element instanceof defaultView.HTMLAnchorElement || -1 890 element instanceof defaultView.HTMLAreaElement) -1 891 return element.hasAttribute('href'); -1 892 if (element instanceof defaultView.HTMLInputElement || -1 893 element instanceof defaultView.HTMLSelectElement || -1 894 element instanceof defaultView.HTMLTextAreaElement || -1 895 element instanceof defaultView.HTMLButtonElement || -1 896 element instanceof defaultView.HTMLIFrameElement) -1 897 return !element.disabled; 1181 898 return false; 1182 899 }; 1183 900 1184 901 /**1185 -1 * Determine if this element natively supports being disabled (i.e. via the `disabled` attribute.1186 -1 * Disabled here means that the element should be considered disabled according to specification.1187 -1 * This element may or may not be effectively disabled in practice as this is dependent on implementation.1188 -1 *1189 -1 * @param {Element} element An element to check.1190 -1 * @return {boolean} true If the element supports being natively disabled.1191 -1 */1192 -1 axs.utils.isNativelyDisableable = function(element) {1193 -1 var tagName = element.tagName.toUpperCase();1194 -1 return (tagName in axs.constants.NATIVELY_DISABLEABLE);1195 -1 };1196 -11197 -1 /**1198 -1 * Determine if this element is disabled directly or indirectly by a disabled ancestor.1199 -1 * Disabled here means that the element should be considered disabled according to specification.1200 -1 * This element may or may not be effectively disabled in practice as this is dependent on implementation.1201 -1 *1202 -1 * @param {Element} element An element to check.1203 -1 * @param {boolean=} ignoreAncestors If true do not check for disabled ancestors.1204 -1 * @return {boolean} true if the element or one of its ancestors is disabled.-1 902 * Returns an array containing the values of the given JSON-compatible object. -1 903 * (Simply ignores any function values.) -1 904 * @param {Object} obj -1 905 * @return {Array} 1205 906 */1206 -1 axs.utils.isElementDisabled = function(element, ignoreAncestors) {1207 -1 var selector = ignoreAncestors ? '[aria-disabled=true]' : '[aria-disabled=true], [aria-disabled=true] *';1208 -1 if (axs.browserUtils.matchSelector(element, selector)) {1209 -1 return true;1210 -1 }1211 -1 if (!axs.utils.isNativelyDisableable(element) ||1212 -1 axs.browserUtils.matchSelector(element, 'fieldset>legend:first-of-type *')) {1213 -1 return false;1214 -1 }1215 -1 for (var next = element; next !== null; next = axs.dom.parentElement(next)) {1216 -1 if (next.hasAttribute('disabled')) {1217 -1 return true;1218 -1 }1219 -1 if (ignoreAncestors) {1220 -1 return false;1221 -1 }-1 907 axs.utils.values = function(obj) { -1 908 var values = []; -1 909 for (var key in obj) { -1 910 if (obj.hasOwnProperty(key) && typeof obj[key] != 'function') -1 911 values.push(obj[key]); 1222 912 }1223 -1 return false;-1 913 return values; 1224 914 }; 1225 915 1226 916 /**1227 -1 * @param {Element} element An element to check.1228 -1 * @return {boolean} True if the element is hidden from accessibility.-1 917 * Returns an object containing the same keys and values as the given -1 918 * JSON-compatible object. (Simply ignores any function values.) -1 919 * @param {Object} obj -1 920 * @return {Object} 1229 921 */1230 -1 axs.utils.isElementHidden = function(element) {1231 -1 if (!(element instanceof element.ownerDocument.defaultView.HTMLElement))1232 -1 return false;1233 -11234 -1 if (element.hasAttribute('chromevoxignoreariahidden'))1235 -1 var chromevoxignoreariahidden = true;1236 -11237 -1 var style = window.getComputedStyle(element, null);1238 -1 if (style.display == 'none' || style.visibility == 'hidden')1239 -1 return true;1240 -11241 -1 if (element.hasAttribute('aria-hidden') &&1242 -1 element.getAttribute('aria-hidden').toLowerCase() == 'true') {1243 -1 return !chromevoxignoreariahidden;-1 922 axs.utils.namedValues = function(obj) { -1 923 var values = {}; -1 924 for (var key in obj) { -1 925 if (obj.hasOwnProperty(key) && typeof obj[key] != 'function') -1 926 values[key] = obj[key]; 1244 927 }1245 -11246 -1 return false;-1 928 return values; 1247 929 }; 1248 930 1249 931 /**1250 -1 * @param {Element} element An element to check.1251 -1 * @return {boolean} True if the element or one of its ancestors is1252 -1 * hidden from accessibility.-1 932 * Escapes a given ID to be used in a CSS selector -1 933 * -1 934 * @private -1 935 * @param {!string} id The ID to be escaped -1 936 * @return {string} The escaped ID -1 937 */ -1 938 function escapeId(id) { -1 939 return id.replace(/[^a-zA-Z0-9_-]/g,function(match) { return '\\' + match; }); -1 940 } -1 941 -1 942 /** Gets a CSS selector text for a DOM object. -1 943 * @param {Node} obj The DOM object. -1 944 * @return {string} CSS selector text for the DOM object. 1253 945 */1254 -1 axs.utils.isElementOrAncestorHidden = function(element) {1255 -1 if (axs.utils.isElementHidden(element))1256 -1 return true;-1 946 axs.utils.getQuerySelectorText = function(obj) { -1 947 if (obj == null || obj.tagName == 'HTML') { -1 948 return 'html'; -1 949 } else if (obj.tagName == 'BODY') { -1 950 return 'body'; -1 951 } 1257 9521258 -1 if (axs.dom.parentElement(element))1259 -1 return axs.utils.isElementOrAncestorHidden(axs.dom.parentElement(element));1260 -1 else1261 -1 return false;1262 -1 };-1 953 if (obj.hasAttribute) { -1 954 if (obj.id) { -1 955 return '#' + escapeId(obj.id); -1 956 } 1263 9571264 -1 /**1265 -1 * @param {Element} element An element to check1266 -1 * @return {boolean} True if the given element is an inline element, false1267 -1 * otherwise.1268 -1 */1269 -1 axs.utils.isInlineElement = function(element) {1270 -1 var tagName = element.tagName.toUpperCase();1271 -1 return axs.constants.InlineElements[tagName];-1 958 if (obj.className) { -1 959 var selector = ''; -1 960 for (var i = 0; i < obj.classList.length; i++) -1 961 selector += '.' + obj.classList[i]; -1 962 -1 963 var total = 0; -1 964 if (obj.parentNode) { -1 965 for (i = 0; i < obj.parentNode.children.length; i++) { -1 966 var similar = obj.parentNode.children[i]; -1 967 if (axs.browserUtils.matchSelector(similar, selector)) -1 968 total++; -1 969 if (similar === obj) -1 970 break; -1 971 } -1 972 } else { -1 973 total = 1; -1 974 } -1 975 -1 976 if (total == 1) { -1 977 return axs.utils.getQuerySelectorText(obj.parentNode) + -1 978 ' > ' + selector; -1 979 } -1 980 } -1 981 -1 982 if (obj.parentNode) { -1 983 var similarTags = obj.parentNode.children; -1 984 var total = 1; -1 985 var i = 0; -1 986 while (similarTags[i] !== obj) { -1 987 if (similarTags[i].tagName == obj.tagName) { -1 988 total++; -1 989 } -1 990 i++; -1 991 } -1 992 -1 993 var next = ''; -1 994 if (obj.parentNode.tagName != 'BODY') { -1 995 next = axs.utils.getQuerySelectorText(obj.parentNode) + -1 996 ' > '; -1 997 } -1 998 -1 999 if (total == 1) { -1 1000 return next + -1 1001 obj.tagName; -1 1002 } else { -1 1003 return next + -1 1004 obj.tagName + -1 1005 ':nth-of-type(' + total + ')'; -1 1006 } -1 1007 } -1 1008 -1 1009 } else if (obj.selectorText) { -1 1010 return obj.selectorText; -1 1011 } -1 1012 -1 1013 return ''; 1272 1014 }; 1273 1015 1274 1016 /**1275 -1 *1276 -1 * Gets role details from an element.1277 -1 * @param {Element} element The DOM element whose role we want.1278 -1 * @param {boolean=} implicit if true then implicit semantics will be considered if there is no role attribute.1279 -1 *1280 -1 * @return {Object}-1 1017 * Gets elements that refer to this element in an ARIA attribute that takes an ID reference list or -1 1018 * single ID reference. -1 1019 * @param {Element} element a potential referent. -1 1020 * @param {string=} opt_attributeName Name of an ARIA attribute to limit the results to, e.g. 'aria-owns'. -1 1021 * @return {NodeList} The elements that refer to this element or null. 1281 1022 */1282 -1 axs.utils.getRoles = function(element, implicit) {1283 -1 if (!element || element.nodeType !== Node.ELEMENT_NODE || (!element.hasAttribute('role') && !implicit))-1 1023 axs.utils.getAriaIdReferrers = function(element, opt_attributeName) { -1 1024 var propertyToSelector = function(propertyKey) { -1 1025 var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyKey]; -1 1026 if (propertyDetails) { -1 1027 if (propertyDetails.valueType === ('idref')) { -1 1028 return '[aria-' + propertyKey + '=\'' + id + '\']'; -1 1029 } else if (propertyDetails.valueType === ('idref_list')) { -1 1030 return '[aria-' + propertyKey + '~=\'' + id + '\']'; -1 1031 } -1 1032 } -1 1033 return ''; -1 1034 }; -1 1035 if (!element) 1284 1036 return null;1285 -1 var roleValue = element.getAttribute('role');1286 -1 if (!roleValue && implicit)1287 -1 roleValue = axs.properties.getImplicitRole(element);1288 -1 if (!roleValue) // role='' or implicit role came up empty-1 1037 var id = element.id; -1 1038 if (!id) 1289 1039 return null;1290 -1 var roleNames = roleValue.split(' ');1291 -1 var result = { roles: [], valid: false };1292 -1 for (var i = 0; i < roleNames.length; i++) {1293 -1 var role = roleNames[i];1294 -1 var ariaRole = axs.constants.ARIA_ROLES[role];1295 -1 var roleObject = { 'name': role };1296 -1 if (ariaRole && !ariaRole.abstract) {1297 -1 roleObject.details = ariaRole;1298 -1 if (!result.applied) {1299 -1 result.applied = roleObject;-1 1040 id = id.replace(/'/g, "\\'"); // make it safe to use in a selector -1 1041 -1 1042 if (opt_attributeName) { -1 1043 var propertyKey = opt_attributeName.replace(/^aria-/, ''); -1 1044 var referrerQuery = propertyToSelector(propertyKey); -1 1045 if (referrerQuery) { -1 1046 return element.ownerDocument.querySelectorAll(referrerQuery); -1 1047 } -1 1048 } else { -1 1049 var selectors = []; -1 1050 for (var propertyKey in axs.constants.ARIA_PROPERTIES) { -1 1051 var referrerQuery = propertyToSelector(propertyKey); -1 1052 if (referrerQuery) { -1 1053 selectors.push(referrerQuery); 1300 1054 }1301 -1 roleObject.valid = result.valid = true;1302 -1 } else {1303 -1 roleObject.valid = false;1304 1055 }1305 -1 result.roles.push(roleObject);-1 1056 return element.ownerDocument.querySelectorAll(selectors.join(',')); 1306 1057 }1307 -11308 -1 return result;-1 1058 return null; 1309 1059 }; 1310 1060 1311 1061 /**1312 -1 * @param {!string} propertyName1313 -1 * @param {!string} value1314 -1 * @param {!Element} element1315 -1 * @return {!Object}-1 1062 * Gets elements that refer to this element in an HTML attribute that takes an ID reference list or -1 1063 * single ID reference. -1 1064 * @param {Element} element a potential referent. -1 1065 * @return {NodeList} The elements that refer to this element. 1316 1066 */1317 -1 axs.utils.getAriaPropertyValue = function(propertyName, value, element) {1318 -1 var propertyKey = propertyName.replace(/^aria-/, '');1319 -1 var property = axs.constants.ARIA_PROPERTIES[propertyKey];1320 -1 var result = { 'name': propertyName, 'rawValue': value };1321 -1 if (!property) {1322 -1 result.valid = false;1323 -1 result.reason = '"' + propertyName + '" is not a valid ARIA property';1324 -1 return result;1325 -1 }1326 -11327 -1 var propertyType = property.valueType;1328 -1 if (!propertyType) {1329 -1 result.valid = false;1330 -1 result.reason = '"' + propertyName + '" is not a valid ARIA property';1331 -1 return result;1332 -1 }-1 1067 axs.utils.getHtmlIdReferrers = function(element) { -1 1068 if (!element) -1 1069 return null; -1 1070 var id = element.id; -1 1071 if (!id) -1 1072 return null; -1 1073 id = id.replace(/'/g, "\\'"); // make it safe to use in a selector -1 1074 var selectorTemplates = [ -1 1075 '[contextmenu=\'{id}\']', -1 1076 '[itemref~=\'{id}\']', -1 1077 'button[form=\'{id}\']', -1 1078 'button[menu=\'{id}\']', -1 1079 'fieldset[form=\'{id}\']', -1 1080 'input[form=\'{id}\']', -1 1081 'input[list=\'{id}\']', -1 1082 'keygen[form=\'{id}\']', -1 1083 'label[for=\'{id}\']', -1 1084 'label[form=\'{id}\']', -1 1085 'menuitem[command=\'{id}\']', -1 1086 'object[form=\'{id}\']', -1 1087 'output[for~=\'{id}\']', -1 1088 'output[form=\'{id}\']', -1 1089 'select[form=\'{id}\']', -1 1090 'td[headers~=\'{id}\']', -1 1091 'textarea[form=\'{id}\']', -1 1092 'tr[headers~=\'{id}\']']; -1 1093 var selectors = selectorTemplates.map(function(selector) { -1 1094 return selector.replace('\{id\}', id); -1 1095 }); -1 1096 return element.ownerDocument.querySelectorAll(selectors.join(',')); -1 1097 }; 1333 10981334 -1 switch (propertyType) {1335 -1 case "idref":1336 -1 var isValid = axs.utils.isValidIDRefValue(value, element);1337 -1 result.valid = isValid.valid;1338 -1 result.reason = isValid.reason;1339 -1 result.idref = isValid.idref;1340 -1 // falls through1341 -1 case "idref_list":1342 -1 var idrefValues = value.split(/\s+/);1343 -1 result.valid = true;1344 -1 for (var i = 0; i < idrefValues.length; i++) {1345 -1 var refIsValid = axs.utils.isValidIDRefValue(idrefValues[i], element);1346 -1 if (!refIsValid.valid)1347 -1 result.valid = false;1348 -1 if (result.values)1349 -1 result.values.push(refIsValid);1350 -1 else1351 -1 result.values = [refIsValid];1352 -1 }1353 -1 return result;1354 -1 case "integer":1355 -1 var validNumber = axs.utils.isValidNumber(value);1356 -1 if (!validNumber.valid) {1357 -1 result.valid = false;1358 -1 result.reason = validNumber.reason;1359 -1 return result;1360 -1 }1361 -1 if (Math.floor(validNumber.value) !== validNumber.value) {1362 -1 result.valid = false;1363 -1 result.reason = '' + value + ' is not a whole integer';1364 -1 } else {1365 -1 result.valid = true;1366 -1 result.value = validNumber.value;1367 -1 }1368 -1 return result;1369 -1 case "decimal":1370 -1 case "number":1371 -1 var validNumber = axs.utils.isValidNumber(value);1372 -1 result.valid = validNumber.valid;1373 -1 if (!validNumber.valid) {1374 -1 result.reason = validNumber.reason;1375 -1 return result;1376 -1 }1377 -1 result.value = validNumber.value;1378 -1 return result;1379 -1 case "string":1380 -1 result.valid = true;1381 -1 result.value = value;1382 -1 return result;1383 -1 case "token":1384 -1 var validTokenValue = axs.utils.isValidTokenValue(propertyName, value.toLowerCase());1385 -1 if (validTokenValue.valid) {1386 -1 result.valid = true;1387 -1 result.value = validTokenValue.value;1388 -1 return result;1389 -1 } else {1390 -1 result.valid = false;1391 -1 result.value = value;1392 -1 result.reason = validTokenValue.reason;1393 -1 return result;1394 -1 }1395 -1 // falls through1396 -1 case "token_list":1397 -1 var tokenValues = value.split(/\s+/);1398 -1 result.valid = true;1399 -1 for (var i = 0; i < tokenValues.length; i++) {1400 -1 var validTokenValue = axs.utils.isValidTokenValue(propertyName, tokenValues[i].toLowerCase());1401 -1 if (!validTokenValue.valid) {1402 -1 result.valid = false;1403 -1 if (result.reason) {1404 -1 result.reason = [ result.reason ];1405 -1 result.reason.push(validTokenValue.reason);-1 1099 /** -1 1100 * Gets a list of all IDs this element references in either ARIA or HTML attributes. -1 1101 * -1 1102 * @param {Element} element The element to check for idref attributes. -1 1103 * @returns {Array.<string>} Any IDs this element references. -1 1104 */ -1 1105 axs.utils.getReferencedIds = function(element) { -1 1106 var result = []; -1 1107 var addResult = function(ids) { -1 1108 if (ids) { -1 1109 if (ids.indexOf(' ') > 0) { -1 1110 result = result.concat(attrib.value.split(' ')); 1406 1111 } else {1407 -1 result.reason = validTokenValue.reason;1408 -1 result.possibleValues = validTokenValue.possibleValues;-1 1112 result.push(ids); 1409 1113 } 1410 1114 }1411 -1 // TODO (more structured result)1412 -1 if (result.values)1413 -1 result.values.push(validTokenValue.value);1414 -1 else1415 -1 result.values = [validTokenValue.value];1416 -1 }1417 -1 return result;1418 -1 case "tristate":1419 -1 var validTristate = axs.utils.isPossibleValue(value.toLowerCase(), axs.constants.MIXED_VALUES, propertyName);1420 -1 if (validTristate.valid) {1421 -1 result.valid = true;1422 -1 result.value = validTristate.value;1423 -1 } else {1424 -1 result.valid = false;1425 -1 result.value = value;1426 -1 result.reason = validTristate.reason;1427 -1 }1428 -1 return result;1429 -1 case "boolean":1430 -1 var validBoolean = axs.utils.isValidBoolean(value);1431 -1 if (validBoolean.valid) {1432 -1 result.valid = true;1433 -1 result.value = validBoolean.value;1434 -1 } else {1435 -1 result.valid = false;1436 -1 result.value = value;1437 -1 result.reason = validBoolean.reason;-1 1115 }; -1 1116 for (var i = 0; i < element.attributes.length; i++) { -1 1117 var tagName = element.tagName.toLowerCase(); -1 1118 var attrib = element.attributes[i]; -1 1119 if (attrib.specified) { -1 1120 var attribName = attrib.name; -1 1121 var ariaAttr = attribName.match(/aria-(.+)/); -1 1122 if (ariaAttr) { -1 1123 var details = axs.constants.ARIA_PROPERTIES[ariaAttr[1]]; -1 1124 if (details && (details.valueType === ('idref') || details.valueType === ('idref_list'))) { -1 1125 addResult(attrib.value); -1 1126 } -1 1127 continue; -1 1128 } -1 1129 switch (attribName) { -1 1130 case 'contextmenu': -1 1131 case 'itemref': -1 1132 addResult(attrib.value); -1 1133 break; -1 1134 case 'form': -1 1135 if (tagName == 'button' || tagName == 'fieldset' || tagName == 'input' || -1 1136 tagName == 'keygen' || tagName == 'label' || tagName == 'object' || -1 1137 tagName == 'output' || tagName == 'select' || tagName == 'textarea') { -1 1138 addResult(attrib.value); -1 1139 } -1 1140 break; -1 1141 case 'for': -1 1142 if (tagName == 'label' || tagName == 'output') { -1 1143 addResult(attrib.value); -1 1144 } -1 1145 break; -1 1146 case 'menu': -1 1147 if (tagName == 'button') { -1 1148 addResult(attrib.value); -1 1149 } -1 1150 break; -1 1151 case 'list': -1 1152 if (tagName == 'input') { -1 1153 addResult(attrib.value); -1 1154 } -1 1155 break; -1 1156 case 'command': -1 1157 if (tagName == 'menuitem') { -1 1158 addResult(attrib.value); -1 1159 } -1 1160 break; -1 1161 case 'headers': -1 1162 if (tagName == 'td' || tagName == 'tr') { -1 1163 addResult(attrib.value); -1 1164 } -1 1165 break; -1 1166 } 1438 1167 }1439 -1 return result;1440 1168 }1441 -1 result.valid = false;1442 -1 result.reason = 'Not a valid ARIA property';1443 1169 return result; 1444 1170 }; 1445 1171 1446 1172 /**1447 -1 * @param {string} propertyName The name of the property.1448 -1 * @param {string} value The value to check.1449 -1 * @return {!Object}-1 1173 * Gets elements that refer to this element in an attribute that takes an ID reference list or -1 1174 * single ID reference. -1 1175 * @param {Element} element a potential referent. -1 1176 * @return {Array<Element>} The elements that refer to this element. 1450 1177 */1451 -1 axs.utils.isValidTokenValue = function(propertyName, value) {1452 -1 var propertyKey = propertyName.replace(/^aria-/, '');1453 -1 var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyKey];1454 -1 var possibleValues = propertyDetails.valuesSet;1455 -1 return axs.utils.isPossibleValue(value, possibleValues, propertyName);-1 1178 axs.utils.getIdReferrers = function(element) { -1 1179 var result = []; -1 1180 var referrers = axs.utils.getHtmlIdReferrers(element); -1 1181 if (referrers) { -1 1182 result = result.concat(Array.prototype.slice.call(referrers)); -1 1183 } -1 1184 referrers = axs.utils.getAriaIdReferrers(element); -1 1185 if (referrers) { -1 1186 result = result.concat(Array.prototype.slice.call(referrers)); -1 1187 } -1 1188 return result; 1456 1189 }; 1457 1190 1458 1191 /**1459 -1 * @param {string} value1460 -1 * @param {Object.<string, boolean>} possibleValues1461 -1 * @param {string} propertyName The name of the property.1462 -1 * @return {!Object}-1 1192 * Gets elements which this element refers to in the given attribute. -1 1193 * @param {!string} attributeName Name of an ARIA attribute, e.g. 'aria-owns'. -1 1194 * @param {Element} element The DOM element which has the ARIA attribute. -1 1195 * @return {!Array.<Element>} An array of elements that are referred to by this element. -1 1196 * @example -1 1197 * var owner = document.body.appendChild(document.createElement("div")); -1 1198 * var owned = document.body.appendChild(document.createElement("div")); -1 1199 * owner.setAttribute("aria-owns", "kungfu"); -1 1200 * owned.setAttribute("id", "kungfu"); -1 1201 * console.log(axs.utils.getIdReferents("aria-owns", owner)[0] === owned); // This will log 'true' 1463 1202 */1464 -1 axs.utils.isPossibleValue = function(value, possibleValues, propertyName) {1465 -1 if (!possibleValues[value])1466 -1 return { 'valid': false,1467 -1 'value': value,1468 -1 'reason': '"' + value + '" is not a valid value for ' + propertyName,1469 -1 'possibleValues': Object.keys(possibleValues) };1470 -1 return { 'valid': true, 'value': value };-1 1203 axs.utils.getIdReferents = function(attributeName, element) { -1 1204 var result = []; -1 1205 var propertyKey = attributeName.replace(/^aria-/, ''); -1 1206 var property = axs.constants.ARIA_PROPERTIES[propertyKey]; -1 1207 if (!property || !element.hasAttribute(attributeName)) -1 1208 return result; -1 1209 var propertyType = property.valueType; -1 1210 if (propertyType === 'idref_list' || propertyType === 'idref') { -1 1211 var ownerDocument = element.ownerDocument; -1 1212 var ids = element.getAttribute(attributeName); -1 1213 ids = ids.split(/\s+/); -1 1214 for (var i = 0, len = ids.length; i < len; i++) { -1 1215 var next = ownerDocument.getElementById(ids[i]); -1 1216 if (next) { -1 1217 result[result.length] = next; -1 1218 } -1 1219 } -1 1220 } -1 1221 return result; 1471 1222 }; 1472 1223 1473 1224 /**1474 -1 * @param {string} value1475 -1 * @return {!Object}-1 1225 * Gets a subset of 'axs.constants.ARIA_PROPERTIES' filtered by 'valueType'. -1 1226 * @param {!Array.<string>} valueTypes Types to match, e.g. ['idref_list']. -1 1227 * @return {Object.<string, Object>} axs.constants.ARIA_PROPERTIES which match. 1476 1228 */1477 -1 axs.utils.isValidBoolean = function(value) {1478 -1 try {1479 -1 var parsedValue = JSON.parse(value);1480 -1 } catch (e) {1481 -1 parsedValue = '';-1 1229 axs.utils.getAriaPropertiesByValueType = function(valueTypes) { -1 1230 var result = {}; -1 1231 for (var propertyName in axs.constants.ARIA_PROPERTIES) { -1 1232 var property = axs.constants.ARIA_PROPERTIES[propertyName]; -1 1233 if (property && valueTypes.indexOf(property.valueType) >= 0) { -1 1234 result[propertyName] = property; -1 1235 } 1482 1236 }1483 -1 if (typeof(parsedValue) != 'boolean')1484 -1 return { 'valid': false,1485 -1 'value': value,1486 -1 'reason': '"' + value + '" is not a true/false value' };1487 -1 return { 'valid': true, 'value': parsedValue };-1 1237 return result; 1488 1238 }; 1489 1239 1490 1240 /**1491 -1 * @param {string} value1492 -1 * @param {!Element} element1493 -1 * @return {!Object}-1 1241 * Builds a selector that matches an element with any of these ARIA properties. -1 1242 * @param {Object.<string, Object>} ariaProperties axs.constants.ARIA_PROPERTIES -1 1243 * @return {!string} The selector. 1494 1244 */1495 -1 axs.utils.isValidIDRefValue = function(value, element) {1496 -1 if (value.length == 0)1497 -1 return { 'valid': true, 'idref': value };1498 -1 if (!element.ownerDocument.getElementById(value))1499 -1 return { 'valid': false,1500 -1 'idref': value,1501 -1 'reason': 'No element with ID "' + value + '"' };1502 -1 return { 'valid': true, 'idref': value };-1 1245 axs.utils.getSelectorForAriaProperties = function(ariaProperties) { -1 1246 var propertyNames = Object.keys(/** @type {!Object} */(ariaProperties)); -1 1247 var result = propertyNames.map(function(propertyName) { -1 1248 return '[aria-' + propertyName + ']'; -1 1249 }); -1 1250 result.sort(); // facilitates reading long selectors and unit testing -1 1251 return result.join(','); 1503 1252 }; 1504 1253 1505 1254 /**1506 -1 * Tests if a number is real number for a11y purposes.1507 -1 * Must be a real, numerical, decimal value; heavily inspired by1508 -1 * http://www.w3.org/TR/wai-aria/states_and_properties#valuetype_number1509 -1 * @param {string} value1510 -1 * @return {!Object}-1 1255 * Finds descendants of this element which implement the given ARIA role. -1 1256 * Will look for descendants with implicit or explicit role. -1 1257 * @param {Element} element an HTML DOM element. -1 1258 * @param {string} role The role you seek. -1 1259 * @return {!Array.<Element>} An array of matching elements. -1 1260 * @example -1 1261 * var container = document.createElement("div"); -1 1262 * var button = document.createElement("button"); -1 1263 * var span = document.createElement("span"); -1 1264 * span.setAttribute("role", "button"); -1 1265 * container.appendChild(button); -1 1266 * container.appendChild(span); -1 1267 * var result = axs.utils.findDescendantsWithRole(container, "button"); // result is an array containing both 'button' and 'span' 1511 1268 */1512 -1 axs.utils.isValidNumber = function(value) {1513 -1 var failResult = {1514 -1 'valid': false,1515 -1 'value': value,1516 -1 'reason': '"' + value + '" is not a number'1517 -1 };1518 -1 if (!value) {1519 -1 return failResult;1520 -1 }1521 -1 if (/^0x/i.test(value)) {1522 -1 failResult.reason = '"' + value + '" is not a decimal number'; // hex is not accepted1523 -1 return failResult;1524 -1 }1525 -1 var parsedValue = value * 1;1526 -1 if (!isFinite(parsedValue)) {1527 -1 return failResult;-1 1269 axs.utils.findDescendantsWithRole = function(element, role) { -1 1270 if (!(element && role)) -1 1271 return []; -1 1272 var selector = axs.properties.getSelectorForRole(role); -1 1273 if (!selector) -1 1274 return []; -1 1275 var result = element.querySelectorAll(selector); -1 1276 if (result) { // Convert NodeList to Array; methinks 80/20 that's what callers want. -1 1277 result = Array.prototype.map.call(result, function(item) { return item; }); -1 1278 } else { -1 1279 return []; 1528 1280 }1529 -1 return { 'valid': true, 'value': parsedValue };-1 1281 return result; 1530 1282 }; 1531 1283 -1 1284 },{}],2:[function(require,module,exports){ -1 1285 // Copyright 2013 Google Inc. -1 1286 // -1 1287 // Licensed under the Apache License, Version 2.0 (the "License"); -1 1288 // you may not use this file except in compliance with the License. -1 1289 // You may obtain a copy of the License at -1 1290 // -1 1291 // http://www.apache.org/licenses/LICENSE-2.0 -1 1292 // -1 1293 // Unless required by applicable law or agreed to in writing, software -1 1294 // distributed under the License is distributed on an "AS IS" BASIS, -1 1295 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -1 1296 // See the License for the specific language governing permissions and -1 1297 // limitations under the License. -1 1298 -1 1299 goog.provide('axs.browserUtils'); -1 1300 1532 1301 /** -1 1302 * Use Webkit matcher when matches() is not supported. -1 1303 * Use Firefox matcher when Webkit is not supported. -1 1304 * Use IE matcher when neither webkit nor Firefox supported. 1533 1305 * @param {Element} element1534 -1 * @return {boolean}-1 1306 * @param {string} selector -1 1307 * @return {boolean} true if the element matches the selector 1535 1308 */1536 -1 axs.utils.isElementImplicitlyFocusable = function(element) {1537 -1 var defaultView = element.ownerDocument.defaultView;1538 -11539 -1 if (element instanceof defaultView.HTMLAnchorElement ||1540 -1 element instanceof defaultView.HTMLAreaElement)1541 -1 return element.hasAttribute('href');1542 -1 if (element instanceof defaultView.HTMLInputElement ||1543 -1 element instanceof defaultView.HTMLSelectElement ||1544 -1 element instanceof defaultView.HTMLTextAreaElement ||1545 -1 element instanceof defaultView.HTMLButtonElement ||1546 -1 element instanceof defaultView.HTMLIFrameElement)1547 -1 return !element.disabled;-1 1309 axs.browserUtils.matchSelector = function(element, selector) { -1 1310 if (element.matches) -1 1311 return element.matches(selector); -1 1312 if (element.webkitMatchesSelector) -1 1313 return element.webkitMatchesSelector(selector); -1 1314 if (element.mozMatchesSelector) -1 1315 return element.mozMatchesSelector(selector); -1 1316 if (element.msMatchesSelector) -1 1317 return element.msMatchesSelector(selector); 1548 1318 return false; 1549 1319 }; 1550 13201551 -1 /**1552 -1 * Returns an array containing the values of the given JSON-compatible object.1553 -1 * (Simply ignores any function values.)1554 -1 * @param {Object} obj1555 -1 * @return {Array}1556 -1 */1557 -1 axs.utils.values = function(obj) {1558 -1 var values = [];1559 -1 for (var key in obj) {1560 -1 if (obj.hasOwnProperty(key) && typeof obj[key] != 'function')1561 -1 values.push(obj[key]);1562 -1 }1563 -1 return values;1564 -1 };-1 1321 },{}],3:[function(require,module,exports){ -1 1322 // Copyright 2015 Google Inc. -1 1323 // -1 1324 // Licensed under the Apache License, Version 2.0 (the "License"); -1 1325 // you may not use this file except in compliance with the License. -1 1326 // You may obtain a copy of the License at -1 1327 // -1 1328 // http://www.apache.org/licenses/LICENSE-2.0 -1 1329 // -1 1330 // Unless required by applicable law or agreed to in writing, software -1 1331 // distributed under the License is distributed on an "AS IS" BASIS, -1 1332 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -1 1333 // See the License for the specific language governing permissions and -1 1334 // limitations under the License. -1 1335 -1 1336 goog.provide('axs.color'); -1 1337 goog.provide('axs.color.Color'); 1565 1338 1566 1339 /**1567 -1 * Returns an object containing the same keys and values as the given1568 -1 * JSON-compatible object. (Simply ignores any function values.)1569 -1 * @param {Object} obj1570 -1 * @return {Object}-1 1340 * @constructor -1 1341 * @param {number} red -1 1342 * @param {number} green -1 1343 * @param {number} blue -1 1344 * @param {number} alpha 1571 1345 */1572 -1 axs.utils.namedValues = function(obj) {1573 -1 var values = {};1574 -1 for (var key in obj) {1575 -1 if (obj.hasOwnProperty(key) && typeof obj[key] != 'function')1576 -1 values[key] = obj[key];1577 -1 }1578 -1 return values;-1 1346 axs.color.Color = function(red, green, blue, alpha) { -1 1347 /** @type {number} */ -1 1348 this.red = red; -1 1349 -1 1350 /** @type {number} */ -1 1351 this.green = green; -1 1352 -1 1353 /** @type {number} */ -1 1354 this.blue = blue; -1 1355 -1 1356 /** @type {number} */ -1 1357 this.alpha = alpha; 1579 1358 }; 1580 1359 1581 1360 /**1582 -1 * Escapes a given ID to be used in a CSS selector1583 -1 *1584 -1 * @private1585 -1 * @param {!string} id The ID to be escaped1586 -1 * @return {string} The escaped ID1587 -1 */1588 -1 function escapeId(id) {1589 -1 return id.replace(/[^a-zA-Z0-9_-]/g,function(match) { return '\\' + match; });1590 -1 }1591 -11592 -1 /** Gets a CSS selector text for a DOM object.1593 -1 * @param {Node} obj The DOM object.1594 -1 * @return {string} CSS selector text for the DOM object.-1 1361 * @constructor -1 1362 * See https://en.wikipedia.org/wiki/YCbCr for more information. -1 1363 * @param {Array.<number>} coords The YCbCr values as a 3 element array, in the order [luma, Cb, Cr]. -1 1364 * All numbers are in the range [0, 1]. 1595 1365 */1596 -1 axs.utils.getQuerySelectorText = function(obj) {1597 -1 if (obj == null || obj.tagName == 'HTML') {1598 -1 return 'html';1599 -1 } else if (obj.tagName == 'BODY') {1600 -1 return 'body';1601 -1 }-1 1366 axs.color.YCbCr = function(coords) { -1 1367 /** @type {number} */ -1 1368 this.luma = this.z = coords[0]; 1602 13691603 -1 if (obj.hasAttribute) {1604 -1 if (obj.id) {1605 -1 return '#' + escapeId(obj.id);1606 -1 }-1 1370 /** @type {number} */ -1 1371 this.Cb = this.x = coords[1]; 1607 13721608 -1 if (obj.className) {1609 -1 var selector = '';1610 -1 for (var i = 0; i < obj.classList.length; i++)1611 -1 selector += '.' + obj.classList[i];-1 1373 /** @type {number} */ -1 1374 this.Cr = this.y = coords[2]; -1 1375 }; 1612 13761613 -1 var total = 0;1614 -1 if (obj.parentNode) {1615 -1 for (i = 0; i < obj.parentNode.children.length; i++) {1616 -1 var similar = obj.parentNode.children[i];1617 -1 if (axs.browserUtils.matchSelector(similar, selector))1618 -1 total++;1619 -1 if (similar === obj)1620 -1 break;1621 -1 }1622 -1 } else {1623 -1 total = 1;1624 -1 }-1 1377 axs.color.YCbCr.prototype = { -1 1378 /** -1 1379 * @param {number} scalar -1 1380 * @return {axs.color.YCbCr} This color multiplied by the given scalar -1 1381 */ -1 1382 multiply: function(scalar) { -1 1383 var result = [ this.luma * scalar, this.Cb * scalar, this.Cr * scalar ]; -1 1384 return new axs.color.YCbCr(result); -1 1385 }, 1625 13861626 -1 if (total == 1) {1627 -1 return axs.utils.getQuerySelectorText(obj.parentNode) +1628 -1 ' > ' + selector;1629 -1 }1630 -1 }-1 1387 /** -1 1388 * @param {axs.color.YCbCr} other -1 1389 * @return {axs.color.YCbCr} This plus other -1 1390 */ -1 1391 add: function(other) { -1 1392 var result = [ this.luma + other.luma, this.Cb + other.Cb, this.Cr + other.Cr ]; -1 1393 return new axs.color.YCbCr(result); -1 1394 }, 1631 13951632 -1 if (obj.parentNode) {1633 -1 var similarTags = obj.parentNode.children;1634 -1 var total = 1;1635 -1 var i = 0;1636 -1 while (similarTags[i] !== obj) {1637 -1 if (similarTags[i].tagName == obj.tagName) {1638 -1 total++;1639 -1 }1640 -1 i++;1641 -1 }-1 1396 /** -1 1397 * @param {axs.color.YCbCr} other -1 1398 * @return {axs.color.YCbCr} This minus other -1 1399 */ -1 1400 subtract: function(other) { -1 1401 var result = [ this.luma - other.luma, this.Cb - other.Cb, this.Cr - other.Cr ]; -1 1402 return new axs.color.YCbCr(result); -1 1403 } 1642 14041643 -1 var next = '';1644 -1 if (obj.parentNode.tagName != 'BODY') {1645 -1 next = axs.utils.getQuerySelectorText(obj.parentNode) +1646 -1 ' > ';1647 -1 }-1 1405 }; 1648 14061649 -1 if (total == 1) {1650 -1 return next +1651 -1 obj.tagName;1652 -1 } else {1653 -1 return next +1654 -1 obj.tagName +1655 -1 ':nth-of-type(' + total + ')';1656 -1 }1657 -1 }1658 14071659 -1 } else if (obj.selectorText) {1660 -1 return obj.selectorText;1661 -1 }-1 1408 /** -1 1409 * Calculate the contrast ratio between the two given colors. Returns the ratio -1 1410 * to 1, for example for two two colors with a contrast ratio of 21:1, this -1 1411 * function will return 21. -1 1412 * @param {axs.color.Color} fgColor -1 1413 * @param {axs.color.Color} bgColor -1 1414 * @return {!number} -1 1415 */ -1 1416 axs.color.calculateContrastRatio = function(fgColor, bgColor) { -1 1417 if (fgColor.alpha < 1) -1 1418 fgColor = axs.color.flattenColors(fgColor, bgColor); 1662 14191663 -1 return '';-1 1420 var fgLuminance = axs.color.calculateLuminance(fgColor); -1 1421 var bgLuminance = axs.color.calculateLuminance(bgColor); -1 1422 var contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) / -1 1423 (Math.min(fgLuminance, bgLuminance) + 0.05); -1 1424 return contrastRatio; 1664 1425 }; 1665 1426 1666 1427 /**1667 -1 * Gets elements that refer to this element in an ARIA attribute that takes an ID reference list or1668 -1 * single ID reference.1669 -1 * @param {Element} element a potential referent.1670 -1 * @param {string=} opt_attributeName Name of an ARIA attribute to limit the results to, e.g. 'aria-owns'.1671 -1 * @return {NodeList} The elements that refer to this element or null.-1 1428 * Calculate the luminance of the given color using the WCAG algorithm. -1 1429 * @param {axs.color.Color} color -1 1430 * @return {number} 1672 1431 */1673 -1 axs.utils.getAriaIdReferrers = function(element, opt_attributeName) {1674 -1 var propertyToSelector = function(propertyKey) {1675 -1 var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyKey];1676 -1 if (propertyDetails) {1677 -1 if (propertyDetails.valueType === ('idref')) {1678 -1 return '[aria-' + propertyKey + '=\'' + id + '\']';1679 -1 } else if (propertyDetails.valueType === ('idref_list')) {1680 -1 return '[aria-' + propertyKey + '~=\'' + id + '\']';1681 -1 }1682 -1 }1683 -1 return '';1684 -1 };1685 -1 if (!element)1686 -1 return null;1687 -1 var id = element.id;1688 -1 if (!id)1689 -1 return null;1690 -1 id = id.replace(/'/g, "\\'"); // make it safe to use in a selector-1 1432 axs.color.calculateLuminance = function(color) { -1 1433 /* var rSRGB = color.red / 255; -1 1434 var gSRGB = color.green / 255; -1 1435 var bSRGB = color.blue / 255; 1691 14361692 -1 if (opt_attributeName) {1693 -1 var propertyKey = opt_attributeName.replace(/^aria-/, '');1694 -1 var referrerQuery = propertyToSelector(propertyKey);1695 -1 if (referrerQuery) {1696 -1 return element.ownerDocument.querySelectorAll(referrerQuery);1697 -1 }1698 -1 } else {1699 -1 var selectors = [];1700 -1 for (var propertyKey in axs.constants.ARIA_PROPERTIES) {1701 -1 var referrerQuery = propertyToSelector(propertyKey);1702 -1 if (referrerQuery) {1703 -1 selectors.push(referrerQuery);1704 -1 }1705 -1 }1706 -1 return element.ownerDocument.querySelectorAll(selectors.join(','));1707 -1 }1708 -1 return null;-1 1437 var r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055)/1.055), 2.4); -1 1438 var g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow(((gSRGB + 0.055)/1.055), 2.4); -1 1439 var b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055)/1.055), 2.4); -1 1440 -1 1441 return 0.2126 * r + 0.7152 * g + 0.0722 * b; */ -1 1442 var ycc = axs.color.toYCbCr(color); -1 1443 return ycc.luma; 1709 1444 }; 1710 1445 1711 1446 /**1712 -1 * Gets elements that refer to this element in an HTML attribute that takes an ID reference list or1713 -1 * single ID reference.1714 -1 * @param {Element} element a potential referent.1715 -1 * @return {NodeList} The elements that refer to this element.-1 1447 * Compute the luminance ratio between two luminance values. -1 1448 * @param {number} luminance1 -1 1449 * @param {number} luminance2 1716 1450 */1717 -1 axs.utils.getHtmlIdReferrers = function(element) {1718 -1 if (!element)1719 -1 return null;1720 -1 var id = element.id;1721 -1 if (!id)1722 -1 return null;1723 -1 id = id.replace(/'/g, "\\'"); // make it safe to use in a selector1724 -1 var selectorTemplates = [1725 -1 '[contextmenu=\'{id}\']',1726 -1 '[itemref~=\'{id}\']',1727 -1 'button[form=\'{id}\']',1728 -1 'button[menu=\'{id}\']',1729 -1 'fieldset[form=\'{id}\']',1730 -1 'input[form=\'{id}\']',1731 -1 'input[list=\'{id}\']',1732 -1 'keygen[form=\'{id}\']',1733 -1 'label[for=\'{id}\']',1734 -1 'label[form=\'{id}\']',1735 -1 'menuitem[command=\'{id}\']',1736 -1 'object[form=\'{id}\']',1737 -1 'output[for~=\'{id}\']',1738 -1 'output[form=\'{id}\']',1739 -1 'select[form=\'{id}\']',1740 -1 'td[headers~=\'{id}\']',1741 -1 'textarea[form=\'{id}\']',1742 -1 'tr[headers~=\'{id}\']'];1743 -1 var selectors = selectorTemplates.map(function(selector) {1744 -1 return selector.replace('\{id\}', id);1745 -1 });1746 -1 return element.ownerDocument.querySelectorAll(selectors.join(','));-1 1451 axs.color.luminanceRatio = function(luminance1, luminance2) { -1 1452 return (Math.max(luminance1, luminance2) + 0.05) / -1 1453 (Math.min(luminance1, luminance2) + 0.05); 1747 1454 }; 1748 1455 1749 1456 /**1750 -1 * Gets a list of all IDs this element references in either ARIA or HTML attributes.1751 -1 *1752 -1 * @param {Element} element The element to check for idref attributes.1753 -1 * @returns {Array.<string>} Any IDs this element references.-1 1457 * @param {string} colorString The color string from CSS. -1 1458 * @return {?axs.color.Color} 1754 1459 */1755 -1 axs.utils.getReferencedIds = function(element) {1756 -1 var result = [];1757 -1 var addResult = function(ids) {1758 -1 if (ids) {1759 -1 if (ids.indexOf(' ') > 0) {1760 -1 result = result.concat(attrib.value.split(' '));1761 -1 } else {1762 -1 result.push(ids);1763 -1 }1764 -1 }1765 -1 };1766 -1 for (var i = 0; i < element.attributes.length; i++) {1767 -1 var tagName = element.tagName.toLowerCase();1768 -1 var attrib = element.attributes[i];1769 -1 if (attrib.specified) {1770 -1 var attribName = attrib.name;1771 -1 var ariaAttr = attribName.match(/aria-(.+)/);1772 -1 if (ariaAttr) {1773 -1 var details = axs.constants.ARIA_PROPERTIES[ariaAttr[1]];1774 -1 if (details && (details.valueType === ('idref') || details.valueType === ('idref_list'))) {1775 -1 addResult(attrib.value);1776 -1 }1777 -1 continue;1778 -1 }1779 -1 switch (attribName) {1780 -1 case 'contextmenu':1781 -1 case 'itemref':1782 -1 addResult(attrib.value);1783 -1 break;1784 -1 case 'form':1785 -1 if (tagName == 'button' || tagName == 'fieldset' || tagName == 'input' ||1786 -1 tagName == 'keygen' || tagName == 'label' || tagName == 'object' ||1787 -1 tagName == 'output' || tagName == 'select' || tagName == 'textarea') {1788 -1 addResult(attrib.value);1789 -1 }1790 -1 break;1791 -1 case 'for':1792 -1 if (tagName == 'label' || tagName == 'output') {1793 -1 addResult(attrib.value);1794 -1 }1795 -1 break;1796 -1 case 'menu':1797 -1 if (tagName == 'button') {1798 -1 addResult(attrib.value);1799 -1 }1800 -1 break;1801 -1 case 'list':1802 -1 if (tagName == 'input') {1803 -1 addResult(attrib.value);1804 -1 }1805 -1 break;1806 -1 case 'command':1807 -1 if (tagName == 'menuitem') {1808 -1 addResult(attrib.value);1809 -1 }1810 -1 break;1811 -1 case 'headers':1812 -1 if (tagName == 'td' || tagName == 'tr') {1813 -1 addResult(attrib.value);1814 -1 }1815 -1 break;1816 -1 }1817 -1 }-1 1460 axs.color.parseColor = function(colorString) { -1 1461 if (colorString === "transparent") { -1 1462 return new axs.color.Color(0, 0, 0, 0); 1818 1463 }1819 -1 return result;1820 -1 };-1 1464 var rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)$/; -1 1465 var match = colorString.match(rgbRegex); 1821 14661822 -1 /**1823 -1 * Gets elements that refer to this element in an attribute that takes an ID reference list or1824 -1 * single ID reference.1825 -1 * @param {Element} element a potential referent.1826 -1 * @return {Array<Element>} The elements that refer to this element.1827 -1 */1828 -1 axs.utils.getIdReferrers = function(element) {1829 -1 var result = [];1830 -1 var referrers = axs.utils.getHtmlIdReferrers(element);1831 -1 if (referrers) {1832 -1 result = result.concat(Array.prototype.slice.call(referrers));-1 1467 if (match) { -1 1468 var r = parseInt(match[1], 10); -1 1469 var g = parseInt(match[2], 10); -1 1470 var b = parseInt(match[3], 10); -1 1471 var a = 1; -1 1472 return new axs.color.Color(r, g, b, a); 1833 1473 }1834 -1 referrers = axs.utils.getAriaIdReferrers(element);1835 -1 if (referrers) {1836 -1 result = result.concat(Array.prototype.slice.call(referrers));-1 1474 -1 1475 var rgbaRegex = /^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/; -1 1476 match = colorString.match(rgbaRegex); -1 1477 if (match) { -1 1478 var r = parseInt(match[1], 10); -1 1479 var g = parseInt(match[2], 10); -1 1480 var b = parseInt(match[3], 10); -1 1481 var a = parseFloat(match[4]); -1 1482 return new axs.color.Color(r, g, b, a); 1837 1483 }1838 -1 return result;-1 1484 -1 1485 return null; 1839 1486 }; 1840 1487 1841 1488 /**1842 -1 * Gets elements which this element refers to in the given attribute.1843 -1 * @param {!string} attributeName Name of an ARIA attribute, e.g. 'aria-owns'.1844 -1 * @param {Element} element The DOM element which has the ARIA attribute.1845 -1 * @return {!Array.<Element>} An array of elements that are referred to by this element.1846 -1 * @example1847 -1 * var owner = document.body.appendChild(document.createElement("div"));1848 -1 * var owned = document.body.appendChild(document.createElement("div"));1849 -1 * owner.setAttribute("aria-owns", "kungfu");1850 -1 * owned.setAttribute("id", "kungfu");1851 -1 * console.log(axs.utils.getIdReferents("aria-owns", owner)[0] === owned); // This will log 'true'-1 1489 * @param {number} value The value of a color channel, 0 <= value <= 0xFF -1 1490 * @return {!string} 1852 1491 */1853 -1 axs.utils.getIdReferents = function(attributeName, element) {1854 -1 var result = [];1855 -1 var propertyKey = attributeName.replace(/^aria-/, '');1856 -1 var property = axs.constants.ARIA_PROPERTIES[propertyKey];1857 -1 if (!property || !element.hasAttribute(attributeName))1858 -1 return result;1859 -1 var propertyType = property.valueType;1860 -1 if (propertyType === 'idref_list' || propertyType === 'idref') {1861 -1 var ownerDocument = element.ownerDocument;1862 -1 var ids = element.getAttribute(attributeName);1863 -1 ids = ids.split(/\s+/);1864 -1 for (var i = 0, len = ids.length; i < len; i++) {1865 -1 var next = ownerDocument.getElementById(ids[i]);1866 -1 if (next) {1867 -1 result[result.length] = next;1868 -1 }1869 -1 }1870 -1 }1871 -1 return result;-1 1492 axs.color.colorChannelToString = function(value) { -1 1493 value = Math.round(value); -1 1494 if (value <= 0xF) -1 1495 return '0' + value.toString(16); -1 1496 return value.toString(16); 1872 1497 }; 1873 1498 1874 1499 /**1875 -1 * Gets a subset of 'axs.constants.ARIA_PROPERTIES' filtered by 'valueType'.1876 -1 * @param {!Array.<string>} valueTypes Types to match, e.g. ['idref_list'].1877 -1 * @return {Object.<string, Object>} axs.constants.ARIA_PROPERTIES which match.-1 1500 * @param {axs.color.Color} color -1 1501 * @return {!string} 1878 1502 */1879 -1 axs.utils.getAriaPropertiesByValueType = function(valueTypes) {1880 -1 var result = {};1881 -1 for (var propertyName in axs.constants.ARIA_PROPERTIES) {1882 -1 var property = axs.constants.ARIA_PROPERTIES[propertyName];1883 -1 if (property && valueTypes.indexOf(property.valueType) >= 0) {1884 -1 result[propertyName] = property;1885 -1 }-1 1503 axs.color.colorToString = function(color) { -1 1504 if (color.alpha == 1) { -1 1505 return '#' + axs.color.colorChannelToString(color.red) + -1 1506 axs.color.colorChannelToString(color.green) + axs.color.colorChannelToString(color.blue); 1886 1507 }1887 -1 return result;-1 1508 else -1 1509 return 'rgba(' + [color.red, color.green, color.blue, color.alpha].join(',') + ')'; 1888 1510 }; 1889 1511 1890 1512 /**1891 -1 * Builds a selector that matches an element with any of these ARIA properties.1892 -1 * @param {Object.<string, Object>} ariaProperties axs.constants.ARIA_PROPERTIES1893 -1 * @return {!string} The selector.1894 -1 */1895 -1 axs.utils.getSelectorForAriaProperties = function(ariaProperties) {1896 -1 var propertyNames = Object.keys(/** @type {!Object} */(ariaProperties));1897 -1 var result = propertyNames.map(function(propertyName) {1898 -1 return '[aria-' + propertyName + ']';1899 -1 });1900 -1 result.sort(); // facilitates reading long selectors and unit testing1901 -1 return result.join(',');1902 -1 };1903 -11904 -1 /**1905 -1 * Finds descendants of this element which implement the given ARIA role.1906 -1 * Will look for descendants with implicit or explicit role.1907 -1 * @param {Element} element an HTML DOM element.1908 -1 * @param {string} role The role you seek.1909 -1 * @return {!Array.<Element>} An array of matching elements.1910 -1 * @example1911 -1 * var container = document.createElement("div");1912 -1 * var button = document.createElement("button");1913 -1 * var span = document.createElement("span");1914 -1 * span.setAttribute("role", "button");1915 -1 * container.appendChild(button);1916 -1 * container.appendChild(span);1917 -1 * var result = axs.utils.findDescendantsWithRole(container, "button"); // result is an array containing both 'button' and 'span'-1 1513 * Compute a desired luminance given a given luminance and a desired contrast ratio. -1 1514 * @param {number} luminance The given luminance. -1 1515 * @param {number} contrast The desired contrast ratio. -1 1516 * @param {boolean} higher Whether the desired luminance is higher or lower than the given luminance. -1 1517 * @return {number} The desired luminance. 1918 1518 */1919 -1 axs.utils.findDescendantsWithRole = function(element, role) {1920 -1 if (!(element && role))1921 -1 return [];1922 -1 var selector = axs.properties.getSelectorForRole(role);1923 -1 if (!selector)1924 -1 return [];1925 -1 var result = element.querySelectorAll(selector);1926 -1 if (result) { // Convert NodeList to Array; methinks 80/20 that's what callers want.1927 -1 result = Array.prototype.map.call(result, function(item) { return item; });-1 1519 axs.color.luminanceFromContrastRatio = function(luminance, contrast, higher) { -1 1520 if (higher) { -1 1521 var newLuminance = (luminance + 0.05) * contrast - 0.05; -1 1522 return newLuminance; 1928 1523 } else {1929 -1 return [];-1 1524 var newLuminance = (luminance + 0.05) / contrast - 0.05; -1 1525 return newLuminance; 1930 1526 }1931 -1 return result;1932 1527 }; 1933 15281934 -1 },{}],7:[function(require,module,exports){1935 -1 // Copyright 2013 Google Inc.1936 -1 //1937 -1 // Licensed under the Apache License, Version 2.0 (the "License");1938 -1 // you may not use this file except in compliance with the License.1939 -1 // You may obtain a copy of the License at1940 -1 //1941 -1 // http://www.apache.org/licenses/LICENSE-2.01942 -1 //1943 -1 // Unless required by applicable law or agreed to in writing, software1944 -1 // distributed under the License is distributed on an "AS IS" BASIS,1945 -1 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.1946 -1 // See the License for the specific language governing permissions and1947 -1 // limitations under the License.1948 -11949 -1 goog.provide('axs.browserUtils');1950 -11951 1529 /**1952 -1 * Use Webkit matcher when matches() is not supported.1953 -1 * Use Firefox matcher when Webkit is not supported.1954 -1 * Use IE matcher when neither webkit nor Firefox supported.1955 -1 * @param {Element} element1956 -1 * @param {string} selector1957 -1 * @return {boolean} true if the element matches the selector-1 1530 * Given a color in YCbCr format and a desired luminance, pick a new color with the desired luminance which is -1 1531 * as close as possible to the original color. -1 1532 * @param {axs.color.YCbCr} ycc The original color in YCbCr form. -1 1533 * @param {number} luma The desired luminance -1 1534 * @return {!axs.color.Color} A new color in RGB. 1958 1535 */1959 -1 axs.browserUtils.matchSelector = function(element, selector) {1960 -1 if (element.matches)1961 -1 return element.matches(selector);1962 -1 if (element.webkitMatchesSelector)1963 -1 return element.webkitMatchesSelector(selector);1964 -1 if (element.mozMatchesSelector)1965 -1 return element.mozMatchesSelector(selector);1966 -1 if (element.msMatchesSelector)1967 -1 return element.msMatchesSelector(selector);1968 -1 return false;1969 -1 };1970 -11971 -1 },{}],8:[function(require,module,exports){1972 -1 // Copyright 2015 Google Inc.1973 -1 //1974 -1 // Licensed under the Apache License, Version 2.0 (the "License");1975 -1 // you may not use this file except in compliance with the License.1976 -1 // You may obtain a copy of the License at1977 -1 //1978 -1 // http://www.apache.org/licenses/LICENSE-2.01979 -1 //1980 -1 // Unless required by applicable law or agreed to in writing, software1981 -1 // distributed under the License is distributed on an "AS IS" BASIS,1982 -1 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.1983 -1 // See the License for the specific language governing permissions and1984 -1 // limitations under the License.-1 1536 axs.color.translateColor = function(ycc, luma) { -1 1537 var endpoint = (luma > ycc.luma) ? axs.color.WHITE_YCC : axs.color.BLACK_YCC; -1 1538 var cubeFaces = (endpoint == axs.color.WHITE_YCC) ? axs.color.YCC_CUBE_FACES_WHITE -1 1539 : axs.color.YCC_CUBE_FACES_BLACK; 1985 15401986 -1 goog.provide('axs.color');1987 -1 goog.provide('axs.color.Color');-1 1541 var a = new axs.color.YCbCr([0, ycc.Cb, ycc.Cr]); -1 1542 var b = new axs.color.YCbCr([1, ycc.Cb, ycc.Cr]); -1 1543 var line = { a: a, b: b }; 1988 15441989 -1 /**1990 -1 * @constructor1991 -1 * @param {number} red1992 -1 * @param {number} green1993 -1 * @param {number} blue1994 -1 * @param {number} alpha1995 -1 */1996 -1 axs.color.Color = function(red, green, blue, alpha) {1997 -1 /** @type {number} */1998 -1 this.red = red;-1 1545 var intersection = null; -1 1546 for (var i = 0; i < cubeFaces.length; i++) { -1 1547 var cubeFace = cubeFaces[i]; -1 1548 intersection = axs.color.findIntersection(line, cubeFace); -1 1549 // If intersection within [0, 1] in Z axis, it is within the cube. -1 1550 if (intersection.z >= 0 && intersection.z <= 1) -1 1551 break; -1 1552 } -1 1553 if (!intersection) { -1 1554 // Should never happen -1 1555 throw "Couldn't find intersection with YCbCr color cube for Cb=" + ycc.Cb + ", Cr=" + ycc.Cr + "."; -1 1556 } -1 1557 if (intersection.x != ycc.x || intersection.y != ycc.y) { -1 1558 // Should never happen -1 1559 throw "Intersection has wrong Cb/Cr values."; -1 1560 } 1999 15612000 -1 /** @type {number} */2001 -1 this.green = green;-1 1562 // If intersection.luma is closer to endpoint than desired luma, then luma is inside cube -1 1563 // and we can immediately return new value. -1 1564 if (Math.abs(endpoint.luma - intersection.luma) < Math.abs(endpoint.luma - luma)) { -1 1565 var translatedColor = [luma, ycc.Cb, ycc.Cr]; -1 1566 return axs.color.fromYCbCrArray(translatedColor); -1 1567 } 2002 15682003 -1 /** @type {number} */2004 -1 this.blue = blue;-1 1569 // Otherwise, translate from intersection towards white/black such that luma is correct. -1 1570 var dLuma = luma - intersection.luma; -1 1571 var scale = dLuma / (endpoint.luma - intersection.luma); -1 1572 var translatedColor = [ luma, -1 1573 intersection.Cb - (intersection.Cb * scale), -1 1574 intersection.Cr - (intersection.Cr * scale) ]; 2005 15752006 -1 /** @type {number} */2007 -1 this.alpha = alpha;-1 1576 return axs.color.fromYCbCrArray(translatedColor); 2008 1577 }; 2009 1578 -1 1579 /** @typedef {{fg: string, bg: string, contrast: string}} */ -1 1580 axs.color.SuggestedColors; -1 1581 2010 1582 /**2011 -1 * @constructor2012 -1 * See https://en.wikipedia.org/wiki/YCbCr for more information.2013 -1 * @param {Array.<number>} coords The YCbCr values as a 3 element array, in the order [luma, Cb, Cr].2014 -1 * All numbers are in the range [0, 1].-1 1583 * @param {axs.color.Color} bgColor -1 1584 * @param {axs.color.Color} fgColor -1 1585 * @param {Object.<string, number>} desiredContrastRatios A map of label to desired contrast ratio. -1 1586 * @return {Object.<string, axs.color.SuggestedColors>} 2015 1587 */2016 -1 axs.color.YCbCr = function(coords) {2017 -1 /** @type {number} */2018 -1 this.luma = this.z = coords[0];2019 -12020 -1 /** @type {number} */2021 -1 this.Cb = this.x = coords[1];2022 -12023 -1 /** @type {number} */2024 -1 this.Cr = this.y = coords[2];2025 -1 };-1 1588 axs.color.suggestColors = function(bgColor, fgColor, desiredContrastRatios) { -1 1589 var colors = {}; -1 1590 var bgLuminance = axs.color.calculateLuminance(bgColor); -1 1591 var fgLuminance = axs.color.calculateLuminance(fgColor); 2026 15922027 -1 axs.color.YCbCr.prototype = {2028 -1 /**2029 -1 * @param {number} scalar2030 -1 * @return {axs.color.YCbCr} This color multiplied by the given scalar2031 -1 */2032 -1 multiply: function(scalar) {2033 -1 var result = [ this.luma * scalar, this.Cb * scalar, this.Cr * scalar ];2034 -1 return new axs.color.YCbCr(result);2035 -1 },-1 1593 var fgLuminanceIsHigher = fgLuminance > bgLuminance; -1 1594 var fgYCbCr = axs.color.toYCbCr(fgColor); -1 1595 var bgYCbCr = axs.color.toYCbCr(bgColor); -1 1596 for (var desiredLabel in desiredContrastRatios) { -1 1597 var desiredContrast = desiredContrastRatios[desiredLabel]; 2036 15982037 -1 /**2038 -1 * @param {axs.color.YCbCr} other2039 -1 * @return {axs.color.YCbCr} This plus other2040 -1 */2041 -1 add: function(other) {2042 -1 var result = [ this.luma + other.luma, this.Cb + other.Cb, this.Cr + other.Cr ];2043 -1 return new axs.color.YCbCr(result);2044 -1 },-1 1599 var desiredFgLuminance = axs.color.luminanceFromContrastRatio(bgLuminance, desiredContrast + 0.02, fgLuminanceIsHigher); -1 1600 if (desiredFgLuminance <= 1 && desiredFgLuminance >= 0) { -1 1601 var newFgColor = axs.color.translateColor(fgYCbCr, desiredFgLuminance); -1 1602 var newContrastRatio = axs.color.calculateContrastRatio(newFgColor, bgColor); -1 1603 var suggestedColors = {}; -1 1604 suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(newFgColor)); -1 1605 suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(bgColor)); -1 1606 suggestedColors.contrast = /** @type {!string} */ (newContrastRatio.toFixed(2)); -1 1607 colors[desiredLabel] = /** @type {axs.color.SuggestedColors} */ (suggestedColors); -1 1608 continue; -1 1609 } 2045 16102046 -1 /**2047 -1 * @param {axs.color.YCbCr} other2048 -1 * @return {axs.color.YCbCr} This minus other2049 -1 */2050 -1 subtract: function(other) {2051 -1 var result = [ this.luma - other.luma, this.Cb - other.Cb, this.Cr - other.Cr ];2052 -1 return new axs.color.YCbCr(result);-1 1611 var desiredBgLuminance = axs.color.luminanceFromContrastRatio(fgLuminance, desiredContrast + 0.02, !fgLuminanceIsHigher); -1 1612 if (desiredBgLuminance <= 1 && desiredBgLuminance >= 0) { -1 1613 var newBgColor = axs.color.translateColor(bgYCbCr, desiredBgLuminance); -1 1614 var newContrastRatio = axs.color.calculateContrastRatio(fgColor, newBgColor); -1 1615 var suggestedColors = {}; -1 1616 suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(newBgColor)); -1 1617 suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(fgColor)); -1 1618 suggestedColors.contrast = /** @type {!string} */ (newContrastRatio.toFixed(2)); -1 1619 colors[desiredLabel] = /** @type {axs.color.SuggestedColors} */ (suggestedColors); -1 1620 } 2053 1621 }2054 -1-1 1622 return colors; 2055 1623 }; 2056 16242057 -12058 1625 /**2059 -1 * Calculate the contrast ratio between the two given colors. Returns the ratio2060 -1 * to 1, for example for two two colors with a contrast ratio of 21:1, this2061 -1 * function will return 21.-1 1626 * Combine the two given color according to alpha blending. 2062 1627 * @param {axs.color.Color} fgColor 2063 1628 * @param {axs.color.Color} bgColor2064 -1 * @return {!number}-1 1629 * @return {axs.color.Color} 2065 1630 */2066 -1 axs.color.calculateContrastRatio = function(fgColor, bgColor) {2067 -1 if (fgColor.alpha < 1)2068 -1 fgColor = axs.color.flattenColors(fgColor, bgColor);-1 1631 axs.color.flattenColors = function(fgColor, bgColor) { -1 1632 var alpha = fgColor.alpha; -1 1633 var r = ((1 - alpha) * bgColor.red) + (alpha * fgColor.red); -1 1634 var g = ((1 - alpha) * bgColor.green) + (alpha * fgColor.green); -1 1635 var b = ((1 - alpha) * bgColor.blue) + (alpha * fgColor.blue); -1 1636 var a = fgColor.alpha + (bgColor.alpha * (1 - fgColor.alpha)); 2069 16372070 -1 var fgLuminance = axs.color.calculateLuminance(fgColor);2071 -1 var bgLuminance = axs.color.calculateLuminance(bgColor);2072 -1 var contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) /2073 -1 (Math.min(fgLuminance, bgLuminance) + 0.05);2074 -1 return contrastRatio;2075 -1 };2076 -12077 -1 /**2078 -1 * Calculate the luminance of the given color using the WCAG algorithm.2079 -1 * @param {axs.color.Color} color2080 -1 * @return {number}2081 -1 */2082 -1 axs.color.calculateLuminance = function(color) {2083 -1 /* var rSRGB = color.red / 255;2084 -1 var gSRGB = color.green / 255;2085 -1 var bSRGB = color.blue / 255;2086 -12087 -1 var r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow(((rSRGB + 0.055)/1.055), 2.4);2088 -1 var g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow(((gSRGB + 0.055)/1.055), 2.4);2089 -1 var b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow(((bSRGB + 0.055)/1.055), 2.4);2090 -12091 -1 return 0.2126 * r + 0.7152 * g + 0.0722 * b; */2092 -1 var ycc = axs.color.toYCbCr(color);2093 -1 return ycc.luma;2094 -1 };2095 -12096 -1 /**2097 -1 * Compute the luminance ratio between two luminance values.2098 -1 * @param {number} luminance12099 -1 * @param {number} luminance22100 -1 */2101 -1 axs.color.luminanceRatio = function(luminance1, luminance2) {2102 -1 return (Math.max(luminance1, luminance2) + 0.05) /2103 -1 (Math.min(luminance1, luminance2) + 0.05);2104 -1 };2105 -12106 -1 /**2107 -1 * @param {string} colorString The color string from CSS.2108 -1 * @return {?axs.color.Color}2109 -1 */2110 -1 axs.color.parseColor = function(colorString) {2111 -1 if (colorString === "transparent") {2112 -1 return new axs.color.Color(0, 0, 0, 0);2113 -1 }2114 -1 var rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)$/;2115 -1 var match = colorString.match(rgbRegex);2116 -12117 -1 if (match) {2118 -1 var r = parseInt(match[1], 10);2119 -1 var g = parseInt(match[2], 10);2120 -1 var b = parseInt(match[3], 10);2121 -1 var a = 1;2122 -1 return new axs.color.Color(r, g, b, a);2123 -1 }2124 -12125 -1 var rgbaRegex = /^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/;2126 -1 match = colorString.match(rgbaRegex);2127 -1 if (match) {2128 -1 var r = parseInt(match[1], 10);2129 -1 var g = parseInt(match[2], 10);2130 -1 var b = parseInt(match[3], 10);2131 -1 var a = parseFloat(match[4]);2132 -1 return new axs.color.Color(r, g, b, a);2133 -1 }2134 -12135 -1 return null;2136 -1 };2137 -12138 -1 /**2139 -1 * @param {number} value The value of a color channel, 0 <= value <= 0xFF2140 -1 * @return {!string}2141 -1 */2142 -1 axs.color.colorChannelToString = function(value) {2143 -1 value = Math.round(value);2144 -1 if (value <= 0xF)2145 -1 return '0' + value.toString(16);2146 -1 return value.toString(16);2147 -1 };2148 -12149 -1 /**2150 -1 * @param {axs.color.Color} color2151 -1 * @return {!string}2152 -1 */2153 -1 axs.color.colorToString = function(color) {2154 -1 if (color.alpha == 1) {2155 -1 return '#' + axs.color.colorChannelToString(color.red) +2156 -1 axs.color.colorChannelToString(color.green) + axs.color.colorChannelToString(color.blue);2157 -1 }2158 -1 else2159 -1 return 'rgba(' + [color.red, color.green, color.blue, color.alpha].join(',') + ')';2160 -1 };2161 -12162 -1 /**2163 -1 * Compute a desired luminance given a given luminance and a desired contrast ratio.2164 -1 * @param {number} luminance The given luminance.2165 -1 * @param {number} contrast The desired contrast ratio.2166 -1 * @param {boolean} higher Whether the desired luminance is higher or lower than the given luminance.2167 -1 * @return {number} The desired luminance.2168 -1 */2169 -1 axs.color.luminanceFromContrastRatio = function(luminance, contrast, higher) {2170 -1 if (higher) {2171 -1 var newLuminance = (luminance + 0.05) * contrast - 0.05;2172 -1 return newLuminance;2173 -1 } else {2174 -1 var newLuminance = (luminance + 0.05) / contrast - 0.05;2175 -1 return newLuminance;2176 -1 }2177 -1 };2178 -12179 -1 /**2180 -1 * Given a color in YCbCr format and a desired luminance, pick a new color with the desired luminance which is2181 -1 * as close as possible to the original color.2182 -1 * @param {axs.color.YCbCr} ycc The original color in YCbCr form.2183 -1 * @param {number} luma The desired luminance2184 -1 * @return {!axs.color.Color} A new color in RGB.2185 -1 */2186 -1 axs.color.translateColor = function(ycc, luma) {2187 -1 var endpoint = (luma > ycc.luma) ? axs.color.WHITE_YCC : axs.color.BLACK_YCC;2188 -1 var cubeFaces = (endpoint == axs.color.WHITE_YCC) ? axs.color.YCC_CUBE_FACES_WHITE2189 -1 : axs.color.YCC_CUBE_FACES_BLACK;2190 -12191 -1 var a = new axs.color.YCbCr([0, ycc.Cb, ycc.Cr]);2192 -1 var b = new axs.color.YCbCr([1, ycc.Cb, ycc.Cr]);2193 -1 var line = { a: a, b: b };2194 -12195 -1 var intersection = null;2196 -1 for (var i = 0; i < cubeFaces.length; i++) {2197 -1 var cubeFace = cubeFaces[i];2198 -1 intersection = axs.color.findIntersection(line, cubeFace);2199 -1 // If intersection within [0, 1] in Z axis, it is within the cube.2200 -1 if (intersection.z >= 0 && intersection.z <= 1)2201 -1 break;2202 -1 }2203 -1 if (!intersection) {2204 -1 // Should never happen2205 -1 throw "Couldn't find intersection with YCbCr color cube for Cb=" + ycc.Cb + ", Cr=" + ycc.Cr + ".";2206 -1 }2207 -1 if (intersection.x != ycc.x || intersection.y != ycc.y) {2208 -1 // Should never happen2209 -1 throw "Intersection has wrong Cb/Cr values.";2210 -1 }2211 -12212 -1 // If intersection.luma is closer to endpoint than desired luma, then luma is inside cube2213 -1 // and we can immediately return new value.2214 -1 if (Math.abs(endpoint.luma - intersection.luma) < Math.abs(endpoint.luma - luma)) {2215 -1 var translatedColor = [luma, ycc.Cb, ycc.Cr];2216 -1 return axs.color.fromYCbCrArray(translatedColor);2217 -1 }2218 -12219 -1 // Otherwise, translate from intersection towards white/black such that luma is correct.2220 -1 var dLuma = luma - intersection.luma;2221 -1 var scale = dLuma / (endpoint.luma - intersection.luma);2222 -1 var translatedColor = [ luma,2223 -1 intersection.Cb - (intersection.Cb * scale),2224 -1 intersection.Cr - (intersection.Cr * scale) ];2225 -12226 -1 return axs.color.fromYCbCrArray(translatedColor);2227 -1 };2228 -12229 -1 /** @typedef {{fg: string, bg: string, contrast: string}} */2230 -1 axs.color.SuggestedColors;2231 -12232 -1 /**2233 -1 * @param {axs.color.Color} bgColor2234 -1 * @param {axs.color.Color} fgColor2235 -1 * @param {Object.<string, number>} desiredContrastRatios A map of label to desired contrast ratio.2236 -1 * @return {Object.<string, axs.color.SuggestedColors>}2237 -1 */2238 -1 axs.color.suggestColors = function(bgColor, fgColor, desiredContrastRatios) {2239 -1 var colors = {};2240 -1 var bgLuminance = axs.color.calculateLuminance(bgColor);2241 -1 var fgLuminance = axs.color.calculateLuminance(fgColor);2242 -12243 -1 var fgLuminanceIsHigher = fgLuminance > bgLuminance;2244 -1 var fgYCbCr = axs.color.toYCbCr(fgColor);2245 -1 var bgYCbCr = axs.color.toYCbCr(bgColor);2246 -1 for (var desiredLabel in desiredContrastRatios) {2247 -1 var desiredContrast = desiredContrastRatios[desiredLabel];2248 -12249 -1 var desiredFgLuminance = axs.color.luminanceFromContrastRatio(bgLuminance, desiredContrast + 0.02, fgLuminanceIsHigher);2250 -1 if (desiredFgLuminance <= 1 && desiredFgLuminance >= 0) {2251 -1 var newFgColor = axs.color.translateColor(fgYCbCr, desiredFgLuminance);2252 -1 var newContrastRatio = axs.color.calculateContrastRatio(newFgColor, bgColor);2253 -1 var suggestedColors = {};2254 -1 suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(newFgColor));2255 -1 suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(bgColor));2256 -1 suggestedColors.contrast = /** @type {!string} */ (newContrastRatio.toFixed(2));2257 -1 colors[desiredLabel] = /** @type {axs.color.SuggestedColors} */ (suggestedColors);2258 -1 continue;2259 -1 }2260 -12261 -1 var desiredBgLuminance = axs.color.luminanceFromContrastRatio(fgLuminance, desiredContrast + 0.02, !fgLuminanceIsHigher);2262 -1 if (desiredBgLuminance <= 1 && desiredBgLuminance >= 0) {2263 -1 var newBgColor = axs.color.translateColor(bgYCbCr, desiredBgLuminance);2264 -1 var newContrastRatio = axs.color.calculateContrastRatio(fgColor, newBgColor);2265 -1 var suggestedColors = {};2266 -1 suggestedColors.bg = /** @type {!string} */ (axs.color.colorToString(newBgColor));2267 -1 suggestedColors.fg = /** @type {!string} */ (axs.color.colorToString(fgColor));2268 -1 suggestedColors.contrast = /** @type {!string} */ (newContrastRatio.toFixed(2));2269 -1 colors[desiredLabel] = /** @type {axs.color.SuggestedColors} */ (suggestedColors);2270 -1 }2271 -1 }2272 -1 return colors;2273 -1 };2274 -12275 -1 /**2276 -1 * Combine the two given color according to alpha blending.2277 -1 * @param {axs.color.Color} fgColor2278 -1 * @param {axs.color.Color} bgColor2279 -1 * @return {axs.color.Color}2280 -1 */2281 -1 axs.color.flattenColors = function(fgColor, bgColor) {2282 -1 var alpha = fgColor.alpha;2283 -1 var r = ((1 - alpha) * bgColor.red) + (alpha * fgColor.red);2284 -1 var g = ((1 - alpha) * bgColor.green) + (alpha * fgColor.green);2285 -1 var b = ((1 - alpha) * bgColor.blue) + (alpha * fgColor.blue);2286 -1 var a = fgColor.alpha + (bgColor.alpha * (1 - fgColor.alpha));2287 -12288 -1 return new axs.color.Color(r, g, b, a);-1 1638 return new axs.color.Color(r, g, b, a); 2289 1639 }; 2290 1640 2291 1641 /** @@ -2510,7 +1860,7 @@ axs.color.YCC_CUBE_FACES_WHITE = [ { p0: axs.color.WHITE_YCC, p1: axs.color.CYAN 2510 1860 { p0: axs.color.WHITE_YCC, p1: axs.color.MAGENTA_YCC, p2: axs.color.YELLOW_YCC }, 2511 1861 { p0: axs.color.WHITE_YCC, p1: axs.color.YELLOW_YCC, p2: axs.color.CYAN_YCC } ]; 2512 18622513 -1 },{}],9:[function(require,module,exports){-1 1863 },{}],4:[function(require,module,exports){ 2514 1864 // Copyright 2012 Google Inc. 2515 1865 // 2516 1866 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -4194,7 +3544,7 @@ axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO = { 4194 3544 }] 4195 3545 }; 4196 35464197 -1 },{}],10:[function(require,module,exports){-1 3547 },{}],5:[function(require,module,exports){ 4198 3548 // Copyright 2015 Google Inc. 4199 3549 // 4200 3550 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -4408,7 +3758,7 @@ axs.dom.composedTreeSearch = function(node, end, callbacks, parentFlags, opt_sha 4408 3758 return found; 4409 3759 }; 4410 37604411 -1 },{}],11:[function(require,module,exports){-1 3761 },{}],6:[function(require,module,exports){ 4412 3762 // Copyright 2012 Google Inc. 4413 3763 // 4414 3764 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -4813,529 +4163,1170 @@ axs.properties.findTextAlternatives = function(node, textAlternatives, opt_recur 4813 4163 textAlternatives['content'] = textFromContentValue; 4814 4164 } 4815 41654816 -1 // 2D. The last resort is to use text from a tooltip attribute (such as the title attribute in4817 -1 // HTML). This is used only if nothing else, including subtree content, has provided results.4818 -1 if (element.hasAttribute('title')) {4819 -1 var titleValue = {};4820 -1 titleValue.type = 'string';4821 -1 titleValue.valid = true;4822 -1 titleValue.text = element.getAttribute('title');4823 -1 titleValue.lastWord = axs.properties.getLastWord(titleValue.lastWord);4824 -1 if (computedName)4825 -1 titleValue.unused = true;4826 -1 else4827 -1 computedName = titleValue.text;4828 -1 textAlternatives['title'] = titleValue;4829 -1 }-1 4166 // 2D. The last resort is to use text from a tooltip attribute (such as the title attribute in -1 4167 // HTML). This is used only if nothing else, including subtree content, has provided results. -1 4168 if (element.hasAttribute('title')) { -1 4169 var titleValue = {}; -1 4170 titleValue.type = 'string'; -1 4171 titleValue.valid = true; -1 4172 titleValue.text = element.getAttribute('title'); -1 4173 titleValue.lastWord = axs.properties.getLastWord(titleValue.lastWord); -1 4174 if (computedName) -1 4175 titleValue.unused = true; -1 4176 else -1 4177 computedName = titleValue.text; -1 4178 textAlternatives['title'] = titleValue; -1 4179 } -1 4180 -1 4181 if (Object.keys(textAlternatives).length == 0 && computedName == null) -1 4182 return null; -1 4183 -1 4184 return computedName; -1 4185 }; -1 4186 -1 4187 /** -1 4188 * @param {Element} element -1 4189 * @param {boolean=} opt_force Whether to return text alternatives for this -1 4190 * element regardless of its hidden state. -1 4191 * @return {?string} -1 4192 */ -1 4193 axs.properties.getTextFromDescendantContent = function(element, opt_force) { -1 4194 var children = element.childNodes; -1 4195 var childrenTextContent = []; -1 4196 for (var i = 0; i < children.length; i++) { -1 4197 var childTextContent = axs.properties.findTextAlternatives(children[i], {}, true, opt_force); -1 4198 if (childTextContent) -1 4199 childrenTextContent.push(childTextContent.trim()); -1 4200 } -1 4201 if (childrenTextContent.length) { -1 4202 var result = ''; -1 4203 // Empty children are allowed, but collapse all of them -1 4204 for (var i = 0; i < childrenTextContent.length; i++) -1 4205 result = [result, childrenTextContent[i]].join(' ').trim(); -1 4206 return result; -1 4207 } -1 4208 return null; -1 4209 }; -1 4210 -1 4211 /** -1 4212 * @param {Element} element -1 4213 * @param {Object} textAlternatives -1 4214 * @return {?string} -1 4215 */ -1 4216 axs.properties.getTextFromAriaLabelledby = function(element, textAlternatives) { -1 4217 var computedName = null; -1 4218 if (!element.hasAttribute('aria-labelledby')) -1 4219 return computedName; -1 4220 -1 4221 var labelledbyAttr = element.getAttribute('aria-labelledby'); -1 4222 var labelledbyIds = labelledbyAttr.split(/\s+/); -1 4223 var labelledbyValue = {}; -1 4224 labelledbyValue.valid = true; -1 4225 var labelledbyText = []; -1 4226 var labelledbyValues = []; -1 4227 for (var i = 0; i < labelledbyIds.length; i++) { -1 4228 var labelledby = {}; -1 4229 labelledby.type = 'element'; -1 4230 var labelledbyId = labelledbyIds[i]; -1 4231 labelledby.value = labelledbyId; -1 4232 var labelledbyElement = document.getElementById(labelledbyId); -1 4233 if (!labelledbyElement) { -1 4234 labelledby.valid = false; -1 4235 labelledbyValue.valid = false; -1 4236 labelledby.errorMessage = { 'messageKey': 'noElementWithId', 'args': [labelledbyId] }; -1 4237 } else { -1 4238 labelledby.valid = true; -1 4239 labelledby.text = axs.properties.findTextAlternatives(labelledbyElement, {}, true, true); -1 4240 labelledby.lastWord = axs.properties.getLastWord(labelledby.text); -1 4241 labelledbyText.push(labelledby.text); -1 4242 labelledby.element = labelledbyElement; -1 4243 } -1 4244 labelledbyValues.push(labelledby); -1 4245 } -1 4246 if (labelledbyValues.length > 0) { -1 4247 labelledbyValues[labelledbyValues.length - 1].last = true; -1 4248 labelledbyValue.values = labelledbyValues; -1 4249 labelledbyValue.text = labelledbyText.join(' '); -1 4250 labelledbyValue.lastWord = axs.properties.getLastWord(labelledbyValue.text); -1 4251 computedName = labelledbyValue.text; -1 4252 textAlternatives['ariaLabelledby'] = labelledbyValue; -1 4253 } -1 4254 -1 4255 return computedName; -1 4256 }; -1 4257 -1 4258 -1 4259 /** -1 4260 * Determine the text description/label for an element. -1 4261 * For example will attempt to find the alt text for an image or label text for a form control. -1 4262 * @param {!Element} element -1 4263 * @param {!Object} textAlternatives An object that will be updated with information. -1 4264 * @param {?string} existingComputedname -1 4265 * @param {boolean} recursive Whether this method is being called recursively as described in -1 4266 * http://www.w3.org/TR/wai-aria/roles#textalternativecomputation section 2A. -1 4267 * @return {Object} -1 4268 */ -1 4269 axs.properties.getTextFromHostLanguageAttributes = function(element, -1 4270 textAlternatives, -1 4271 existingComputedname, -1 4272 recursive) { -1 4273 var computedName = existingComputedname; -1 4274 if (axs.browserUtils.matchSelector(element, 'img') && element.hasAttribute('alt')) { -1 4275 var altValue = {}; -1 4276 altValue.type = 'string'; -1 4277 altValue.valid = true; -1 4278 altValue.text = element.getAttribute('alt'); -1 4279 if (computedName) -1 4280 altValue.unused = true; -1 4281 else -1 4282 computedName = altValue.text; -1 4283 textAlternatives['alt'] = altValue; -1 4284 } -1 4285 -1 4286 var controlsSelector = ['input:not([type="hidden"]):not([disabled])', -1 4287 'select:not([disabled])', -1 4288 'textarea:not([disabled])', -1 4289 'button:not([disabled])', -1 4290 'video:not([disabled])'].join(', '); -1 4291 if (axs.browserUtils.matchSelector(element, controlsSelector) && !recursive) { -1 4292 if (element.hasAttribute('id')) { -1 4293 var labelForQuerySelector = 'label[for="' + element.id + '"]'; -1 4294 var labelsFor = document.querySelectorAll(labelForQuerySelector); -1 4295 var labelForValue = {}; -1 4296 var labelForValues = []; -1 4297 var labelForText = []; -1 4298 for (var i = 0; i < labelsFor.length; i++) { -1 4299 var labelFor = {}; -1 4300 labelFor.type = 'element'; -1 4301 var label = labelsFor[i]; -1 4302 var labelText = axs.properties.findTextAlternatives(label, {}, true); -1 4303 if (labelText && labelText.trim().length > 0) { -1 4304 labelFor.text = labelText.trim(); -1 4305 labelForText.push(labelText.trim()); -1 4306 } -1 4307 labelFor.element = label; -1 4308 labelForValues.push(labelFor); -1 4309 } -1 4310 if (labelForValues.length > 0) { -1 4311 labelForValues[labelForValues.length - 1].last = true; -1 4312 labelForValue.values = labelForValues; -1 4313 labelForValue.text = labelForText.join(' '); -1 4314 labelForValue.lastWord = axs.properties.getLastWord(labelForValue.text); -1 4315 if (computedName) -1 4316 labelForValue.unused = true; -1 4317 else -1 4318 computedName = labelForValue.text; -1 4319 textAlternatives['labelFor'] = labelForValue; -1 4320 } -1 4321 } -1 4322 -1 4323 var parent = axs.dom.parentElement(element); -1 4324 var labelWrappedValue = {}; -1 4325 while (parent) { -1 4326 if (parent.tagName.toLowerCase() == 'label') { -1 4327 var parentLabel = /** @type {HTMLLabelElement} */ (parent); -1 4328 if (parentLabel.control == element) { -1 4329 labelWrappedValue.type = 'element'; -1 4330 labelWrappedValue.text = axs.properties.findTextAlternatives(parentLabel, {}, true); -1 4331 labelWrappedValue.lastWord = axs.properties.getLastWord(labelWrappedValue.text); -1 4332 labelWrappedValue.element = parentLabel; -1 4333 break; -1 4334 } -1 4335 } -1 4336 parent = axs.dom.parentElement(parent); -1 4337 } -1 4338 if (labelWrappedValue.text) { -1 4339 if (computedName) -1 4340 labelWrappedValue.unused = true; -1 4341 else -1 4342 computedName = labelWrappedValue.text; -1 4343 textAlternatives['labelWrapped'] = labelWrappedValue; -1 4344 } -1 4345 // If all else fails input of type image can fall back to its alt text -1 4346 if (axs.browserUtils.matchSelector(element, 'input[type="image"]') && element.hasAttribute('alt')) { -1 4347 var altValue = {}; -1 4348 altValue.type = 'string'; -1 4349 altValue.valid = true; -1 4350 altValue.text = element.getAttribute('alt'); -1 4351 if (computedName) -1 4352 altValue.unused = true; -1 4353 else -1 4354 computedName = altValue.text; -1 4355 textAlternatives['alt'] = altValue; -1 4356 } -1 4357 if (!Object.keys(textAlternatives).length) -1 4358 textAlternatives['noLabel'] = true; -1 4359 } -1 4360 return computedName; -1 4361 }; -1 4362 -1 4363 /** -1 4364 * @param {?string} text -1 4365 * @return {?string} -1 4366 */ -1 4367 axs.properties.getLastWord = function(text) { -1 4368 if (!text) -1 4369 return null; -1 4370 -1 4371 // TODO: this makes a lot of assumptions. -1 4372 var lastSpace = text.lastIndexOf(' ') + 1; -1 4373 var MAXLENGTH = 10; -1 4374 var cutoff = text.length - MAXLENGTH; -1 4375 var wordStart = lastSpace > cutoff ? lastSpace : cutoff; -1 4376 return text.substring(wordStart); -1 4377 }; -1 4378 -1 4379 /** -1 4380 * @param {Node} node -1 4381 * @return {Object} -1 4382 */ -1 4383 axs.properties.getTextProperties = function(node) { -1 4384 var textProperties = {}; -1 4385 var computedName = axs.properties.findTextAlternatives(node, textProperties, false, true); -1 4386 -1 4387 if (Object.keys(textProperties).length == 0) { -1 4388 /** @type {Element} */ var element = axs.dom.asElement(node); -1 4389 if (element && axs.browserUtils.matchSelector(element, 'img')) { -1 4390 var altValue = {}; -1 4391 altValue.valid = false; -1 4392 altValue.errorMessage = 'No alt value provided'; -1 4393 textProperties['alt'] = altValue; -1 4394 -1 4395 var src = element.src; -1 4396 if (typeof src == 'string') { -1 4397 var parts = src.split('/'); -1 4398 var filename = parts.pop(); -1 4399 var filenameValue = { text: filename }; -1 4400 textProperties['filename'] = filenameValue; -1 4401 computedName = filename; -1 4402 } -1 4403 } -1 4404 -1 4405 if (!computedName) -1 4406 return null; -1 4407 } -1 4408 -1 4409 textProperties.hasProperties = Boolean(Object.keys(textProperties).length); -1 4410 textProperties.computedText = computedName; -1 4411 textProperties.lastWord = axs.properties.getLastWord(computedName); -1 4412 return textProperties; -1 4413 }; -1 4414 -1 4415 /** -1 4416 * Finds any ARIA attributes (roles, states and properties) explicitly set on this element. -1 4417 * @param {Element} element -1 4418 * @return {Object} -1 4419 */ -1 4420 axs.properties.getAriaProperties = function(element) { -1 4421 var ariaProperties = {}; -1 4422 var statesAndProperties = axs.properties.getGlobalAriaProperties(element); -1 4423 -1 4424 for (var property in axs.constants.ARIA_PROPERTIES) { -1 4425 var attributeName = 'aria-' + property; -1 4426 if (element.hasAttribute(attributeName)) { -1 4427 var propertyValue = element.getAttribute(attributeName); -1 4428 statesAndProperties[attributeName] = -1 4429 axs.utils.getAriaPropertyValue(attributeName, propertyValue, element); -1 4430 } -1 4431 } -1 4432 if (Object.keys(statesAndProperties).length > 0) -1 4433 ariaProperties['properties'] = axs.utils.values(statesAndProperties); -1 4434 -1 4435 var roles = axs.utils.getRoles(element); -1 4436 if (!roles) { -1 4437 if (Object.keys(ariaProperties).length) -1 4438 return ariaProperties; -1 4439 return null; -1 4440 } -1 4441 ariaProperties['roles'] = roles; -1 4442 if (!roles.valid || !roles['roles']) -1 4443 return ariaProperties; -1 4444 -1 4445 var roleDetails = roles['roles']; -1 4446 for (var i = 0; i < roleDetails.length; i++) { -1 4447 var role = roleDetails[i]; -1 4448 if (!role.details || !role.details.propertiesSet) -1 4449 continue; -1 4450 for (var property in role.details.propertiesSet) { -1 4451 if (property in statesAndProperties) -1 4452 continue; -1 4453 if (element.hasAttribute(property)) { -1 4454 var propertyValue = element.getAttribute(property); -1 4455 statesAndProperties[property] = -1 4456 axs.utils.getAriaPropertyValue(property, propertyValue, element); -1 4457 if ('values' in statesAndProperties[property]) { -1 4458 var values = statesAndProperties[property].values; -1 4459 values[values.length - 1].isLast = true; -1 4460 } -1 4461 } else if (role.details.requiredPropertiesSet[property]) { -1 4462 statesAndProperties[property] = -1 4463 { 'name': property, 'valid': false, 'reason': 'Required property not set' }; -1 4464 } -1 4465 } -1 4466 } -1 4467 if (Object.keys(statesAndProperties).length > 0) -1 4468 ariaProperties['properties'] = axs.utils.values(statesAndProperties); -1 4469 if (Object.keys(ariaProperties).length > 0) -1 4470 return ariaProperties; -1 4471 return null; -1 4472 }; -1 4473 -1 4474 /** -1 4475 * Gets the ARIA properties found on this element which apply to all elements, not just elements with ARIA roles. -1 4476 * @param {Element} element -1 4477 * @return {!Object} -1 4478 */ -1 4479 axs.properties.getGlobalAriaProperties = function(element) { -1 4480 var globalProperties = {}; -1 4481 for (var property in axs.constants.GLOBAL_PROPERTIES) { -1 4482 if (element.hasAttribute(property)) { -1 4483 var propertyValue = element.getAttribute(property); -1 4484 globalProperties[property] = -1 4485 axs.utils.getAriaPropertyValue(property, propertyValue, element); -1 4486 } -1 4487 } -1 4488 return globalProperties; -1 4489 }; -1 4490 -1 4491 /** -1 4492 * @param {Element} element -1 4493 * @return {Object.<string, Object>} -1 4494 */ -1 4495 axs.properties.getVideoProperties = function(element) { -1 4496 var videoSelector = 'video'; -1 4497 if (!axs.browserUtils.matchSelector(element, videoSelector)) -1 4498 return null; -1 4499 var videoProperties = {}; -1 4500 videoProperties['captionTracks'] = axs.properties.getTrackElements(element, 'captions'); -1 4501 videoProperties['descriptionTracks'] = axs.properties.getTrackElements(element, 'descriptions'); -1 4502 videoProperties['chapterTracks'] = axs.properties.getTrackElements(element, 'chapters'); -1 4503 // error if no text alternatives? -1 4504 return videoProperties; -1 4505 }; -1 4506 -1 4507 /** -1 4508 * @param {Element} element -1 4509 * @param {string} kind -1 4510 * @return {Object} -1 4511 */ -1 4512 axs.properties.getTrackElements = function(element, kind) { -1 4513 // error if resource is not available -1 4514 var trackElements = element.querySelectorAll('track[kind=' + kind + ']'); -1 4515 var result = {}; -1 4516 if (!trackElements.length) { -1 4517 result.valid = false; -1 4518 result.reason = { 'messageKey': 'noTracksProvided', 'args': [[kind]] }; -1 4519 return result; -1 4520 } -1 4521 result.valid = true; -1 4522 var values = []; -1 4523 for (var i = 0; i < trackElements.length; i++) { -1 4524 var trackElement = {}; -1 4525 var src = trackElements[i].getAttribute('src'); -1 4526 var srcLang = trackElements[i].getAttribute('srcLang'); -1 4527 var label = trackElements[i].getAttribute('label'); -1 4528 if (!src) { -1 4529 trackElement.valid = false; -1 4530 trackElement.reason = { 'messageKey': 'noSrcProvided' }; -1 4531 } else { -1 4532 trackElement.valid = true; -1 4533 trackElement.src = src; -1 4534 } -1 4535 var name = ''; -1 4536 if (label) { -1 4537 name += label; -1 4538 if (srcLang) -1 4539 name += ' '; -1 4540 } -1 4541 if (srcLang) -1 4542 name += '(' + srcLang + ')'; -1 4543 if (name == '') -1 4544 name = '[' + { 'messageKey': 'unnamed' } + ']'; -1 4545 trackElement.name = name; -1 4546 values.push(trackElement); -1 4547 } -1 4548 result.values = values; -1 4549 return result; -1 4550 }; -1 4551 -1 4552 /** -1 4553 * @param {Node} node -1 4554 * @return {Object.<string, Object>} -1 4555 */ -1 4556 axs.properties.getAllProperties = function(node) { -1 4557 /** @type {Element} */ var element = axs.dom.asElement(node); -1 4558 if (!element) -1 4559 return {}; -1 4560 -1 4561 var allProperties = {}; -1 4562 allProperties['ariaProperties'] = axs.properties.getAriaProperties(element); -1 4563 allProperties['colorProperties'] = axs.properties.getColorProperties(element); -1 4564 allProperties['focusProperties'] = axs.properties.getFocusProperties(element); -1 4565 allProperties['textProperties'] = axs.properties.getTextProperties(node); -1 4566 allProperties['videoProperties'] = axs.properties.getVideoProperties(element); -1 4567 return allProperties; -1 4568 }; -1 4569 -1 4570 (function() { -1 4571 /** -1 4572 * Helper for implicit semantic functionality. -1 4573 * Can be made part of the public API if need be. -1 4574 * @param {Element} element -1 4575 * @return {?axs.constants.HtmlInfo} -1 4576 */ -1 4577 function getHtmlInfo(element) { -1 4578 if (!element) -1 4579 return null; -1 4580 var tagName = element.tagName; -1 4581 if (!tagName) -1 4582 return null; -1 4583 tagName = tagName.toUpperCase(); -1 4584 var infos = axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName]; -1 4585 if (!infos || !infos.length) -1 4586 return null; -1 4587 var defaultInfo = null; // will contain the info with no specific selector if no others match -1 4588 for (var i = 0, len = infos.length; i < len; i++) { -1 4589 var htmlInfo = infos[i]; -1 4590 if (htmlInfo.selector) { -1 4591 if (axs.browserUtils.matchSelector(element, htmlInfo.selector)) -1 4592 return htmlInfo; -1 4593 } else { -1 4594 defaultInfo = htmlInfo; -1 4595 } -1 4596 } -1 4597 return defaultInfo; -1 4598 } -1 4599 -1 4600 /** -1 4601 * @param {Element} element -1 4602 * @return {string} role -1 4603 */ -1 4604 axs.properties.getImplicitRole = function(element) { -1 4605 var htmlInfo = getHtmlInfo(element); -1 4606 if (htmlInfo) -1 4607 return htmlInfo.role; -1 4608 return ''; -1 4609 }; -1 4610 -1 4611 /** -1 4612 * Determine if this element can take ANY ARIA attributes including roles, state and properties. -1 4613 * If false then even global attributes should not be used. -1 4614 * @param {Element} element -1 4615 * @return {boolean} -1 4616 */ -1 4617 axs.properties.canTakeAriaAttributes = function(element) { -1 4618 var htmlInfo = getHtmlInfo(element); -1 4619 if (htmlInfo) -1 4620 return !htmlInfo.reserved; -1 4621 return true; -1 4622 }; -1 4623 })(); -1 4624 -1 4625 /** -1 4626 * This lists the ARIA attributes that are supported implicitly by native properties of this element. -1 4627 * -1 4628 * @param {Element} element The element to check. -1 4629 * @return {!Array.<string>} An array of ARIA attributes. -1 4630 * -1 4631 * example: -1 4632 * var element = document.createElement("input"); -1 4633 * element.setAttribute("type", "range"); -1 4634 * var supported = axs.properties.getNativelySupportedAttributes(element); // an array of ARIA attributes -1 4635 * console.log(supported.indexOf("aria-valuemax") >=0); // logs 'true' -1 4636 */ -1 4637 axs.properties.getNativelySupportedAttributes = function(element) { -1 4638 var result = []; -1 4639 if (!element) { -1 4640 return result; -1 4641 } -1 4642 var testElement = element.cloneNode(false); // gets rid of expandos -1 4643 var ariaAttributes = Object.keys(/** @type {!Object} */(axs.constants.ARIA_TO_HTML_ATTRIBUTE)); -1 4644 for (var i = 0; i < ariaAttributes.length; i++) { -1 4645 var ariaAttribute = ariaAttributes[i]; -1 4646 var nativeAttribute = axs.constants.ARIA_TO_HTML_ATTRIBUTE[ariaAttribute]; -1 4647 if (nativeAttribute in testElement) { -1 4648 result[result.length] = ariaAttribute; -1 4649 } -1 4650 } -1 4651 return result; -1 4652 }; -1 4653 -1 4654 (function() { -1 4655 var roleToSelectorCache = {}; // performance optimization, cache results from getSelectorForRole -1 4656 -1 4657 /** -1 4658 * Build a selector that will match elements which implicity or explicitly have this role. -1 4659 * Note that the selector will probably not look elegant but it will work. -1 4660 * @param {string} role -1 4661 * @return {string} selector -1 4662 */ -1 4663 axs.properties.getSelectorForRole = function(role) { -1 4664 if (!role) -1 4665 return ''; -1 4666 if (roleToSelectorCache[role] && roleToSelectorCache.hasOwnProperty(role)) -1 4667 return roleToSelectorCache[role]; -1 4668 var selectors = ['[role="' + role + '"]']; -1 4669 var tagNames = Object.keys(/** @type {!Object} */(axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO)); -1 4670 tagNames.forEach(function(tagName) { -1 4671 var htmlInfos = axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName]; -1 4672 if (htmlInfos && htmlInfos.length) { -1 4673 for (var i = 0; i < htmlInfos.length; i++) { -1 4674 var htmlInfo = htmlInfos[i]; -1 4675 if (htmlInfo.role === role) { -1 4676 if (htmlInfo.selector) { -1 4677 selectors[selectors.length] = htmlInfo.selector; -1 4678 } else { -1 4679 selectors[selectors.length] = tagName; // Selectors API is not case sensitive. -1 4680 break; // No need to continue adding selectors since we will match the tag itself. -1 4681 } -1 4682 } -1 4683 } -1 4684 } -1 4685 }); -1 4686 return (roleToSelectorCache[role] = selectors.join(',')); -1 4687 }; -1 4688 })(); -1 4689 -1 4690 },{}],7:[function(require,module,exports){ -1 4691 var query = require('./lib/query.js'); -1 4692 var name = require('./lib/name.js'); -1 4693 -1 4694 module.exports = { -1 4695 getRole: query.getRole, -1 4696 getAttribute: query.getAttribute, -1 4697 getName: name.getName, -1 4698 getDescription: name.getDescription, -1 4699 -1 4700 matches: query.matches, -1 4701 querySelector: query.querySelector, -1 4702 querySelectorAll: query.querySelectorAll, -1 4703 closest: query.closest, -1 4704 }; -1 4705 -1 4706 },{"./lib/name.js":9,"./lib/query.js":10}],8:[function(require,module,exports){ -1 4707 exports.attributes = { -1 4708 // widget -1 4709 'autocomplete': 'token', -1 4710 'checked': 'tristate', -1 4711 'current': 'token', -1 4712 'disabled': 'bool', -1 4713 'expanded': 'bool-undefined', -1 4714 'haspopup': 'token', -1 4715 'hidden': 'bool', // ! -1 4716 'invalid': 'token', -1 4717 'keyshortcuts': 'string', -1 4718 'label': 'string', -1 4719 'level': 'int', -1 4720 'modal': 'bool', -1 4721 'multiline': 'bool', -1 4722 'multiselectable': 'bool', -1 4723 'orientation': 'token', -1 4724 'placeholder': 'string', -1 4725 'pressed': 'tristate', -1 4726 'readonly': 'bool', -1 4727 'required': 'bool', -1 4728 'roledescription': 'string', -1 4729 'selected': 'bool-undefined', -1 4730 'valuemax': 'number', -1 4731 'valuemin': 'number', -1 4732 'valuenow': 'number', -1 4733 'valuetext': 'string', -1 4734 -1 4735 // live -1 4736 'atomic': 'bool', -1 4737 'busy': 'bool', -1 4738 'live': 'token', -1 4739 'relevant': 'token-list', -1 4740 -1 4741 // dragndrop -1 4742 'dropeffect': 'token-list', -1 4743 'grabbed': 'bool-undefined', -1 4744 -1 4745 // relationship -1 4746 'activedescendant': 'id', -1 4747 'colcount': 'int', -1 4748 'colindex': 'int', -1 4749 'colspan': 'int', -1 4750 'controls': 'id-list', -1 4751 'describedby': 'id-list', -1 4752 'details': 'id', -1 4753 'errormessage': 'id', -1 4754 'flowto': 'id-list', -1 4755 'labelledby': 'id-list', -1 4756 'owns': 'id-list', -1 4757 'posinset': 'int', -1 4758 'rowcount': 'int', -1 4759 'rowindex': 'int', -1 4760 'rowspan': 'int', -1 4761 'setsize': 'int', -1 4762 'sort': 'token', -1 4763 }; -1 4764 -1 4765 // https://www.w3.org/TR/html-aria/#docconformance -1 4766 exports.extraSelectors = { -1 4767 article: ['article'], -1 4768 button: [ -1 4769 'button', -1 4770 'input[type="button"]', -1 4771 'input[type="image"]', -1 4772 'input[type="reset"]', -1 4773 'input[type="submit"]', -1 4774 'summary', -1 4775 ], -1 4776 cell: ['td'], -1 4777 checkbox: ['input[type="checkbox"]'], -1 4778 combobox: [ -1 4779 'input[type="email"][list]', -1 4780 'input[type="search"][list]', -1 4781 'input[type="tel"][list]', -1 4782 'input[type="text"][list]', -1 4783 'input[type="url"][list]', -1 4784 ], -1 4785 complementary: ['aside'], -1 4786 definition: ['dd'], -1 4787 dialog: ['dialog'], -1 4788 document: ['body'], -1 4789 figure: ['figure'], -1 4790 form: ['form[aria-label]', 'form[aria-labelledby]'], -1 4791 group: ['details', 'optgroup'], -1 4792 heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], -1 4793 img: ['img:not([alt=""])'], -1 4794 link: ['a[href]', 'area[href]', 'link[href]'], -1 4795 list: ['dl', 'ol', 'ul'], -1 4796 listitem: ['dt', 'ul > li', 'ol > li'], -1 4797 main: ['main'], -1 4798 math: ['math'], -1 4799 menuitemcheckbox: ['menuitem[type="checkbox"]'], -1 4800 menuitem: ['menuitem[type="command"]'], -1 4801 menuitemradio: ['menuitem[type="radio"]'], -1 4802 menu: ['menu[type="context"]'], -1 4803 navigation: ['nav'], -1 4804 option: ['option'], -1 4805 progressbar: ['progress'], -1 4806 radio: ['input[type="radio"]'], -1 4807 region: ['section'], -1 4808 rowgroup: ['tbody', 'thead', 'tfoot'], -1 4809 row: ['tr'], -1 4810 searchbox: ['input[type="search"]:not([list])'], -1 4811 separator: ['hr'], -1 4812 slider: ['input[type="range"]'], -1 4813 spinbutton: ['input[type="number"]'], -1 4814 status: ['output'], -1 4815 table: ['table'], -1 4816 textbox: [ -1 4817 'input[type="email"]:not([list])', -1 4818 'input[type="tel"]:not([list])', -1 4819 'input[type="text"]:not([list])', -1 4820 'input[type="url"]:not([list])', -1 4821 'textarea', -1 4822 ], -1 4823 -1 4824 // if scope is missing, it is calculated automatically -1 4825 rowheader: ['th[scope="row"]'], -1 4826 columnheader: ['th[scope="col"]'], -1 4827 }; -1 4828 -1 4829 exports.scoped = [ -1 4830 'article *', 'aside *', 'main *', 'nav *', 'section *', -1 4831 ].join(','); -1 4832 -1 4833 // https://www.w3.org/TR/wai-aria/roles -1 4834 var subRoles = { -1 4835 cell: ['gridcell', 'rowheader'], -1 4836 command: ['button', 'link', 'menuitem'], -1 4837 composite: ['grid', 'select', 'spinbutton', 'tablist'], -1 4838 img: ['doc-cover'], -1 4839 input: ['checkbox', 'option', 'radio', 'slider', 'spinbutton', 'textbox'], -1 4840 landmark: [ -1 4841 'banner', -1 4842 'complementary', -1 4843 'contentinfo', -1 4844 'doc-acknowledgments', -1 4845 'doc-afterword', -1 4846 'doc-appendix', -1 4847 'doc-bibliography', -1 4848 'doc-chapter', -1 4849 'doc-conclusion', -1 4850 'doc-credits', -1 4851 'doc-endnotes', -1 4852 'doc-epilogue', -1 4853 'doc-errata', -1 4854 'doc-foreword', -1 4855 'doc-glossary', -1 4856 'doc-introduction', -1 4857 'doc-part', -1 4858 'doc-preface', -1 4859 'doc-prologue', -1 4860 'form', -1 4861 'main', -1 4862 'navigation', -1 4863 'region', -1 4864 'search', -1 4865 ], -1 4866 range: ['progressbar', 'scrollbar', 'slider', 'spinbutton'], -1 4867 roletype: ['structure', 'widget', 'window'], -1 4868 section: [ -1 4869 'alert', -1 4870 'cell', -1 4871 'definition', -1 4872 'doc-abstract', -1 4873 'doc-colophon', -1 4874 'doc-credit', -1 4875 'doc-dedication', -1 4876 'doc-epigraph', -1 4877 'doc-example', -1 4878 'doc-footnote', -1 4879 'doc-qna', -1 4880 'figure', -1 4881 'group', -1 4882 'img', -1 4883 'landmark', -1 4884 'list', -1 4885 'listitem', -1 4886 'log', -1 4887 'marquee', -1 4888 'math', -1 4889 'note', -1 4890 'status', -1 4891 'table', -1 4892 'tabpanel', -1 4893 'term', -1 4894 'tooltip', -1 4895 ], -1 4896 sectionhead: [ -1 4897 'columnheader', -1 4898 'doc-subtitle', -1 4899 'heading', -1 4900 'rowheader', -1 4901 'tab', -1 4902 ], -1 4903 select: ['combobox', 'listbox', 'menu', 'radiogroup', 'tree'], -1 4904 separator: ['doc-pagebreak'], -1 4905 structure: [ -1 4906 'application', -1 4907 'document', -1 4908 'none', -1 4909 'presentation', -1 4910 'rowgroup', -1 4911 'section', -1 4912 'sectionhead', -1 4913 'separator', -1 4914 ], -1 4915 table: ['grid'], -1 4916 textbox: ['searchbox'], -1 4917 widget: [ -1 4918 'command', -1 4919 'composite', -1 4920 'gridcell', -1 4921 'input', -1 4922 'range', -1 4923 'row', -1 4924 'separator', -1 4925 'tab', -1 4926 ], -1 4927 window: ['dialog'], -1 4928 alert: ['alertdialog'], -1 4929 checkbox: ['menuitemcheckbox', 'switch'], -1 4930 dialog: ['alertdialog'], -1 4931 gridcell: ['columnheader', 'rowheader'], -1 4932 menuitem: ['menuitemcheckbox'], -1 4933 menuitemcheckbox: ['menuitemradio'], -1 4934 option: ['treeitem'], -1 4935 radio: ['menuitemradio'], -1 4936 status: ['timer'], -1 4937 grid: ['treegrid'], -1 4938 menu: ['menubar'], -1 4939 tree: ['treegrid'], -1 4940 document: ['article'], -1 4941 group: ['row', 'select', 'toolbar'], -1 4942 link: ['doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref'], -1 4943 list: ['directory', 'feed'], -1 4944 listitem: ['doc-biblioentry', 'doc-endnote', 'treeitem'], -1 4945 navigation: ['doc-index', 'doc-pagelist', 'doc-toc'], -1 4946 note: ['doc-notice', 'doc-tip'], -1 4947 }; -1 4948 -1 4949 var getSubRoles = function(role) { -1 4950 var children = subRoles[role] || []; -1 4951 var descendents = children.map(getSubRoles); -1 4952 -1 4953 var result = [role]; -1 4954 -1 4955 descendents.forEach(function(list) { -1 4956 list.forEach(function(r) { -1 4957 if (result.indexOf(r) === -1) { -1 4958 result.push(r); -1 4959 } -1 4960 }); -1 4961 }); -1 4962 -1 4963 return result; -1 4964 }; -1 4965 -1 4966 exports.subRoles = {}; -1 4967 for (var role in subRoles) { -1 4968 exports.subRoles[role] = getSubRoles(role); -1 4969 } -1 4970 exports.subRoles['none'] = ['none', 'presentation']; -1 4971 exports.subRoles['presentation'] = ['presentation', 'none']; -1 4972 -1 4973 exports.nameFromContents = [ -1 4974 'button', -1 4975 'checkbox', -1 4976 'columnheader', -1 4977 'doc-backlink', -1 4978 'doc-biblioref', -1 4979 'doc-glossref', -1 4980 'doc-noteref', -1 4981 'gridcell', -1 4982 'heading', -1 4983 'link', -1 4984 'menuitem', -1 4985 'menuitemcheckbox', -1 4986 'menuitemradio', -1 4987 'option', -1 4988 'radio', -1 4989 'row', -1 4990 'rowgroup', -1 4991 'rowheader', -1 4992 'sectionhead', -1 4993 'tab', -1 4994 'tooltip', -1 4995 'treeitem', -1 4996 'switch', -1 4997 ]; -1 4998 -1 4999 exports.labelable = [ -1 5000 'button', -1 5001 'input:not([type="hidden"])', -1 5002 'keygen', -1 5003 'meter', -1 5004 'output', -1 5005 'progress', -1 5006 'select', -1 5007 'textarea', -1 5008 ]; 4830 50094831 -1 if (Object.keys(textAlternatives).length == 0 && computedName == null)4832 -1 return null;-1 5010 },{}],9:[function(require,module,exports){ -1 5011 var constants = require('./constants.js'); -1 5012 var query = require('./query.js'); 4833 50134834 -1 return computedName;-1 5014 var getPseudoContent = function(node, selector) { -1 5015 var styles = window.getComputedStyle(node, selector); -1 5016 var ret = styles.getPropertyValue('content'); -1 5017 if (ret === 'none' || ret.substr(0, 4) === '-moz') { -1 5018 return ''; -1 5019 } else { -1 5020 return ret -1 5021 .replace(/^["']/, '') -1 5022 .replace(/["']$/, ''); -1 5023 } 4835 5024 }; 4836 50254837 -1 /**4838 -1 * @param {Element} element4839 -1 * @param {boolean=} opt_force Whether to return text alternatives for this4840 -1 * element regardless of its hidden state.4841 -1 * @return {?string}4842 -1 */4843 -1 axs.properties.getTextFromDescendantContent = function(element, opt_force) {4844 -1 var children = element.childNodes;4845 -1 var childrenTextContent = [];4846 -1 for (var i = 0; i < children.length; i++) {4847 -1 var childTextContent = axs.properties.findTextAlternatives(children[i], {}, true, opt_force);4848 -1 if (childTextContent)4849 -1 childrenTextContent.push(childTextContent.trim());4850 -1 }4851 -1 if (childrenTextContent.length) {4852 -1 var result = '';4853 -1 // Empty children are allowed, but collapse all of them4854 -1 for (var i = 0; i < childrenTextContent.length; i++)4855 -1 result = [result, childrenTextContent[i]].join(' ').trim();4856 -1 return result;4857 -1 }4858 -1 return null;-1 5026 var getContent = function(root, referenced) { -1 5027 var ret = getPseudoContent(root, ':before'); -1 5028 var node = root.firstChild; -1 5029 while (node) { -1 5030 if (node.nodeType === node.TEXT_NODE) { -1 5031 ret += node.textContent; -1 5032 } else if (node.nodeType === node.ELEMENT_NODE) { -1 5033 ret += getName(node, true, referenced); -1 5034 } -1 5035 node = node.nextSibling; -1 5036 } -1 5037 ret += getPseudoContent(root, ':after'); -1 5038 return ret; 4859 5039 }; 4860 50404861 -1 /**4862 -1 * @param {Element} element4863 -1 * @param {Object} textAlternatives4864 -1 * @return {?string}4865 -1 */4866 -1 axs.properties.getTextFromAriaLabelledby = function(element, textAlternatives) {4867 -1 var computedName = null;4868 -1 if (!element.hasAttribute('aria-labelledby'))4869 -1 return computedName;4870 -14871 -1 var labelledbyAttr = element.getAttribute('aria-labelledby');4872 -1 var labelledbyIds = labelledbyAttr.split(/\s+/);4873 -1 var labelledbyValue = {};4874 -1 labelledbyValue.valid = true;4875 -1 var labelledbyText = [];4876 -1 var labelledbyValues = [];4877 -1 for (var i = 0; i < labelledbyIds.length; i++) {4878 -1 var labelledby = {};4879 -1 labelledby.type = 'element';4880 -1 var labelledbyId = labelledbyIds[i];4881 -1 labelledby.value = labelledbyId;4882 -1 var labelledbyElement = document.getElementById(labelledbyId);4883 -1 if (!labelledbyElement) {4884 -1 labelledby.valid = false;4885 -1 labelledbyValue.valid = false;4886 -1 labelledby.errorMessage = { 'messageKey': 'noElementWithId', 'args': [labelledbyId] };4887 -1 } else {4888 -1 labelledby.valid = true;4889 -1 labelledby.text = axs.properties.findTextAlternatives(labelledbyElement, {}, true, true);4890 -1 labelledby.lastWord = axs.properties.getLastWord(labelledby.text);4891 -1 labelledbyText.push(labelledby.text);4892 -1 labelledby.element = labelledbyElement;4893 -1 }4894 -1 labelledbyValues.push(labelledby);4895 -1 }4896 -1 if (labelledbyValues.length > 0) {4897 -1 labelledbyValues[labelledbyValues.length - 1].last = true;4898 -1 labelledbyValue.values = labelledbyValues;4899 -1 labelledbyValue.text = labelledbyText.join(' ');4900 -1 labelledbyValue.lastWord = axs.properties.getLastWord(labelledbyValue.text);4901 -1 computedName = labelledbyValue.text;4902 -1 textAlternatives['ariaLabelledby'] = labelledbyValue;4903 -1 }4904 -14905 -1 return computedName;-1 5041 var allowNameFromContent = function(el) { -1 5042 var role = query.getRole(el); -1 5043 return !role || constants.nameFromContents.indexOf(role) !== -1; 4906 5044 }; 4907 5045 -1 5046 var isLabelable = function(el) { -1 5047 var selector = constants.labelable.join(','); -1 5048 return el.matches(selector); -1 5049 }; 4908 50504909 -1 /**4910 -1 * Determine the text description/label for an element.4911 -1 * For example will attempt to find the alt text for an image or label text for a form control.4912 -1 * @param {!Element} element4913 -1 * @param {!Object} textAlternatives An object that will be updated with information.4914 -1 * @param {?string} existingComputedname4915 -1 * @param {boolean} recursive Whether this method is being called recursively as described in4916 -1 * http://www.w3.org/TR/wai-aria/roles#textalternativecomputation section 2A.4917 -1 * @return {Object}4918 -1 */4919 -1 axs.properties.getTextFromHostLanguageAttributes = function(element,4920 -1 textAlternatives,4921 -1 existingComputedname,4922 -1 recursive) {4923 -1 var computedName = existingComputedname;4924 -1 if (axs.browserUtils.matchSelector(element, 'img') && element.hasAttribute('alt')) {4925 -1 var altValue = {};4926 -1 altValue.type = 'string';4927 -1 altValue.valid = true;4928 -1 altValue.text = element.getAttribute('alt');4929 -1 if (computedName)4930 -1 altValue.unused = true;4931 -1 else4932 -1 computedName = altValue.text;4933 -1 textAlternatives['alt'] = altValue;4934 -1 }4935 -14936 -1 var controlsSelector = ['input:not([type="hidden"]):not([disabled])',4937 -1 'select:not([disabled])',4938 -1 'textarea:not([disabled])',4939 -1 'button:not([disabled])',4940 -1 'video:not([disabled])'].join(', ');4941 -1 if (axs.browserUtils.matchSelector(element, controlsSelector) && !recursive) {4942 -1 if (element.hasAttribute('id')) {4943 -1 var labelForQuerySelector = 'label[for="' + element.id + '"]';4944 -1 var labelsFor = document.querySelectorAll(labelForQuerySelector);4945 -1 var labelForValue = {};4946 -1 var labelForValues = [];4947 -1 var labelForText = [];4948 -1 for (var i = 0; i < labelsFor.length; i++) {4949 -1 var labelFor = {};4950 -1 labelFor.type = 'element';4951 -1 var label = labelsFor[i];4952 -1 var labelText = axs.properties.findTextAlternatives(label, {}, true);4953 -1 if (labelText && labelText.trim().length > 0) {4954 -1 labelFor.text = labelText.trim();4955 -1 labelForText.push(labelText.trim());4956 -1 }4957 -1 labelFor.element = label;4958 -1 labelForValues.push(labelFor);4959 -1 }4960 -1 if (labelForValues.length > 0) {4961 -1 labelForValues[labelForValues.length - 1].last = true;4962 -1 labelForValue.values = labelForValues;4963 -1 labelForValue.text = labelForText.join(' ');4964 -1 labelForValue.lastWord = axs.properties.getLastWord(labelForValue.text);4965 -1 if (computedName)4966 -1 labelForValue.unused = true;4967 -1 else4968 -1 computedName = labelForValue.text;4969 -1 textAlternatives['labelFor'] = labelForValue;4970 -1 }4971 -1 }-1 5051 // Control.labels is part of the standard, but not supported in most browsers -1 5052 var getLabelNode = function(node) { -1 5053 if (node.id) { -1 5054 var selector = 'label[for="' + node.id + '"]'; -1 5055 var label = document.querySelector(selector); -1 5056 if (label) { -1 5057 return label; -1 5058 } -1 5059 } 4972 50604973 -1 var parent = axs.dom.parentElement(element);4974 -1 var labelWrappedValue = {};4975 -1 while (parent) {4976 -1 if (parent.tagName.toLowerCase() == 'label') {4977 -1 var parentLabel = /** @type {HTMLLabelElement} */ (parent);4978 -1 if (parentLabel.control == element) {4979 -1 labelWrappedValue.type = 'element';4980 -1 labelWrappedValue.text = axs.properties.findTextAlternatives(parentLabel, {}, true);4981 -1 labelWrappedValue.lastWord = axs.properties.getLastWord(labelWrappedValue.text);4982 -1 labelWrappedValue.element = parentLabel;4983 -1 break;4984 -1 }4985 -1 }4986 -1 parent = axs.dom.parentElement(parent);4987 -1 }4988 -1 if (labelWrappedValue.text) {4989 -1 if (computedName)4990 -1 labelWrappedValue.unused = true;4991 -1 else4992 -1 computedName = labelWrappedValue.text;4993 -1 textAlternatives['labelWrapped'] = labelWrappedValue;4994 -1 }4995 -1 // If all else fails input of type image can fall back to its alt text4996 -1 if (axs.browserUtils.matchSelector(element, 'input[type="image"]') && element.hasAttribute('alt')) {4997 -1 var altValue = {};4998 -1 altValue.type = 'string';4999 -1 altValue.valid = true;5000 -1 altValue.text = element.getAttribute('alt');5001 -1 if (computedName)5002 -1 altValue.unused = true;5003 -1 else5004 -1 computedName = altValue.text;5005 -1 textAlternatives['alt'] = altValue;5006 -1 }5007 -1 if (!Object.keys(textAlternatives).length)5008 -1 textAlternatives['noLabel'] = true;5009 -1 }5010 -1 return computedName;-1 5061 var p = node.parentElement; -1 5062 while (p) { -1 5063 if (p.tagName.toLowerCase() === 'label') { -1 5064 return p; -1 5065 } -1 5066 p = p.parentElement; -1 5067 } 5011 5068 }; 5012 50695013 -1 /**5014 -1 * @param {?string} text5015 -1 * @return {?string}5016 -1 */5017 -1 axs.properties.getLastWord = function(text) {5018 -1 if (!text)5019 -1 return null;-1 5070 // http://www.ssbbartgroup.com/blog/how-the-w3c-text-alternative-computation-works/ -1 5071 // https://www.w3.org/TR/accname-aam-1.1/#h-mapping_additional_nd_te -1 5072 var getName = function(el, recursive, referenced) { -1 5073 var ret; -1 5074 -1 5075 if (query.getAttribute(el, 'hidden', referenced)) { -1 5076 return ''; -1 5077 } -1 5078 if (query.matches(el, 'presentation')) { -1 5079 return getContent(el, referenced); -1 5080 } -1 5081 if (!recursive && el.matches('[aria-labelledby]')) { -1 5082 var ids = el.getAttribute('aria-labelledby').split(/\s+/); -1 5083 var strings = ids.map(function(id) { -1 5084 var label = document.getElementById(id); -1 5085 return getName(label, true, label); -1 5086 }); -1 5087 ret = strings.join(' '); -1 5088 } -1 5089 if (!ret && el.matches('[aria-label]')) { -1 5090 ret = el.getAttribute('aria-label'); -1 5091 } -1 5092 if (!query.matches(el, 'presentation')) { -1 5093 if (!ret && isLabelable(el)) { -1 5094 var label = getLabelNode(el); -1 5095 if (!recursive && label) { -1 5096 ret = getName(label, true, label); -1 5097 } -1 5098 } -1 5099 if (!ret) { -1 5100 ret = el.getAttribute('placeholder'); -1 5101 } -1 5102 // figcaption -1 5103 if (!ret) { -1 5104 ret = el.getAttribute('alt'); -1 5105 } -1 5106 // caption -1 5107 // table -1 5108 } -1 5109 // FIXME only if this is embedded in a label -1 5110 if (!ret && query.matches(el, 'input')) { -1 5111 // combobox -1 5112 // button -1 5113 if (query.matches(el, 'range')) { -1 5114 ret = query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value; -1 5115 } else { -1 5116 ret = el.value; -1 5117 } -1 5118 ret = '' + ret; -1 5119 } -1 5120 if (!ret && (recursive || allowNameFromContent(el))) { -1 5121 ret = getContent(el, referenced); -1 5122 } -1 5123 if (!ret) { -1 5124 ret = el.getAttribute('title'); -1 5125 } 5020 51265021 -1 // TODO: this makes a lot of assumptions.5022 -1 var lastSpace = text.lastIndexOf(' ') + 1;5023 -1 var MAXLENGTH = 10;5024 -1 var cutoff = text.length - MAXLENGTH;5025 -1 var wordStart = lastSpace > cutoff ? lastSpace : cutoff;5026 -1 return text.substring(wordStart);-1 5127 return (ret || '').trim().replace(/\s+/g, ' '); 5027 5128 }; 5028 51295029 -1 /**5030 -1 * @param {Node} node5031 -1 * @return {Object}5032 -1 */5033 -1 axs.properties.getTextProperties = function(node) {5034 -1 var textProperties = {};5035 -1 var computedName = axs.properties.findTextAlternatives(node, textProperties, false, true);5036 -15037 -1 if (Object.keys(textProperties).length == 0) {5038 -1 /** @type {Element} */ var element = axs.dom.asElement(node);5039 -1 if (element && axs.browserUtils.matchSelector(element, 'img')) {5040 -1 var altValue = {};5041 -1 altValue.valid = false;5042 -1 altValue.errorMessage = 'No alt value provided';5043 -1 textProperties['alt'] = altValue;-1 5130 var getDescription = function(el) { -1 5131 var ret = ''; 5044 51325045 -1 var src = element.src;5046 -1 if (typeof src == 'string') {5047 -1 var parts = src.split('/');5048 -1 var filename = parts.pop();5049 -1 var filenameValue = { text: filename };5050 -1 textProperties['filename'] = filenameValue;5051 -1 computedName = filename;5052 -1 }5053 -1 }-1 5133 if (el.matches('[aria-describedby]')) { -1 5134 var ids = el.getAttribute('aria-describedby').split(/\s+/); -1 5135 var strings = ids.map(function(id) { -1 5136 var label = document.getElementById(id); -1 5137 return getName(label, true, label); -1 5138 }); -1 5139 ret = strings.join(' '); -1 5140 } else if (el.title) { -1 5141 ret = el.title; -1 5142 } else if (el.placeholder) { -1 5143 ret = el.placeholder; -1 5144 } 5054 51455055 -1 if (!computedName)5056 -1 return null;5057 -1 }-1 5146 return (ret || '').trim().replace(/\s+/g, ' '); -1 5147 }; 5058 51485059 -1 textProperties.hasProperties = Boolean(Object.keys(textProperties).length);5060 -1 textProperties.computedText = computedName;5061 -1 textProperties.lastWord = axs.properties.getLastWord(computedName);5062 -1 return textProperties;-1 5149 module.exports = { -1 5150 getName: getName, -1 5151 getDescription: getDescription, 5063 5152 }; 5064 51535065 -1 /**5066 -1 * Finds any ARIA attributes (roles, states and properties) explicitly set on this element.5067 -1 * @param {Element} element5068 -1 * @return {Object}5069 -1 */5070 -1 axs.properties.getAriaProperties = function(element) {5071 -1 var ariaProperties = {};5072 -1 var statesAndProperties = axs.properties.getGlobalAriaProperties(element);-1 5154 },{"./constants.js":8,"./query.js":10}],10:[function(require,module,exports){ -1 5155 var constants = require('./constants.js'); -1 5156 var util = require('./util.js'); 5073 51575074 -1 for (var property in axs.constants.ARIA_PROPERTIES) {5075 -1 var attributeName = 'aria-' + property;5076 -1 if (element.hasAttribute(attributeName)) {5077 -1 var propertyValue = element.getAttribute(attributeName);5078 -1 statesAndProperties[attributeName] =5079 -1 axs.utils.getAriaPropertyValue(attributeName, propertyValue, element);5080 -1 }5081 -1 }5082 -1 if (Object.keys(statesAndProperties).length > 0)5083 -1 ariaProperties['properties'] = axs.utils.values(statesAndProperties);-1 5158 var getSubRoles = function(roles) { -1 5159 return [].concat.apply([], roles.map(function(role) { -1 5160 return constants.subRoles[role] || [role]; -1 5161 })); -1 5162 }; 5084 51635085 -1 var roles = axs.utils.getRoles(element);5086 -1 if (!roles) {5087 -1 if (Object.keys(ariaProperties).length)5088 -1 return ariaProperties;5089 -1 return null;5090 -1 }5091 -1 ariaProperties['roles'] = roles;5092 -1 if (!roles.valid || !roles['roles'])5093 -1 return ariaProperties;-1 5164 // candidates can be passed for performance optimization -1 5165 var _getRole = function(el, candidates) { -1 5166 if (el.hasAttribute('role')) { -1 5167 return el.getAttribute('role'); -1 5168 } -1 5169 for (var role in constants.extraSelectors) { -1 5170 var selector = constants.extraSelectors[role].join(','); -1 5171 if ((!candidates || candidates.indexOf(role) !== -1) && el.matches(selector)) { -1 5172 return role; -1 5173 } -1 5174 } 5094 51755095 -1 var roleDetails = roles['roles'];5096 -1 for (var i = 0; i < roleDetails.length; i++) {5097 -1 var role = roleDetails[i];5098 -1 if (!role.details || !role.details.propertiesSet)5099 -1 continue;5100 -1 for (var property in role.details.propertiesSet) {5101 -1 if (property in statesAndProperties)5102 -1 continue;5103 -1 if (element.hasAttribute(property)) {5104 -1 var propertyValue = element.getAttribute(property);5105 -1 statesAndProperties[property] =5106 -1 axs.utils.getAriaPropertyValue(property, propertyValue, element);5107 -1 if ('values' in statesAndProperties[property]) {5108 -1 var values = statesAndProperties[property].values;5109 -1 values[values.length - 1].isLast = true;5110 -1 }5111 -1 } else if (role.details.requiredPropertiesSet[property]) {5112 -1 statesAndProperties[property] =5113 -1 { 'name': property, 'valid': false, 'reason': 'Required property not set' };5114 -1 }5115 -1 }5116 -1 }5117 -1 if (Object.keys(statesAndProperties).length > 0)5118 -1 ariaProperties['properties'] = axs.utils.values(statesAndProperties);5119 -1 if (Object.keys(ariaProperties).length > 0)5120 -1 return ariaProperties;5121 -1 return null;5122 -1 };-1 5176 if (!candidates || -1 5177 candidates.indexOf('banner') !== -1 || -1 5178 candidates.indexOf('contentinfo') !== -1) { -1 5179 var scoped = el.matches(constants.scoped); 5123 51805124 -1 /**5125 -1 * Gets the ARIA properties found on this element which apply to all elements, not just elements with ARIA roles.5126 -1 * @param {Element} element5127 -1 * @return {!Object}5128 -1 */5129 -1 axs.properties.getGlobalAriaProperties = function(element) {5130 -1 var globalProperties = {};5131 -1 for (var property in axs.constants.GLOBAL_PROPERTIES) {5132 -1 if (element.hasAttribute(property)) {5133 -1 var propertyValue = element.getAttribute(property);5134 -1 globalProperties[property] =5135 -1 axs.utils.getAriaPropertyValue(property, propertyValue, element);5136 -1 }5137 -1 }5138 -1 return globalProperties;-1 5181 if (el.matches('header') && !scoped) { -1 5182 return 'banner'; -1 5183 } -1 5184 if (el.matches('footer') && !scoped) { -1 5185 return 'contentinfo'; -1 5186 } -1 5187 } 5139 5188 }; 5140 51895141 -1 /**5142 -1 * @param {Element} element5143 -1 * @return {Object.<string, Object>}5144 -1 */5145 -1 axs.properties.getVideoProperties = function(element) {5146 -1 var videoSelector = 'video';5147 -1 if (!axs.browserUtils.matchSelector(element, videoSelector))5148 -1 return null;5149 -1 var videoProperties = {};5150 -1 videoProperties['captionTracks'] = axs.properties.getTrackElements(element, 'captions');5151 -1 videoProperties['descriptionTracks'] = axs.properties.getTrackElements(element, 'descriptions');5152 -1 videoProperties['chapterTracks'] = axs.properties.getTrackElements(element, 'chapters');5153 -1 // error if no text alternatives?5154 -1 return videoProperties;5155 -1 };-1 5190 var getAttribute = function(el, key, _hiddenRoot) { -1 5191 if (key === 'hidden' && el === _hiddenRoot) { // used for name calculation -1 5192 return false; -1 5193 } 5156 51945157 -1 /**5158 -1 * @param {Element} element5159 -1 * @param {string} kind5160 -1 * @return {Object}5161 -1 */5162 -1 axs.properties.getTrackElements = function(element, kind) {5163 -1 // error if resource is not available5164 -1 var trackElements = element.querySelectorAll('track[kind=' + kind + ']');5165 -1 var result = {};5166 -1 if (!trackElements.length) {5167 -1 result.valid = false;5168 -1 result.reason = { 'messageKey': 'noTracksProvided', 'args': [[kind]] };5169 -1 return result;5170 -1 }5171 -1 result.valid = true;5172 -1 var values = [];5173 -1 for (var i = 0; i < trackElements.length; i++) {5174 -1 var trackElement = {};5175 -1 var src = trackElements[i].getAttribute('src');5176 -1 var srcLang = trackElements[i].getAttribute('srcLang');5177 -1 var label = trackElements[i].getAttribute('label');5178 -1 if (!src) {5179 -1 trackElement.valid = false;5180 -1 trackElement.reason = { 'messageKey': 'noSrcProvided' };5181 -1 } else {5182 -1 trackElement.valid = true;5183 -1 trackElement.src = src;5184 -1 }5185 -1 var name = '';5186 -1 if (label) {5187 -1 name += label;5188 -1 if (srcLang)5189 -1 name += ' ';5190 -1 }5191 -1 if (srcLang)5192 -1 name += '(' + srcLang + ')';5193 -1 if (name == '')5194 -1 name = '[' + { 'messageKey': 'unnamed' } + ']';5195 -1 trackElement.name = name;5196 -1 values.push(trackElement);5197 -1 }5198 -1 result.values = values;5199 -1 return result;-1 5195 var type = constants.attributes[key]; -1 5196 var raw = el.getAttribute('aria-' + key); -1 5197 -1 5198 if (raw) { -1 5199 if (type === 'bool') { -1 5200 return raw === 'true'; -1 5201 } else if (type === 'tristate') { -1 5202 return raw === 'true' ? true : raw === 'false' ? false : 'mixed'; -1 5203 } else if (type === 'bool-undefined') { -1 5204 return raw === 'true' ? true : raw === 'false' ? false : undefined; -1 5205 } else if (type === 'id-list') { -1 5206 return raw.split(/\s+/); -1 5207 } else if (type === 'integer') { -1 5208 return parseInt(raw); -1 5209 } else if (type === 'number') { -1 5210 return parseFloat(raw); -1 5211 } else if (type === 'token-list') { -1 5212 return raw.split(/\s+/); -1 5213 } else { -1 5214 return raw; -1 5215 } -1 5216 } -1 5217 -1 5218 if (key === 'level') { -1 5219 for (var i = 1; i <= 6; i++) { -1 5220 if (el.tagName.toLowerCase() === 'h' + i) { -1 5221 return i; -1 5222 } -1 5223 } -1 5224 } else if (key === 'disabled') { -1 5225 return el.disabled; -1 5226 } else if (key === 'placeholder') { -1 5227 return el.placeholder; -1 5228 } else if (key === 'required') { -1 5229 return el.required; -1 5230 } else if (key === 'readonly') { -1 5231 return el.readOnly && !el.isContentEditable; -1 5232 } else if (key === 'hidden') { -1 5233 var style = window.getComputedStyle(el); -1 5234 if (el.hidden || style.display === 'none' || style.visibility === 'hidden') { -1 5235 return true; -1 5236 } else if (el.clientHeight === 0) { // rough check for performance -1 5237 return el.parentNode && getAttribute(el.parentNode, 'hidden', _hiddenRoot); -1 5238 } -1 5239 } else if (key === 'invalid' && el.checkValidity) { -1 5240 return el.checkValidity(); -1 5241 } -1 5242 -1 5243 if (type === 'bool' || type === 'tristate') { -1 5244 return false; -1 5245 } 5200 5246 }; 5201 52475202 -1 /**5203 -1 * @param {Node} node5204 -1 * @return {Object.<string, Object>}5205 -1 */5206 -1 axs.properties.getAllProperties = function(node) {5207 -1 /** @type {Element} */ var element = axs.dom.asElement(node);5208 -1 if (!element)5209 -1 return {};-1 5248 var matches = function(el, selector) { -1 5249 var actual; 5210 52505211 -1 var allProperties = {};5212 -1 allProperties['ariaProperties'] = axs.properties.getAriaProperties(element);5213 -1 allProperties['colorProperties'] = axs.properties.getColorProperties(element);5214 -1 allProperties['focusProperties'] = axs.properties.getFocusProperties(element);5215 -1 allProperties['textProperties'] = axs.properties.getTextProperties(node);5216 -1 allProperties['videoProperties'] = axs.properties.getVideoProperties(element);5217 -1 return allProperties;-1 5251 if (selector.substr(0, 1) === ':') { -1 5252 var attr = selector.substr(1); -1 5253 return getAttribute(el, attr); -1 5254 } else if (selector.substr(0, 1) === '[') { -1 5255 var match = /\[([a-z]+)="(.*)"\]/.exec(selector); -1 5256 actual = getAttribute(el, match[1]); -1 5257 var rawValue = match[2]; -1 5258 return actual.toString() == rawValue; -1 5259 } else { -1 5260 var candidates = getSubRoles(selector.split(',')); -1 5261 actual = _getRole(el, candidates); -1 5262 return candidates.indexOf(actual) !== -1; -1 5263 } 5218 5264 }; 5219 52655220 -1 (function() {5221 -1 /**5222 -1 * Helper for implicit semantic functionality.5223 -1 * Can be made part of the public API if need be.5224 -1 * @param {Element} element5225 -1 * @return {?axs.constants.HtmlInfo}5226 -1 */5227 -1 function getHtmlInfo(element) {5228 -1 if (!element)5229 -1 return null;5230 -1 var tagName = element.tagName;5231 -1 if (!tagName)5232 -1 return null;5233 -1 tagName = tagName.toUpperCase();5234 -1 var infos = axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName];5235 -1 if (!infos || !infos.length)5236 -1 return null;5237 -1 var defaultInfo = null; // will contain the info with no specific selector if no others match5238 -1 for (var i = 0, len = infos.length; i < len; i++) {5239 -1 var htmlInfo = infos[i];5240 -1 if (htmlInfo.selector) {5241 -1 if (axs.browserUtils.matchSelector(element, htmlInfo.selector))5242 -1 return htmlInfo;5243 -1 } else {5244 -1 defaultInfo = htmlInfo;5245 -1 }5246 -1 }5247 -1 return defaultInfo;5248 -1 }-1 5266 var _querySelector = function(all) { -1 5267 return function(root, role) { -1 5268 var results = []; -1 5269 util.walkDOM(root, function(node) { -1 5270 if (node.nodeType === node.ELEMENT_NODE) { -1 5271 // FIXME: skip hidden elements -1 5272 if (matches(node, role)) { -1 5273 results.push(node); -1 5274 if (!all) { -1 5275 return false; -1 5276 } -1 5277 } -1 5278 } -1 5279 }); -1 5280 return all ? results : results[0]; -1 5281 }; -1 5282 }; 5249 52835250 -1 /**5251 -1 * @param {Element} element5252 -1 * @return {string} role5253 -1 */5254 -1 axs.properties.getImplicitRole = function(element) {5255 -1 var htmlInfo = getHtmlInfo(element);5256 -1 if (htmlInfo)5257 -1 return htmlInfo.role;5258 -1 return '';5259 -1 };-1 5284 var closest = function(el, selector) { -1 5285 return util.searchUp(el, function(candidate) { -1 5286 return matches(candidate, selector); -1 5287 }); -1 5288 }; 5260 52895261 -1 /**5262 -1 * Determine if this element can take ANY ARIA attributes including roles, state and properties.5263 -1 * If false then even global attributes should not be used.5264 -1 * @param {Element} element5265 -1 * @return {boolean}5266 -1 */5267 -1 axs.properties.canTakeAriaAttributes = function(element) {5268 -1 var htmlInfo = getHtmlInfo(element);5269 -1 if (htmlInfo)5270 -1 return !htmlInfo.reserved;5271 -1 return true;5272 -1 };5273 -1 })();-1 5290 module.exports = { -1 5291 getRole: function(el) { -1 5292 return _getRole(el); -1 5293 }, -1 5294 getAttribute: getAttribute, -1 5295 matches: matches, -1 5296 querySelector: _querySelector(), -1 5297 querySelectorAll: _querySelector(true), -1 5298 closest: closest, -1 5299 }; 5274 53005275 -1 /**5276 -1 * This lists the ARIA attributes that are supported implicitly by native properties of this element.5277 -1 *5278 -1 * @param {Element} element The element to check.5279 -1 * @return {!Array.<string>} An array of ARIA attributes.5280 -1 *5281 -1 * example:5282 -1 * var element = document.createElement("input");5283 -1 * element.setAttribute("type", "range");5284 -1 * var supported = axs.properties.getNativelySupportedAttributes(element); // an array of ARIA attributes5285 -1 * console.log(supported.indexOf("aria-valuemax") >=0); // logs 'true'5286 -1 */5287 -1 axs.properties.getNativelySupportedAttributes = function(element) {5288 -1 var result = [];5289 -1 if (!element) {5290 -1 return result;5291 -1 }5292 -1 var testElement = element.cloneNode(false); // gets rid of expandos5293 -1 var ariaAttributes = Object.keys(/** @type {!Object} */(axs.constants.ARIA_TO_HTML_ATTRIBUTE));5294 -1 for (var i = 0; i < ariaAttributes.length; i++) {5295 -1 var ariaAttribute = ariaAttributes[i];5296 -1 var nativeAttribute = axs.constants.ARIA_TO_HTML_ATTRIBUTE[ariaAttribute];5297 -1 if (nativeAttribute in testElement) {5298 -1 result[result.length] = ariaAttribute;5299 -1 }5300 -1 }5301 -1 return result;-1 5301 },{"./constants.js":8,"./util.js":11}],11:[function(require,module,exports){ -1 5302 var walkDOM = function(root, fn) { -1 5303 if (fn(root) === false) { -1 5304 return false; -1 5305 } -1 5306 var node = root.firstChild; -1 5307 while (node) { -1 5308 if (walkDOM(node, fn) === false) { -1 5309 return false; -1 5310 } -1 5311 node = node.nextSibling; -1 5312 } 5302 5313 }; 5303 53145304 -1 (function() {5305 -1 var roleToSelectorCache = {}; // performance optimization, cache results from getSelectorForRole-1 5315 var searchUp = function(el, test) { -1 5316 var candidate = el.parentElement; -1 5317 if (candidate) { -1 5318 if (test(candidate)) { -1 5319 return candidate; -1 5320 } else { -1 5321 return searchUp(candidate, test); -1 5322 } -1 5323 } -1 5324 }; 5306 53255307 -1 /**5308 -1 * Build a selector that will match elements which implicity or explicitly have this role.5309 -1 * Note that the selector will probably not look elegant but it will work.5310 -1 * @param {string} role5311 -1 * @return {string} selector5312 -1 */5313 -1 axs.properties.getSelectorForRole = function(role) {5314 -1 if (!role)5315 -1 return '';5316 -1 if (roleToSelectorCache[role] && roleToSelectorCache.hasOwnProperty(role))5317 -1 return roleToSelectorCache[role];5318 -1 var selectors = ['[role="' + role + '"]'];5319 -1 var tagNames = Object.keys(/** @type {!Object} */(axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO));5320 -1 tagNames.forEach(function(tagName) {5321 -1 var htmlInfos = axs.constants.TAG_TO_IMPLICIT_SEMANTIC_INFO[tagName];5322 -1 if (htmlInfos && htmlInfos.length) {5323 -1 for (var i = 0; i < htmlInfos.length; i++) {5324 -1 var htmlInfo = htmlInfos[i];5325 -1 if (htmlInfo.role === role) {5326 -1 if (htmlInfo.selector) {5327 -1 selectors[selectors.length] = htmlInfo.selector;5328 -1 } else {5329 -1 selectors[selectors.length] = tagName; // Selectors API is not case sensitive.5330 -1 break; // No need to continue adding selectors since we will match the tag itself.5331 -1 }5332 -1 }5333 -1 }5334 -1 }5335 -1 });5336 -1 return (roleToSelectorCache[role] = selectors.join(','));5337 -1 };5338 -1 })();-1 5326 module.exports = { -1 5327 walkDOM: walkDOM, -1 5328 searchUp: searchUp, -1 5329 }; 5339 5330 5340 5331 },{}],12:[function(require,module,exports){ 5341 5332 /*! aXe v2.6.1 @@ -14199,23 +14190,249 @@ axs.properties.getNativelySupportedAttributes = function(element) { 14199 14190 })(typeof window === 'object' ? window : this); 14200 14191 },{}],13:[function(require,module,exports){ 14201 14192 /*!14202 -1 calcNames 1.2, compute the Name and Description property values for a DOM node-1 14193 CalcNames 1.3, compute the Name and Description property values for a DOM node 14203 14194 Returns an object with 'name' and 'desc' properties.14204 -1 Authored by Bryan Garaventa plus contrabutions by Tobias Bengfort-1 14195 Functionality mirrors the steps within the W3C Accessible Name and Description computation algorithm. -1 14196 http://www.w3.org/TR/accname-aam-1.1/ -1 14197 Authored by Bryan Garaventa plus refactoring contrabutions by Tobias Bengfort -1 14198 https://github.com/accdc/w3c-alternative-text-computation 14205 14199 Distributed under the terms of the Open Source Initiative OSI - MIT License 14206 14200 */ 14207 1420114208 -1 var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) {14209 -1 if (!node || node.nodeType !== 1) {14210 -1 return;14211 -1 }-1 14202 var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { -1 14203 if (!node || node.nodeType !== 1) { -1 14204 return; -1 14205 } -1 14206 -1 14207 // Track nodes to prevent duplicate node reference parsing. -1 14208 var nodes = []; -1 14209 -1 14210 // Recursively process a DOM node to compute an accessible name in accordance with the spec -1 14211 var walk = function(refNode, stop, skip, nodesToIgnoreValues) { -1 14212 var fullName = ''; -1 14213 -1 14214 // Placeholder for storing CSS before and after pseudo element text values for the top level node -1 14215 var cssOP = { -1 14216 before: '', -1 14217 after: '' -1 14218 }; -1 14219 -1 14220 if (nodes.indexOf(refNode) === -1) { -1 14221 // Store the before and after pseudo element 'content' values for the top level DOM node -1 14222 // Note: If the pseudo element includes block level styling, a space will be added, otherwise inline is asumed and no spacing is added. -1 14223 cssOP = getCSSText(refNode, null); 14212 1422414213 -1 var trim = function(str) {14214 -1 if (typeof str !== 'string') {14215 -1 return '';-1 14225 // Enabled in Visual ARIA to prevent self referencing by Visual ARIA tooltips -1 14226 if (preventVisualARIASelfCSSRef) { -1 14227 if (cssOP.before.indexOf(' [ARIA] ') !== -1 || cssOP.before.indexOf(' aria-') !== -1 || cssOP.before.indexOf(' accName: ') !== -1) cssOP.before = ''; -1 14228 if (cssOP.after.indexOf(' [ARIA] ') !== -1 || cssOP.after.indexOf(' aria-') !== -1 || cssOP.after.indexOf(' accDescription: ') !== -1) cssOP.after = ''; -1 14229 } 14216 14230 } 14217 1423114218 -1 return str.replace(/^\s+|\s+$/g, '');-1 14232 var blockNodeStack = []; -1 14233 -1 14234 var hasLeftBlockNodeStack = function(node) { -1 14235 var blocks = blockNodeStack.length; -1 14236 for (var i = blocks; i; i--) { -1 14237 if (!inParent(node, blockNodeStack[i - 1], refNode)) { -1 14238 blockNodeStack.splice(i - 1, 1); -1 14239 } -1 14240 } -1 14241 if (blockNodeStack.length < blocks) { -1 14242 return true; -1 14243 } -1 14244 return false; -1 14245 }; -1 14246 -1 14247 // Recursively apply the same naming computation to all nodes within the referenced structure -1 14248 walkDOM(refNode, function(node) { -1 14249 -1 14250 if (skip || !node || nodes.indexOf(node) !== -1 || (isHidden(node, refNode))) { -1 14251 // Abort if algorithm step is already completed, or if node is a hidden child of refNode, or if this node has already been processed. -1 14252 return; -1 14253 } -1 14254 -1 14255 if (nodes.indexOf(node) === -1) { -1 14256 nodes.push(node); -1 14257 } -1 14258 -1 14259 // Store name for the current node. -1 14260 var name = ''; -1 14261 // Placeholder for storing CSS before and after pseudo element text values for the current node container element -1 14262 var cssO = { -1 14263 before: '', -1 14264 after: '' -1 14265 }; -1 14266 -1 14267 var parent = refNode === node ? node : node.parentNode; -1 14268 if (nodes.indexOf(parent) === -1) { -1 14269 nodes.push(parent); -1 14270 // Store the before and after pseudo element 'content' values for the current node container element -1 14271 // Note: If the pseudo element includes block level styling, a space will be added, otherwise inline is asumed and no spacing is added. -1 14272 cssO = getCSSText(parent, refNode); -1 14273 -1 14274 // Enabled in Visual ARIA to prevent self referencing by Visual ARIA tooltips -1 14275 if (preventVisualARIASelfCSSRef) { -1 14276 if (cssO.before.indexOf(' [ARIA] ') !== -1 || cssO.before.indexOf(' aria-') !== -1 || cssO.before.indexOf(' accName: ') !== -1) cssO.before = ''; -1 14277 if (cssO.after.indexOf(' [ARIA] ') !== -1 || cssO.after.indexOf(' aria-') !== -1 || cssO.after.indexOf(' accDescription: ') !== -1) cssO.after = ''; -1 14278 } -1 14279 -1 14280 } -1 14281 -1 14282 // Process standard DOM element node -1 14283 if (node.nodeType === 1) { -1 14284 -1 14285 var nodeIsBlock = isBlockLevelElement(node); -1 14286 if (nodeIsBlock && blockNodeStack.indexOf(node) === -1) { -1 14287 blockNodeStack.push(node); -1 14288 } -1 14289 if (nodeIsBlock && node !== refNode) { -1 14290 // Add space at beginning of block level element if detected. -1 14291 name += ' '; -1 14292 } -1 14293 -1 14294 var aLabelledby = node.getAttribute('aria-labelledby') || ''; -1 14295 var aLabel = node.getAttribute('aria-label') || ''; -1 14296 var nTitle = node.getAttribute('title') || ''; -1 14297 var nTag = node.nodeName.toLowerCase(); -1 14298 var nRole = node.getAttribute('role'); -1 14299 var rolePresentation = ['presentation', 'none'].indexOf(nRole) !== -1; -1 14300 var isNativeFormField = ['input', 'select', 'textarea'].indexOf(nTag) !== -1; -1 14301 var isSimulatedFormField = ['searchbox', 'scrollbar', 'slider', 'spinbutton', 'textbox', 'combobox', 'grid', 'listbox', 'tablist', 'tree', 'treegrid'].indexOf(nRole) !== -1; -1 14302 var aOwns = node.getAttribute('aria-owns') || ''; -1 14303 -1 14304 // Check for non-empty value of aria-labelledby if current node equals reference node, follow each ID ref, then stop and process no deeper. -1 14305 if (!stop && node === refNode && aLabelledby) { -1 14306 if (!rolePresentation) { -1 14307 var ids = aLabelledby.split(/\s+/); -1 14308 var parts = []; -1 14309 for (var i = 0; i < ids.length; i++) { -1 14310 var element = document.getElementById(ids[i]); -1 14311 // Also prevent the current form field from having its value included in the naming computation if nested as a child of label -1 14312 parts.push(walk(element, true, skip, [node])); -1 14313 } -1 14314 // Check for blank value, since whitespace chars alone are not valid as a name -1 14315 name = addSpacing(trim(parts.join(' '))); -1 14316 } -1 14317 -1 14318 if (name || rolePresentation) { -1 14319 // Abort further recursion if name is valid or if the referenced node is presentational. -1 14320 skip = true; -1 14321 } -1 14322 } -1 14323 -1 14324 // Otherwise, if the current node is non-presentational and is a nested widget control within the parent ref obj, then add only its value and process no deeper -1 14325 if (!rolePresentation && node !== refNode && (isNativeFormField || isSimulatedFormField)) { -1 14326 -1 14327 // Prevent the referencing node from having its value included in the case of form control labels that contain the element with focus. -1 14328 if (!(nodesToIgnoreValues && nodesToIgnoreValues.length && nodesToIgnoreValues.indexOf(node) !== -1)) { -1 14329 -1 14330 if (isSimulatedFormField && ['scrollbar', 'slider', 'spinbutton'].indexOf(nRole) !== -1) { -1 14331 // For range widgets, append aria-valuetext if non-empty, or aria-valuenow if non-empty, or node.value if applicable. -1 14332 name = getObjectValue(nRole, node, true); -1 14333 } -1 14334 else if (isSimulatedFormField && ['searchbox', 'textbox', 'combobox'].indexOf(nRole) !== -1) { -1 14335 // For simulated edit widgets, append text from content if applicable, or node.value if applicable. -1 14336 name = getObjectValue(nRole, node, false, true); -1 14337 } -1 14338 else if (isSimulatedFormField && ['grid', 'listbox', 'tablist', 'tree', 'treegrid'].indexOf(nRole) !== -1) { -1 14339 // For simulated select widgets, append same naming computation algorithm for all child nodes including aria-selected="true" separated by a space when multiple. -1 14340 // Also filter nodes so that only valid child roles of relevant parent role that include aria-selected="true" are included. -1 14341 name = getObjectValue(nRole, node, false, false, true); -1 14342 } -1 14343 else if (isNativeFormField && ['input', 'textarea'].indexOf(nTag) !== -1) { -1 14344 // For native edit fields, append node.value when applicable. -1 14345 name = getObjectValue(nRole, node, false, false, false, true); -1 14346 } -1 14347 else if (isNativeFormField && nTag === 'select') { -1 14348 // For native select fields, append node.value for single select, or text from content for all options with selected attribute separated by a space when multiple. -1 14349 name = getObjectValue(nRole, node, false, false, true, true); -1 14350 } -1 14351 -1 14352 // Check for blank value, since whitespace chars alone are not valid as a name -1 14353 name = addSpacing(trim(name)); -1 14354 -1 14355 } -1 14356 } -1 14357 -1 14358 // Otherwise, if current node is non-presentational and has a non-empty aria-label then set as name and process no deeper. -1 14359 else if (!name && !rolePresentation && aLabel) { -1 14360 // Check for blank value, since whitespace chars alone are not valid as a name -1 14361 name = addSpacing(trim(aLabel)); -1 14362 -1 14363 if (name && node === refNode) { -1 14364 // If name is non-empty and both the current and refObject nodes match, then don't process any deeper. -1 14365 skip = true; -1 14366 } -1 14367 } -1 14368 -1 14369 // Otherwise, if name is still empty and the current node is non-presentational and matches the ref node and is a standard form field with a non-empty associated label element, process label with same naming computation algorithm. -1 14370 if (!name && !rolePresentation && node === refNode && isNativeFormField && node.id && document.querySelectorAll('label[for="' + node.id + '"]').length) { -1 14371 var label = document.querySelector('label[for="' + node.id + '"]'); -1 14372 // Check for blank value, since whitespace chars alone are not valid as a name -1 14373 name = addSpacing(trim(walk(label, true, skip, [node]))); -1 14374 } -1 14375 -1 14376 // Otherwise, if name is still empty and the current node is non-presentational and matches the ref node and is a standard form field with an implicit label element surrounding it, process label with same naming computation algorithm. -1 14377 if (!name && !rolePresentation && node === refNode && isNativeFormField && getParent(node, 'label').nodeType === 1) { -1 14378 // Check for blank value, since whitespace chars alone are not valid as a name -1 14379 name = addSpacing(trim(walk(getParent(node, 'label'), true, skip, [node]))); -1 14380 } -1 14381 -1 14382 // Otherwise, if name is still empty and current node is non-presentational and is a standard img with a non-empty alt attribute, set alt attribute value as the accessible name. -1 14383 else if (!name && !rolePresentation && nTag == 'img' && node.getAttribute('alt')) { -1 14384 // Check for blank value, since whitespace chars alone are not valid as a name -1 14385 name = addSpacing(trim(node.getAttribute('alt'))); -1 14386 } -1 14387 -1 14388 // Otherwise, if name is still empty and current node is non-presentational and includes a non-empty title attribute, set title attribute value as the accessible name. -1 14389 if (!name && !rolePresentation && nTitle) { -1 14390 // Check for blank value, since whitespace chars alone are not valid as a name -1 14391 name = addSpacing(trim(nTitle)); -1 14392 } -1 14393 -1 14394 // Check for non-empty value of aria-owns, follow each ID ref, then process with same naming computation. -1 14395 // Also abort aria-owns processing if contained on an element that does not support child elements. -1 14396 if (aOwns && !isNativeFormField && nTag != 'img') { -1 14397 var ids = aOwns.split(/\s+/); -1 14398 var parts = []; -1 14399 for (var i = 0; i < ids.length; i++) { -1 14400 var element = document.getElementById(ids[i]); -1 14401 // Abort processing if the referenced node is already a child DOM node -1 14402 if (!inParent(element, node)) { -1 14403 parts.push(trim(walk(element, true, skip))); -1 14404 } -1 14405 } -1 14406 // Surround returned aria-owns naming computation with spaces since these will be separated visually if not already included as nested DOM nodes. -1 14407 name += addSpacing(parts.join(' ')); -1 14408 } -1 14409 -1 14410 } -1 14411 -1 14412 // Otherwise, process text node -1 14413 else if (node.nodeType === 3) { -1 14414 -1 14415 // Add space at end of block level element if detected. -1 14416 name = (hasLeftBlockNodeStack(node) ? ' ' : '') + node.data; -1 14417 -1 14418 } -1 14419 -1 14420 // Prepend and append the current CSS pseudo element text, plus normalize all whitespace such as newline characters and others into flat spaces. -1 14421 name = cssO.before + name.replace(/\s+/g, ' ') + cssO.after; -1 14422 -1 14423 if (name && !hasParentLabel(node, false, refNode)) { -1 14424 fullName += name; -1 14425 } -1 14426 -1 14427 }, refNode); -1 14428 -1 14429 // Prepend and append the refObj CSS pseudo element text, plus normalize whitespace chars into flat spaces. -1 14430 fullName = cssOP.before + fullName.replace(/\s+/g, ' ') + cssOP.after; -1 14431 -1 14432 // Clear the tracked nodes array for garbage collection. -1 14433 nodes = []; -1 14434 -1 14435 return fullName; 14219 14436 }; 14220 14437 14221 14438 var walkDOM = function(node, fn, refNode) { @@ -14223,10 +14440,8 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14223 14440 return; 14224 14441 } 14225 14442 fn(node);14226 -114227 14443 if (!isException(node, refNode)) { 14228 14444 node = node.firstChild;14229 -114230 14445 while (node) { 14231 14446 walkDOM(node, fn, refNode); 14232 14447 node = node.nextSibling; @@ -14234,9 +14449,15 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14234 14449 } 14235 14450 }; 14236 14451 -1 14452 var trim = function(str) { -1 14453 if (typeof str !== 'string') { -1 14454 return ''; -1 14455 } -1 14456 return str.replace(/^\s+|\s+$/g, ''); -1 14457 }; -1 14458 14237 14459 var isFocusable = function(node) { 14238 14460 var nodeName = node.nodeName.toLowerCase();14239 -114240 14461 if (node.getAttribute('tabindex')) { 14241 14462 return true; 14242 14463 } @@ -14254,16 +14475,19 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14254 14475 return false; 14255 14476 } 14256 14477 -1 14478 // Always include name from content when the referenced node matches list1, as well as when child nodes match those within list3 14257 14479 var list1 = { 14258 14480 roles: ['link', 'button', 'checkbox', 'option', 'radio', 'switch', 'tab', 'treeitem', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'cell', 'columnheader', 'rowheader', 'tooltip', 'heading'], 14259 14481 tags: ['a', 'button', 'summary', 'input', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'menuitem', 'option', 'td', 'th'] 14260 14482 }; 14261 14483 -1 14484 // Never include name from content when current node matches list2 14262 14485 var list2 = { 14263 14486 roles: ['application', 'alert', 'log', 'marquee', 'timer', 'alertdialog', 'dialog', 'banner', 'complementary', 'form', 'main', 'navigation', 'region', 'search', 'article', 'document', 'feed', 'figure', 'img', 'math', 'toolbar', 'menu', 'menubar', 'grid', 'listbox', 'radiogroup', 'textbox', 'searchbox', 'spinbutton', 'scrollbar', 'slider', 'tablist', 'tabpanel', 'tree', 'treegrid', 'separator'], 14264 14487 tags: ['article', 'aside', 'body', 'select', 'datalist', 'optgroup', 'dialog', 'figure', 'footer', 'form', 'header', 'hr', 'img', 'textarea', 'input', 'main', 'math', 'menu', 'nav', 'section'] 14265 14488 }; 14266 14489 -1 14490 // As an override of list2, conditionally include name from content if current node is focusable, or if the current node matches list3 while the referenced parent node matches list1. 14267 14491 var list3 = { 14268 14492 roles: ['combobox', 'term', 'definition', 'directory', 'list', 'group', 'note', 'status', 'table', 'rowgroup', 'row', 'contentinfo'], 14269 14493 tags: ['dl', 'ul', 'ol', 'dd', 'details', 'output', 'table', 'thead', 'tbody', 'tfoot', 'tr'] @@ -14291,6 +14515,54 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14291 14515 } 14292 14516 }; 14293 14517 -1 14518 var getStyleObject = function(node) { -1 14519 var style = {}; -1 14520 if (document.defaultView && document.defaultView.getComputedStyle) { -1 14521 style = document.defaultView.getComputedStyle(node, ''); -1 14522 } else if (node.currentStyle) { -1 14523 style = node.currentStyle; -1 14524 } -1 14525 return style; -1 14526 }; -1 14527 -1 14528 var isBlockLevelElement = function(node, cssObj) { -1 14529 var styleObject = cssObj || getStyleObject(node); -1 14530 for (var prop in blockStyles) { -1 14531 var values = blockStyles[prop]; -1 14532 for (var i = 0; i < values.length; i++) { -1 14533 if (styleObject[prop] && ((values[i].indexOf('!') === 0 && [values[i].slice(1), 'inherit', 'initial', 'unset'].indexOf(styleObject[prop]) === -1) || styleObject[prop].indexOf(values[i]) !== -1)) { -1 14534 return true; -1 14535 } -1 14536 } -1 14537 } -1 14538 if (!cssObj && node.nodeName && blockElements.indexOf(node.nodeName.toLowerCase()) !== -1) { -1 14539 return true; -1 14540 } -1 14541 return false; -1 14542 }; -1 14543 -1 14544 /* -1 14545 CSS Block Styles indexed from: -1 14546 https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context -1 14547 */ -1 14548 var blockStyles = { -1 14549 'display': ['block', 'grid', 'table', 'flow-root', 'flex'], -1 14550 'position': ['absolute', 'fixed'], -1 14551 'float': ['left', 'right', 'inline'], -1 14552 'clear': ['left', 'right', 'both', 'inline'], -1 14553 'overflow': ['hidden', 'scroll', 'auto'], -1 14554 'column-count': ['!auto'], -1 14555 'column-width': ['!auto'], -1 14556 'column-span': ['all'], -1 14557 'contain': ['layout', 'content', 'strict'] -1 14558 }; -1 14559 -1 14560 /* -1 14561 HTML5 Block Elements indexed from: -1 14562 https://github.com/webmodules/block-elements -1 14563 */ -1 14564 var blockElements = ['address', 'article', 'aside', 'blockquote', 'canvas', 'dd', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'li', 'main', 'nav', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table', 'tfoot', 'ul', 'video']; -1 14565 14294 14566 var isHidden = function(node, refNode) { 14295 14567 if (node.nodeType !== 1 || node == refNode) { 14296 14568 return false; @@ -14300,12 +14572,7 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14300 14572 return true; 14301 14573 } 14302 1457414303 -1 var style = {};14304 -1 if (document.defaultView && document.defaultView.getComputedStyle) {14305 -1 style = document.defaultView.getComputedStyle(node, '');14306 -1 } else if (node.currentStyle) {14307 -1 style = node.currentStyle;14308 -1 }-1 14575 var style = getStyleObject(node); 14309 14576 if (style['display'] === 'none' || style['visibility'] === 'hidden') { 14310 14577 return true; 14311 14578 } @@ -14313,20 +14580,96 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14313 14580 return false; 14314 14581 }; 14315 1458214316 -1 var getCSSText = function(node, refNode) {14317 -1 if (node.nodeType !== 1 || node == refNode || ['input', 'select', 'textarea', 'img', 'iframe'].indexOf(node.nodeName.toLowerCase()) !== -1) {14318 -1 return {before: '', after: ''};-1 14583 var getObjectValue = function(role, node, isRange, isEdit, isSelect, isNative) { -1 14584 var val = ''; -1 14585 var bypass = false; -1 14586 -1 14587 if (isRange && !isNative) { -1 14588 val = node.getAttribute('aria-valuetext') || node.getAttribute('aria-valuenow') || ''; -1 14589 } -1 14590 else if (isEdit && !isNative) { -1 14591 val = getText(node) || ''; -1 14592 } -1 14593 else if (isSelect && !isNative) { -1 14594 var childRoles = []; -1 14595 if (role == 'grid' || role == 'treegrid') { -1 14596 childRoles = ['gridcell', 'rowheader', 'columnheader']; -1 14597 } -1 14598 else if (role == 'listbox') { -1 14599 childRoles = ['option']; -1 14600 } -1 14601 else if (role == 'tablist') { -1 14602 childRoles = ['tab']; -1 14603 } -1 14604 else if (role == 'tree') { -1 14605 childRoles = ['treeitem']; -1 14606 } -1 14607 val = joinSelectedParts(node, node.querySelectorAll('*[aria-selected="true"]'), false, childRoles); -1 14608 bypass = true; -1 14609 } -1 14610 val = trim(val); -1 14611 if (!val && (isRange || isEdit) && node.value) { -1 14612 val = node.value; -1 14613 } -1 14614 if (!bypass && !val && isNative) { -1 14615 val = (isSelect && node.multiple) ? joinSelectedParts(node, node.querySelectorAll('option[selected]'), true) : node.value; 14319 14616 } 14320 1461714321 -1 var getText = function(node, position) {14322 -1 var text = document.defaultView.getComputedStyle(node, position).getPropertyValue('content').replace(/^\"|\"$/g, '');14323 -1 if (!text || text === 'none') {14324 -1 return '';14325 -1 } else {14326 -1 return text;-1 14618 return val; -1 14619 }; -1 14620 -1 14621 var addSpacing = function(str) { -1 14622 return str.length ? ' ' + str + ' ' : ''; -1 14623 }; -1 14624 -1 14625 var joinSelectedParts = function(node, nOA, isNative, childRoles) { -1 14626 if (!nOA || !nOA.length) { -1 14627 return ''; -1 14628 } -1 14629 var parts = []; -1 14630 for (var i = 0; i < nOA.length; i++) { -1 14631 var role = nOA[i].getAttribute('role'); -1 14632 var isValidChildRole = !childRoles || childRoles.indexOf(role) !== -1; -1 14633 if (isValidChildRole) { -1 14634 parts.push(isNative ? getText(nOA[i]) : walk(nOA[i], true)); 14327 14635 }14328 -1 };-1 14636 } -1 14637 return parts.join(' '); -1 14638 }; -1 14639 -1 14640 var getPseudoElStyleObj = function(node, position) { -1 14641 var styleObj = {}; -1 14642 for (var prop in blockStyles) { -1 14643 styleObj[prop] = document.defaultView.getComputedStyle(node, position).getPropertyValue(prop); -1 14644 } -1 14645 styleObj['content'] = document.defaultView.getComputedStyle(node, position).getPropertyValue('content').replace(/^\"|\\|\"$/g, ''); -1 14646 return styleObj; -1 14647 }; -1 14648 -1 14649 var getText = function(node, position) { -1 14650 if (!position && node.nodeType === 1) { -1 14651 return node.innerText || node.textContent || ''; -1 14652 } -1 14653 var styles = getPseudoElStyleObj(node, position); -1 14654 var text = styles['content']; -1 14655 if (!text || text === 'none') { -1 14656 return ''; -1 14657 } -1 14658 if (isBlockLevelElement({}, styles)) { -1 14659 if (position == ':before') { -1 14660 text += ' '; -1 14661 } -1 14662 else if (position == ':after') { -1 14663 text = ' ' + text; -1 14664 } -1 14665 } -1 14666 return text; -1 14667 }; 14329 14668 -1 14669 var getCSSText = function(node, refNode) { -1 14670 if (node.nodeType !== 1 || node == refNode || ['input', 'select', 'textarea', 'img', 'iframe'].indexOf(node.nodeName.toLowerCase()) !== -1) { -1 14671 return {before: '', after: ''}; -1 14672 } 14330 14673 if (document.defaultView && document.defaultView.getComputedStyle) { 14331 14674 return { 14332 14675 before: getText(node, ':before'), @@ -14337,6 +14680,29 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14337 14680 } 14338 14681 }; 14339 14682 -1 14683 var inParent = function(node, parent, refNode) { -1 14684 while (node) { -1 14685 node = node.parentNode; -1 14686 if (node == parent) { -1 14687 return true; -1 14688 } -1 14689 else if (node == refNode) { -1 14690 return false; -1 14691 } -1 14692 } -1 14693 return false; -1 14694 }; -1 14695 -1 14696 var getParent = function(node, nTag) { -1 14697 while (node) { -1 14698 node = node.parentNode; -1 14699 if (node.nodeName.toLowerCase() == nTag) { -1 14700 return node; -1 14701 } -1 14702 } -1 14703 return {}; -1 14704 }; -1 14705 14340 14706 var hasParentLabel = function(node, noLabel, refNode) { 14341 14707 while (node && node !== refNode) { 14342 14708 node = node.parentNode; @@ -14356,146 +14722,42 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14356 14722 return false; 14357 14723 }; 14358 1472414359 -1 var walk = function(refNode, stop, skip) {14360 -1 var fullName = '';14361 -1 var nodes = [];14362 -1 var cssOP = {14363 -1 before: '',14364 -1 after: ''14365 -1 };14366 -114367 -1 if (nodes.indexOf(refNode) === -1) {14368 -1 nodes.push(refNode);14369 -1 cssOP = getCSSText(refNode, null);14370 -114371 -1 // Enabled in Visual ARIA to prevent self referencing by Visual ARIA tooltips14372 -1 if (preventVisualARIASelfCSSRef) {14373 -1 if (cssOP.before.indexOf(' [ARIA] ') !== -1 || cssOP.before.indexOf(' aria-') !== -1)14374 -1 cssOP.before = '';14375 -1 if (cssOP.after.indexOf(' [ARIA] ') !== -1 || cssOP.after.indexOf(' aria-') !== -1)14376 -1 cssOP.after = '';14377 -1 }14378 -1 }14379 -114380 -1 walkDOM(refNode, function(node) {14381 -1 if (skip || !node || (isHidden(node, refNode))) {14382 -1 return;14383 -1 }14384 -114385 -1 var name = '';14386 -1 var cssO = {14387 -1 before: '',14388 -1 after: ''14389 -1 };14390 -114391 -1 var parent = refNode === node ? node : node.parentNode;14392 -1 if (nodes.indexOf(parent) === -1) {14393 -1 nodes.push(parent);14394 -1 cssO = getCSSText(parent, refNode);14395 -114396 -1 // Enabled in Visual ARIA to prevent self referencing by Visual ARIA tooltips14397 -1 if (preventVisualARIASelfCSSRef) {14398 -1 if (cssO.before.indexOf(' [ARIA] ') !== -1 || cssO.before.indexOf(' aria-') !== -1)14399 -1 cssO.before = '';14400 -1 if (cssO.after.indexOf(' [ARIA] ') !== -1 || cssO.after.indexOf(' aria-') !== -1)14401 -1 cssO.after = '';14402 -1 }14403 -114404 -1 }14405 -114406 -1 if (node.nodeType === 1) {14407 -1 var aLabelledby = node.getAttribute('aria-labelledby') || '';14408 -1 var aLabel = node.getAttribute('aria-label') || '';14409 -1 var nTitle = node.getAttribute('title') || '';14410 -1 var rolePresentation = ['presentation', 'none'].indexOf(node.getAttribute('role')) !== -1;14411 -114412 -1 if (!node.firstChild || (node == refNode && (aLabelledby || aLabel)) || (node.firstChild && node != refNode && aLabel)) {14413 -1 if (!stop && node === refNode && aLabelledby) {14414 -1 if (!rolePresentation) {14415 -1 var ids = aLabelledby.split(/\s+/);14416 -1 var parts = [];14417 -114418 -1 for (var i = 0; i < ids.length; i++) {14419 -1 var element = document.getElementById(ids[i]);14420 -1 parts.push(walk(element, true, skip));14421 -1 }14422 -1 name = parts.join(' ');14423 -1 }14424 -114425 -1 if (name || rolePresentation) {14426 -1 skip = true;14427 -1 }14428 -1 }14429 -114430 -1 /*!@ Add values of custom controls here if recursive controls with values */14431 -114432 -1 if (!name && !rolePresentation && aLabel) {14433 -1 name = aLabel;14434 -114435 -1 if (name && node === refNode) {14436 -1 skip = true;14437 -1 }14438 -1 }14439 -114440 -1 if (!name && !rolePresentation && ['input', 'select', 'textarea'].indexOf(node.nodeName.toLowerCase()) !== -1 && node.id && document.querySelectorAll('label[for="' + node.id + '"]').length) {14441 -1 var label = document.querySelector('label[for="' + node.id + '"]');14442 -1 name = walk(label, true, skip);14443 -1 }14444 -114445 -1 if (!name && !rolePresentation && node.nodeName.toLowerCase() == 'img' && node.getAttribute('alt')) {14446 -1 name = node.getAttribute('alt');14447 -1 }14448 -114449 -1 if (!name && !rolePresentation && nTitle) {14450 -1 name = nTitle;14451 -1 }14452 -1 }14453 -1 } else if (node.nodeType === 3) {14454 -1 name = node.data;14455 -1 }14456 -114457 -1 name = cssO.before + name + cssO.after;14458 -114459 -1 if (name && !hasParentLabel(node, false, refNode)) {14460 -1 fullName += name;14461 -1 }14462 -1 }, refNode);14463 -114464 -1 fullName = cssOP.before + fullName + cssOP.after;14465 -1 return fullName;14466 -1 };14467 -114468 14725 if (isHidden(node, document.body) || hasParentLabel(node, true, document.body)) { 14469 14726 return; 14470 14727 } 14471 14728 -1 14729 // Compute accessible Name property value for node 14472 14730 var accName = walk(node, false);14473 -1 var accDesc = '';14474 14731 -1 14732 var accDesc = ''; 14475 14733 if (['presentation', 'none'].indexOf(node.getAttribute('role')) === -1) {14476 -1 var desc = '';14477 -114478 -1 var title = node.getAttribute('title') || '';-1 14734 // Check for blank value, since whitespace chars alone are not valid as a name -1 14735 var title = trim(node.getAttribute('title')); 14479 14736 if (title) { 14480 14737 if (!accName) { -1 14738 // Set accessible Name to title value as a fallback if no other labelling mechanism is available. 14481 14739 accName = title; 14482 14740 } else { -1 14741 // Otherwise, set Description using title attribute if available and including more than whitespace characters. 14483 14742 accDesc = title; 14484 14743 } 14485 14744 } 14486 14745 -1 14746 // Compute accessible Description property value 14487 14747 var describedby = node.getAttribute('aria-describedby') || ''; 14488 14748 if (describedby) { 14489 14749 var ids = describedby.split(/\s+/); 14490 14750 var parts = [];14491 -114492 14751 for (var j = 0; j < ids.length; j++) { 14493 14752 var element = document.getElementById(ids[j]); 14494 14753 parts.push(walk(element, true)); 14495 14754 }14496 -114497 -1 if (parts.length) {14498 -1 accDesc = parts.join(' ');-1 14755 // Check for blank value, since whitespace chars alone are not valid as a name -1 14756 var desc = trim(parts.join(' ')); -1 14757 if (desc) { -1 14758 // Set Description if computation includes more than whitespace characters. -1 14759 // Note: Setting the Description property using computation from aria-describedby will overwrite any prior Description set using the title attribute. -1 14760 accDesc = desc; 14499 14761 } 14500 14762 } 14501 14763 } @@ -14504,6 +14766,7 @@ var calcNames = function(node, fnc, preventVisualARIASelfCSSRef) { 14504 14766 accDesc = trim(accDesc.replace(/\s+/g, ' ')); 14505 14767 14506 14768 if (accName === accDesc) { -1 14769 // If both Name and Description properties match, then clear the Description property value. 14507 14770 accDesc = ''; 14508 14771 } 14509 14772 @@ -14560,7 +14823,7 @@ require('accessibility-developer-tools/src/js/Properties'); 14560 14823 module.exports = global.axs; 14561 14824 14562 14825 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})14563 -1 },{"accessibility-developer-tools/src/js/AccessibilityUtils":6,"accessibility-developer-tools/src/js/BrowserUtils":7,"accessibility-developer-tools/src/js/Color":8,"accessibility-developer-tools/src/js/Constants":9,"accessibility-developer-tools/src/js/DOMUtils":10,"accessibility-developer-tools/src/js/Properties":11}],15:[function(require,module,exports){-1 14826 },{"accessibility-developer-tools/src/js/AccessibilityUtils":1,"accessibility-developer-tools/src/js/BrowserUtils":2,"accessibility-developer-tools/src/js/Color":3,"accessibility-developer-tools/src/js/Constants":4,"accessibility-developer-tools/src/js/DOMUtils":5,"accessibility-developer-tools/src/js/Properties":6}],15:[function(require,module,exports){ 14564 14827 var ariaApi = require('aria-api'); 14565 14828 var accdc = require('w3c-alternative-text-computation'); 14566 14829 var axe = require('axe-core'); @@ -14658,4 +14921,4 @@ try { 14658 14921 }); 14659 14922 } 14660 1492314661 -1 },{"./axs":14,"aria-api":1,"axe-core":12,"w3c-alternative-text-computation":13}]},{},[15]);-1 14924 },{"./axs":14,"aria-api":7,"axe-core":12,"w3c-alternative-text-computation":13}]},{},[15]);