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 constant indicating no role name.
30 cvox.AriaUtil.NO_ROLE_NAME = ' ';
33 * A mapping from ARIA role names to their message ids.
34 * Note: If you are adding a new mapping, the new message identifier needs a
35 * corresponding braille message. For example, a message id 'tag_button'
36 * requires another message 'tag_button_brl' within messages.js.
37 * @type {Object<string, string>}
39 cvox.AriaUtil.WIDGET_ROLE_TO_NAME = {
40 'alert' : 'aria_role_alert',
41 'alertdialog' : 'aria_role_alertdialog',
42 'button' : 'aria_role_button',
43 'checkbox' : 'aria_role_checkbox',
44 'columnheader' : 'aria_role_columnheader',
45 'combobox' : 'aria_role_combobox',
46 'dialog' : 'aria_role_dialog',
47 'grid' : 'aria_role_grid',
48 'gridcell' : 'aria_role_gridcell',
49 'link' : 'aria_role_link',
50 'listbox' : 'aria_role_listbox',
51 'log' : 'aria_role_log',
52 'marquee' : 'aria_role_marquee',
53 'menu' : 'aria_role_menu',
54 'menubar' : 'aria_role_menubar',
55 'menuitem' : 'aria_role_menuitem',
56 'menuitemcheckbox' : 'aria_role_menuitemcheckbox',
57 'menuitemradio' : 'aria_role_menuitemradio',
58 'option' : cvox.AriaUtil.NO_ROLE_NAME,
59 'progressbar' : 'aria_role_progressbar',
60 'radio' : 'aria_role_radio',
61 'radiogroup' : 'aria_role_radiogroup',
62 'rowheader' : 'aria_role_rowheader',
63 'scrollbar' : 'aria_role_scrollbar',
64 'slider' : 'aria_role_slider',
65 'spinbutton' : 'aria_role_spinbutton',
66 'status' : 'aria_role_status',
67 'tab' : 'aria_role_tab',
68 'tablist' : 'aria_role_tablist',
69 'tabpanel' : 'aria_role_tabpanel',
70 'textbox' : 'aria_role_textbox',
71 'timer' : 'aria_role_timer',
72 'toolbar' : 'aria_role_toolbar',
73 'tooltip' : 'aria_role_tooltip',
74 'treeitem' : 'aria_role_treeitem'
79 * Note: If you are adding a new mapping, the new message identifier needs a
80 * corresponding braille message. For example, a message id 'tag_button'
81 * requires another message 'tag_button_brl' within messages.js.
82 * @type {Object<string, string>}
84 cvox.AriaUtil.STRUCTURE_ROLE_TO_NAME = {
85 'article' : 'aria_role_article',
86 'application' : 'aria_role_application',
87 'banner' : 'aria_role_banner',
88 'columnheader' : 'aria_role_columnheader',
89 'complementary' : 'aria_role_complementary',
90 'contentinfo' : 'aria_role_contentinfo',
91 'definition' : 'aria_role_definition',
92 'directory' : 'aria_role_directory',
93 'document' : 'aria_role_document',
94 'form' : 'aria_role_form',
95 'group' : 'aria_role_group',
96 'heading' : 'aria_role_heading',
97 'img' : 'aria_role_img',
98 'list' : 'aria_role_list',
99 'listitem' : 'aria_role_listitem',
100 'main' : 'aria_role_main',
101 'math' : 'aria_role_math',
102 'navigation' : 'aria_role_navigation',
103 'note' : 'aria_role_note',
104 'region' : 'aria_role_region',
105 'rowheader' : 'aria_role_rowheader',
106 'search' : 'aria_role_search',
107 'separator' : 'aria_role_separator'
112 * @type {Array<Object>}
114 cvox.AriaUtil.ATTRIBUTE_VALUE_TO_STATUS = [
115 { name: 'aria-autocomplete', values:
116 {'inline' : 'aria_autocomplete_inline',
117 'list' : 'aria_autocomplete_list',
118 'both' : 'aria_autocomplete_both'} },
119 { name: 'aria-checked', values:
120 {'true' : 'aria_checked_true',
121 'false' : 'aria_checked_false',
122 'mixed' : 'aria_checked_mixed'} },
123 { name: 'aria-disabled', values:
124 {'true' : 'aria_disabled_true'} },
125 { name: 'aria-expanded', values:
126 {'true' : 'aria_expanded_true',
127 'false' : 'aria_expanded_false'} },
128 { name: 'aria-invalid', values:
129 {'true' : 'aria_invalid_true',
130 'grammar' : 'aria_invalid_grammar',
131 'spelling' : 'aria_invalid_spelling'} },
132 { name: 'aria-multiline', values:
133 {'true' : 'aria_multiline_true'} },
134 { name: 'aria-multiselectable', values:
135 {'true' : 'aria_multiselectable_true'} },
136 { name: 'aria-pressed', values:
137 {'true' : 'aria_pressed_true',
138 'false' : 'aria_pressed_false',
139 'mixed' : 'aria_pressed_mixed'} },
140 { name: 'aria-readonly', values:
141 {'true' : 'aria_readonly_true'} },
142 { name: 'aria-required', values:
143 {'true' : 'aria_required_true'} },
144 { name: 'aria-selected', values:
145 {'true' : 'aria_selected_true',
146 'false' : 'aria_selected_false'} }
151 * Checks if a node should be treated as a hidden node because of its ARIA
154 * @param {Node} targetNode The node to check.
155 * @return {boolean} True if the targetNode should be treated as hidden.
157 cvox.AriaUtil.isHiddenRecursive = function(targetNode) {
158 if (cvox.AriaUtil.isHidden(targetNode)) {
161 var parent = targetNode.parentElement;
163 if ((parent.getAttribute('aria-hidden') == 'true') &&
164 (parent.getAttribute('chromevoxignoreariahidden') != 'true')) {
167 parent = parent.parentElement;
174 * Checks if a node should be treated as a hidden node because of its ARIA
175 * markup. Does not check parents, so if you need to know if this is a
176 * descendant of a hidden node, call isHiddenRecursive.
178 * @param {Node} targetNode The node to check.
179 * @return {boolean} True if the targetNode should be treated as hidden.
181 cvox.AriaUtil.isHidden = function(targetNode) {
185 if (targetNode.getAttribute) {
186 if ((targetNode.getAttribute('aria-hidden') == 'true') &&
187 (targetNode.getAttribute('chromevoxignoreariahidden') != 'true')) {
196 * Checks if a node should be treated as a visible node because of its ARIA
197 * markup, regardless of whatever other styling/attributes it may have.
198 * It is possible to force a node to be visible by setting aria-hidden to
201 * @param {Node} targetNode The node to check.
202 * @return {boolean} True if the targetNode should be treated as visible.
204 cvox.AriaUtil.isForcedVisibleRecursive = function(targetNode) {
205 var node = targetNode;
207 if (node.getAttribute) {
208 // Stop and return the result based on the closest node that has
210 if (node.hasAttribute('aria-hidden') &&
211 (node.getAttribute('chromevoxignoreariahidden') != 'true')) {
212 return node.getAttribute('aria-hidden') == 'false';
215 node = node.parentElement;
222 * Checks if a node should be treated as a leaf node because of its ARIA
223 * markup. Does not check recursively, and does not check isControlWidget.
224 * Note that elements with aria-label are treated as leaf elements. See:
225 * http://www.w3.org/TR/wai-aria/roles#textalternativecomputation
227 * @param {Element} targetElement The node to check.
228 * @return {boolean} True if the targetNode should be treated as a leaf node.
230 cvox.AriaUtil.isLeafElement = function(targetElement) {
231 var role = targetElement.getAttribute('role');
232 var hasArialLabel = targetElement.hasAttribute('aria-label') &&
233 (targetElement.getAttribute('aria-label').length > 0);
234 return (role == 'img' || role == 'progressbar' || hasArialLabel);
239 * Determines whether or not a node is or is the descendant of a node
240 * with a particular role.
242 * @param {Node} node The node to be checked.
243 * @param {string} roleName The role to check for.
244 * @return {boolean} True if the node or one of its ancestor has the specified
247 cvox.AriaUtil.isDescendantOfRole = function(node, roleName) {
249 if (roleName && node && (node.getAttribute('role') == roleName)) {
252 node = node.parentNode;
259 * Helper function to return the role name message identifier for a role.
260 * @param {string} role The role.
261 * @return {?string} The role name message identifier.
264 cvox.AriaUtil.getRoleNameMsgForRole_ = function(role) {
265 var msgId = cvox.AriaUtil.WIDGET_ROLE_TO_NAME[role];
269 if (msgId == cvox.AriaUtil.NO_ROLE_NAME) {
270 // TODO(dtseng): This isn't the way to insert silence; beware!
277 * Returns true is the node is any kind of button.
279 * @param {Node} node The node to check.
280 * @return {boolean} True if the node is a button.
282 cvox.AriaUtil.isButton = function(node) {
283 var role = cvox.AriaUtil.getRoleAttribute(node);
284 if (role == 'button') {
287 if (node.tagName == 'BUTTON') {
290 if (node.tagName == 'INPUT') {
291 return (node.type == 'submit' ||
292 node.type == 'reset' ||
293 node.type == 'button');
299 * Returns a role message identifier for a node.
300 * For a localized string, see cvox.AriaUtil.getRoleName.
301 * @param {Node} targetNode The node to get the role name for.
302 * @return {string} The role name message identifier for the targetNode.
304 cvox.AriaUtil.getRoleNameMsg = function(targetNode) {
306 if (targetNode && targetNode.getAttribute) {
307 var role = cvox.AriaUtil.getRoleAttribute(targetNode);
309 // Special case for pop-up buttons.
310 if (targetNode.getAttribute('aria-haspopup') == 'true' &&
311 cvox.AriaUtil.isButton(targetNode)) {
312 return 'aria_role_popup_button';
316 roleName = cvox.AriaUtil.getRoleNameMsgForRole_(role);
318 roleName = cvox.AriaUtil.STRUCTURE_ROLE_TO_NAME[role];
322 // To a user, a menu item within a menu bar is called a "menu";
323 // any other menu item is called a "menu item".
325 // TODO(deboer): This block feels like a hack. dmazzoni suggests
326 // using css-like syntax for names. Investigate further if
327 // we need more of these hacks.
328 if (role == 'menuitem') {
329 var container = targetNode.parentElement;
331 if (container.getAttribute &&
332 (cvox.AriaUtil.getRoleAttribute(container) == 'menu' ||
333 cvox.AriaUtil.getRoleAttribute(container) == 'menubar')) {
336 container = container.parentElement;
338 if (container && cvox.AriaUtil.getRoleAttribute(container) == 'menubar') {
339 roleName = cvox.AriaUtil.getRoleNameMsgForRole_('menu');
340 } // else roleName is already 'Menu item', no need to change it.
350 * Returns a string to be presented to the user that identifies what the
351 * targetNode's role is.
353 * @param {Node} targetNode The node to get the role name for.
354 * @return {string} The role name for the targetNode.
356 cvox.AriaUtil.getRoleName = function(targetNode) {
357 var roleMsg = cvox.AriaUtil.getRoleNameMsg(targetNode);
358 var roleName = cvox.ChromeVox.msgs.getMsg(roleMsg);
359 var role = cvox.AriaUtil.getRoleAttribute(targetNode);
360 if ((role == 'heading') && (targetNode.hasAttribute('aria-level'))) {
361 roleName += ' ' + targetNode.getAttribute('aria-level');
363 return roleName ? roleName : '';
367 * Returns a string that gives information about the state of the targetNode.
369 * @param {Node} targetNode The node to get the state information for.
370 * @param {boolean} primary Whether this is the primary node we're
371 * interested in, where we might want extra information - as
372 * opposed to an ancestor, where we might be more brief.
373 * @return {cvox.NodeState} The status information about the node.
375 cvox.AriaUtil.getStateMsgs = function(targetNode, primary) {
377 if (!targetNode || !targetNode.getAttribute) {
381 for (var i = 0, attr; attr = cvox.AriaUtil.ATTRIBUTE_VALUE_TO_STATUS[i];
383 var value = targetNode.getAttribute(attr.name);
384 var msgId = attr.values[value];
389 if (targetNode.getAttribute('role') == 'grid') {
390 return cvox.AriaUtil.getGridState_(targetNode, targetNode);
393 var role = cvox.AriaUtil.getRoleAttribute(targetNode);
394 if (targetNode.getAttribute('aria-haspopup') == 'true') {
395 if (role == 'menuitem') {
396 state.push(['has_submenu']);
397 } else if (cvox.AriaUtil.isButton(targetNode)) {
398 // Do nothing - the role name will be 'pop-up button'.
400 state.push(['has_popup']);
404 var valueText = targetNode.getAttribute('aria-valuetext');
406 // If there is a valueText, that always wins.
407 state.push(['aria_value_text', valueText]);
411 var valueNow = targetNode.getAttribute('aria-valuenow');
412 var valueMin = targetNode.getAttribute('aria-valuemin');
413 var valueMax = targetNode.getAttribute('aria-valuemax');
415 // Scrollbar and progressbar should speak the percentage.
416 // http://www.w3.org/TR/wai-aria/roles#scrollbar
417 // http://www.w3.org/TR/wai-aria/roles#progressbar
418 if ((valueNow != null) && (valueMin != null) && (valueMax != null)) {
419 if ((role == 'scrollbar') || (role == 'progressbar')) {
420 var percent = Math.round((valueNow / (valueMax - valueMin)) * 100);
421 state.push(['state_percent', percent]);
426 // Return as many of the value attributes as possible.
427 if (valueNow != null) {
428 state.push(['aria_value_now', valueNow]);
430 if (valueMin != null) {
431 state.push(['aria_value_min', valueMin]);
433 if (valueMax != null) {
434 state.push(['aria_value_max', valueMax]);
437 // If this is a composite control or an item within a composite control,
438 // get the index and count of the current descendant or active
440 var parentControl = targetNode;
441 var currentDescendant = null;
443 if (cvox.AriaUtil.isCompositeControl(parentControl) && primary) {
444 currentDescendant = cvox.AriaUtil.getActiveDescendant(parentControl);
446 role = cvox.AriaUtil.getRoleAttribute(targetNode);
447 if (role == 'option' ||
448 role == 'menuitem' ||
449 role == 'menuitemcheckbox' ||
450 role == 'menuitemradio' ||
453 role == 'treeitem') {
454 currentDescendant = targetNode;
455 parentControl = targetNode.parentElement;
456 while (parentControl &&
457 !cvox.AriaUtil.isCompositeControl(parentControl)) {
458 parentControl = parentControl.parentElement;
460 cvox.AriaUtil.getRoleAttribute(parentControl) == 'treeitem') {
468 (cvox.AriaUtil.isCompositeControl(parentControl) ||
469 cvox.AriaUtil.getRoleAttribute(parentControl) == 'treeitem') &&
471 var parentRole = cvox.AriaUtil.getRoleAttribute(parentControl);
472 var descendantRoleList;
473 switch (parentRole) {
476 descendantRoleList = ['option'];
479 descendantRoleList = ['menuitem',
484 descendantRoleList = ['radio'];
487 descendantRoleList = ['tab'];
492 descendantRoleList = ['treeitem'];
496 if (descendantRoleList) {
501 parseInt(currentDescendant.getAttribute('aria-setsize'), 10);
502 if (!isNaN(ariaLength)) {
503 listLength = ariaLength;
506 parseInt(currentDescendant.getAttribute('aria-posinset'), 10);
507 if (!isNaN(ariaIndex)) {
508 currentIndex = ariaIndex;
511 if (listLength == undefined || currentIndex == undefined) {
512 var descendants = cvox.AriaUtil.getNextLevel(parentControl,
514 if (listLength == undefined) {
515 listLength = descendants.length;
517 if (currentIndex == undefined) {
518 for (var j = 0; j < descendants.length; j++) {
519 if (descendants[j] == currentDescendant) {
520 currentIndex = j + 1;
525 if (currentIndex && listLength) {
526 state.push(['list_position', currentIndex, listLength]);
535 * Returns a string that gives information about the state of the grid node.
537 * @param {Node} targetNode The node to get the state information for.
538 * @param {Node} parentControl The parent composite control.
539 * @return {cvox.NodeState} The status information about the node.
542 cvox.AriaUtil.getGridState_ = function(targetNode, parentControl) {
543 var activeDescendant = cvox.AriaUtil.getActiveDescendant(parentControl);
545 if (activeDescendant) {
546 var descendantSelector = '*[role~="row"]';
547 var rows = parentControl.querySelectorAll(descendantSelector);
548 var currentIndex = null;
549 for (var j = 0; j < rows.length; j++) {
550 var gridcells = rows[j].querySelectorAll('*[role~="gridcell"]');
551 for (var k = 0; k < gridcells.length; k++) {
552 if (gridcells[k] == activeDescendant) {
553 return /** @type {cvox.NodeState} */ (
554 [['aria_role_gridcell_pos', j + 1, k + 1]]);
564 * Returns the id of a node's active descendant
565 * @param {Node} targetNode The node.
566 * @return {?string} The id of the active descendant.
569 cvox.AriaUtil.getActiveDescendantId_ = function(targetNode) {
570 if (!targetNode.getAttribute) {
574 var activeId = targetNode.getAttribute('aria-activedescendant');
583 * Returns the list of elements that are one aria-level below.
585 * @param {Node} parentControl The node whose descendants should be analyzed.
586 * @param {Array<string>} role The role(s) of descendant we are looking for.
587 * @return {Array<Node>} The array of matching nodes.
589 cvox.AriaUtil.getNextLevel = function(parentControl, role) {
591 var children = parentControl.childNodes;
592 var length = children.length;
593 for (var i = 0; i < children.length; i++) {
594 if (cvox.AriaUtil.isHidden(children[i]) ||
595 !cvox.DomUtil.isVisible(children[i])) {
598 var nextLevel = cvox.AriaUtil.getNextLevelItems(children[i], role);
599 if (nextLevel.length > 0) {
600 result = result.concat(nextLevel);
608 * Recursively finds the first node(s) that match the role.
610 * @param {Element} current The node to start looking at.
611 * @param {Array<string>} role The role(s) to match.
612 * @return {Array<Element>} The array of matching nodes.
614 cvox.AriaUtil.getNextLevelItems = function(current, role) {
615 if (current.nodeType != 1) { // If reached a node that is not an element.
618 if (role.indexOf(cvox.AriaUtil.getRoleAttribute(current)) != -1) {
621 var children = current.childNodes;
622 var length = children.length;
626 var resultArray = [];
627 for (var i = 0; i < length; i++) {
628 var result = cvox.AriaUtil.getNextLevelItems(children[i], role);
629 if (result.length > 0) {
630 resultArray = resultArray.concat(result);
640 * If the node is an object with an active descendant, returns the
643 * This function will fully resolve an active descendant chain. If a circular
644 * chain is detected, it will return null.
646 * @param {Node} targetNode The node to get descendant information for.
647 * @return {Node} The descendant node or null if no node exists.
649 cvox.AriaUtil.getActiveDescendant = function(targetNode) {
651 var node = targetNode;
654 var activeId = cvox.AriaUtil.getActiveDescendantId_(node);
658 if (activeId in seenIds) {
659 // A circlar activeDescendant is an error, so return null.
662 seenIds[activeId] = true;
663 node = document.getElementById(activeId);
666 if (node == targetNode) {
674 * Given a node, returns true if it's an ARIA control widget. Control widgets
675 * are treated as leaf nodes.
677 * @param {Node} targetNode The node to be checked.
678 * @return {boolean} Whether the targetNode is an ARIA control widget.
680 cvox.AriaUtil.isControlWidget = function(targetNode) {
681 if (targetNode && targetNode.getAttribute) {
682 var role = cvox.AriaUtil.getRoleAttribute(targetNode);
689 case 'menuitemcheckbox':
690 case 'menuitemradio':
707 * Given a node, returns true if it's an ARIA composite control.
709 * @param {Node} targetNode The node to be checked.
710 * @return {boolean} Whether the targetNode is an ARIA composite control.
712 cvox.AriaUtil.isCompositeControl = function(targetNode) {
713 if (targetNode && targetNode.getAttribute) {
714 var role = cvox.AriaUtil.getRoleAttribute(targetNode);
733 * Given a node, returns its 'aria-live' value if it's a live region, or
736 * @param {Node} node The node to be checked.
737 * @return {?string} The live region value, like 'polite' or
738 * 'assertive', or null if 'off' or none.
740 cvox.AriaUtil.getAriaLive = function(node) {
741 if (!node.hasAttribute)
743 var value = node.getAttribute('aria-live');
744 if (value == 'off') {
749 var role = cvox.AriaUtil.getRoleAttribute(node);
763 * Given a node, returns its 'aria-atomic' value.
765 * @param {Node} node The node to be checked.
766 * @return {boolean} The aria-atomic live region value, either true or false.
768 cvox.AriaUtil.getAriaAtomic = function(node) {
769 if (!node.hasAttribute)
771 var value = node.getAttribute('aria-atomic');
773 return (value === 'true');
775 var role = cvox.AriaUtil.getRoleAttribute(node);
776 if (role == 'alert') {
784 * Given a node, returns its 'aria-busy' value.
786 * @param {Node} node The node to be checked.
787 * @return {boolean} The aria-busy live region value, either true or false.
789 cvox.AriaUtil.getAriaBusy = function(node) {
790 if (!node.hasAttribute)
792 var value = node.getAttribute('aria-busy');
794 return (value === 'true');
801 * Given a node, checks its aria-relevant attribute (with proper inheritance)
802 * and determines whether the given change (additions, removals, text, all)
803 * is relevant and should be announced.
805 * @param {Node} node The node to be checked.
806 * @param {string} change The name of the change to check - one of
807 * 'additions', 'removals', 'text', 'all'.
808 * @return {boolean} True if that change is relevant to that node as part of
811 cvox.AriaUtil.getAriaRelevant = function(node, change) {
812 if (!node.hasAttribute)
815 if (node.hasAttribute('aria-relevant')) {
816 value = node.getAttribute('aria-relevant');
818 value = 'additions text';
820 if (value == 'all') {
821 value = 'additions removals text';
824 var tokens = value.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '').split(' ');
826 if (change == 'all') {
827 return (tokens.indexOf('additions') >= 0 &&
828 tokens.indexOf('text') >= 0 &&
829 tokens.indexOf('removals') >= 0);
831 return (tokens.indexOf(change) >= 0);
837 * Given a node, return all live regions that are either rooted at this
838 * node or contain this node.
840 * @param {Node} node The node to be checked.
841 * @return {Array<Element>} All live regions affected by this node changing.
843 cvox.AriaUtil.getLiveRegions = function(node) {
845 if (node.querySelectorAll) {
846 var nodes = node.querySelectorAll(
847 '[role="alert"], [role="log"], [role="marquee"], ' +
848 '[role="status"], [role="timer"], [aria-live]');
850 for (var i = 0; i < nodes.length; i++) {
851 result.push(nodes[i]);
857 if (cvox.AriaUtil.getAriaLive(node)) {
861 node = node.parentElement;
869 * Checks to see whether or not a node is an ARIA landmark.
871 * @param {Node} node The node to be checked.
872 * @return {boolean} Whether or not the node is an ARIA landmark.
874 cvox.AriaUtil.isLandmark = function(node) {
875 if (!node || !node.getAttribute) {
878 var role = cvox.AriaUtil.getRoleAttribute(node);
882 case 'complementary':
895 * Checks to see whether or not a node is an ARIA grid.
897 * @param {Node} node The node to be checked.
898 * @return {boolean} Whether or not the node is an ARIA grid.
900 cvox.AriaUtil.isGrid = function(node) {
901 if (!node || !node.getAttribute) {
904 var role = cvox.AriaUtil.getRoleAttribute(node);
915 * Returns the id of an earcon to play along with the description for a node.
917 * @param {Node} node The node to get the earcon for.
918 * @return {number?} The earcon id, or null if none applies.
920 cvox.AriaUtil.getEarcon = function(node) {
921 if (!node || !node.getAttribute) {
924 var role = cvox.AriaUtil.getRoleAttribute(node);
927 return cvox.AbstractEarcons.BUTTON;
930 case 'menuitemcheckbox':
931 case 'menuitemradio':
932 var checked = node.getAttribute('aria-checked');
933 if (checked == 'true') {
934 return cvox.AbstractEarcons.CHECK_ON;
936 return cvox.AbstractEarcons.CHECK_OFF;
940 return cvox.AbstractEarcons.LISTBOX;
942 return cvox.AbstractEarcons.EDITABLE_TEXT;
944 return cvox.AbstractEarcons.BULLET;
946 return cvox.AbstractEarcons.LINK;
954 * Returns the role of the node.
956 * This is equivalent to targetNode.getAttribute('role')
957 * except it also takes into account cases where ChromeVox
958 * itself has changed the role (ie, adding role="application"
959 * to BODY elements for better screen reader compatibility.
961 * @param {Node} targetNode The node to get the role for.
962 * @return {string} role of the targetNode.
964 cvox.AriaUtil.getRoleAttribute = function(targetNode) {
965 if (!targetNode.getAttribute) {
968 var role = targetNode.getAttribute('role');
969 if (targetNode.hasAttribute('chromevoxoriginalrole')) {
970 role = targetNode.getAttribute('chromevoxoriginalrole');
977 * Checks to see whether or not a node is an ARIA math node.
979 * @param {Node} node The node to be checked.
980 * @return {boolean} Whether or not the node is an ARIA math node.
982 cvox.AriaUtil.isMath = function(node) {
983 if (!node || !node.getAttribute) {
986 var role = cvox.AriaUtil.getRoleAttribute(node);
987 return role == 'math';