Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/a11y/aria/aria.js
4386 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
8
/**
9
* @fileoverview Utilities for adding, removing and setting ARIA roles and
10
* states as defined by W3C ARIA standard: http://www.w3.org/TR/wai-aria/
11
* All modern browsers have some form of ARIA support, so no browser checks are
12
* performed when adding ARIA to components.
13
*/
14
15
goog.provide('goog.a11y.aria');
16
17
goog.require('goog.a11y.aria.Role');
18
goog.require('goog.a11y.aria.State');
19
goog.require('goog.a11y.aria.datatables');
20
goog.require('goog.array');
21
goog.require('goog.asserts');
22
goog.require('goog.dom');
23
goog.require('goog.dom.TagName');
24
goog.require('goog.object');
25
goog.require('goog.string');
26
27
28
/**
29
* ARIA states/properties prefix.
30
* @private
31
*/
32
goog.a11y.aria.ARIA_PREFIX_ = 'aria-';
33
34
35
/**
36
* ARIA role attribute.
37
* @private
38
*/
39
goog.a11y.aria.ROLE_ATTRIBUTE_ = 'role';
40
41
42
/**
43
* A list of tag names for which we don't need to set ARIA role and states
44
* because they have well supported semantics for screen readers or because
45
* they don't contain content to be made accessible.
46
* @private
47
*/
48
goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_ = goog.object.createSet([
49
goog.dom.TagName.A, goog.dom.TagName.AREA, goog.dom.TagName.BUTTON,
50
goog.dom.TagName.HEAD, goog.dom.TagName.INPUT, goog.dom.TagName.LINK,
51
goog.dom.TagName.MENU, goog.dom.TagName.META, goog.dom.TagName.OPTGROUP,
52
goog.dom.TagName.OPTION, goog.dom.TagName.PROGRESS, goog.dom.TagName.STYLE,
53
goog.dom.TagName.SELECT, goog.dom.TagName.SOURCE, goog.dom.TagName.TEXTAREA,
54
goog.dom.TagName.TITLE, goog.dom.TagName.TRACK
55
]);
56
57
58
/**
59
* A list of roles which are considered container roles.
60
* Container roles are ARIA roles which use the aria-activedescendant property
61
* to manage their active descendants or children. See
62
* {@link http://www.w3.org/TR/wai-aria/states_and_properties
63
* #aria-activedescendant} for more information.
64
* @private @const {!Array<goog.a11y.aria.Role>}
65
*/
66
goog.a11y.aria.CONTAINER_ROLES_ = [
67
goog.a11y.aria.Role.COMBOBOX, goog.a11y.aria.Role.GRID,
68
goog.a11y.aria.Role.GROUP, goog.a11y.aria.Role.LISTBOX,
69
goog.a11y.aria.Role.MENU, goog.a11y.aria.Role.MENUBAR,
70
goog.a11y.aria.Role.RADIOGROUP, goog.a11y.aria.Role.ROW,
71
goog.a11y.aria.Role.ROWGROUP, goog.a11y.aria.Role.TAB_LIST,
72
goog.a11y.aria.Role.TEXTBOX, goog.a11y.aria.Role.TOOLBAR,
73
goog.a11y.aria.Role.TREE, goog.a11y.aria.Role.TREEGRID
74
];
75
76
77
/**
78
* Sets the role of an element. If the roleName is
79
* empty string or null, the role for the element is removed.
80
* We encourage clients to call the goog.a11y.aria.removeRole
81
* method instead of setting null and empty string values.
82
* Special handling for this case is added to ensure
83
* backword compatibility with existing code.
84
*
85
* @param {!Element} element DOM node to set role of.
86
* @param {!goog.a11y.aria.Role|string} roleName role name(s).
87
*/
88
goog.a11y.aria.setRole = function(element, roleName) {
89
'use strict';
90
if (!roleName) {
91
// Setting the ARIA role to empty string is not allowed
92
// by the ARIA standard.
93
goog.a11y.aria.removeRole(element);
94
} else {
95
if (goog.asserts.ENABLE_ASSERTS) {
96
goog.asserts.assert(
97
goog.object.containsValue(goog.a11y.aria.Role, roleName),
98
'No such ARIA role ' + roleName);
99
}
100
element.setAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_, roleName);
101
}
102
};
103
104
105
/**
106
* Gets role of an element.
107
* @param {!Element} element DOM element to get role of.
108
* @return {?goog.a11y.aria.Role} ARIA Role name.
109
*/
110
goog.a11y.aria.getRole = function(element) {
111
'use strict';
112
var role = element.getAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_);
113
return /** @type {goog.a11y.aria.Role} */ (role) || null;
114
};
115
116
117
/**
118
* Removes role of an element.
119
* @param {!Element} element DOM element to remove the role from.
120
*/
121
goog.a11y.aria.removeRole = function(element) {
122
'use strict';
123
element.removeAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_);
124
};
125
126
127
/**
128
* Sets the state or property of an element.
129
* @param {!Element} element DOM node where we set state.
130
* @param {!(goog.a11y.aria.State|string)} stateName State attribute being set.
131
* Automatically adds prefix 'aria-' to the state name if the attribute is
132
* not an extra attribute.
133
* @param {string|boolean|number|!Array<string>} value Value
134
* for the state attribute.
135
*/
136
goog.a11y.aria.setState = function(element, stateName, value) {
137
'use strict';
138
if (Array.isArray(value)) {
139
value = value.join(' ');
140
}
141
var attrStateName = goog.a11y.aria.getAriaAttributeName_(stateName);
142
if (value === '' || value == undefined) {
143
var defaultValueMap = goog.a11y.aria.datatables.getDefaultValuesMap();
144
// Work around for browsers that don't properly support ARIA.
145
// According to the ARIA W3C standard, user agents should allow
146
// setting empty value which results in setting the default value
147
// for the ARIA state if such exists. The exact text from the ARIA W3C
148
// standard (http://www.w3.org/TR/wai-aria/states_and_properties):
149
// "When a value is indicated as the default, the user agent
150
// MUST follow the behavior prescribed by this value when the state or
151
// property is empty or undefined."
152
// The defaultValueMap contains the default values for the ARIA states
153
// and has as a key the goog.a11y.aria.State constant for the state.
154
if (stateName in defaultValueMap) {
155
element.setAttribute(attrStateName, defaultValueMap[stateName]);
156
} else {
157
element.removeAttribute(attrStateName);
158
}
159
} else {
160
element.setAttribute(attrStateName, value);
161
}
162
};
163
164
165
/**
166
* Toggles the ARIA attribute of an element.
167
* Meant for attributes with a true/false value, but works with any attribute.
168
* If the attribute does not have a true/false value, the following rules apply:
169
* A not empty attribute will be removed.
170
* An empty attribute will be set to true.
171
* @param {!Element} el DOM node for which to set attribute.
172
* @param {!(goog.a11y.aria.State|string)} attr ARIA attribute being set.
173
* Automatically adds prefix 'aria-' to the attribute name if the attribute
174
* is not an extra attribute.
175
*/
176
goog.a11y.aria.toggleState = function(el, attr) {
177
'use strict';
178
var val = goog.a11y.aria.getState(el, attr);
179
if (!goog.string.isEmptyOrWhitespace(goog.string.makeSafe(val)) &&
180
!(val == 'true' || val == 'false')) {
181
goog.a11y.aria.removeState(el, /** @type {!goog.a11y.aria.State} */ (attr));
182
return;
183
}
184
goog.a11y.aria.setState(el, attr, val == 'true' ? 'false' : 'true');
185
};
186
187
188
/**
189
* Remove the state or property for the element.
190
* @param {!Element} element DOM node where we set state.
191
* @param {!goog.a11y.aria.State} stateName State name.
192
*/
193
goog.a11y.aria.removeState = function(element, stateName) {
194
'use strict';
195
element.removeAttribute(goog.a11y.aria.getAriaAttributeName_(stateName));
196
};
197
198
199
/**
200
* Gets value of specified state or property.
201
* @param {!Element} element DOM node to get state from.
202
* @param {!goog.a11y.aria.State|string} stateName State name.
203
* @return {string} Value of the state attribute.
204
*/
205
goog.a11y.aria.getState = function(element, stateName) {
206
'use strict';
207
// TODO(user): return properly typed value result --
208
// boolean, number, string, null. We should be able to chain
209
// getState(...) and setState(...) methods.
210
211
var attr =
212
/** @type {string|number|boolean} */ (
213
element.getAttribute(
214
goog.a11y.aria.getAriaAttributeName_(stateName)));
215
var isNullOrUndefined = attr == null || attr == undefined;
216
return isNullOrUndefined ? '' : String(attr);
217
};
218
219
220
/**
221
* Returns the activedescendant element for the input element by
222
* using the activedescendant ARIA property of the given element.
223
* @param {!Element} element DOM node to get activedescendant
224
* element for.
225
* @return {?Element} DOM node of the activedescendant, if found.
226
*/
227
goog.a11y.aria.getActiveDescendant = function(element) {
228
'use strict';
229
var id =
230
goog.a11y.aria.getState(element, goog.a11y.aria.State.ACTIVEDESCENDANT);
231
return goog.dom.getOwnerDocument(element).getElementById(id);
232
};
233
234
235
/**
236
* Sets the activedescendant ARIA property value for an element.
237
* If the activeElement is not null, it should have an id set.
238
* @param {!Element} element DOM node to set activedescendant ARIA property to.
239
* @param {?Element} activeElement DOM node being set as activedescendant.
240
*/
241
goog.a11y.aria.setActiveDescendant = function(element, activeElement) {
242
'use strict';
243
var id = '';
244
if (activeElement) {
245
id = activeElement.id;
246
goog.asserts.assert(id, 'The active element should have an id.');
247
}
248
249
goog.a11y.aria.setState(element, goog.a11y.aria.State.ACTIVEDESCENDANT, id);
250
};
251
252
253
/**
254
* Gets the label of the given element.
255
* @param {!Element} element DOM node to get label from.
256
* @return {string} label The label.
257
*/
258
goog.a11y.aria.getLabel = function(element) {
259
'use strict';
260
return goog.a11y.aria.getState(element, goog.a11y.aria.State.LABEL);
261
};
262
263
264
/**
265
* Sets the label of the given element.
266
* @param {!Element} element DOM node to set label to.
267
* @param {string} label The label to set.
268
*/
269
goog.a11y.aria.setLabel = function(element, label) {
270
'use strict';
271
goog.a11y.aria.setState(element, goog.a11y.aria.State.LABEL, label);
272
};
273
274
275
/**
276
* Asserts that the element has a role set if it's not an HTML element whose
277
* semantics is well supported by most screen readers.
278
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
279
* @param {!Element} element The element to assert an ARIA role set.
280
* @param {!IArrayLike<string>} allowedRoles The child roles of
281
* the roles.
282
*/
283
goog.a11y.aria.assertRoleIsSetInternalUtil = function(element, allowedRoles) {
284
'use strict';
285
if (goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_[element.tagName]) {
286
return;
287
}
288
var elementRole = /** @type {string}*/ (goog.a11y.aria.getRole(element));
289
goog.asserts.assert(
290
elementRole != null, 'The element ARIA role cannot be null.');
291
292
goog.asserts.assert(
293
goog.array.contains(allowedRoles, elementRole),
294
'Non existing or incorrect role set for element.' +
295
'The role set is "' + elementRole + '". The role should be any of "' +
296
allowedRoles + '". Check the ARIA specification for more details ' +
297
'http://www.w3.org/TR/wai-aria/roles.');
298
};
299
300
301
/**
302
* Gets the boolean value of an ARIA state/property.
303
* @param {!Element} element The element to get the ARIA state for.
304
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
305
* @return {?boolean} Boolean value for the ARIA state value or null if
306
* the state value is not 'true', not 'false', or not set.
307
*/
308
goog.a11y.aria.getStateBoolean = function(element, stateName) {
309
'use strict';
310
var attr =
311
/** @type {string|boolean|null} */ (element.getAttribute(
312
goog.a11y.aria.getAriaAttributeName_(stateName)));
313
goog.asserts.assert(
314
typeof attr === 'boolean' || attr == null || attr == 'true' ||
315
attr == 'false');
316
if (attr == null) {
317
return attr;
318
}
319
return typeof attr === 'boolean' ? attr : attr == 'true';
320
};
321
322
323
/**
324
* Gets the number value of an ARIA state/property.
325
* @param {!Element} element The element to get the ARIA state for.
326
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
327
* @return {?number} Number value for the ARIA state value or null if
328
* the state value is not a number or not set.
329
*/
330
goog.a11y.aria.getStateNumber = function(element, stateName) {
331
'use strict';
332
var attr =
333
/** @type {string|number} */ (element.getAttribute(
334
goog.a11y.aria.getAriaAttributeName_(stateName)));
335
goog.asserts.assert(
336
(attr == null || !isNaN(Number(attr))) && typeof attr !== 'boolean');
337
return attr == null ? null : Number(attr);
338
};
339
340
341
/**
342
* Gets the string value of an ARIA state/property.
343
* @param {!Element} element The element to get the ARIA state for.
344
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
345
* @return {?string} String value for the ARIA state value or null if
346
* the state value is empty string or not set.
347
*/
348
goog.a11y.aria.getStateString = function(element, stateName) {
349
'use strict';
350
var attr =
351
element.getAttribute(goog.a11y.aria.getAriaAttributeName_(stateName));
352
goog.asserts.assert(
353
(attr == null || typeof attr === 'string') &&
354
(attr == '' || isNaN(Number(attr))) && attr != 'true' && attr != 'false');
355
return (attr == null || attr == '') ? null : attr;
356
};
357
358
359
/**
360
* Gets array of strings value of the specified state or
361
* property for the element.
362
* Only to be used internally by the ARIA library in goog.a11y.aria.*.
363
* @param {!Element} element DOM node to get state from.
364
* @param {!goog.a11y.aria.State} stateName State name.
365
* @return {!IArrayLike<string>} string Array
366
* value of the state attribute.
367
*/
368
goog.a11y.aria.getStringArrayStateInternalUtil = function(element, stateName) {
369
'use strict';
370
var attrValue =
371
element.getAttribute(goog.a11y.aria.getAriaAttributeName_(stateName));
372
return goog.a11y.aria.splitStringOnWhitespace_(attrValue);
373
};
374
375
376
/**
377
* Returns true if element has an ARIA state/property, false otherwise.
378
* @param {!Element} element The element to get the ARIA state for.
379
* @param {!goog.a11y.aria.State|string} stateName the ARIA state name.
380
* @return {boolean}
381
*/
382
goog.a11y.aria.hasState = function(element, stateName) {
383
'use strict';
384
return element.hasAttribute(goog.a11y.aria.getAriaAttributeName_(stateName));
385
};
386
387
388
/**
389
* Returns whether the element has a container ARIA role.
390
* Container roles are ARIA roles that use the aria-activedescendant property
391
* to manage their active descendants or children. See
392
* {@link http://www.w3.org/TR/wai-aria/states_and_properties
393
* #aria-activedescendant} for more information.
394
* @param {!Element} element
395
* @return {boolean}
396
*/
397
goog.a11y.aria.isContainerRole = function(element) {
398
'use strict';
399
var role = goog.a11y.aria.getRole(element);
400
return goog.array.contains(goog.a11y.aria.CONTAINER_ROLES_, role);
401
};
402
403
404
/**
405
* Splits the input stringValue on whitespace.
406
* @param {string} stringValue The value of the string to split.
407
* @return {!IArrayLike<string>} string Array
408
* value as result of the split.
409
* @private
410
*/
411
goog.a11y.aria.splitStringOnWhitespace_ = function(stringValue) {
412
'use strict';
413
return stringValue ? stringValue.split(/\s+/) : [];
414
};
415
416
417
/**
418
* Adds the 'aria-' prefix to ariaName.
419
* @param {string} ariaName ARIA state/property name.
420
* @private
421
* @return {string} The ARIA attribute name with added 'aria-' prefix.
422
* @throws {Error} If no such attribute exists.
423
*/
424
goog.a11y.aria.getAriaAttributeName_ = function(ariaName) {
425
'use strict';
426
if (goog.asserts.ENABLE_ASSERTS) {
427
goog.asserts.assert(ariaName, 'ARIA attribute cannot be empty.');
428
goog.asserts.assert(
429
goog.object.containsValue(goog.a11y.aria.State, ariaName),
430
'No such ARIA attribute ' + ariaName);
431
}
432
return goog.a11y.aria.ARIA_PREFIX_ + ariaName;
433
};
434
435