1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview A collection of JavaScript utilities used to simplify working
7 * with ARIA (http://www.w3.org/TR/wai-aria).
11 goog
.provide('cvox.AriaUtil');
12 goog
.require('cvox.AbstractEarcons');
13 goog
.require('cvox.ChromeVox');
14 goog
.require('cvox.NodeState');
15 goog
.require('cvox.NodeStateUtil');
19 * Create the namespace
22 cvox
.AriaUtil = function() {
27 * A mapping from ARIA role names to their message ids.
28 * Note: If you are adding a new mapping, the new message identifier needs a
29 * corresponding braille message. For example, a message id 'tag_button'
30 * requires another message 'tag_button_brl' within messages.js.
31 * @type {Object<string>}
33 cvox
.AriaUtil
.WIDGET_ROLE_TO_NAME
= {
34 'alert' : 'aria_role_alert',
35 'alertdialog' : 'aria_role_alertdialog',
36 'button' : 'aria_role_button',
37 'checkbox' : 'aria_role_checkbox',
38 'columnheader' : 'aria_role_columnheader',
39 'combobox' : 'aria_role_combobox',
40 'dialog' : 'aria_role_dialog',
41 'grid' : 'aria_role_grid',
42 'gridcell' : 'aria_role_gridcell',
43 'link' : 'aria_role_link',
44 'listbox' : 'aria_role_listbox',
45 'log' : 'aria_role_log',
46 'marquee' : 'aria_role_marquee',
47 'menu' : 'aria_role_menu',
48 'menubar' : 'aria_role_menubar',
49 'menuitem' : 'aria_role_menuitem',
50 'menuitemcheckbox' : 'aria_role_menuitemcheckbox',
51 'menuitemradio' : 'aria_role_menuitemradio',
52 'option' : 'aria_role_option',
53 'progressbar' : 'aria_role_progressbar',
54 'radio' : 'aria_role_radio',
55 'radiogroup' : 'aria_role_radiogroup',
56 'rowheader' : 'aria_role_rowheader',
57 'scrollbar' : 'aria_role_scrollbar',
58 'slider' : 'aria_role_slider',
59 'spinbutton' : 'aria_role_spinbutton',
60 'status' : 'aria_role_status',
61 'tab' : 'aria_role_tab',
62 'tablist' : 'aria_role_tablist',
63 'tabpanel' : 'aria_role_tabpanel',
64 'textbox' : 'aria_role_textbox',
65 'timer' : 'aria_role_timer',
66 'toolbar' : 'aria_role_toolbar',
67 'tooltip' : 'aria_role_tooltip',
68 'treeitem' : 'aria_role_treeitem'
73 * Note: If you are adding a new mapping, the new message identifier needs a
74 * corresponding braille message. For example, a message id 'tag_button'
75 * requires another message 'tag_button_brl' within messages.js.
76 * @type {Object<string>}
78 cvox
.AriaUtil
.STRUCTURE_ROLE_TO_NAME
= {
79 'article' : 'aria_role_article',
80 'application' : 'aria_role_application',
81 'banner' : 'aria_role_banner',
82 'columnheader' : 'aria_role_columnheader',
83 'complementary' : 'aria_role_complementary',
84 'contentinfo' : 'aria_role_contentinfo',
85 'definition' : 'aria_role_definition',
86 'directory' : 'aria_role_directory',
87 'document' : 'aria_role_document',
88 'form' : 'aria_role_form',
89 'group' : 'aria_role_group',
90 'heading' : 'aria_role_heading',
91 'img' : 'aria_role_img',
92 'list' : 'aria_role_list',
93 'listitem' : 'aria_role_listitem',
94 'main' : 'aria_role_main',
95 'math' : 'aria_role_math',
96 'navigation' : 'aria_role_navigation',
97 'note' : 'aria_role_note',
98 'region' : 'aria_role_region',
99 'rowheader' : 'aria_role_rowheader',
100 'search' : 'aria_role_search',
101 'separator' : 'aria_role_separator'
106 * @type {Array<Object>}
108 cvox
.AriaUtil
.ATTRIBUTE_VALUE_TO_STATUS
= [
109 { name
: 'aria-autocomplete', values
:
110 {'inline' : 'aria_autocomplete_inline',
111 'list' : 'aria_autocomplete_list',
112 'both' : 'aria_autocomplete_both'} },
113 { name
: 'aria-checked', values
:
114 {'true' : 'aria_checked_true',
115 'false' : 'aria_checked_false',
116 'mixed' : 'aria_checked_mixed'} },
117 { name
: 'aria-disabled', values
:
118 {'true' : 'aria_disabled_true'} },
119 { name
: 'aria-expanded', values
:
120 {'true' : 'aria_expanded_true',
121 'false' : 'aria_expanded_false'} },
122 { name
: 'aria-invalid', values
:
123 {'true' : 'aria_invalid_true',
124 'grammar' : 'aria_invalid_grammar',
125 'spelling' : 'aria_invalid_spelling'} },
126 { name
: 'aria-multiline', values
:
127 {'true' : 'aria_multiline_true'} },
128 { name
: 'aria-multiselectable', values
:
129 {'true' : 'aria_multiselectable_true'} },
130 { name
: 'aria-pressed', values
:
131 {'true' : 'aria_pressed_true',
132 'false' : 'aria_pressed_false',
133 'mixed' : 'aria_pressed_mixed'} },
134 { name
: 'aria-readonly', values
:
135 {'true' : 'aria_readonly_true'} },
136 { name
: 'aria-required', values
:
137 {'true' : 'aria_required_true'} },
138 { name
: 'aria-selected', values
:
139 {'true' : 'aria_selected_true',
140 'false' : 'aria_selected_false'} }
145 * Checks if a node should be treated as a hidden node because of its ARIA
148 * @param {Node} targetNode The node to check.
149 * @return {boolean} True if the targetNode should be treated as hidden.
151 cvox
.AriaUtil
.isHiddenRecursive = function(targetNode
) {
152 if (cvox
.AriaUtil
.isHidden(targetNode
)) {
155 var parent
= targetNode
.parentElement
;
157 if ((parent
.getAttribute('aria-hidden') == 'true') &&
158 (parent
.getAttribute('chromevoxignoreariahidden') != 'true')) {
161 parent
= parent
.parentElement
;
168 * Checks if a node should be treated as a hidden node because of its ARIA
169 * markup. Does not check parents, so if you need to know if this is a
170 * descendant of a hidden node, call isHiddenRecursive.
172 * @param {Node} targetNode The node to check.
173 * @return {boolean} True if the targetNode should be treated as hidden.
175 cvox
.AriaUtil
.isHidden = function(targetNode
) {
179 if (targetNode
.getAttribute
) {
180 if ((targetNode
.getAttribute('aria-hidden') == 'true') &&
181 (targetNode
.getAttribute('chromevoxignoreariahidden') != 'true')) {
190 * Checks if a node should be treated as a visible node because of its ARIA
191 * markup, regardless of whatever other styling/attributes it may have.
192 * It is possible to force a node to be visible by setting aria-hidden to
195 * @param {Node} targetNode The node to check.
196 * @return {boolean} True if the targetNode should be treated as visible.
198 cvox
.AriaUtil
.isForcedVisibleRecursive = function(targetNode
) {
199 var node
= targetNode
;
201 if (node
.getAttribute
) {
202 // Stop and return the result based on the closest node that has
204 if (node
.hasAttribute('aria-hidden') &&
205 (node
.getAttribute('chromevoxignoreariahidden') != 'true')) {
206 return node
.getAttribute('aria-hidden') == 'false';
209 node
= node
.parentElement
;
216 * Checks if a node should be treated as a leaf node because of its ARIA
217 * markup. Does not check recursively, and does not check isControlWidget.
218 * Note that elements with aria-label are treated as leaf elements. See:
219 * http://www.w3.org/TR/wai-aria/roles#textalternativecomputation
221 * @param {Element} targetElement The node to check.
222 * @return {boolean} True if the targetNode should be treated as a leaf node.
224 cvox
.AriaUtil
.isLeafElement = function(targetElement
) {
225 var role
= targetElement
.getAttribute('role');
226 var hasArialLabel
= targetElement
.hasAttribute('aria-label') &&
227 (targetElement
.getAttribute('aria-label').length
> 0);
228 return (role
== 'img' || role
== 'progressbar' || hasArialLabel
);
233 * Determines whether or not a node is or is the descendant of a node
234 * with a particular role.
236 * @param {Node} node The node to be checked.
237 * @param {string} roleName The role to check for.
238 * @return {boolean} True if the node or one of its ancestor has the specified
241 cvox
.AriaUtil
.isDescendantOfRole = function(node
, roleName
) {
243 if (roleName
&& node
&& (node
.getAttribute('role') == roleName
)) {
246 node
= node
.parentNode
;
253 * Helper function to return the role name message identifier for a role.
254 * @param {string} role The role.
255 * @return {?string} The role name message identifier.
258 cvox
.AriaUtil
.getRoleNameMsgForRole_ = function(role
) {
259 var msgId
= cvox
.AriaUtil
.WIDGET_ROLE_TO_NAME
[role
];
267 * Returns true is the node is any kind of button.
269 * @param {Node} node The node to check.
270 * @return {boolean} True if the node is a button.
272 cvox
.AriaUtil
.isButton = function(node
) {
273 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
274 if (role
== 'button') {
277 if (node
.tagName
== 'BUTTON') {
280 if (node
.tagName
== 'INPUT') {
281 return (node
.type
== 'submit' ||
282 node
.type
== 'reset' ||
283 node
.type
== 'button');
289 * Returns a role message identifier for a node.
290 * For a localized string, see cvox.AriaUtil.getRoleName.
291 * @param {Node} targetNode The node to get the role name for.
292 * @return {string} The role name message identifier for the targetNode.
294 cvox
.AriaUtil
.getRoleNameMsg = function(targetNode
) {
296 if (targetNode
&& targetNode
.getAttribute
) {
297 var role
= cvox
.AriaUtil
.getRoleAttribute(targetNode
);
299 // Special case for pop-up buttons.
300 if (targetNode
.getAttribute('aria-haspopup') == 'true' &&
301 cvox
.AriaUtil
.isButton(targetNode
)) {
302 return 'aria_role_popup_button';
306 roleName
= cvox
.AriaUtil
.getRoleNameMsgForRole_(role
);
308 roleName
= cvox
.AriaUtil
.STRUCTURE_ROLE_TO_NAME
[role
];
312 // To a user, a menu item within a menu bar is called a "menu";
313 // any other menu item is called a "menu item".
315 // TODO(deboer): This block feels like a hack. dmazzoni suggests
316 // using css-like syntax for names. Investigate further if
317 // we need more of these hacks.
318 if (role
== 'menuitem') {
319 var container
= targetNode
.parentElement
;
321 if (container
.getAttribute
&&
322 (cvox
.AriaUtil
.getRoleAttribute(container
) == 'menu' ||
323 cvox
.AriaUtil
.getRoleAttribute(container
) == 'menubar')) {
326 container
= container
.parentElement
;
328 if (container
&& cvox
.AriaUtil
.getRoleAttribute(container
) == 'menubar') {
329 roleName
= cvox
.AriaUtil
.getRoleNameMsgForRole_('menu');
330 } // else roleName is already 'Menu item', no need to change it.
340 * Returns a string to be presented to the user that identifies what the
341 * targetNode's role is.
343 * @param {Node} targetNode The node to get the role name for.
344 * @return {string} The role name for the targetNode.
346 cvox
.AriaUtil
.getRoleName = function(targetNode
) {
347 var roleMsg
= cvox
.AriaUtil
.getRoleNameMsg(targetNode
);
348 var roleName
= cvox
.ChromeVox
.msgs
.getMsg(roleMsg
);
349 var role
= cvox
.AriaUtil
.getRoleAttribute(targetNode
);
350 if ((role
== 'heading') && (targetNode
.hasAttribute('aria-level'))) {
351 roleName
+= ' ' + targetNode
.getAttribute('aria-level');
353 return roleName
? roleName
: '';
357 * Returns a string that gives information about the state of the targetNode.
359 * @param {Node} targetNode The node to get the state information for.
360 * @param {boolean} primary Whether this is the primary node we're
361 * interested in, where we might want extra information - as
362 * opposed to an ancestor, where we might be more brief.
363 * @return {cvox.NodeState} The status information about the node.
365 cvox
.AriaUtil
.getStateMsgs = function(targetNode
, primary
) {
367 if (!targetNode
|| !targetNode
.getAttribute
) {
371 for (var i
= 0, attr
; attr
= cvox
.AriaUtil
.ATTRIBUTE_VALUE_TO_STATUS
[i
];
373 var value
= targetNode
.getAttribute(attr
.name
);
374 var msgId
= attr
.values
[value
];
379 if (targetNode
.getAttribute('role') == 'grid') {
380 return cvox
.AriaUtil
.getGridState_(targetNode
, targetNode
);
383 var role
= cvox
.AriaUtil
.getRoleAttribute(targetNode
);
384 if (targetNode
.getAttribute('aria-haspopup') == 'true') {
385 if (role
== 'menuitem') {
386 state
.push(['has_submenu']);
387 } else if (cvox
.AriaUtil
.isButton(targetNode
)) {
388 // Do nothing - the role name will be 'pop-up button'.
390 state
.push(['has_popup']);
394 var valueText
= targetNode
.getAttribute('aria-valuetext');
396 // If there is a valueText, that always wins.
397 state
.push(['aria_value_text', valueText
]);
401 var valueNow
= targetNode
.getAttribute('aria-valuenow');
402 var valueMin
= targetNode
.getAttribute('aria-valuemin');
403 var valueMax
= targetNode
.getAttribute('aria-valuemax');
405 // Scrollbar and progressbar should speak the percentage.
406 // http://www.w3.org/TR/wai-aria/roles#scrollbar
407 // http://www.w3.org/TR/wai-aria/roles#progressbar
408 if ((valueNow
!= null) && (valueMin
!= null) && (valueMax
!= null)) {
409 if ((role
== 'scrollbar') || (role
== 'progressbar')) {
410 var percent
= Math
.round((valueNow
/ (valueMax
- valueMin
)) * 100);
411 state
.push(['state_percent', percent
]);
416 // Return as many of the value attributes as possible.
417 if (valueNow
!= null) {
418 state
.push(['aria_value_now', valueNow
]);
420 if (valueMin
!= null) {
421 state
.push(['aria_value_min', valueMin
]);
423 if (valueMax
!= null) {
424 state
.push(['aria_value_max', valueMax
]);
427 // If this is a composite control or an item within a composite control,
428 // get the index and count of the current descendant or active
430 var parentControl
= targetNode
;
431 var currentDescendant
= null;
433 if (cvox
.AriaUtil
.isCompositeControl(parentControl
) && primary
) {
434 currentDescendant
= cvox
.AriaUtil
.getActiveDescendant(parentControl
);
436 role
= cvox
.AriaUtil
.getRoleAttribute(targetNode
);
437 if (role
== 'option' ||
438 role
== 'menuitem' ||
439 role
== 'menuitemcheckbox' ||
440 role
== 'menuitemradio' ||
443 role
== 'treeitem') {
444 currentDescendant
= targetNode
;
445 parentControl
= targetNode
.parentElement
;
446 while (parentControl
&&
447 !cvox
.AriaUtil
.isCompositeControl(parentControl
)) {
448 parentControl
= parentControl
.parentElement
;
450 cvox
.AriaUtil
.getRoleAttribute(parentControl
) == 'treeitem') {
458 (cvox
.AriaUtil
.isCompositeControl(parentControl
) ||
459 cvox
.AriaUtil
.getRoleAttribute(parentControl
) == 'treeitem') &&
461 var parentRole
= cvox
.AriaUtil
.getRoleAttribute(parentControl
);
462 var descendantRoleList
;
463 switch (parentRole
) {
466 descendantRoleList
= ['option'];
469 descendantRoleList
= ['menuitem',
474 descendantRoleList
= ['radio'];
477 descendantRoleList
= ['tab'];
482 descendantRoleList
= ['treeitem'];
486 if (descendantRoleList
) {
491 parseInt(currentDescendant
.getAttribute('aria-setsize'), 10);
492 if (!isNaN(ariaLength
)) {
493 listLength
= ariaLength
;
496 parseInt(currentDescendant
.getAttribute('aria-posinset'), 10);
497 if (!isNaN(ariaIndex
)) {
498 currentIndex
= ariaIndex
;
501 if (listLength
== undefined || currentIndex
== undefined) {
502 var descendants
= cvox
.AriaUtil
.getNextLevel(parentControl
,
504 if (listLength
== undefined) {
505 listLength
= descendants
.length
;
507 if (currentIndex
== undefined) {
508 for (var j
= 0; j
< descendants
.length
; j
++) {
509 if (descendants
[j
] == currentDescendant
) {
510 currentIndex
= j
+ 1;
515 if (currentIndex
&& listLength
) {
516 state
.push(['list_position', currentIndex
, listLength
]);
525 * Returns a string that gives information about the state of the grid node.
527 * @param {Node} targetNode The node to get the state information for.
528 * @param {Node} parentControl The parent composite control.
529 * @return {cvox.NodeState} The status information about the node.
532 cvox
.AriaUtil
.getGridState_ = function(targetNode
, parentControl
) {
533 var activeDescendant
= cvox
.AriaUtil
.getActiveDescendant(parentControl
);
535 if (activeDescendant
) {
536 var descendantSelector
= '*[role~="row"]';
537 var rows
= parentControl
.querySelectorAll(descendantSelector
);
538 var currentIndex
= null;
539 for (var j
= 0; j
< rows
.length
; j
++) {
540 var gridcells
= rows
[j
].querySelectorAll('*[role~="gridcell"]');
541 for (var k
= 0; k
< gridcells
.length
; k
++) {
542 if (gridcells
[k
] == activeDescendant
) {
543 return /** @type {cvox.NodeState} */ (
544 [['aria_role_gridcell_pos', j
+ 1, k
+ 1]]);
554 * Returns the id of a node's active descendant
555 * @param {Node} targetNode The node.
556 * @return {?string} The id of the active descendant.
559 cvox
.AriaUtil
.getActiveDescendantId_ = function(targetNode
) {
560 if (!targetNode
.getAttribute
) {
564 var activeId
= targetNode
.getAttribute('aria-activedescendant');
573 * Returns the list of elements that are one aria-level below.
575 * @param {Node} parentControl The node whose descendants should be analyzed.
576 * @param {Array<string>} role The role(s) of descendant we are looking for.
577 * @return {Array<Node>} The array of matching nodes.
579 cvox
.AriaUtil
.getNextLevel = function(parentControl
, role
) {
581 var children
= parentControl
.childNodes
;
582 var length
= children
.length
;
583 for (var i
= 0; i
< children
.length
; i
++) {
584 if (cvox
.AriaUtil
.isHidden(children
[i
]) ||
585 !cvox
.DomUtil
.isVisible(children
[i
])) {
588 var nextLevel
= cvox
.AriaUtil
.getNextLevelItems(children
[i
], role
);
589 if (nextLevel
.length
> 0) {
590 result
= result
.concat(nextLevel
);
598 * Recursively finds the first node(s) that match the role.
600 * @param {Element} current The node to start looking at.
601 * @param {Array<string>} role The role(s) to match.
602 * @return {Array<Element>} The array of matching nodes.
604 cvox
.AriaUtil
.getNextLevelItems = function(current
, role
) {
605 if (current
.nodeType
!= 1) { // If reached a node that is not an element.
608 if (role
.indexOf(cvox
.AriaUtil
.getRoleAttribute(current
)) != -1) {
611 var children
= current
.childNodes
;
612 var length
= children
.length
;
616 var resultArray
= [];
617 for (var i
= 0; i
< length
; i
++) {
618 var result
= cvox
.AriaUtil
.getNextLevelItems(children
[i
], role
);
619 if (result
.length
> 0) {
620 resultArray
= resultArray
.concat(result
);
630 * If the node is an object with an active descendant, returns the
633 * This function will fully resolve an active descendant chain. If a circular
634 * chain is detected, it will return null.
636 * @param {Node} targetNode The node to get descendant information for.
637 * @return {Node} The descendant node or null if no node exists.
639 cvox
.AriaUtil
.getActiveDescendant = function(targetNode
) {
641 var node
= targetNode
;
644 var activeId
= cvox
.AriaUtil
.getActiveDescendantId_(node
);
648 if (activeId
in seenIds
) {
649 // A circlar activeDescendant is an error, so return null.
652 seenIds
[activeId
] = true;
653 node
= document
.getElementById(activeId
);
656 if (node
== targetNode
) {
664 * Given a node, returns true if it's an ARIA control widget. Control widgets
665 * are treated as leaf nodes.
667 * @param {Node} targetNode The node to be checked.
668 * @return {boolean} Whether the targetNode is an ARIA control widget.
670 cvox
.AriaUtil
.isControlWidget = function(targetNode
) {
671 if (targetNode
&& targetNode
.getAttribute
) {
672 var role
= cvox
.AriaUtil
.getRoleAttribute(targetNode
);
679 case 'menuitemcheckbox':
680 case 'menuitemradio':
697 * Given a node, returns true if it's an ARIA composite control.
699 * @param {Node} targetNode The node to be checked.
700 * @return {boolean} Whether the targetNode is an ARIA composite control.
702 cvox
.AriaUtil
.isCompositeControl = function(targetNode
) {
703 if (targetNode
&& targetNode
.getAttribute
) {
704 var role
= cvox
.AriaUtil
.getRoleAttribute(targetNode
);
723 * Given a node, returns its 'aria-live' value if it's a live region, or
726 * @param {Node} node The node to be checked.
727 * @return {?string} The live region value, like 'polite' or
728 * 'assertive', or null if 'off' or none.
730 cvox
.AriaUtil
.getAriaLive = function(node
) {
731 if (!node
.hasAttribute
)
733 var value
= node
.getAttribute('aria-live');
734 if (value
== 'off') {
739 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
753 * Given a node, returns its 'aria-atomic' value.
755 * @param {Node} node The node to be checked.
756 * @return {boolean} The aria-atomic live region value, either true or false.
758 cvox
.AriaUtil
.getAriaAtomic = function(node
) {
759 if (!node
.hasAttribute
)
761 var value
= node
.getAttribute('aria-atomic');
763 return (value
=== 'true');
765 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
766 if (role
== 'alert') {
774 * Given a node, returns its 'aria-busy' value.
776 * @param {Node} node The node to be checked.
777 * @return {boolean} The aria-busy live region value, either true or false.
779 cvox
.AriaUtil
.getAriaBusy = function(node
) {
780 if (!node
.hasAttribute
)
782 var value
= node
.getAttribute('aria-busy');
784 return (value
=== 'true');
791 * Given a node, checks its aria-relevant attribute (with proper inheritance)
792 * and determines whether the given change (additions, removals, text, all)
793 * is relevant and should be announced.
795 * @param {Node} node The node to be checked.
796 * @param {string} change The name of the change to check - one of
797 * 'additions', 'removals', 'text', 'all'.
798 * @return {boolean} True if that change is relevant to that node as part of
801 cvox
.AriaUtil
.getAriaRelevant = function(node
, change
) {
802 if (!node
.hasAttribute
)
805 if (node
.hasAttribute('aria-relevant')) {
806 value
= node
.getAttribute('aria-relevant');
808 value
= 'additions text';
810 if (value
== 'all') {
811 value
= 'additions removals text';
814 var tokens
= value
.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '').split(' ');
816 if (change
== 'all') {
817 return (tokens
.indexOf('additions') >= 0 &&
818 tokens
.indexOf('text') >= 0 &&
819 tokens
.indexOf('removals') >= 0);
821 return (tokens
.indexOf(change
) >= 0);
827 * Given a node, return all live regions that are either rooted at this
828 * node or contain this node.
830 * @param {Node} node The node to be checked.
831 * @return {Array<Element>} All live regions affected by this node changing.
833 cvox
.AriaUtil
.getLiveRegions = function(node
) {
835 if (node
.querySelectorAll
) {
836 var nodes
= node
.querySelectorAll(
837 '[role="alert"], [role="log"], [role="marquee"], ' +
838 '[role="status"], [role="timer"], [aria-live]');
840 for (var i
= 0; i
< nodes
.length
; i
++) {
841 result
.push(nodes
[i
]);
847 if (cvox
.AriaUtil
.getAriaLive(node
)) {
851 node
= node
.parentElement
;
859 * Checks to see whether or not a node is an ARIA landmark.
861 * @param {Node} node The node to be checked.
862 * @return {boolean} Whether or not the node is an ARIA landmark.
864 cvox
.AriaUtil
.isLandmark = function(node
) {
865 if (!node
|| !node
.getAttribute
) {
868 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
872 case 'complementary':
885 * Checks to see whether or not a node is an ARIA grid.
887 * @param {Node} node The node to be checked.
888 * @return {boolean} Whether or not the node is an ARIA grid.
890 cvox
.AriaUtil
.isGrid = function(node
) {
891 if (!node
|| !node
.getAttribute
) {
894 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
905 * Returns the id of an earcon to play along with the description for a node.
907 * @param {Node} node The node to get the earcon for.
908 * @return {cvox.Earcon?} The earcon id, or null if none applies.
910 cvox
.AriaUtil
.getEarcon = function(node
) {
911 if (!node
|| !node
.getAttribute
) {
914 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
917 return cvox
.Earcon
.BUTTON
;
920 case 'menuitemcheckbox':
921 case 'menuitemradio':
922 var checked
= node
.getAttribute('aria-checked');
923 if (checked
== 'true') {
924 return cvox
.Earcon
.CHECK_ON
;
926 return cvox
.Earcon
.CHECK_OFF
;
930 return cvox
.Earcon
.LISTBOX
;
932 return cvox
.Earcon
.EDITABLE_TEXT
;
934 return cvox
.Earcon
.LIST_ITEM
;
936 return cvox
.Earcon
.LINK
;
944 * Returns the role of the node.
946 * This is equivalent to targetNode.getAttribute('role')
947 * except it also takes into account cases where ChromeVox
948 * itself has changed the role (ie, adding role="application"
949 * to BODY elements for better screen reader compatibility.
951 * @param {Node} targetNode The node to get the role for.
952 * @return {string} role of the targetNode.
954 cvox
.AriaUtil
.getRoleAttribute = function(targetNode
) {
955 if (!targetNode
.getAttribute
) {
958 var role
= targetNode
.getAttribute('role');
959 if (targetNode
.hasAttribute('chromevoxoriginalrole')) {
960 role
= targetNode
.getAttribute('chromevoxoriginalrole');
967 * Checks to see whether or not a node is an ARIA math node.
969 * @param {Node} node The node to be checked.
970 * @return {boolean} Whether or not the node is an ARIA math node.
972 cvox
.AriaUtil
.isMath = function(node
) {
973 if (!node
|| !node
.getAttribute
) {
976 var role
= cvox
.AriaUtil
.getRoleAttribute(node
);
977 return role
== 'math';