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