aria-api

access ARIA information from JavaScript
git clone https://git.ce9e.org/aria-api.git

commit
59c4ae58633197735b9549f49ffed1778f0ecbd9
parent
50669758a836cd75e57ebb586d33c79f5fa7ec1b
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-02-04 14:29
fix: rework getRole

- ignore invalid roles
- ignore abstract roles
- fall back to implicit role if all explicit roles are invalid

Diffstat

M lib/attrs.js 38 ++++++++++++++++++++------------------
M lib/constants.js 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
M lib/name.js 4 +++-

3 files changed, 82 insertions, 20 deletions


diff --git a/lib/attrs.js b/lib/attrs.js

@@ -8,40 +8,42 @@ var flatten = function(arr) {
    8     8 	return [].concat.apply([], arr);
    9     9 };
   10    10 
   11    -1 var normalizeRoles = function(roles) {
   12    -1 	return unique(roles.map(r => constants.aliases[r] || r));
   -1    11 var normalizeRoles = function(roles, includeAbstract) {
   -1    12 	return unique(roles
   -1    13 		.map(r => constants.aliases[r] || r)
   -1    14 		.filter(r => constants.roles[r])
   -1    15 		.filter(r => includeAbstract || !constants.roles[r].abstract)
   -1    16 	);
   13    17 };
   14    18 
   15    19 // candidates can be passed for performance optimization
   16    20 const getRole = function(el, candidates) {
   17    -1 	if (el.hasAttribute('role')) {
   18    -1 		let roles = el.getAttribute('role').toLowerCase().split(/\s+/);
   19    -1 		roles = normalizeRoles(roles);
   20    -1 		if (roles.length > 1 && candidates) {
   21    -1 			return [roles, candidates];
   22    -1 		}
   -1    21 	// TODO: filter out any invalid roles (e.g. name or context required)
   -1    22 	const roles = normalizeRoles(
   -1    23 		(el.getAttribute('role') || '').toLowerCase().split(/\s+/)
   -1    24 	);
   -1    25 
   -1    26 	if (roles.length > 1 && candidates) {
   -1    27 		return [roles, candidates];
   -1    28 	} else if (roles.length) {
   23    29 		for (const role of roles) {
   24    30 			if (!candidates || candidates.includes(role)) {
   25    31 				return role;
   26    32 			}
   27    33 		}
   28    34 	} else {
   29    -1 		const roles = candidates || Object.keys(constants.roles);
   30    -1 		for (const role of roles) {
   -1    35 		for (const role of (candidates || Object.keys(constants.roles))) {
   31    36 			const r = constants.roles[role];
   32    -1 			if (r) {
   33    -1 				const selector = (r.selectors || []).join(',');
   34    -1 				if (selector && el.matches(selector)) {
   35    -1 					return role;
   36    -1 				}
   -1    37 			if (!r.abstract && r.selectors && el.matches(r.selectors.join(','))) {
   -1    38 				return role;
   37    39 			}
   38    40 		}
   39    41 	}
   40    42 };
   41    43 
   42    44 const hasRole = function(el, roles) {
   43    -1 	const subRoles = normalizeRoles(roles).map(role => {
   44    -1 		return (constants.roles[role] || {}).subRoles || [role];
   -1    45 	const subRoles = normalizeRoles(roles, true).map(role => {
   -1    46 		return constants.roles[role].subRoles || [role];
   45    47 	});
   46    48 	return !!getRole(el, unique(flatten(subRoles)));
   47    49 };
@@ -112,7 +114,7 @@ const getAttribute = function(el, key) {
  112   114 
  113   115 	if (key in constants.attrsWithDefaults) {
  114   116 		const role = getRole(el);
  115    -1 		const defaults = (constants.roles[role] || {}).defaults;
   -1   117 		const defaults = constants.roles[role].defaults;
  116   118 		if (defaults && defaults.hasOwnProperty(key)) {
  117   119 			return defaults[key];
  118   120 		}

diff --git a/lib/constants.js b/lib/constants.js

@@ -79,6 +79,8 @@ exports.roles = {
   79    79 			'atomic': true,
   80    80 		},
   81    81 	},
   -1    82 	alertdialog: {},
   -1    83 	application: {},
   82    84 	article: {
   83    85 		selectors: ['article'],
   84    86 	},
@@ -140,6 +142,7 @@ exports.roles = {
  140   142 		},
  141   143 	},
  142   144 	command: {
   -1   145 		abstract: true,
  143   146 		childRoles: ['button', 'link', 'menuitem'],
  144   147 	},
  145   148 	complementary: {
@@ -151,6 +154,7 @@ exports.roles = {
  151   154 		],
  152   155 	},
  153   156 	composite: {
   -1   157 		abstract: true,
  154   158 		childRoles: ['grid', 'select', 'spinbutton', 'tablist'],
  155   159 	},
  156   160 	contentinfo: {
@@ -166,24 +170,60 @@ exports.roles = {
  166   170 		selectors: ['dialog'],
  167   171 		childRoles: ['alertdialog'],
  168   172 	},
   -1   173 	directory: {},
   -1   174 	'doc-abstract': {},
   -1   175 	'doc-acknowledgments': {},
   -1   176 	'doc-afterword': {},
   -1   177 	'doc-appendix': {},
  169   178 	'doc-backlink': {
  170   179 		nameFromContents: true,
  171   180 	},
   -1   181 	'doc-biblioentry': {},
   -1   182 	'doc-bibliography': {},
  172   183 	'doc-biblioref': {
  173   184 		nameFromContents: true,
  174   185 	},
   -1   186 	'doc-chapter': {},
   -1   187 	'doc-colophon': {},
   -1   188 	'doc-conclusion': {},
   -1   189 	'doc-cover': {},
   -1   190 	'doc-credit': {},
   -1   191 	'doc-credits': {},
   -1   192 	'doc-dedication': {},
   -1   193 	'doc-endnote': {},
   -1   194 	'doc-endnotes': {},
   -1   195 	'doc-epilogue': {},
   -1   196 	'doc-epigraph': {},
   -1   197 	'doc-errata': {},
   -1   198 	'doc-example': {},
   -1   199 	'doc-footnote': {},
   -1   200 	'doc-foreword': {},
   -1   201 	'doc-glossary': {},
  175   202 	'doc-glossref': {
  176   203 		nameFromContents: true,
  177   204 	},
   -1   205 	'doc-index': {},
   -1   206 	'doc-introduction': {},
  178   207 	'doc-noteref': {
  179   208 		nameFromContents: true,
  180   209 	},
   -1   210 	'doc-notice': {},
  181   211 	'doc-pagebreak': {
  182   212 		nameFromContents: true,
  183   213 	},
   -1   214 	'doc-pagefooter': {},
   -1   215 	'doc-pageheader': {},
   -1   216 	'doc-pagelist': {},
   -1   217 	'doc-part': {},
   -1   218 	'doc-preface': {},
   -1   219 	'doc-prologue': {},
   -1   220 	'doc-pullquote': {},
   -1   221 	'doc-qna': {},
  184   222 	'doc-subtitle': {
  185   223 		nameFromContents: true,
  186   224 	},
   -1   225 	'doc-tip': {},
   -1   226 	'doc-toc': {},
  187   227 	document: {
  188   228 		selectors: ['html'],
  189   229 		childRoles: ['article', 'graphics-document'],
@@ -191,6 +231,7 @@ exports.roles = {
  191   231 	emphasis: {
  192   232 		selectors: ['em'],
  193   233 	},
   -1   234 	feed: {},
  194   235 	figure: {
  195   236 		selectors: ['figure'],
  196   237 		childRoles: ['doc-example'],
@@ -225,6 +266,8 @@ exports.roles = {
  225   266 	'graphics-document': {
  226   267 		selectors: ['svg'],
  227   268 	},
   -1   269 	'graphics-object': {},
   -1   270 	'graphics-symbol': {},
  228   271 	grid: {
  229   272 		childRoles: ['treegrid'],
  230   273 	},
@@ -255,6 +298,7 @@ exports.roles = {
  255   298 		childRoles: ['doc-cover'],
  256   299 	},
  257   300 	input: {
   -1   301 		abstract: true,
  258   302 		childRoles: [
  259   303 			'checkbox',
  260   304 			'combobox',
@@ -269,6 +313,7 @@ exports.roles = {
  269   313 		selectors: ['ins'],
  270   314 	},
  271   315 	landmark: {
   -1   316 		abstract: true,
  272   317 		childRoles: [
  273   318 			'banner',
  274   319 			'complementary',
@@ -327,6 +372,7 @@ exports.roles = {
  327   372 	main: {
  328   373 		selectors: ['main'],
  329   374 	},
   -1   375 	marquee: {},
  330   376 	math: {
  331   377 		selectors: ['math'],
  332   378 	},
@@ -401,13 +447,16 @@ exports.roles = {
  401   447 			'checked': 'false',
  402   448 		},
  403   449 	},
   -1   450 	radiogroup: {},
  404   451 	range: {
   -1   452 		abstract: true,
  405   453 		childRoles: ['meter', 'progressbar', 'scrollbar', 'slider', 'spinbutton'],
  406   454 	},
  407   455 	region: {
  408   456 		selectors: ['section[aria-label]', 'section[aria-labelledby]', 'section[title]'],
  409   457 	},
  410   458 	roletype: {
   -1   459 		abstract: true,
  411   460 		childRoles: ['structure', 'widget', 'window'],
  412   461 	},
  413   462 	row: {
@@ -435,6 +484,7 @@ exports.roles = {
  435   484 		selectors: ['input[type="search"]:not([list])'],
  436   485 	},
  437   486 	section: {
   -1   487 		abstract: true,
  438   488 		childRoles: [
  439   489 			'alert',
  440   490 			'blockquote',
@@ -478,6 +528,7 @@ exports.roles = {
  478   528 		],
  479   529 	},
  480   530 	sectionhead: {
   -1   531 		abstract: true,
  481   532 		childRoles: [
  482   533 			'columnheader',
  483   534 			'doc-subtitle',
@@ -488,6 +539,7 @@ exports.roles = {
  488   539 		nameFromContents: true,
  489   540 	},
  490   541 	select: {
   -1   542 		abstract: true,
  491   543 		childRoles: ['listbox', 'menu', 'radiogroup', 'tree'],
  492   544 	},
  493   545 	separator: {
@@ -528,6 +580,7 @@ exports.roles = {
  528   580 		selectors: ['strong'],
  529   581 	},
  530   582 	structure: {
   -1   583 		abstract: true,
  531   584 		childRoles: [
  532   585 			'application',
  533   586 			'document',
@@ -540,6 +593,7 @@ exports.roles = {
  540   593 			'separator',
  541   594 		],
  542   595 	},
   -1   596 	suggestion: {},
  543   597 	subscript: {
  544   598 		selectors: ['sub'],
  545   599 	},
@@ -567,6 +621,7 @@ exports.roles = {
  567   621 			'orientation': 'horizontal',
  568   622 		},
  569   623 	},
   -1   624 	tabpanel: {},
  570   625 	term: {
  571   626 		selectors: ['dfn', 'dt'],
  572   627 	},
@@ -603,10 +658,12 @@ exports.roles = {
  603   658 			'orientation': 'vertical',
  604   659 		},
  605   660 	},
   -1   661 	treegrid: {},
  606   662 	treeitem: {
  607   663 		nameFromContents: true,
  608   664 	},
  609   665 	widget: {
   -1   666 		abstract: true,
  610   667 		childRoles: [
  611   668 			'command',
  612   669 			'composite',
@@ -620,12 +677,13 @@ exports.roles = {
  620   677 		],
  621   678 	},
  622   679 	window: {
   -1   680 		abstract: true,
  623   681 		childRoles: ['dialog'],
  624   682 	},
  625   683 };
  626   684 
  627   685 const getSubRoles = function(role) {
  628    -1 	const children = (exports.roles[role] || {}).childRoles || [];
   -1   686 	const children = (exports.roles[role]).childRoles || [];
  629   687 	const descendents = children.map(getSubRoles);
  630   688 
  631   689 	const result = [role];

diff --git a/lib/name.js b/lib/name.js

@@ -46,7 +46,9 @@ const getContent = function(root, visited) {
   46    46 
   47    47 const allowNameFromContent = function(el) {
   48    48 	const role = query.getRole(el);
   49    -1 	return (constants.roles[role] || {}).nameFromContents;
   -1    49 	if (role) {
   -1    50 		return constants.roles[role].nameFromContents;
   -1    51 	}
   50    52 };
   51    53 
   52    54 const getName = function(el, recursive, visited, directReference) {