(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.aria = {})); })(this, (function (exports) { 'use strict'; // https://www.w3.org/TR/wai-aria/#state_prop_def const attributes = { 'activedescendant': 'id', 'atomic': 'bool', 'autocomplete': 'token', 'braillelabel': 'string', 'brailleroledescription': 'string', 'busy': 'bool', 'checked': 'tristate', 'colcount': 'int', 'colindex': 'int', 'colindextext': 'string', 'colspan': 'int', 'controls': 'id-list', 'current': 'token', 'describedby': 'id-list', 'description': 'string', 'details': 'id', 'disabled': 'bool', 'dropeffect': 'token-list', 'errormessage': 'id', 'expanded': 'bool-undefined', 'flowto': 'id-list', 'grabbed': 'bool-undefined', 'haspopup': 'token', 'hidden': 'bool-undefined', 'invalid': 'token', 'keyshortcuts': 'string', 'label': 'string', 'labelledby': 'id-list', 'level': 'int', 'live': 'token', 'modal': 'bool', 'multiline': 'bool', 'multiselectable': 'bool', 'orientation': 'token', 'owns': 'id-list', 'placeholder': 'string', 'posinset': 'int', 'pressed': 'tristate', 'readonly': 'bool', 'relevant': 'token-list', 'required': 'bool', 'roledescription': 'string', 'rowcount': 'int', 'rowindex': 'int', 'rowindextext': 'string', 'rowspan': 'int', 'selected': 'bool-undefined', 'setsize': 'int', 'sort': 'token', 'valuemax': 'number', 'valuemin': 'number', 'valuenow': 'number', 'valuetext': 'string', }; const attributeStrongMapping = { 'disabled': 'disabled', 'placeholder': 'placeholder', 'readonly': 'readOnly', 'required': 'required', }; const attributeWeakMapping = { 'checked': 'checked', 'colspan': 'colSpan', 'expanded': 'open', 'multiselectable': 'multiple', 'rowspan': 'rowSpan', 'selected': 'selected', }; // https://www.w3.org/TR/html/dom.html#sectioning-content-2 const scoped = ['article *', 'aside *', 'nav *', 'section *'].join(','); const svgSelectors = function(selector) { return [ // `${selector}:has(> title:not(:empty))`, // `${selector}:has(> desc:not(:empty))`, `${selector}[aria-label]`, `${selector}[aria-roledescription]`, `${selector}[aria-labelledby]`, `${selector}[aria-describedby]`, `${selector}[tabindex]`, `${selector}[role]`, ]; }; // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings // https://www.w3.org/TR/wai-aria/roles const roles = { alert: { childRoles: ['alertdialog'], defaults: { 'live': 'assertive', 'atomic': true, }, }, alertdialog: {}, application: {}, article: { selectors: ['article'], childRoles: ['comment'], }, banner: { selectors: [`header:not(main *, ${scoped})`], }, blockquote: { selectors: ['blockquote'], }, button: { selectors: [ 'button', 'input[type="button"]', 'input[type="image"]', 'input[type="reset"]', 'input[type="submit"]', 'summary', ], nameFromContents: true, }, caption: { selectors: ['caption', 'figcaption'], }, cell: { selectors: ['td', 'td ~ th:not([scope])'], childRoles: ['columnheader', 'gridcell', 'rowheader'], nameFromContents: true, }, checkbox: { selectors: ['input[type="checkbox"]'], childRoles: ['switch'], nameFromContents: true, defaults: { 'checked': 'false', }, }, code: { selectors: ['code'], }, columnheader: { selectors: ['th[scope="col"]'], nameFromContents: true, }, combobox: { selectors: [ 'input:not([type])[list]', 'input[type="email"][list]', 'input[type="search"][list]', 'input[type="tel"][list]', 'input[type="text"][list]', 'input[type="url"][list]', 'select:not([size]):not([multiple])', 'select[size="0"]:not([multiple])', 'select[size="1"]:not([multiple])', ], defaults: { 'expanded': false, 'haspopup': 'listbox', }, }, command: { abstract: true, childRoles: ['button', 'link', 'menuitem'], }, comment: { nameFromContents: true, }, complementary: { selectors: [ `aside:not(${scoped})`, 'aside[aria-label]', 'aside[aria-labelledby]', 'aside[title]', ], }, composite: { abstract: true, childRoles: ['grid', 'select', 'spinbutton', 'tablist'], }, contentinfo: { selectors: [`footer:not(main *, ${scoped})`], }, definition: { selectors: ['dd'], }, deletion: { selectors: ['del', 's'], }, dialog: { selectors: ['dialog'], childRoles: ['alertdialog'], }, 'doc-abstract': {}, 'doc-acknowledgments': {}, 'doc-afterword': {}, 'doc-appendix': {}, 'doc-backlink': { nameFromContents: true, }, 'doc-biblioentry': {}, 'doc-bibliography': {}, 'doc-biblioref': { nameFromContents: true, }, 'doc-chapter': {}, 'doc-colophon': {}, 'doc-conclusion': {}, 'doc-cover': {}, 'doc-credit': {}, 'doc-credits': {}, 'doc-dedication': {}, 'doc-endnote': {}, 'doc-endnotes': {}, 'doc-epilogue': {}, 'doc-epigraph': {}, 'doc-errata': {}, 'doc-example': {}, 'doc-footnote': {}, 'doc-foreword': {}, 'doc-glossary': {}, 'doc-glossref': { nameFromContents: true, }, 'doc-index': {}, 'doc-introduction': {}, 'doc-noteref': { nameFromContents: true, }, 'doc-notice': {}, 'doc-pagebreak': { nameFromContents: true, }, 'doc-pagefooter': {}, 'doc-pageheader': {}, 'doc-pagelist': {}, 'doc-part': {}, 'doc-preface': {}, 'doc-prologue': {}, 'doc-pullquote': {}, 'doc-qna': {}, 'doc-subtitle': { nameFromContents: true, }, 'doc-tip': {}, 'doc-toc': {}, document: { selectors: ['html'], childRoles: ['article', 'graphics-document'], }, emphasis: { selectors: ['em'], }, feed: {}, figure: { selectors: ['figure'], childRoles: ['doc-example'], }, form: { selectors: ['form[aria-label]', 'form[aria-labelledby]', 'form[title]'], }, generic: { selectors: [ 'a:not([*|href])', 'area:not([*|href])', `aside:not(${scoped}):not([aria-label]):not([aria-labelledby]):not([title])`, 'b', 'bdi', 'bdo', 'body', 'data', 'div', // footer scoped // header scoped 'i', 'li:not(ul > li):not(ol > li)', 'pre', 'q', 'samp', 'section:not([aria-label]):not([aria-labelledby]):not([title])', 'small', 'span', 'u', ], }, 'graphics-document': { selectors: ['svg'], }, 'graphics-object': { selectors: [ ...svgSelectors('symbol'), ...svgSelectors('use'), ], }, 'graphics-symbol': { selectors: [ ...svgSelectors('circle'), ...svgSelectors('ellipse'), ...svgSelectors('line'), ...svgSelectors('path'), ...svgSelectors('polygon'), ...svgSelectors('polyline'), ...svgSelectors('rect'), ], }, grid: { childRoles: ['treegrid'], }, gridcell: { childRoles: ['columnheader', 'rowheader'], nameFromContents: true, }, group: { selectors: [ 'address', 'details', 'fieldset', 'hgroup', 'optgroup', ...svgSelectors('foreignObject'), ...svgSelectors('g'), 'text', ...svgSelectors('textPath'), ...svgSelectors('tspan'), ], childRoles: ['row', 'select', 'toolbar', 'graphics-object'], }, heading: { selectors: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], nameFromContents: true, defaults: { 'level': 2, }, }, image: { selectors: [ 'img:not([alt=""])', 'graphics-symbol', ...svgSelectors('image'), ...svgSelectors('mesh'), ], childRoles: ['doc-cover'], }, input: { abstract: true, childRoles: [ 'checkbox', 'combobox', 'option', 'radio', 'slider', 'spinbutton', 'textbox', ], }, insertion: { selectors: ['ins'], }, landmark: { abstract: true, childRoles: [ 'banner', 'complementary', 'contentinfo', 'doc-acknowledgments', 'doc-afterword', 'doc-appendix', 'doc-bibliography', 'doc-chapter', 'doc-conclusion', 'doc-credits', 'doc-endnotes', 'doc-epilogue', 'doc-errata', 'doc-foreword', 'doc-glossary', 'doc-introduction', 'doc-part', 'doc-preface', 'doc-prologue', 'form', 'main', 'navigation', 'region', 'search', ], }, link: { selectors: ['a[*|href]', 'area[*|href]'], childRoles: ['doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref'], nameFromContents: true, }, list: { selectors: ['dl', 'ol', 'ul', 'menu'], childRoles: ['feed'], }, listbox: { selectors: [ 'datalist', 'select[multiple]', 'select[size]:not([size="0"]):not([size="1"])', ], defaults: { 'orientation': 'vertical', }, }, listitem: { selectors: ['ol > li', 'ul > li'], childRoles: ['doc-biblioentry', 'doc-endnote', 'treeitem'], }, log: { defaults: { 'live': 'polite', }, }, main: { selectors: ['main'], }, mark: { selectors: ['mark'], }, marquee: {}, math: { selectors: ['math'], }, meter: { selectors: ['meter'], defaults: { 'valuemin': 0, 'valuemax': 100, }, }, menu: { childRoles: ['menubar'], defaults: { 'orientation': 'vertical', }, }, menubar: { defaults: { 'orientation': 'horizontal', }, }, menuitem: { childRoles: ['menuitemcheckbox', 'menuitemradio'], nameFromContents: true, }, menuitemcheckbox: { nameFromContents: true, defaults: { 'checked': 'false', }, }, menuitemradio: { nameFromContents: true, defaults: { 'checked': 'false', }, }, navigation: { selectors: ['nav'], childRoles: ['doc-index', 'doc-pagelist', 'doc-toc'], }, none: { selectors: ['img[alt=""]'], }, note: { childRoles: ['doc-notice', 'doc-tip'], }, option: { selectors: ['option'], childRoles: ['treeitem'], nameFromContents: true, defaults: { 'selected': 'false', }, }, paragraph: { selectors: ['p'], }, progressbar: { selectors: ['progress'], defaults: { 'valuemin': 0, 'valuemax': 100, }, }, radio: { selectors: ['input[type="radio"]'], childRoles: ['menuitemradio'], nameFromContents: true, defaults: { 'checked': 'false', }, }, radiogroup: {}, range: { abstract: true, childRoles: ['meter', 'progressbar', 'scrollbar', 'slider', 'spinbutton'], }, region: { selectors: ['section[aria-label]', 'section[aria-labelledby]', 'section[title]'], }, roletype: { abstract: true, childRoles: ['structure', 'widget', 'window'], }, row: { selectors: ['tr'], nameFromContents: true, }, rowgroup: { selectors: ['tbody', 'thead', 'tfoot'], }, rowheader: { selectors: ['th[scope="row"]', 'th:not([scope]):not(td ~ th)'], nameFromContents: true, }, scrollbar: { defaults: { 'orientation': 'vertical', 'valuemin': 0, 'valuemax': 100, }, }, search: { selectors: ['search'], }, searchbox: { selectors: ['input[type="search"]:not([list])'], }, section: { abstract: true, childRoles: [ 'alert', 'blockquote', 'caption', 'cell', 'code', 'definition', 'deletion', 'doc-abstract', 'doc-colophon', 'doc-credit', 'doc-dedication', 'doc-epigraph', 'doc-footnote', 'doc-pagefooter', 'doc-pageheader', 'doc-pullquote', 'doc-qna', 'emphasis', 'figure', 'group', 'image', 'insertion', 'landmark', 'list', 'listitem', 'log', 'mark', 'marquee', 'math', 'note', 'paragraph', 'status', 'strong', 'subscript', 'suggestion', 'superscript', 'table', 'tabpanel', 'term', 'time', 'tooltip', ], }, sectionhead: { abstract: true, childRoles: [ 'columnheader', 'doc-subtitle', 'heading', 'rowheader', 'tab', ], nameFromContents: true, }, select: { abstract: true, childRoles: ['listbox', 'menu', 'radiogroup', 'tree'], }, separator: { // assume not focussable because
is not selectors: ['hr'], childRoles: ['doc-pagebreak'], defaults: { 'orientation': 'horizontal', 'valuemin': 0, 'valuemax': 100, }, }, slider: { selectors: ['input[type="range"]'], defaults: { 'orientation': 'horizontal', 'valuemin': 0, 'valuemax': 100, // FIXME: halfway between actual valuemin and valuemax 'valuenow': 50, }, }, spinbutton: { selectors: ['input[type="number"]'], defaults: { // FIXME: no valuemin/valuemax/valuenow }, }, status: { selectors: ['output'], childRoles: ['timer'], defaults: { 'live': 'polite', 'atomic': true, }, }, strong: { selectors: ['strong'], }, structure: { abstract: true, childRoles: [ 'application', 'document', 'none', 'generic', 'range', 'rowgroup', 'section', 'sectionhead', 'separator', ], }, suggestion: {}, subscript: { selectors: ['sub'], }, superscript: { selectors: ['sup'], }, switch: { nameFromContents: true, defaults: { 'checked': false, }, }, tab: { nameFromContents: true, defaults: { 'selected': false, }, }, table: { selectors: ['table'], childRoles: ['grid'], }, tablist: { defaults: { 'orientation': 'horizontal', }, }, tabpanel: {}, term: { selectors: ['dfn', 'dt'], }, textbox: { selectors: [ 'input:not([type]):not([list])', 'input[type="email"]:not([list])', 'input[type="tel"]:not([list])', 'input[type="text"]:not([list])', 'input[type="url"]:not([list])', 'textarea', ], childRoles: ['searchbox'], }, time: { selectors: ['time'], }, timer: { defaults: { 'live': 'off', }, }, toolbar: { defaults: { 'orientation': 'horizontal', }, }, tooltip: { nameFromContents: true, }, tree: { childRoles: ['treegrid'], defaults: { 'orientation': 'vertical', }, }, treegrid: {}, treeitem: { nameFromContents: true, }, widget: { abstract: true, childRoles: [ 'command', 'composite', 'gridcell', 'input', 'progressbar', 'row', 'scrollbar', 'separator', 'tab', ], }, window: { abstract: true, childRoles: ['dialog'], }, }; const getSubRoles = function(role) { const children = (roles[role]).childRoles || []; const descendents = children.map(getSubRoles); const result = [role]; descendents.forEach(list => { list.forEach(r => { if (!result.includes(r)) { result.push(r); } }); }); return result; }; const attrsWithDefaults = []; for (const role in roles) { roles[role].subRoles = getSubRoles(role); for (const key in roles[role].defaults) { if (!attrsWithDefaults.includes(key)) { attrsWithDefaults.push(key); } } } const aliases = { 'presentation': 'none', 'directory': 'list', 'img': 'image', }; const nameFromDescendant = { 'figure': 'figcaption', 'table': 'caption', 'fieldset': 'legend', }; const nameDefaults = { 'input[type="submit"]': 'Submit', 'input[type="reset"]': 'Reset', 'summary': 'Details', }; var unique = function(arr) { return arr.filter((a, i) => arr.indexOf(a) === i); }; var flatten = function(arr) { return [].concat.apply([], arr); }; var normalizeRoles = function(roles$1, includeAbstract) { return unique(roles$1 .map(r => aliases[r] || r) .filter(r => roles[r]) .filter(r => includeAbstract || !roles[r].abstract) ); }; // candidates can be passed for performance optimization const getRoleRaw = function(el, candidates) { // TODO: filter out any invalid roles (e.g. name or context required) const roles$1 = normalizeRoles( (el.getAttribute('role') || '').toLowerCase().split(/\s+/) ); if (roles$1.length > 1 && candidates) { return [roles$1, candidates]; } else if (roles$1.length) { for (const role of roles$1) { if (!candidates || candidates.includes(role)) { return role; } } } else { for (const role of (candidates || Object.keys(roles))) { const r = roles[role]; if (!r.abstract && r.selectors && el.matches(r.selectors.join(','))) { return role; } } } }; const getRole = function(el) { return getRoleRaw(el); }; const hasRole = function(el, roles$1) { const subRoles = normalizeRoles(roles$1, true).map(role => { return roles[role].subRoles || [role]; }); return !!getRoleRaw(el, unique(flatten(subRoles))); }; const getAttribute = function(el, key) { if (attributeStrongMapping.hasOwnProperty(key)) { const value = el[attributeStrongMapping[key]]; if (value) { return value; } } if (key === 'readonly' && el.contentEditable) { return false; } else if (key === 'invalid' && el.checkValidity) { return !el.checkValidity(); } else if (key === 'hidden') { // workaround for chromium if (el.matches('noscript')) { return true; } if (el.matches('details:not([open]) > :not(summary)')) { return true; } const style = window.getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse') { return true; } } const type = attributes[key]; const raw = el.getAttribute('aria-' + key); if (raw) { if (type === 'bool') { return raw === 'true'; } else if (type === 'tristate') { return raw === 'true' ? true : raw === 'false' ? false : 'mixed'; } else if (type === 'bool-undefined') { return raw === 'true' ? true : raw === 'false' ? false : undefined; } else if (type === 'id-list') { return raw.split(/\s+/); } else if (type === 'integer') { return parseInt(raw, 10); } else if (type === 'number') { return parseFloat(raw); } else if (type === 'token-list') { return raw.split(/\s+/); } else { return raw; } } // TODO // autocomplete // contextmenu -> aria-haspopup // indeterminate -> aria-checked="mixed" // list -> aria-controls if (key === 'level') { for (let i = 1; i <= 6; i++) { if (el.tagName.toLowerCase() === 'h' + i) { return i; } } } else if (attributeWeakMapping.hasOwnProperty(key)) { return el[attributeWeakMapping[key]]; } if (key in attrsWithDefaults) { const role = getRole(el); const defaults = roles[role].defaults; if (defaults && defaults.hasOwnProperty(key)) { return defaults[key]; } } if (type === 'bool' || type === 'tristate') { return false; } }; const _getOwner = function(node) { if (node.nodeType === node.ELEMENT_NODE && node.id) { const selector = `[aria-owns~="${CSS.escape(node.id)}"]`; return node.getRootNode().querySelector(selector); } }; const _getParentNode = function(node) { return _getOwner(node) || node.parentNode || node.host; }; const detectLoop = function(node) { const seen = [node]; while ((node = _getParentNode(node))) { if (seen.includes(node)) { return true; } seen.push(node); } }; const getOwner = function(node) { const owner = _getOwner(node); if (owner && !detectLoop(node)) { return owner; } }; const getParentNode = function(node) { return getOwner(node) || node.parentNode || node.host; }; const isHidden = function(node) { return node.nodeType === node.ELEMENT_NODE && getAttribute(node, 'hidden'); }; const getChildNodes = function(node) { const childNodes = []; let rawChildNodes = []; if (node.shadowRoot) { rawChildNodes = node.shadowRoot.childNodes; } else if (node.assignedNodes) { rawChildNodes = node.assignedNodes(); } else { rawChildNodes = node.childNodes; } for (let i = 0; i < rawChildNodes.length; i++) { const child = rawChildNodes[i]; if (!getOwner(child) && !isHidden(child)) { childNodes.push(child); } } if (node.nodeType === node.ELEMENT_NODE) { const owns = getAttribute(node, 'owns') || []; for (let i = 0; i < owns.length; i++) { const child = node.getRootNode().getElementById(owns[i]); // double check with getOwner for consistency if (child && getOwner(child) === node && !isHidden(child)) { childNodes.push(child); } } } return childNodes; }; const walk = function(root, fn) { let queue = [root]; while (queue.length) { const item = queue.shift(); fn(item); queue = getChildNodes(item).concat(queue); } }; const searchUp = function(node, test) { const candidate = getParentNode(node); if (candidate) { if (test(candidate)) { return candidate; } else { return searchUp(candidate, test); } } }; const matches = function(el, selector) { if (selector.substr(0, 1) === ':') { const attr = selector.substr(1); return getAttribute(el, attr); } else if (selector.substr(0, 1) === '[') { const match = /\[([a-z]+)="(.*)"\]/.exec(selector); const actual = getAttribute(el, match[1]); const rawValue = match[2]; return actual.toString() === rawValue; } else { return hasRole(el, selector.split(',')); } }; const _querySelector = function(all) { return function(root, selector) { const results = []; try { walk(root, node => { if (node.nodeType === node.ELEMENT_NODE) { // FIXME: skip hidden elements if (matches(node, selector)) { results.push(node); if (!all) { throw 'StopIteration'; } } } }); } catch (e) { if (e !== 'StopIteration') { throw e; } } return all ? results : results[0]; }; }; const closest = function(el, selector) { return searchUp(el, candidate => { if (candidate.nodeType === candidate.ELEMENT_NODE) { return matches(candidate, selector); } }); }; const querySelector = _querySelector(); const querySelectorAll = _querySelector(true); const addSpaces = function(text, el, pseudoSelector) { // https://github.com/w3c/accname/issues/3 const styles = window.getComputedStyle(el, pseudoSelector); const inline = styles.display === 'inline'; return inline ? text : ` ${text} `; }; const getPseudoContent = function(el, pseudoSelector) { const styles = window.getComputedStyle(el, pseudoSelector); let tail = styles.getPropertyValue('content').trim(); let ret = []; let match; while (tail.length) { if ((match = tail.match(/^"([^"]*)"/))) { ret.push(match[1]); } else if ((match = tail.match(/^([a-z-]+)\(([^)]*)\)/))) { if (match[1] === 'attr') { ret.push(el.getAttribute(match[2]) || ''); } } else if ((match = tail.match(/^([a-z-]+)/))) { if (match[1] === 'open-quote' || match[1] === 'close-quote') { ret.push('"'); } } else if ((match = tail.match(/^\//))) { ret = []; } else { // invalid content, ignore return ''; } tail = tail.slice(match[0].length).trim(); } return addSpaces(ret.join(''), el, pseudoSelector); }; const getContent = function(root, ongoingLabelledBy, visited) { const children = getChildNodes(root); let ret = ''; for (let i = 0; i < children.length; i++) { const node = children[i]; if (node.nodeType === node.TEXT_NODE) { if (node.parentElement) { const styles = window.getComputedStyle(node.parentElement); if (styles.textTransform === 'uppercase') { ret += node.textContent.toUpperCase(); } else if (styles.textTransform === 'lowercase') { ret += node.textContent.toLowerCase(); } else if (styles.textTransform === 'capitalize') { ret += node.textContent.replace(/\b\w/g, c => c.toUpperCase()); } else { ret += node.textContent; } } else { ret += node.textContent; } } else if (node.nodeType === node.ELEMENT_NODE) { if (node.tagName.toLowerCase() === 'br') { ret += '\n'; } else { ret += getNameRaw(node, true, ongoingLabelledBy, visited); } } } return ret; }; const allowNameFromContent = function(el) { const role = getRole(el); if (role) { return roles[role].nameFromContents; } }; const getNameRaw = function(el, recursive, ongoingLabelledBy, visited, directReference) { let ret = ''; visited = visited || []; if (visited.includes(el)) { if (!directReference) { return ''; } } else { visited.push(el); } // A // handled in atree // B if (!ongoingLabelledBy && el.matches('[aria-labelledby]')) { const ids = el.getAttribute('aria-labelledby').split(/\s+/); const strings = ids.map(id => { const label = el.getRootNode().getElementById(id); return label ? getNameRaw(label, true, true, visited, true) : ''; }); ret = strings.join(' '); } // E (the current draft has this at this high priority) if (!ret.trim() && recursive) { if (matches(el, 'textbox')) { ret = el.value || el.textContent; } else if (matches(el, 'combobox,listbox')) { const selected = querySelector(el, ':selected') || querySelector(el, 'option'); if (selected) { ret = getNameRaw(selected, recursive, ongoingLabelledBy, visited); } else { ret = el.value || ''; } } else if (matches(el, 'range')) { ret = '' + (getAttribute(el, 'valuetext') || getAttribute(el, 'valuenow') || el.value); } } // C if (!ret.trim() && el.matches('[aria-label]')) { // FIXME: may skip to 2E ret = el.getAttribute('aria-label'); } // D if (!ret.trim() && !recursive && el.labels) { const strings = Array.prototype.map.call(el.labels, label => { return getNameRaw(label, true, ongoingLabelledBy, visited); }); ret = strings.join(' '); } if (!ret.trim()) { ret = el.alt || ''; } if (!ret.trim() && el.matches('abbr,acronym') && el.title) { ret = el.title; } if (!ret.trim()) { for (const selector in nameFromDescendant) { if (el.matches(selector)) { const descendant = el.querySelector(nameFromDescendant[selector]); if (descendant) { ret = getNameRaw(descendant, true, ongoingLabelledBy, visited); } } } } if (!ret.trim() && el.matches('svg *')) { const svgTitle = el.querySelector('title'); if (svgTitle && svgTitle.parentElement === el) { ret = svgTitle.textContent; } } if (!ret.trim() && el.matches('a')) { ret = el.getAttribute('xlink:title') || ''; } // F // FIXME: menu is not mentioned in the spec if (!ret.trim() && (recursive || allowNameFromContent(el) || el.closest('label')) && !matches(el, 'menu')) { ret = getContent(el, ongoingLabelledBy, visited); } if (!ret.trim() && matches(el, 'button')) { ret = el.value || ''; } if (!ret.trim()) { for (const selector in nameDefaults) { if (el.matches(selector)) { ret = nameDefaults[selector]; } } } // G/H // handled in getContent // I if (!ret.trim()) { ret = el.title || el.placeholder || ''; } // FIXME: not exactly sure about this, but it reduces the number of failing // WPT tests. Whitespace is hard. if (!ret.trim()) { ret = ' '; } const before = getPseudoContent(el, ':before'); const after = getPseudoContent(el, ':after'); return addSpaces(before + ret + after, el); }; const getName = function(el) { return getNameRaw(el) .replace(/[ \n\r\t\f]+/g, ' ') .replace(/^ /, '') .replace(/ $/, ''); }; const getDescription = function(el) { let ret = ''; if (el.matches('[aria-describedby]')) { const ids = el.getAttribute('aria-describedby').split(/\s+/); const strings = ids.map(id => { const label = el.getRootNode().getElementById(id); return label ? getNameRaw(label, true, true) : ''; }); ret = strings.join(' '); } else if (el.matches('[aria-description]')) { ret = el.getAttribute('aria-description'); } else if (el.matches('svg *')) { const svgDesc = el.querySelector('desc'); if (svgDesc && svgDesc.parentElement === el) { ret = svgDesc.textContent; } } else if (el.title) { ret = el.title; } if (!ret.trim() && el.matches('a')) { ret = el.getAttribute('xlink:title') || ''; } ret = (ret || '').trim().replace(/\s+/g, ' '); if (ret === getName(el)) { ret = ''; } return ret; }; exports.closest = closest; exports.getAttribute = getAttribute; exports.getChildNodes = getChildNodes; exports.getDescription = getDescription; exports.getName = getName; exports.getParentNode = getParentNode; exports.getRole = getRole; exports.matches = matches; exports.querySelector = querySelector; exports.querySelectorAll = querySelectorAll; }));