Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(332)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/common/dom_util.js

Issue 2943193002: Run clang-format on .js files in c/b/r/chromeos/chromevox (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview A collection of JavaScript utilities used to simplify working 6 * @fileoverview A collection of JavaScript utilities used to simplify working
7 * with the DOM. 7 * with the DOM.
8 */ 8 */
9 9
10 10
11 goog.provide('cvox.DomUtil'); 11 goog.provide('cvox.DomUtil');
12 12
13 goog.require('cvox.AbstractTts'); 13 goog.require('cvox.AbstractTts');
14 goog.require('cvox.AriaUtil'); 14 goog.require('cvox.AriaUtil');
15 goog.require('cvox.ChromeVox'); 15 goog.require('cvox.ChromeVox');
16 goog.require('cvox.DomPredicates'); 16 goog.require('cvox.DomPredicates');
17 goog.require('cvox.Memoize'); 17 goog.require('cvox.Memoize');
18 goog.require('cvox.NodeState'); 18 goog.require('cvox.NodeState');
19 goog.require('cvox.XpathUtil'); 19 goog.require('cvox.XpathUtil');
20 20
21 21
22 22
23 /** 23 /**
24 * Create the namespace 24 * Create the namespace
25 * @constructor 25 * @constructor
26 */ 26 */
27 cvox.DomUtil = function() { 27 cvox.DomUtil = function() {};
28 };
29 28
30 29
31 /** 30 /**
32 * Note: If you are adding a new mapping, the new message identifier needs a 31 * Note: If you are adding a new mapping, the new message identifier needs a
33 * corresponding braille message. For example, a message id 'tag_button' 32 * corresponding braille message. For example, a message id 'tag_button'
34 * requires another message 'tag_button_brl' within messages.js. 33 * requires another message 'tag_button_brl' within messages.js.
35 * @type {Object} 34 * @type {Object}
36 */ 35 */
37 cvox.DomUtil.INPUT_TYPE_TO_INFORMATION_TABLE_MSG = { 36 cvox.DomUtil.INPUT_TYPE_TO_INFORMATION_TABLE_MSG = {
38 'button' : 'role_button', 37 'button': 'role_button',
39 'checkbox' : 'role_checkbox', 38 'checkbox': 'role_checkbox',
40 'color' : 'input_type_color', 39 'color': 'input_type_color',
41 'datetime' : 'input_type_datetime', 40 'datetime': 'input_type_datetime',
42 'datetime-local' : 'input_type_datetime_local', 41 'datetime-local': 'input_type_datetime_local',
43 'date' : 'input_type_date', 42 'date': 'input_type_date',
44 'email' : 'input_type_email', 43 'email': 'input_type_email',
45 'file' : 'input_type_file', 44 'file': 'input_type_file',
46 'image' : 'role_button', 45 'image': 'role_button',
47 'month' : 'input_type_month', 46 'month': 'input_type_month',
48 'number' : 'input_type_number', 47 'number': 'input_type_number',
49 'password' : 'input_type_password', 48 'password': 'input_type_password',
50 'radio' : 'role_radio', 49 'radio': 'role_radio',
51 'range' : 'role_slider', 50 'range': 'role_slider',
52 'reset' : 'input_type_reset', 51 'reset': 'input_type_reset',
53 'search' : 'input_type_search', 52 'search': 'input_type_search',
54 'submit' : 'role_button', 53 'submit': 'role_button',
55 'tel' : 'input_type_number', 54 'tel': 'input_type_number',
56 'text' : 'input_type_text', 55 'text': 'input_type_text',
57 'url' : 'input_type_url', 56 'url': 'input_type_url',
58 'week' : 'input_type_week' 57 'week': 'input_type_week'
59 }; 58 };
60 59
61 60
62 /** 61 /**
63 * Note: If you are adding a new mapping, the new message identifier needs a 62 * Note: If you are adding a new mapping, the new message identifier needs a
64 * corresponding braille message. For example, a message id 'tag_button' 63 * corresponding braille message. For example, a message id 'tag_button'
65 * requires another message 'tag_button_brl' within messages.js. 64 * requires another message 'tag_button_brl' within messages.js.
66 * @type {Object} 65 * @type {Object}
67 */ 66 */
68 cvox.DomUtil.TAG_TO_INFORMATION_TABLE_VERBOSE_MSG = { 67 cvox.DomUtil.TAG_TO_INFORMATION_TABLE_VERBOSE_MSG = {
69 'A' : 'role_link', 68 'A': 'role_link',
70 'ARTICLE' : 'tag_article', 69 'ARTICLE': 'tag_article',
71 'ASIDE' : 'tag_aside', 70 'ASIDE': 'tag_aside',
72 'AUDIO' : 'tag_audio', 71 'AUDIO': 'tag_audio',
73 'BUTTON' : 'role_button', 72 'BUTTON': 'role_button',
74 'FOOTER' : 'tag_footer', 73 'FOOTER': 'tag_footer',
75 'H1' : 'tag_h1', 74 'H1': 'tag_h1',
76 'H2' : 'tag_h2', 75 'H2': 'tag_h2',
77 'H3' : 'tag_h3', 76 'H3': 'tag_h3',
78 'H4' : 'tag_h4', 77 'H4': 'tag_h4',
79 'H5' : 'tag_h5', 78 'H5': 'tag_h5',
80 'H6' : 'tag_h6', 79 'H6': 'tag_h6',
81 'HEADER' : 'tag_header', 80 'HEADER': 'tag_header',
82 'HGROUP' : 'tag_hgroup', 81 'HGROUP': 'tag_hgroup',
83 'LI' : 'tag_li', 82 'LI': 'tag_li',
84 'MARK' : 'tag_mark', 83 'MARK': 'tag_mark',
85 'NAV' : 'tag_nav', 84 'NAV': 'tag_nav',
86 'OL' : 'tag_ol', 85 'OL': 'tag_ol',
87 'SECTION' : 'tag_section', 86 'SECTION': 'tag_section',
88 'SELECT' : 'tag_select', 87 'SELECT': 'tag_select',
89 'TABLE' : 'tag_table', 88 'TABLE': 'tag_table',
90 'TEXTAREA' : 'tag_textarea', 89 'TEXTAREA': 'tag_textarea',
91 'TIME' : 'tag_time', 90 'TIME': 'tag_time',
92 'UL' : 'tag_ul', 91 'UL': 'tag_ul',
93 'VIDEO' : 'tag_video' 92 'VIDEO': 'tag_video'
94 }; 93 };
95 94
96 /** 95 /**
97 * ChromeVox does not speak the omitted tags. 96 * ChromeVox does not speak the omitted tags.
98 * @type {Object} 97 * @type {Object}
99 */ 98 */
100 cvox.DomUtil.TAG_TO_INFORMATION_TABLE_BRIEF_MSG = { 99 cvox.DomUtil.TAG_TO_INFORMATION_TABLE_BRIEF_MSG = {
101 'AUDIO' : 'tag_audio', 100 'AUDIO': 'tag_audio',
102 'BUTTON' : 'role_button', 101 'BUTTON': 'role_button',
103 'SELECT' : 'tag_select', 102 'SELECT': 'tag_select',
104 'TABLE' : 'tag_table', 103 'TABLE': 'tag_table',
105 'TEXTAREA' : 'tag_textarea', 104 'TEXTAREA': 'tag_textarea',
106 'VIDEO' : 'tag_video' 105 'VIDEO': 'tag_video'
107 }; 106 };
108 107
109 /** 108 /**
110 * These tags are treated as text formatters. 109 * These tags are treated as text formatters.
111 * @type {Array<string>} 110 * @type {Array<string>}
112 */ 111 */
113 cvox.DomUtil.FORMATTING_TAGS = 112 cvox.DomUtil.FORMATTING_TAGS = [
114 ['B', 'BIG', 'CITE', 'CODE', 'DFN', 'EM', 'I', 'KBD', 'SAMP', 'SMALL', 113 'B', 'BIG', 'CITE', 'CODE', 'DFN', 'EM', 'I', 'KBD', 'SAMP', 'SMALL', 'SPAN',
115 'SPAN', 'STRIKE', 'STRONG', 'SUB', 'SUP', 'U', 'VAR']; 114 'STRIKE', 'STRONG', 'SUB', 'SUP', 'U', 'VAR'
115 ];
116 116
117 /** 117 /**
118 * Determine if the given node is visible on the page. This does not check if 118 * Determine if the given node is visible on the page. This does not check if
119 * it is inside the document view-port as some sites try to communicate with 119 * it is inside the document view-port as some sites try to communicate with
120 * screen readers with such elements. 120 * screen readers with such elements.
121 * @param {Node} node The node to determine as visible or not. 121 * @param {Node} node The node to determine as visible or not.
122 * @param {{checkAncestors: (boolean|undefined), 122 * @param {{checkAncestors: (boolean|undefined),
123 checkDescendants: (boolean|undefined)}=} opt_options 123 checkDescendants: (boolean|undefined)}=} opt_options
124 * In certain cases, we already have information 124 * In certain cases, we already have information
125 * on the context of the node. To improve performance and avoid redundant 125 * on the context of the node. To improve performance and avoid redundant
(...skipping 17 matching lines...) Expand all
143 } 143 }
144 } 144 }
145 145
146 // Generate a unique function name based on the arguments, and 146 // Generate a unique function name based on the arguments, and
147 // memoize the result of the internal visibility computation so that 147 // memoize the result of the internal visibility computation so that
148 // within the same call stack, we don't need to recompute the visibility 148 // within the same call stack, we don't need to recompute the visibility
149 // of the same node. 149 // of the same node.
150 var fname = 'isVisible-' + checkAncestors + '-' + checkDescendants; 150 var fname = 'isVisible-' + checkAncestors + '-' + checkDescendants;
151 return /** @type {boolean} */ (cvox.Memoize.memoize( 151 return /** @type {boolean} */ (cvox.Memoize.memoize(
152 cvox.DomUtil.computeIsVisible_.bind( 152 cvox.DomUtil.computeIsVisible_.bind(
153 this, node, checkAncestors, checkDescendants), fname, node)); 153 this, node, checkAncestors, checkDescendants),
154 fname, node));
154 }; 155 };
155 156
156 /** 157 /**
157 * Implementation of |cvox.DomUtil.isVisible|. 158 * Implementation of |cvox.DomUtil.isVisible|.
158 * @param {Node} node The node to determine as visible or not. 159 * @param {Node} node The node to determine as visible or not.
159 * @param {boolean} checkAncestors True if we should check the ancestor chain 160 * @param {boolean} checkAncestors True if we should check the ancestor chain
160 * for forced invisibility traits of descendants. 161 * for forced invisibility traits of descendants.
161 * @param {boolean} checkDescendants True if we should consider descendants of 162 * @param {boolean} checkDescendants True if we should consider descendants of
162 * the given node for visible elements. 163 * the given node for visible elements.
163 * @return {boolean} True if the node is visible. 164 * @return {boolean} True if the node is visible.
164 * @private 165 * @private
165 */ 166 */
166 cvox.DomUtil.computeIsVisible_ = function( 167 cvox.DomUtil.computeIsVisible_ = function(
167 node, checkAncestors, checkDescendants) { 168 node, checkAncestors, checkDescendants) {
168 // If the node is an iframe that we can never inject into, consider it hidden. 169 // If the node is an iframe that we can never inject into, consider it hidden.
169 if (node.tagName == 'IFRAME' && !node.src) { 170 if (node.tagName == 'IFRAME' && !node.src) {
170 return false; 171 return false;
171 } 172 }
172 173
173 // If the node is being forced visible by ARIA, ARIA wins. 174 // If the node is being forced visible by ARIA, ARIA wins.
174 if (cvox.AriaUtil.isForcedVisibleRecursive(node)) { 175 if (cvox.AriaUtil.isForcedVisibleRecursive(node)) {
175 return true; 176 return true;
176 } 177 }
177 178
178 // Confirm that no subtree containing node is invisible. 179 // Confirm that no subtree containing node is invisible.
179 if (checkAncestors && 180 if (checkAncestors && cvox.DomUtil.hasInvisibleAncestor_(node)) {
180 cvox.DomUtil.hasInvisibleAncestor_(node)) {
181 return false; 181 return false;
182 } 182 }
183 183
184 // If the node's subtree has a visible node, we declare it as visible. 184 // If the node's subtree has a visible node, we declare it as visible.
185 if (cvox.DomUtil.hasVisibleNodeSubtree_(node, checkDescendants)) { 185 if (cvox.DomUtil.hasVisibleNodeSubtree_(node, checkDescendants)) {
186 return true; 186 return true;
187 } 187 }
188 188
189 return false; 189 return false;
190 }; 190 };
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
226 * Sometimes we already have information about the descendants, and we do 226 * Sometimes we already have information about the descendants, and we do
227 * not need to check them again. 227 * not need to check them again.
228 * @return {boolean} True if the subtree contains a visible node. 228 * @return {boolean} True if the subtree contains a visible node.
229 * @private 229 * @private
230 */ 230 */
231 cvox.DomUtil.hasVisibleNodeSubtree_ = function(root, recursive) { 231 cvox.DomUtil.hasVisibleNodeSubtree_ = function(root, recursive) {
232 if (!(root instanceof Element)) { 232 if (!(root instanceof Element)) {
233 if (!root.parentElement) { 233 if (!root.parentElement) {
234 return false; 234 return false;
235 } 235 }
236 var parentStyle = document.defaultView 236 var parentStyle =
237 .getComputedStyle(root.parentElement, null); 237 document.defaultView.getComputedStyle(root.parentElement, null);
238 var isVisibleParent = !cvox.DomUtil.isInvisibleStyle(parentStyle); 238 var isVisibleParent = !cvox.DomUtil.isInvisibleStyle(parentStyle);
239 return isVisibleParent; 239 return isVisibleParent;
240 } 240 }
241 241
242 var rootStyle = document.defaultView.getComputedStyle(root, null); 242 var rootStyle = document.defaultView.getComputedStyle(root, null);
243 var isRootVisible = !cvox.DomUtil.isInvisibleStyle(rootStyle); 243 var isRootVisible = !cvox.DomUtil.isInvisibleStyle(rootStyle);
244 if (isRootVisible) { 244 if (isRootVisible) {
245 return true; 245 return true;
246 } 246 }
247 var isSubtreeInvisible = cvox.DomUtil.isInvisibleStyle(rootStyle, true); 247 var isSubtreeInvisible = cvox.DomUtil.isInvisibleStyle(rootStyle, true);
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
343 * @param {boolean=} opt_allowHidden Allows hidden nodes during descent. 343 * @param {boolean=} opt_allowHidden Allows hidden nodes during descent.
344 * @return {boolean} True if the node is a leaf node. 344 * @return {boolean} True if the node is a leaf node.
345 */ 345 */
346 cvox.DomUtil.isLeafNode = function(node, opt_allowHidden) { 346 cvox.DomUtil.isLeafNode = function(node, opt_allowHidden) {
347 // If it's not an Element, then it's a leaf if it has no first child. 347 // If it's not an Element, then it's a leaf if it has no first child.
348 if (!(node instanceof Element)) { 348 if (!(node instanceof Element)) {
349 return (node.firstChild == null); 349 return (node.firstChild == null);
350 } 350 }
351 351
352 // Now we know for sure it's an element. 352 // Now we know for sure it's an element.
353 var element = /** @type {Element} */(node); 353 var element = /** @type {Element} */ (node);
354 if (!opt_allowHidden && 354 if (!opt_allowHidden &&
355 !cvox.DomUtil.isVisible(element, {checkAncestors: false})) { 355 !cvox.DomUtil.isVisible(element, {checkAncestors: false})) {
356 return true; 356 return true;
357 } 357 }
358 if (!opt_allowHidden && cvox.AriaUtil.isHidden(element)) { 358 if (!opt_allowHidden && cvox.AriaUtil.isHidden(element)) {
359 return true; 359 return true;
360 } 360 }
361 if (cvox.AriaUtil.isLeafElement(element)) { 361 if (cvox.AriaUtil.isLeafElement(element)) {
362 return true; 362 return true;
363 } 363 }
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
401 * @param {Node} node The node to be checked. 401 * @param {Node} node The node to be checked.
402 * @param {?string} tagName The tag to check for, or null if the tag 402 * @param {?string} tagName The tag to check for, or null if the tag
403 * doesn't matter. 403 * doesn't matter.
404 * @param {?string=} className The class to check for, or null if the class 404 * @param {?string=} className The class to check for, or null if the class
405 * doesn't matter. 405 * doesn't matter.
406 * @return {boolean} True if the node or one of its ancestor has the specified 406 * @return {boolean} True if the node or one of its ancestor has the specified
407 * tag. 407 * tag.
408 */ 408 */
409 cvox.DomUtil.isDescendantOf = function(node, tagName, className) { 409 cvox.DomUtil.isDescendantOf = function(node, tagName, className) {
410 while (node) { 410 while (node) {
411 411 if (tagName && className && (node.tagName && (node.tagName == tagName)) &&
412 if (tagName && className &&
413 (node.tagName && (node.tagName == tagName)) &&
414 (node.className && (node.className == className))) { 412 (node.className && (node.className == className))) {
415 return true; 413 return true;
416 } else if (tagName && !className && 414 } else if (
417 (node.tagName && (node.tagName == tagName))) { 415 tagName && !className && (node.tagName && (node.tagName == tagName))) {
418 return true; 416 return true;
419 } else if (!tagName && className && 417 } else if (
420 (node.className && (node.className == className))) { 418 !tagName && className &&
419 (node.className && (node.className == className))) {
421 return true; 420 return true;
422 } 421 }
423 node = node.parentNode; 422 node = node.parentNode;
424 } 423 }
425 return false; 424 return false;
426 }; 425 };
427 426
428 427
429 /** 428 /**
430 * Determines whether or not a node is or is the descendant of another node. 429 * Determines whether or not a node is or is the descendant of another node.
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
466 * @private 465 * @private
467 */ 466 */
468 cvox.DomUtil.getBaseLabel_ = function(node, recursive, includeControls) { 467 cvox.DomUtil.getBaseLabel_ = function(node, recursive, includeControls) {
469 var label = ''; 468 var label = '';
470 if (node.hasAttribute) { 469 if (node.hasAttribute) {
471 if (node.hasAttribute('aria-labelledby')) { 470 if (node.hasAttribute('aria-labelledby')) {
472 var labelNodeIds = node.getAttribute('aria-labelledby').split(' '); 471 var labelNodeIds = node.getAttribute('aria-labelledby').split(' ');
473 for (var labelNodeId, i = 0; labelNodeId = labelNodeIds[i]; i++) { 472 for (var labelNodeId, i = 0; labelNodeId = labelNodeIds[i]; i++) {
474 var labelNode = document.getElementById(labelNodeId); 473 var labelNode = document.getElementById(labelNodeId);
475 if (labelNode) { 474 if (labelNode) {
476 label += ' ' + cvox.DomUtil.getName( 475 label += ' ' +
477 labelNode, true, includeControls, true); 476 cvox.DomUtil.getName(labelNode, true, includeControls, true);
478 } 477 }
479 } 478 }
480 } else if (node.hasAttribute('aria-label')) { 479 } else if (node.hasAttribute('aria-label')) {
481 label = node.getAttribute('aria-label'); 480 label = node.getAttribute('aria-label');
482 } else if (node.constructor == HTMLImageElement) { 481 } else if (node.constructor == HTMLImageElement) {
483 label = cvox.DomUtil.getImageTitle(node); 482 label = cvox.DomUtil.getImageTitle(node);
484 } else if (node.tagName == 'FIELDSET') { 483 } else if (node.tagName == 'FIELDSET') {
485 // Other labels will trump fieldset legend with this implementation. 484 // Other labels will trump fieldset legend with this implementation.
486 // Depending on how this works out on the web, we may later switch this 485 // Depending on how this works out on the web, we may later switch this
487 // to appending the fieldset legend to any existing label. 486 // to appending the fieldset legend to any existing label.
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
580 * Determines if a node has a name obtained from concatinating the names of its 579 * Determines if a node has a name obtained from concatinating the names of its
581 * children. 580 * children.
582 * @param {!Node} node The node under consideration. 581 * @param {!Node} node The node under consideration.
583 * @param {boolean=} opt_allowHidden Allows hidden nodes in name computation. 582 * @param {boolean=} opt_allowHidden Allows hidden nodes in name computation.
584 * @return {boolean} True if node has name based on children. 583 * @return {boolean} True if node has name based on children.
585 * @private 584 * @private
586 */ 585 */
587 cvox.DomUtil.hasChildrenBasedName_ = function(node, opt_allowHidden) { 586 cvox.DomUtil.hasChildrenBasedName_ = function(node, opt_allowHidden) {
588 if (!!cvox.DomPredicates.linkPredicate([node]) || 587 if (!!cvox.DomPredicates.linkPredicate([node]) ||
589 !!cvox.DomPredicates.headingPredicate([node]) || 588 !!cvox.DomPredicates.headingPredicate([node]) ||
590 node.tagName == 'BUTTON' || 589 node.tagName == 'BUTTON' || cvox.AriaUtil.isControlWidget(node) ||
591 cvox.AriaUtil.isControlWidget(node) ||
592 !cvox.DomUtil.isLeafNode(node, opt_allowHidden)) { 590 !cvox.DomUtil.isLeafNode(node, opt_allowHidden)) {
593 return true; 591 return true;
594 } else { 592 } else {
595 return false; 593 return false;
596 } 594 }
597 }; 595 };
598 596
599 /** 597 /**
600 * Get the name of a node: this includes all static text content and any 598 * Get the name of a node: this includes all static text content and any
601 * HTML-author-specified label, title, alt text, aria-label, etc. - but 599 * HTML-author-specified label, title, alt text, aria-label, etc. - but
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
658 return placeholder; 656 return placeholder;
659 } 657 }
660 } 658 }
661 659
662 if (label.length > 0) { 660 if (label.length > 0) {
663 return label; 661 return label;
664 } 662 }
665 663
666 // Fall back to naming via title only if there is no text content. 664 // Fall back to naming via title only if there is no text content.
667 if (cvox.DomUtil.collapseWhitespace(node.textContent).length == 0 && 665 if (cvox.DomUtil.collapseWhitespace(node.textContent).length == 0 &&
668 node.hasAttribute && 666 node.hasAttribute && node.hasAttribute('title')) {
669 node.hasAttribute('title')) {
670 return node.getAttribute('title'); 667 return node.getAttribute('title');
671 } 668 }
672 669
673 if (!recursive) { 670 if (!recursive) {
674 return ''; 671 return '';
675 } 672 }
676 673
677 if (cvox.AriaUtil.isCompositeControl(node)) { 674 if (cvox.AriaUtil.isCompositeControl(node)) {
678 return ''; 675 return '';
679 } 676 }
(...skipping 22 matching lines...) Expand all
702 var name = ''; 699 var name = '';
703 var delimiter = ''; 700 var delimiter = '';
704 for (var i = 0; i < node.childNodes.length; i++) { 701 for (var i = 0; i < node.childNodes.length; i++) {
705 var child = node.childNodes[i]; 702 var child = node.childNodes[i];
706 var prevChild = node.childNodes[i - 1] || child; 703 var prevChild = node.childNodes[i - 1] || child;
707 if (!includeControls && cvox.DomUtil.isControl(child)) { 704 if (!includeControls && cvox.DomUtil.isControl(child)) {
708 continue; 705 continue;
709 } 706 }
710 var isVisible = cvox.DomUtil.isVisible(child, {checkAncestors: false}); 707 var isVisible = cvox.DomUtil.isVisible(child, {checkAncestors: false});
711 if (opt_allowHidden || (isVisible && !cvox.AriaUtil.isHidden(child))) { 708 if (opt_allowHidden || (isVisible && !cvox.AriaUtil.isHidden(child))) {
712 delimiter = (prevChild.tagName == 'SPAN' || 709 delimiter = (prevChild.tagName == 'SPAN' || child.tagName == 'SPAN' ||
713 child.tagName == 'SPAN' ||
714 child.parentNode.tagName == 'SPAN') ? 710 child.parentNode.tagName == 'SPAN') ?
715 '' : ' '; 711 '' :
712 ' ';
716 name += delimiter + cvox.DomUtil.getName(child, true, includeControls); 713 name += delimiter + cvox.DomUtil.getName(child, true, includeControls);
717 } 714 }
718 } 715 }
719 716
720 return name; 717 return name;
721 }; 718 };
722 719
723 /** 720 /**
724 * Get any prefix text for the given node. 721 * Get any prefix text for the given node.
725 * This includes list style text for the leftmost leaf node under a listitem. 722 * This includes list style text for the leftmost leaf node under a listitem.
726 * @param {Node} node Compute prefix for this node. 723 * @param {Node} node Compute prefix for this node.
727 * @param {number=} opt_index Starting offset into the given node's text. 724 * @param {number=} opt_index Starting offset into the given node's text.
728 * @return {string} Prefix text, if any. 725 * @return {string} Prefix text, if any.
729 */ 726 */
730 cvox.DomUtil.getPrefixText = function(node, opt_index) { 727 cvox.DomUtil.getPrefixText = function(node, opt_index) {
731 opt_index = opt_index || 0; 728 opt_index = opt_index || 0;
732 729
733 // Generate list style text. 730 // Generate list style text.
734 var ancestors = cvox.DomUtil.getAncestors(node); 731 var ancestors = cvox.DomUtil.getAncestors(node);
735 var prefix = ''; 732 var prefix = '';
736 var firstListitem = cvox.DomPredicates.listItemPredicate(ancestors); 733 var firstListitem = cvox.DomPredicates.listItemPredicate(ancestors);
737 734
738 var leftmost = firstListitem; 735 var leftmost = firstListitem;
739 while (leftmost && leftmost.firstChild) { 736 while (leftmost && leftmost.firstChild) {
740 leftmost = leftmost.firstChild; 737 leftmost = leftmost.firstChild;
741 } 738 }
742 739
743 // Do nothing if we're not at the leftmost leaf. 740 // Do nothing if we're not at the leftmost leaf.
744 if (firstListitem && 741 if (firstListitem && firstListitem.parentNode && opt_index == 0 &&
745 firstListitem.parentNode && 742 firstListitem.parentNode.tagName == 'OL' && node == leftmost &&
746 opt_index == 0 &&
747 firstListitem.parentNode.tagName == 'OL' &&
748 node == leftmost &&
749 document.defaultView.getComputedStyle(firstListitem.parentElement) 743 document.defaultView.getComputedStyle(firstListitem.parentElement)
750 .listStyleType != 'none') { 744 .listStyleType != 'none') {
751 var items = cvox.DomUtil.toArray(firstListitem.parentNode.children).filter( 745 var items = cvox.DomUtil.toArray(firstListitem.parentNode.children)
752 function(li) { return li.tagName == 'LI'; }); 746 .filter(function(li) {
747 return li.tagName == 'LI';
748 });
753 var position = items.indexOf(firstListitem) + 1; 749 var position = items.indexOf(firstListitem) + 1;
754 // TODO(dtseng): Support all list style types. 750 // TODO(dtseng): Support all list style types.
755 if (document.defaultView.getComputedStyle( 751 if (document.defaultView.getComputedStyle(firstListitem.parentElement)
756 firstListitem.parentElement).listStyleType.indexOf('latin') != -1) { 752 .listStyleType.indexOf('latin') != -1) {
757 position--; 753 position--;
758 prefix = String.fromCharCode('A'.charCodeAt(0) + position % 26); 754 prefix = String.fromCharCode('A'.charCodeAt(0) + position % 26);
759 } else { 755 } else {
760 prefix = position; 756 prefix = position;
761 } 757 }
762 prefix += '. '; 758 prefix += '. ';
763 } 759 }
764 return prefix; 760 return prefix;
765 }; 761 };
766 762
767 763
768 /** 764 /**
769 * Use heuristics to guess at the label of a control, to be used if one 765 * Use heuristics to guess at the label of a control, to be used if one
770 * is not explicitly set in the DOM. This is useful when a control 766 * is not explicitly set in the DOM. This is useful when a control
771 * field gets focus, but probably not useful when browsing the page 767 * field gets focus, but probably not useful when browsing the page
772 * element at a time. 768 * element at a time.
773 * @param {Node} node The node to get the label from. 769 * @param {Node} node The node to get the label from.
774 * @return {string} The name of the control, using heuristics. 770 * @return {string} The name of the control, using heuristics.
775 */ 771 */
776 cvox.DomUtil.getControlLabelHeuristics = function(node) { 772 cvox.DomUtil.getControlLabelHeuristics = function(node) {
777 // If the node explicitly has aria-label or title set to '', 773 // If the node explicitly has aria-label or title set to '',
778 // treat it the same way as alt='' and do not guess - just assume 774 // treat it the same way as alt='' and do not guess - just assume
779 // the web developer knew what they were doing and wanted 775 // the web developer knew what they were doing and wanted
780 // no title/label for that control. 776 // no title/label for that control.
781 if (node.hasAttribute && 777 if (node.hasAttribute &&
782 ((node.hasAttribute('aria-label') && 778 ((node.hasAttribute('aria-label') &&
783 (node.getAttribute('aria-label') == '')) || 779 (node.getAttribute('aria-label') == '')) ||
784 (node.hasAttribute('aria-title') && 780 (node.hasAttribute('aria-title') &&
785 (node.getAttribute('aria-title') == '')))) { 781 (node.getAttribute('aria-title') == '')))) {
786 return ''; 782 return '';
787 } 783 }
788 784
789 // TODO (clchen, rshearer): Implement heuristics for getting the label 785 // TODO (clchen, rshearer): Implement heuristics for getting the label
790 // information from the table headers once the code for getting table 786 // information from the table headers once the code for getting table
791 // headers quickly is implemented. 787 // headers quickly is implemented.
792 788
793 // If no description has been found yet and heuristics are enabled, 789 // If no description has been found yet and heuristics are enabled,
794 // then try getting the content from the closest node. 790 // then try getting the content from the closest node.
795 var prevNode = cvox.DomUtil.previousLeafNode(node); 791 var prevNode = cvox.DomUtil.previousLeafNode(node);
796 var prevTraversalCount = 0; 792 var prevTraversalCount = 0;
797 while (prevNode && (!cvox.DomUtil.hasContent(prevNode) || 793 while (prevNode &&
798 cvox.DomUtil.isControl(prevNode))) { 794 (!cvox.DomUtil.hasContent(prevNode) ||
795 cvox.DomUtil.isControl(prevNode))) {
799 prevNode = cvox.DomUtil.previousLeafNode(prevNode); 796 prevNode = cvox.DomUtil.previousLeafNode(prevNode);
800 prevTraversalCount++; 797 prevTraversalCount++;
801 } 798 }
802 var nextNode = cvox.DomUtil.directedNextLeafNode(node); 799 var nextNode = cvox.DomUtil.directedNextLeafNode(node);
803 var nextTraversalCount = 0; 800 var nextTraversalCount = 0;
804 while (nextNode && (!cvox.DomUtil.hasContent(nextNode) || 801 while (nextNode &&
805 cvox.DomUtil.isControl(nextNode))) { 802 (!cvox.DomUtil.hasContent(nextNode) ||
803 cvox.DomUtil.isControl(nextNode))) {
806 nextNode = cvox.DomUtil.directedNextLeafNode(nextNode); 804 nextNode = cvox.DomUtil.directedNextLeafNode(nextNode);
807 nextTraversalCount++; 805 nextTraversalCount++;
808 } 806 }
809 var guessedLabelNode; 807 var guessedLabelNode;
810 if (prevNode && nextNode) { 808 if (prevNode && nextNode) {
811 var parentNode = node; 809 var parentNode = node;
812 // Count the number of parent nodes until there is a shared parent; the 810 // Count the number of parent nodes until there is a shared parent; the
813 // label is most likely in the same branch of the DOM as the control. 811 // label is most likely in the same branch of the DOM as the control.
814 // TODO (chaitanyag): Try to generalize this algorithm and move it to 812 // TODO (chaitanyag): Try to generalize this algorithm and move it to
815 // its own function in DOM Utils. 813 // its own function in DOM Utils.
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
856 */ 854 */
857 cvox.DomUtil.getValue = function(node) { 855 cvox.DomUtil.getValue = function(node) {
858 var activeDescendant = cvox.AriaUtil.getActiveDescendant(node); 856 var activeDescendant = cvox.AriaUtil.getActiveDescendant(node);
859 if (activeDescendant) { 857 if (activeDescendant) {
860 return cvox.DomUtil.collapseWhitespace( 858 return cvox.DomUtil.collapseWhitespace(
861 cvox.DomUtil.getValue(activeDescendant) + ' ' + 859 cvox.DomUtil.getValue(activeDescendant) + ' ' +
862 cvox.DomUtil.getName(activeDescendant)); 860 cvox.DomUtil.getName(activeDescendant));
863 } 861 }
864 862
865 if (node.constructor == HTMLSelectElement) { 863 if (node.constructor == HTMLSelectElement) {
866 node = /** @type {HTMLSelectElement} */(node); 864 node = /** @type {HTMLSelectElement} */ (node);
867 var value = ''; 865 var value = '';
868 var start = node.selectedOptions ? node.selectedOptions[0] : null; 866 var start = node.selectedOptions ? node.selectedOptions[0] : null;
869 var end = node.selectedOptions ? 867 var end = node.selectedOptions ?
870 node.selectedOptions[node.selectedOptions.length - 1] : null; 868 node.selectedOptions[node.selectedOptions.length - 1] :
869 null;
871 // TODO(dtseng): Keeping this stateless means we describe the start and end 870 // TODO(dtseng): Keeping this stateless means we describe the start and end
872 // of the selection only since we don't know which was added or 871 // of the selection only since we don't know which was added or
873 // removed. Once we keep the previous selection, we can read the diff. 872 // removed. Once we keep the previous selection, we can read the diff.
874 if (start && end && start != end) { 873 if (start && end && start != end) {
875 value = Msgs.getMsg( 874 value = Msgs.getMsg('selected_options_value', [start.text, end.text]);
876 'selected_options_value', [start.text, end.text]);
877 } else if (start) { 875 } else if (start) {
878 value = start.text + ''; 876 value = start.text + '';
879 } 877 }
880 return value; 878 return value;
881 } 879 }
882 880
883 if (node.constructor == HTMLTextAreaElement) { 881 if (node.constructor == HTMLTextAreaElement) {
884 return node.value; 882 return node.value;
885 } 883 }
886 884
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
920 */ 918 */
921 cvox.DomUtil.getImageTitle = function(node) { 919 cvox.DomUtil.getImageTitle = function(node) {
922 var text; 920 var text;
923 if (node.hasAttribute('alt')) { 921 if (node.hasAttribute('alt')) {
924 text = node.alt; 922 text = node.alt;
925 } else if (node.hasAttribute('title')) { 923 } else if (node.hasAttribute('title')) {
926 text = node.title; 924 text = node.title;
927 } else { 925 } else {
928 var url = node.src; 926 var url = node.src;
929 if (url.substring(0, 4) != 'data') { 927 if (url.substring(0, 4) != 'data') {
930 var filename = url.substring( 928 var filename =
931 url.lastIndexOf('/') + 1, url.lastIndexOf('.')); 929 url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
932 930
933 // Hack to not speak the filename if it's ridiculously long. 931 // Hack to not speak the filename if it's ridiculously long.
934 if (filename.length >= 1 && filename.length <= 16) { 932 if (filename.length >= 1 && filename.length <= 16) {
935 text = filename + ' Image'; 933 text = filename + ' Image';
936 } else { 934 } else {
937 text = 'Image'; 935 text = 'Image';
938 } 936 }
939 } else { 937 } else {
940 text = 'Image'; 938 text = 'Image';
941 } 939 }
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after
1079 // when the control is reached. 1077 // when the control is reached.
1080 var enclosingLabel = node.parentElement; 1078 var enclosingLabel = node.parentElement;
1081 while (enclosingLabel && enclosingLabel.tagName != 'LABEL') { 1079 while (enclosingLabel && enclosingLabel.tagName != 'LABEL') {
1082 enclosingLabel = enclosingLabel.parentElement; 1080 enclosingLabel = enclosingLabel.parentElement;
1083 } 1081 }
1084 if (enclosingLabel) { 1082 if (enclosingLabel) {
1085 var embeddedControl = enclosingLabel.querySelector(controlQuery); 1083 var embeddedControl = enclosingLabel.querySelector(controlQuery);
1086 if (enclosingLabel.hasAttribute('for')) { 1084 if (enclosingLabel.hasAttribute('for')) {
1087 var targetId = enclosingLabel.getAttribute('for'); 1085 var targetId = enclosingLabel.getAttribute('for');
1088 var targetNode = document.getElementById(targetId); 1086 var targetNode = document.getElementById(targetId);
1089 if (targetNode && 1087 if (targetNode && cvox.DomUtil.isControl(targetNode) &&
1090 cvox.DomUtil.isControl(targetNode) &&
1091 !embeddedControl) { 1088 !embeddedControl) {
1092 return false; 1089 return false;
1093 } 1090 }
1094 } else if (embeddedControl) { 1091 } else if (embeddedControl) {
1095 return false; 1092 return false;
1096 } 1093 }
1097 } 1094 }
1098 1095
1099 // Skip any non-control content inside of a legend if the legend is correctly 1096 // Skip any non-control content inside of a legend if the legend is correctly
1100 // nested within a fieldset. The legend text will get spoken when the fieldset 1097 // nested within a fieldset. The legend text will get spoken when the fieldset
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1139 1136
1140 if (cvox.DomUtil.isFocusable(node)) { 1137 if (cvox.DomUtil.isFocusable(node)) {
1141 return true; 1138 return true;
1142 } 1139 }
1143 1140
1144 // Skip anything referenced by another element on the page 1141 // Skip anything referenced by another element on the page
1145 // via aria-labelledby. 1142 // via aria-labelledby.
1146 var labelledByTargets = cvox.DomUtil.getLabelledByTargets(); 1143 var labelledByTargets = cvox.DomUtil.getLabelledByTargets();
1147 var enclosingNodeWithId = node; 1144 var enclosingNodeWithId = node;
1148 while (enclosingNodeWithId) { 1145 while (enclosingNodeWithId) {
1149 if (enclosingNodeWithId.id && 1146 if (enclosingNodeWithId.id && labelledByTargets[enclosingNodeWithId.id]) {
1150 labelledByTargets[enclosingNodeWithId.id]) {
1151 // If we got here, some element on this page has an aria-labelledby 1147 // If we got here, some element on this page has an aria-labelledby
1152 // attribute listing this node as its id. As long as that "some" element 1148 // attribute listing this node as its id. As long as that "some" element
1153 // is not this element, we should return false, indicating this element 1149 // is not this element, we should return false, indicating this element
1154 // should be skipped. 1150 // should be skipped.
1155 var attrValue = enclosingNodeWithId.getAttribute('aria-labelledby'); 1151 var attrValue = enclosingNodeWithId.getAttribute('aria-labelledby');
1156 if (attrValue) { 1152 if (attrValue) {
1157 var ids = attrValue.split(/ +/); 1153 var ids = attrValue.split(/ +/);
1158 if (ids.indexOf(enclosingNodeWithId.id) == -1) { 1154 if (ids.indexOf(enclosingNodeWithId.id) == -1) {
1159 return false; 1155 return false;
1160 } 1156 }
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
1233 * @param {Node} currentNode The current node. 1229 * @param {Node} currentNode The current node.
1234 * @param {boolean=} opt_fallback True returns node's ancestors in the case 1230 * @param {boolean=} opt_fallback True returns node's ancestors in the case
1235 * where node's ancestors is a subset of previousNode's ancestors. 1231 * where node's ancestors is a subset of previousNode's ancestors.
1236 * @return {Array<Node>} An array of unique ancestors for the current node 1232 * @return {Array<Node>} An array of unique ancestors for the current node
1237 * (inclusive). 1233 * (inclusive).
1238 */ 1234 */
1239 cvox.DomUtil.getUniqueAncestors = function( 1235 cvox.DomUtil.getUniqueAncestors = function(
1240 previousNode, currentNode, opt_fallback) { 1236 previousNode, currentNode, opt_fallback) {
1241 var prevAncestors = cvox.DomUtil.getAncestors(previousNode); 1237 var prevAncestors = cvox.DomUtil.getAncestors(previousNode);
1242 var currentAncestors = cvox.DomUtil.getAncestors(currentNode); 1238 var currentAncestors = cvox.DomUtil.getAncestors(currentNode);
1243 var divergence = cvox.DomUtil.compareAncestors(prevAncestors, 1239 var divergence =
1244 currentAncestors); 1240 cvox.DomUtil.compareAncestors(prevAncestors, currentAncestors);
1245 var diff = currentAncestors.slice(divergence); 1241 var diff = currentAncestors.slice(divergence);
1246 return (diff.length == 0 && opt_fallback) ? currentAncestors : diff; 1242 return (diff.length == 0 && opt_fallback) ? currentAncestors : diff;
1247 }; 1243 };
1248 1244
1249 1245
1250 /** 1246 /**
1251 * Returns a role message identifier for a node. 1247 * Returns a role message identifier for a node.
1252 * For a localized string, see cvox.DomUtil.getRole. 1248 * For a localized string, see cvox.DomUtil.getRole.
1253 * @param {Node} targetNode The node to get the role name for. 1249 * @param {Node} targetNode The node to get the role name for.
1254 * @param {number} verbosity The verbosity setting to use. 1250 * @param {number} verbosity The verbosity setting to use.
1255 * @return {string} The role message identifier for the targetNode. 1251 * @return {string} The role message identifier for the targetNode.
1256 */ 1252 */
1257 cvox.DomUtil.getRoleMsg = function(targetNode, verbosity) { 1253 cvox.DomUtil.getRoleMsg = function(targetNode, verbosity) {
1258 var info; 1254 var info;
1259 info = cvox.AriaUtil.getRoleNameMsg(targetNode); 1255 info = cvox.AriaUtil.getRoleNameMsg(targetNode);
1260 if (!info) { 1256 if (!info) {
1261 if (targetNode.tagName == 'INPUT') { 1257 if (targetNode.tagName == 'INPUT') {
1262 info = cvox.DomUtil.INPUT_TYPE_TO_INFORMATION_TABLE_MSG[targetNode.type]; 1258 info = cvox.DomUtil.INPUT_TYPE_TO_INFORMATION_TABLE_MSG[targetNode.type];
1263 } else if (targetNode.tagName == 'A' && 1259 } else if (
1264 cvox.DomUtil.isInternalLink(targetNode)) { 1260 targetNode.tagName == 'A' && cvox.DomUtil.isInternalLink(targetNode)) {
1265 info = 'internal_link'; 1261 info = 'internal_link';
1266 } else if (targetNode.tagName == 'A' && 1262 } else if (
1267 targetNode.getAttribute('href') && 1263 targetNode.tagName == 'A' && targetNode.getAttribute('href') &&
1268 cvox.ChromeVox.visitedUrls[targetNode.href]) { 1264 cvox.ChromeVox.visitedUrls[targetNode.href]) {
1269 info = 'visited_link'; 1265 info = 'visited_link';
1270 } else if (targetNode.tagName == 'A' && 1266 } else if (targetNode.tagName == 'A' && targetNode.getAttribute('name')) {
1271 targetNode.getAttribute('name')) { 1267 info = ''; // Don't want to add any role to anchors.
1272 info = ''; // Don't want to add any role to anchors.
1273 } else if (targetNode.isContentEditable) { 1268 } else if (targetNode.isContentEditable) {
1274 info = 'input_type_text'; 1269 info = 'input_type_text';
1275 } else if (cvox.DomUtil.isMath(targetNode)) { 1270 } else if (cvox.DomUtil.isMath(targetNode)) {
1276 info = 'math_expr'; 1271 info = 'math_expr';
1277 } else if (targetNode.tagName == 'TABLE' && 1272 } else if (
1273 targetNode.tagName == 'TABLE' &&
1278 cvox.DomUtil.isLayoutTable(targetNode)) { 1274 cvox.DomUtil.isLayoutTable(targetNode)) {
1279 info = ''; 1275 info = '';
1280 } else { 1276 } else {
1281 if (verbosity == cvox.VERBOSITY_BRIEF) { 1277 if (verbosity == cvox.VERBOSITY_BRIEF) {
1282 info = 1278 info =
1283 cvox.DomUtil.TAG_TO_INFORMATION_TABLE_BRIEF_MSG[targetNode.tagName]; 1279 cvox.DomUtil.TAG_TO_INFORMATION_TABLE_BRIEF_MSG[targetNode.tagName];
1284 } else { 1280 } else {
1285 info = cvox.DomUtil.TAG_TO_INFORMATION_TABLE_VERBOSE_MSG[ 1281 info = cvox.DomUtil
1286 targetNode.tagName]; 1282 .TAG_TO_INFORMATION_TABLE_VERBOSE_MSG[targetNode.tagName];
1287 1283
1288 if (cvox.DomUtil.hasLongDesc(targetNode)) { 1284 if (cvox.DomUtil.hasLongDesc(targetNode)) {
1289 info = 'image_with_long_desc'; 1285 info = 'image_with_long_desc';
1290 } 1286 }
1291 1287
1292 if (!info && targetNode.onclick) { 1288 if (!info && targetNode.onclick) {
1293 info = 'clickable'; 1289 info = 'clickable';
1294 } 1290 }
1295 } 1291 }
1296 } 1292 }
1297 } 1293 }
1298 1294
1299 return info; 1295 return info;
1300 }; 1296 };
1301 1297
1302 1298
1303 /** 1299 /**
1304 * Returns a string to be presented to the user that identifies what the 1300 * Returns a string to be presented to the user that identifies what the
1305 * targetNode's role is. 1301 * targetNode's role is.
1306 * ARIA roles are given priority; if there is no ARIA role set, the role 1302 * ARIA roles are given priority; if there is no ARIA role set, the role
1307 * will be determined by the HTML tag for the node. 1303 * will be determined by the HTML tag for the node.
1308 * 1304 *
1309 * @param {Node} targetNode The node to get the role name for. 1305 * @param {Node} targetNode The node to get the role name for.
1310 * @param {number} verbosity The verbosity setting to use. 1306 * @param {number} verbosity The verbosity setting to use.
1311 * @return {string} The role name for the targetNode. 1307 * @return {string} The role name for the targetNode.
1312 */ 1308 */
1313 cvox.DomUtil.getRole = function(targetNode, verbosity) { 1309 cvox.DomUtil.getRole = function(targetNode, verbosity) {
1314 var roleMsg = cvox.DomUtil.getRoleMsg(targetNode, verbosity) || ''; 1310 var roleMsg = cvox.DomUtil.getRoleMsg(targetNode, verbosity) || '';
1315 var role = roleMsg && roleMsg != ' ' ? 1311 var role = roleMsg && roleMsg != ' ' ? Msgs.getMsg(roleMsg) : '';
1316 Msgs.getMsg(roleMsg) : '';
1317 return role ? role : roleMsg; 1312 return role ? role : roleMsg;
1318 }; 1313 };
1319 1314
1320 1315
1321 /** 1316 /**
1322 * Count the number of items in a list node. 1317 * Count the number of items in a list node.
1323 * 1318 *
1324 * @param {Node} targetNode The list node. 1319 * @param {Node} targetNode The list node.
1325 * @return {number} The number of items in the list. 1320 * @return {number} The number of items in the list.
1326 */ 1321 */
1327 cvox.DomUtil.getListLength = function(targetNode) { 1322 cvox.DomUtil.getListLength = function(targetNode) {
1328 var count = 0; 1323 var count = 0;
1329 for (var node = targetNode.firstChild; 1324 for (var node = targetNode.firstChild; node; node = node.nextSibling) {
1330 node;
1331 node = node.nextSibling) {
1332 if (cvox.DomUtil.isVisible(node) && 1325 if (cvox.DomUtil.isVisible(node) &&
1333 (node.tagName == 'LI' || 1326 (node.tagName == 'LI' ||
1334 (node.getAttribute && node.getAttribute('role') == 'listitem'))) { 1327 (node.getAttribute && node.getAttribute('role') == 'listitem'))) {
1335 if (node.hasAttribute('aria-setsize')) { 1328 if (node.hasAttribute('aria-setsize')) {
1336 var ariaLength = parseInt(node.getAttribute('aria-setsize'), 10); 1329 var ariaLength = parseInt(node.getAttribute('aria-setsize'), 10);
1337 if (!isNaN(ariaLength)) { 1330 if (!isNaN(ariaLength)) {
1338 return ariaLength; 1331 return ariaLength;
1339 } 1332 }
1340 } 1333 }
1341 count++; 1334 count++;
1342 } 1335 }
1343 } 1336 }
1344 return count; 1337 return count;
(...skipping 20 matching lines...) Expand all
1365 if (!info) { 1358 if (!info) {
1366 info = []; 1359 info = [];
1367 } 1360 }
1368 1361
1369 if (targetNode.tagName == 'INPUT') { 1362 if (targetNode.tagName == 'INPUT') {
1370 if (!targetNode.hasAttribute('aria-checked')) { 1363 if (!targetNode.hasAttribute('aria-checked')) {
1371 var INPUT_MSGS = { 1364 var INPUT_MSGS = {
1372 'checkbox-true': 'checkbox_checked_state', 1365 'checkbox-true': 'checkbox_checked_state',
1373 'checkbox-false': 'checkbox_unchecked_state', 1366 'checkbox-false': 'checkbox_unchecked_state',
1374 'radio-true': 'radio_selected_state', 1367 'radio-true': 'radio_selected_state',
1375 'radio-false': 'radio_unselected_state' }; 1368 'radio-false': 'radio_unselected_state'
1369 };
1376 var msgId = INPUT_MSGS[targetNode.type + '-' + !!targetNode.checked]; 1370 var msgId = INPUT_MSGS[targetNode.type + '-' + !!targetNode.checked];
1377 if (msgId) { 1371 if (msgId) {
1378 info.push([msgId]); 1372 info.push([msgId]);
1379 } 1373 }
1380 } 1374 }
1381 } else if (targetNode.tagName == 'SELECT') { 1375 } else if (targetNode.tagName == 'SELECT') {
1382 if (targetNode.selectedOptions && targetNode.selectedOptions.length <= 1) { 1376 if (targetNode.selectedOptions && targetNode.selectedOptions.length <= 1) {
1383 info.push(['list_position', 1377 info.push([
1384 Msgs.getNumber(targetNode.selectedIndex + 1), 1378 'list_position', Msgs.getNumber(targetNode.selectedIndex + 1),
1385 Msgs.getNumber(targetNode.options.length)]); 1379 Msgs.getNumber(targetNode.options.length)
1380 ]);
1386 } else { 1381 } else {
1387 info.push(['selected_options_state', 1382 info.push([
1388 Msgs.getNumber(targetNode.selectedOptions.length)]); 1383 'selected_options_state',
1384 Msgs.getNumber(targetNode.selectedOptions.length)
1385 ]);
1389 } 1386 }
1390 } else if (targetNode.tagName == 'UL' || 1387 } else if (
1391 targetNode.tagName == 'OL' || 1388 targetNode.tagName == 'UL' || targetNode.tagName == 'OL' ||
1392 role == 'list') { 1389 role == 'list') {
1393 info.push(['list_with_items_not_pluralized', 1390 info.push([
1394 Msgs.getNumber( 1391 'list_with_items_not_pluralized',
1395 cvox.DomUtil.getListLength(targetNode))]); 1392 Msgs.getNumber(cvox.DomUtil.getListLength(targetNode))
1393 ]);
1396 } 1394 }
1397 1395
1398 if (cvox.DomUtil.isDisabled(targetNode)) { 1396 if (cvox.DomUtil.isDisabled(targetNode)) {
1399 info.push(['aria_disabled_true']); 1397 info.push(['aria_disabled_true']);
1400 } 1398 }
1401 1399
1402 if (targetNode.accessKey) { 1400 if (targetNode.accessKey) {
1403 info.push(['access_key', targetNode.accessKey]); 1401 info.push(['access_key', targetNode.accessKey]);
1404 } 1402 }
1405 1403
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1443 // Workaround for http://code.google.com/p/chromium/issues/detail?id=153904 1441 // Workaround for http://code.google.com/p/chromium/issues/detail?id=153904
1444 if ((targetNode.tagName == 'A') && !targetNode.hasAttribute('href') && 1442 if ((targetNode.tagName == 'A') && !targetNode.hasAttribute('href') &&
1445 !targetNode.hasAttribute('tabindex')) { 1443 !targetNode.hasAttribute('tabindex')) {
1446 return false; 1444 return false;
1447 } 1445 }
1448 1446
1449 if (targetNode.tabIndex >= 0) { 1447 if (targetNode.tabIndex >= 0) {
1450 return true; 1448 return true;
1451 } 1449 }
1452 1450
1453 if (targetNode.hasAttribute && 1451 if (targetNode.hasAttribute && targetNode.hasAttribute('tabindex') &&
1454 targetNode.hasAttribute('tabindex') &&
1455 targetNode.getAttribute('tabindex') == '-1') { 1452 targetNode.getAttribute('tabindex') == '-1') {
1456 return true; 1453 return true;
1457 } 1454 }
1458 1455
1459 return false; 1456 return false;
1460 }; 1457 };
1461 1458
1462 1459
1463 /** 1460 /**
1464 * Find a focusable descendant of a given node. This includes nodes whose 1461 * Find a focusable descendant of a given node. This includes nodes whose
(...skipping 20 matching lines...) Expand all
1485 1482
1486 /** 1483 /**
1487 * Returns the number of focusable nodes in root's subtree. The count does not 1484 * Returns the number of focusable nodes in root's subtree. The count does not
1488 * include root. 1485 * include root.
1489 * 1486 *
1490 * @param {Node} targetNode The node whose descendants to check are focusable. 1487 * @param {Node} targetNode The node whose descendants to check are focusable.
1491 * @return {number} The number of focusable descendants. 1488 * @return {number} The number of focusable descendants.
1492 */ 1489 */
1493 cvox.DomUtil.countFocusableDescendants = function(targetNode) { 1490 cvox.DomUtil.countFocusableDescendants = function(targetNode) {
1494 return targetNode ? 1491 return targetNode ?
1495 cvox.DomUtil.countNodes(targetNode, cvox.DomUtil.isFocusable) : 0; 1492 cvox.DomUtil.countNodes(targetNode, cvox.DomUtil.isFocusable) :
1493 0;
1496 }; 1494 };
1497 1495
1498 1496
1499 /** 1497 /**
1500 * Checks if the targetNode is still attached to the document. 1498 * Checks if the targetNode is still attached to the document.
1501 * A node can become detached because of AJAX changes. 1499 * A node can become detached because of AJAX changes.
1502 * 1500 *
1503 * @param {Object} targetNode The node to check. 1501 * @param {Object} targetNode The node to check.
1504 * @return {boolean} True if the targetNode is still attached. 1502 * @return {boolean} True if the targetNode is still attached.
1505 */ 1503 */
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
1555 if (!keepGoing) { 1553 if (!keepGoing) {
1556 // The onclick method ran successfully and returned false, meaning the 1554 // The onclick method ran successfully and returned false, meaning the
1557 // event should not bubble up, so we will return here. 1555 // event should not bubble up, so we will return here.
1558 return; 1556 return;
1559 } 1557 }
1560 } 1558 }
1561 1559
1562 // Send a mousedown (or simply a double click if requested). 1560 // Send a mousedown (or simply a double click if requested).
1563 var evt = document.createEvent('MouseEvents'); 1561 var evt = document.createEvent('MouseEvents');
1564 var evtType = opt_double ? 'dblclick' : 'mousedown'; 1562 var evtType = opt_double ? 'dblclick' : 'mousedown';
1565 evt.initMouseEvent(evtType, true, true, document.defaultView, 1563 evt.initMouseEvent(
1566 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); 1564 evtType, true, true, document.defaultView, 1, 0, 0, 0, 0, false, false,
1565 shiftKey, false, 0, null);
1567 // Unless asked not to, Mark any events we generate so we don't try to 1566 // Unless asked not to, Mark any events we generate so we don't try to
1568 // process our own events. 1567 // process our own events.
1569 evt.fromCvox = !opt_handleOwnEvents; 1568 evt.fromCvox = !opt_handleOwnEvents;
1570 try { 1569 try {
1571 targetNode.dispatchEvent(evt); 1570 targetNode.dispatchEvent(evt);
1572 } catch (e) {} 1571 } catch (e) {
1573 //Send a mouse up 1572 }
1573 // Send a mouse up
1574 evt = document.createEvent('MouseEvents'); 1574 evt = document.createEvent('MouseEvents');
1575 evt.initMouseEvent('mouseup', true, true, document.defaultView, 1575 evt.initMouseEvent(
1576 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); 1576 'mouseup', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false,
1577 shiftKey, false, 0, null);
1577 evt.fromCvox = !opt_handleOwnEvents; 1578 evt.fromCvox = !opt_handleOwnEvents;
1578 try { 1579 try {
1579 targetNode.dispatchEvent(evt); 1580 targetNode.dispatchEvent(evt);
1580 } catch (e) {} 1581 } catch (e) {
1581 //Send a click 1582 }
1583 // Send a click
1582 evt = document.createEvent('MouseEvents'); 1584 evt = document.createEvent('MouseEvents');
1583 evt.initMouseEvent('click', true, true, document.defaultView, 1585 evt.initMouseEvent(
1584 1, 0, 0, 0, 0, false, false, shiftKey, false, 0, null); 1586 'click', true, true, document.defaultView, 1, 0, 0, 0, 0, false, false,
1587 shiftKey, false, 0, null);
1585 evt.fromCvox = !opt_handleOwnEvents; 1588 evt.fromCvox = !opt_handleOwnEvents;
1586 try { 1589 try {
1587 targetNode.dispatchEvent(evt); 1590 targetNode.dispatchEvent(evt);
1588 } catch (e) {} 1591 } catch (e) {
1592 }
1589 1593
1590 if (cvox.DomUtil.isInternalLink(targetNode)) { 1594 if (cvox.DomUtil.isInternalLink(targetNode)) {
1591 cvox.DomUtil.syncInternalLink(targetNode); 1595 cvox.DomUtil.syncInternalLink(targetNode);
1592 } 1596 }
1593 }; 1597 };
1594 1598
1595 1599
1596 /** 1600 /**
1597 * Syncs to an internal link. 1601 * Syncs to an internal link.
1598 * @param {Node} node A link whose href's target we want to sync. 1602 * @param {Node} node A link whose href's target we want to sync.
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
1652 /** 1656 /**
1653 * Given a node, returns true if it's a control. Controls are *not necessarily* 1657 * Given a node, returns true if it's a control. Controls are *not necessarily*
1654 * leaf-level given that some composite controls may have focusable children 1658 * leaf-level given that some composite controls may have focusable children
1655 * if they are managing focus with tabindex: 1659 * if they are managing focus with tabindex:
1656 * ( http://www.w3.org/TR/2010/WD-wai-aria-practices-20100916/#visualfocus ). 1660 * ( http://www.w3.org/TR/2010/WD-wai-aria-practices-20100916/#visualfocus ).
1657 * 1661 *
1658 * @param {Node} node The node to check. 1662 * @param {Node} node The node to check.
1659 * @return {boolean} True if the node is a control. 1663 * @return {boolean} True if the node is a control.
1660 */ 1664 */
1661 cvox.DomUtil.isControl = function(node) { 1665 cvox.DomUtil.isControl = function(node) {
1662 if (cvox.AriaUtil.isControlWidget(node) && 1666 if (cvox.AriaUtil.isControlWidget(node) && cvox.DomUtil.isFocusable(node)) {
1663 cvox.DomUtil.isFocusable(node)) {
1664 return true; 1667 return true;
1665 } 1668 }
1666 if (node.tagName) { 1669 if (node.tagName) {
1667 switch (node.tagName) { 1670 switch (node.tagName) {
1668 case 'BUTTON': 1671 case 'BUTTON':
1669 case 'TEXTAREA': 1672 case 'TEXTAREA':
1670 case 'SELECT': 1673 case 'SELECT':
1671 return true; 1674 return true;
1672 case 'INPUT': 1675 case 'INPUT':
1673 return node.type != 'hidden'; 1676 return node.type != 'hidden';
(...skipping 10 matching lines...) Expand all
1684 * Given a node, returns true if it's a leaf-level control. This includes 1687 * Given a node, returns true if it's a leaf-level control. This includes
1685 * composite controls thare are managing focus for children with 1688 * composite controls thare are managing focus for children with
1686 * activedescendant, but not composite controls with focusable children: 1689 * activedescendant, but not composite controls with focusable children:
1687 * ( http://www.w3.org/TR/2010/WD-wai-aria-practices-20100916/#visualfocus ). 1690 * ( http://www.w3.org/TR/2010/WD-wai-aria-practices-20100916/#visualfocus ).
1688 * 1691 *
1689 * @param {Node} node The node to check. 1692 * @param {Node} node The node to check.
1690 * @return {boolean} True if the node is a leaf-level control. 1693 * @return {boolean} True if the node is a leaf-level control.
1691 */ 1694 */
1692 cvox.DomUtil.isLeafLevelControl = function(node) { 1695 cvox.DomUtil.isLeafLevelControl = function(node) {
1693 if (cvox.DomUtil.isControl(node)) { 1696 if (cvox.DomUtil.isControl(node)) {
1694 return !(cvox.AriaUtil.isCompositeControl(node) && 1697 return !(
1695 cvox.DomUtil.findFocusableDescendant(node)); 1698 cvox.AriaUtil.isCompositeControl(node) &&
1699 cvox.DomUtil.findFocusableDescendant(node));
1696 } 1700 }
1697 return false; 1701 return false;
1698 }; 1702 };
1699 1703
1700 1704
1701 /** 1705 /**
1702 * Given a node that might be inside of a composite control like a listbox, 1706 * Given a node that might be inside of a composite control like a listbox,
1703 * return the surrounding control. 1707 * return the surrounding control.
1704 * @param {Node} node The node from which to start looking. 1708 * @param {Node} node The node from which to start looking.
1705 * @return {Node} The surrounding composite control node, or null if none. 1709 * @return {Node} The surrounding composite control node, or null if none.
1706 */ 1710 */
1707 cvox.DomUtil.getSurroundingControl = function(node) { 1711 cvox.DomUtil.getSurroundingControl = function(node) {
1708 var surroundingControl = null; 1712 var surroundingControl = null;
1709 if (!cvox.DomUtil.isControl(node) && node.hasAttribute && 1713 if (!cvox.DomUtil.isControl(node) && node.hasAttribute &&
1710 node.hasAttribute('role')) { 1714 node.hasAttribute('role')) {
1711 surroundingControl = node.parentElement; 1715 surroundingControl = node.parentElement;
1712 while (surroundingControl && 1716 while (surroundingControl &&
1713 !cvox.AriaUtil.isCompositeControl(surroundingControl)) { 1717 !cvox.AriaUtil.isCompositeControl(surroundingControl)) {
1714 surroundingControl = surroundingControl.parentElement; 1718 surroundingControl = surroundingControl.parentElement;
1715 } 1719 }
1716 } 1720 }
1717 return surroundingControl; 1721 return surroundingControl;
1718 }; 1722 };
1719 1723
1720 1724
1721 /** 1725 /**
1722 * Given a node and a function for determining when to stop 1726 * Given a node and a function for determining when to stop
1723 * descent, return the next leaf-like node. 1727 * descent, return the next leaf-like node.
(...skipping 11 matching lines...) Expand all
1735 if (node != document.body) { 1739 if (node != document.body) {
1736 // if not at the top of the tree, we want to find the next possible 1740 // if not at the top of the tree, we want to find the next possible
1737 // branch forward in the dom, so we climb up the parents until we find a 1741 // branch forward in the dom, so we climb up the parents until we find a
1738 // node that has a nextSibling 1742 // node that has a nextSibling
1739 while (!cvox.DomUtil.directedNextSibling(node, r)) { 1743 while (!cvox.DomUtil.directedNextSibling(node, r)) {
1740 if (!node) { 1744 if (!node) {
1741 return null; 1745 return null;
1742 } 1746 }
1743 // since node is never above document.body, it always has a parent. 1747 // since node is never above document.body, it always has a parent.
1744 // so node.parentNode will never be null. 1748 // so node.parentNode will never be null.
1745 node = /** @type {!Node} */(node.parentNode); 1749 node = /** @type {!Node} */ (node.parentNode);
1746 if (node == document.body) { 1750 if (node == document.body) {
1747 // we've readed the end of the document. 1751 // we've readed the end of the document.
1748 return null; 1752 return null;
1749 } 1753 }
1750 } 1754 }
1751 if (cvox.DomUtil.directedNextSibling(node, r)) { 1755 if (cvox.DomUtil.directedNextSibling(node, r)) {
1752 // we just checked that next sibling is non-null. 1756 // we just checked that next sibling is non-null.
1753 node = /** @type {!Node} */(cvox.DomUtil.directedNextSibling(node, r)); 1757 node = /** @type {!Node} */ (cvox.DomUtil.directedNextSibling(node, r));
1754 } 1758 }
1755 } 1759 }
1756 // once we're at our next sibling, we want to descend down into it as 1760 // once we're at our next sibling, we want to descend down into it as
1757 // far as the child class will allow 1761 // far as the child class will allow
1758 while (cvox.DomUtil.directedFirstChild(node, r) && !isLeaf(node)) { 1762 while (cvox.DomUtil.directedFirstChild(node, r) && !isLeaf(node)) {
1759 node = /** @type {!Node} */(cvox.DomUtil.directedFirstChild(node, r)); 1763 node = /** @type {!Node} */ (cvox.DomUtil.directedFirstChild(node, r));
1760 } 1764 }
1761 1765
1762 // after we've done all that, if we are still at document.body, this must 1766 // after we've done all that, if we are still at document.body, this must
1763 // be an empty document. 1767 // be an empty document.
1764 if (node == document.body) { 1768 if (node == document.body) {
1765 return null; 1769 return null;
1766 } 1770 }
1767 return node; 1771 return node;
1768 }; 1772 };
1769 1773
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
1863 above = !!above; 1867 above = !!above;
1864 deep = !!deep; 1868 deep = !!deep;
1865 if (!cvox.DomUtil.isDescendantOfNode(node, ancestor) || node == ancestor) { 1869 if (!cvox.DomUtil.isDescendantOfNode(node, ancestor) || node == ancestor) {
1866 return null; 1870 return null;
1867 } 1871 }
1868 var next = cvox.DomUtil.directedNextSibling(node, r); 1872 var next = cvox.DomUtil.directedNextSibling(node, r);
1869 while (next) { 1873 while (next) {
1870 if (!deep && pred(next)) { 1874 if (!deep && pred(next)) {
1871 return next; 1875 return next;
1872 } 1876 }
1873 var leaf = (deep ? 1877 var leaf =
1874 cvox.DomUtil.directedFindDeepestNode : 1878 (deep ? cvox.DomUtil.directedFindDeepestNode :
1875 cvox.DomUtil.directedFindFirstNode)(next, r, pred); 1879 cvox.DomUtil.directedFindFirstNode)(next, r, pred);
1876 if (leaf) { 1880 if (leaf) {
1877 return leaf; 1881 return leaf;
1878 } 1882 }
1879 if (deep && pred(next)) { 1883 if (deep && pred(next)) {
1880 return next; 1884 return next;
1881 } 1885 }
1882 next = cvox.DomUtil.directedNextSibling(next, r); 1886 next = cvox.DomUtil.directedNextSibling(next, r);
1883 } 1887 }
1884 var parent = /** @type {!Node} */(node.parentNode); 1888 var parent = /** @type {!Node} */ (node.parentNode);
1885 if (above && pred(parent)) { 1889 if (above && pred(parent)) {
1886 return parent; 1890 return parent;
1887 } 1891 }
1888 return cvox.DomUtil.directedFindNextNode( 1892 return cvox.DomUtil.directedFindNextNode(
1889 parent, ancestor, r, pred, above, deep); 1893 parent, ancestor, r, pred, above, deep);
1890 }; 1894 };
1891 1895
1892 1896
1893 /** 1897 /**
1894 * Get a string representing a control's value and state, i.e. the part 1898 * Get a string representing a control's value and state, i.e. the part
1895 * that changes while interacting with the control 1899 * that changes while interacting with the control
1896 * @param {Element} control A control. 1900 * @param {Element} control A control.
1897 * @return {string} The value and state string. 1901 * @return {string} The value and state string.
1898 */ 1902 */
1899 cvox.DomUtil.getControlValueAndStateString = function(control) { 1903 cvox.DomUtil.getControlValueAndStateString = function(control) {
1900 var parentControl = cvox.DomUtil.getSurroundingControl(control); 1904 var parentControl = cvox.DomUtil.getSurroundingControl(control);
1901 if (parentControl) { 1905 if (parentControl) {
1902 return cvox.DomUtil.collapseWhitespace( 1906 return cvox.DomUtil.collapseWhitespace(
1903 cvox.DomUtil.getValue(control) + ' ' + 1907 cvox.DomUtil.getValue(control) + ' ' + cvox.DomUtil.getName(control) +
1904 cvox.DomUtil.getName(control) + ' ' + 1908 ' ' + cvox.DomUtil.getState(control, true));
1905 cvox.DomUtil.getState(control, true));
1906 } else { 1909 } else {
1907 return cvox.DomUtil.collapseWhitespace( 1910 return cvox.DomUtil.collapseWhitespace(
1908 cvox.DomUtil.getValue(control) + ' ' + 1911 cvox.DomUtil.getValue(control) + ' ' +
1909 cvox.DomUtil.getState(control, true)); 1912 cvox.DomUtil.getState(control, true));
1910 } 1913 }
1911 }; 1914 };
1912 1915
1913 1916
1914 /** 1917 /**
1915 * Determine whether the given node is an internal link. 1918 * Determine whether the given node is an internal link.
1916 * @param {Node} node The node to be examined. 1919 * @param {Node} node The node to be examined.
1917 * @return {boolean} True if the node is an internal link, false otherwise. 1920 * @return {boolean} True if the node is an internal link, false otherwise.
1918 */ 1921 */
1919 cvox.DomUtil.isInternalLink = function(node) { 1922 cvox.DomUtil.isInternalLink = function(node) {
1920 if (node.nodeType == 1) { // Element nodes only. 1923 if (node.nodeType == 1) { // Element nodes only.
1921 var href = node.getAttribute('href'); 1924 var href = node.getAttribute('href');
1922 if (href && href.indexOf('#') != -1) { 1925 if (href && href.indexOf('#') != -1) {
1923 var path = href.split('#')[0]; 1926 var path = href.split('#')[0];
1924 return path == '' || path == window.location.pathname; 1927 return path == '' || path == window.location.pathname;
1925 } 1928 }
1926 } 1929 }
1927 return false; 1930 return false;
1928 }; 1931 };
1929 1932
1930 1933
1931 /** 1934 /**
1932 * Get a string containing the currently selected link's URL. 1935 * Get a string containing the currently selected link's URL.
1933 * @param {Node} node The link from which URL needs to be extracted. 1936 * @param {Node} node The link from which URL needs to be extracted.
1934 * @return {string} The value of the URL. 1937 * @return {string} The value of the URL.
1935 */ 1938 */
1936 cvox.DomUtil.getLinkURL = function(node) { 1939 cvox.DomUtil.getLinkURL = function(node) {
1937 if (node.tagName == 'A') { 1940 if (node.tagName == 'A') {
1938 if (node.getAttribute('href')) { 1941 if (node.getAttribute('href')) {
1939 if (cvox.DomUtil.isInternalLink(node)) { 1942 if (cvox.DomUtil.isInternalLink(node)) {
1940 return Msgs.getMsg('internal_link'); 1943 return Msgs.getMsg('internal_link');
1941 } else { 1944 } else {
1942 return node.getAttribute('href'); 1945 return node.getAttribute('href');
1943 } 1946 }
1944 } else { 1947 } else {
1945 return ''; 1948 return '';
1946 } 1949 }
1947 } else if (cvox.AriaUtil.getRoleName(node) == 1950 } else if (cvox.AriaUtil.getRoleName(node) == Msgs.getMsg('role_link')) {
1948 Msgs.getMsg('role_link')) {
1949 return Msgs.getMsg('unknown_link'); 1951 return Msgs.getMsg('unknown_link');
1950 } 1952 }
1951 1953
1952 return ''; 1954 return '';
1953 }; 1955 };
1954 1956
1955 1957
1956 /** 1958 /**
1957 * Checks if a given node is inside a table and returns the table node if it is 1959 * Checks if a given node is inside a table and returns the table node if it is
1958 * @param {Node} node The node. 1960 * @param {Node} node The node.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1997 1999
1998 /** 2000 /**
1999 * Determines whether a given table is a data table or a layout table 2001 * Determines whether a given table is a data table or a layout table
2000 * @param {Node} tableNode The table node. 2002 * @param {Node} tableNode The table node.
2001 * @return {boolean} If the table is a layout table, returns true. False 2003 * @return {boolean} If the table is a layout table, returns true. False
2002 * otherwise. 2004 * otherwise.
2003 */ 2005 */
2004 cvox.DomUtil.isLayoutTable = function(tableNode) { 2006 cvox.DomUtil.isLayoutTable = function(tableNode) {
2005 // TODO(stoarca): Why are we returning based on this inaccurate heuristic 2007 // TODO(stoarca): Why are we returning based on this inaccurate heuristic
2006 // instead of first trying the better heuristics below? 2008 // instead of first trying the better heuristics below?
2007 if (tableNode.rows && (tableNode.rows.length <= 1 || 2009 if (tableNode.rows &&
2008 (tableNode.rows[0].childElementCount == 1))) { 2010 (tableNode.rows.length <= 1 ||
2011 (tableNode.rows[0].childElementCount == 1))) {
2009 // This table has either 0 or one rows, or only "one" column. 2012 // This table has either 0 or one rows, or only "one" column.
2010 // This is a quick check for column count and may not be accurate. See 2013 // This is a quick check for column count and may not be accurate. See
2011 // TraverseTable.getW3CColCount_ for a more accurate 2014 // TraverseTable.getW3CColCount_ for a more accurate
2012 // (but more complicated) way to determine column count. 2015 // (but more complicated) way to determine column count.
2013 return true; 2016 return true;
2014 } 2017 }
2015 2018
2016 // These heuristics are adapted from the Firefox data and layout table. 2019 // These heuristics are adapted from the Firefox data and layout table.
2017 // heuristics: http://asurkov.blogspot.com/2011/10/data-vs-layout-table.html 2020 // heuristics: http://asurkov.blogspot.com/2011/10/data-vs-layout-table.html
2018 if (cvox.AriaUtil.isGrid(tableNode)) { 2021 if (cvox.AriaUtil.isGrid(tableNode)) {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
2058 } 2061 }
2059 2062
2060 // These heuristics are loosely based on Okada and Miura's "Detection of 2063 // These heuristics are loosely based on Okada and Miura's "Detection of
2061 // Layout-Purpose TABLE Tags Based on Machine Learning" (2007). 2064 // Layout-Purpose TABLE Tags Based on Machine Learning" (2007).
2062 // http://books.google.com/books?id=kUbmdqasONwC&lpg=PA116&ots=Lb3HJ7dISZ&lr&p g=PA116 2065 // http://books.google.com/books?id=kUbmdqasONwC&lpg=PA116&ots=Lb3HJ7dISZ&lr&p g=PA116
2063 2066
2064 // Increase the points for each heuristic. If there are 3 or more points, 2067 // Increase the points for each heuristic. If there are 3 or more points,
2065 // this is probably a layout table. 2068 // this is probably a layout table.
2066 var points = 0; 2069 var points = 0;
2067 2070
2068 if (! cvox.DomUtil.hasBorder(tableNode)) { 2071 if (!cvox.DomUtil.hasBorder(tableNode)) {
2069 // This table has no border. 2072 // This table has no border.
2070 points++; 2073 points++;
2071 } 2074 }
2072 2075
2073 if (tableNode.rows.length <= 6) { 2076 if (tableNode.rows.length <= 6) {
2074 // This table has a limited number of rows. 2077 // This table has a limited number of rows.
2075 points++; 2078 points++;
2076 } 2079 }
2077 2080
2078 if (cvox.DomUtil.countPreviousTags(tableNode) <= 12) { 2081 if (cvox.DomUtil.countPreviousTags(tableNode) <= 12) {
2079 // This table has a limited number of previous tags. 2082 // This table has a limited number of previous tags.
2080 points++; 2083 points++;
2081 } 2084 }
2082 2085
2083 if (cvox.XpathUtil.evalXPath('tbody/tr/td/table', tableNode).length > 0) { 2086 if (cvox.XpathUtil.evalXPath('tbody/tr/td/table', tableNode).length > 0) {
2084 // This table has nested tables. 2087 // This table has nested tables.
2085 points++; 2088 points++;
2086 } 2089 }
2087 return (points >= 3); 2090 return (points >= 3);
2088 }; 2091 };
2089 2092
2090 2093
2091 /** 2094 /**
2092 * Count previous tags, which we dfine as the number of HTML tags that 2095 * Count previous tags, which we dfine as the number of HTML tags that
2093 * appear before the given node. 2096 * appear before the given node.
2094 * @param {Node} node The given node. 2097 * @param {Node} node The given node.
2095 * @return {number} The number of previous tags. 2098 * @return {number} The number of previous tags.
2096 */ 2099 */
(...skipping 240 matching lines...) Expand 10 before | Expand all | Expand 10 after
2337 /** 2340 /**
2338 * Creates a function that sends a click. This is because loop closures 2341 * Creates a function that sends a click. This is because loop closures
2339 * are dangerous. 2342 * are dangerous.
2340 * See: http://joust.kano.net/weblog/archive/2005/08/08/ 2343 * See: http://joust.kano.net/weblog/archive/2005/08/08/
2341 * a-huge-gotcha-with-javascript-closures/ 2344 * a-huge-gotcha-with-javascript-closures/
2342 * @param {Node} targetNode The target node to click on. 2345 * @param {Node} targetNode The target node to click on.
2343 * @return {function()} A function that will click on the given targetNode. 2346 * @return {function()} A function that will click on the given targetNode.
2344 */ 2347 */
2345 cvox.DomUtil.createSimpleClickFunction = function(targetNode) { 2348 cvox.DomUtil.createSimpleClickFunction = function(targetNode) {
2346 var target = targetNode.cloneNode(true); 2349 var target = targetNode.cloneNode(true);
2347 return function() { cvox.DomUtil.clickElem(target, false, false); }; 2350 return function() {
2351 cvox.DomUtil.clickElem(target, false, false);
2352 };
2348 }; 2353 };
2349 2354
2350 /** 2355 /**
2351 * Adds a node to document.head if that node has not already been added. 2356 * Adds a node to document.head if that node has not already been added.
2352 * If document.head does not exist, this will add the node to the body. 2357 * If document.head does not exist, this will add the node to the body.
2353 * @param {Node} node The node to add. 2358 * @param {Node} node The node to add.
2354 * @param {string=} opt_id The id of the node to ensure the node is only 2359 * @param {string=} opt_id The id of the node to ensure the node is only
2355 * added once. 2360 * added once.
2356 */ 2361 */
2357 cvox.DomUtil.addNodeToHead = function(node, opt_id) { 2362 cvox.DomUtil.addNodeToHead = function(node, opt_id) {
2358 if (opt_id && document.getElementById(opt_id)) { 2363 if (opt_id && document.getElementById(opt_id)) {
2359 return; 2364 return;
2360 } 2365 }
2361 var p = document.head || document.body; 2366 var p = document.head || document.body;
2362 p.appendChild(node); 2367 p.appendChild(node);
2363 }; 2368 };
2364 2369
2365 2370
2366 /** 2371 /**
2367 * Checks if a given node is inside a math expressions and 2372 * Checks if a given node is inside a math expressions and
2368 * returns the math node if one exists. 2373 * returns the math node if one exists.
2369 * @param {Node} node The node. 2374 * @param {Node} node The node.
(...skipping 21 matching lines...) Expand all
2391 return null; 2396 return null;
2392 }; 2397 };
2393 2398
2394 2399
2395 /** 2400 /**
2396 * Checks to see wether a node is a math node. 2401 * Checks to see wether a node is a math node.
2397 * @param {Node} node The node to be tested. 2402 * @param {Node} node The node to be tested.
2398 * @return {boolean} Whether or not a node is a math node. 2403 * @return {boolean} Whether or not a node is a math node.
2399 */ 2404 */
2400 cvox.DomUtil.isMath = function(node) { 2405 cvox.DomUtil.isMath = function(node) {
2401 return cvox.DomUtil.isMathml(node) || 2406 return cvox.DomUtil.isMathml(node) || cvox.DomUtil.isMathJax(node) ||
2402 cvox.DomUtil.isMathJax(node) || 2407 cvox.DomUtil.isMathImg(node) || cvox.AriaUtil.isMath(node);
2403 cvox.DomUtil.isMathImg(node) ||
2404 cvox.AriaUtil.isMath(node);
2405 }; 2408 };
2406 2409
2407 2410
2408 /** 2411 /**
2409 * Specifies node classes in which we expect maths expressions a alt text. 2412 * Specifies node classes in which we expect maths expressions a alt text.
2410 * @type {{tex: Array<string>, 2413 * @type {{tex: Array<string>,
2411 * asciimath: Array<string>}} 2414 * asciimath: Array<string>}}
2412 */ 2415 */
2413 // These are the classes for which we assume they contain Maths in the ALT or 2416 // These are the classes for which we assume they contain Maths in the ALT or
2414 // TITLE attribute. 2417 // TITLE attribute.
2415 // tex: Wikipedia; 2418 // tex: Wikipedia;
2416 // latex: Wordpress; 2419 // latex: Wordpress;
2417 // numberedequation, inlineformula, displayformula: MathWorld; 2420 // numberedequation, inlineformula, displayformula: MathWorld;
2418 cvox.DomUtil.ALT_MATH_CLASSES = { 2421 cvox.DomUtil.ALT_MATH_CLASSES = {
2419 tex: ['tex', 'latex'], 2422 tex: ['tex', 'latex'],
2420 asciimath: ['numberedequation', 'inlineformula', 'displayformula'] 2423 asciimath: ['numberedequation', 'inlineformula', 'displayformula']
2421 }; 2424 };
2422 2425
2423 2426
2424 /** 2427 /**
2425 * Composes a query selector string for image nodes with alt math content by 2428 * Composes a query selector string for image nodes with alt math content by
2426 * type of content. 2429 * type of content.
2427 * @param {string} contentType The content type, e.g., tex, asciimath. 2430 * @param {string} contentType The content type, e.g., tex, asciimath.
2428 * @return {!string} The query elector string. 2431 * @return {!string} The query elector string.
2429 */ 2432 */
2430 cvox.DomUtil.altMathQuerySelector = function(contentType) { 2433 cvox.DomUtil.altMathQuerySelector = function(contentType) {
2431 var classes = cvox.DomUtil.ALT_MATH_CLASSES[contentType]; 2434 var classes = cvox.DomUtil.ALT_MATH_CLASSES[contentType];
2432 if (classes) { 2435 if (classes) {
2433 return classes.map(function(x) {return 'img.' + x;}).join(', '); 2436 return classes
2437 .map(function(x) {
2438 return 'img.' + x;
2439 })
2440 .join(', ');
2434 } 2441 }
2435 return ''; 2442 return '';
2436 }; 2443 };
2437 2444
2438 2445
2439 /** 2446 /**
2440 * Check if a given node is potentially a math image with alternative text in 2447 * Check if a given node is potentially a math image with alternative text in
2441 * LaTeX. 2448 * LaTeX.
2442 * @param {Node} node The node to be tested. 2449 * @param {Node} node The node to be tested.
2443 * @return {boolean} Whether or not a node has an image with class TeX or LaTeX. 2450 * @return {boolean} Whether or not a node has an image with class TeX or LaTeX.
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2478 * Checks to see wether a node is a MathJax node. 2485 * Checks to see wether a node is a MathJax node.
2479 * @param {Node} node The node to be tested. 2486 * @param {Node} node The node to be tested.
2480 * @return {boolean} Whether or not a node is a MathJax node. 2487 * @return {boolean} Whether or not a node is a MathJax node.
2481 */ 2488 */
2482 cvox.DomUtil.isMathJax = function(node) { 2489 cvox.DomUtil.isMathJax = function(node) {
2483 if (!node || !node.tagName || !node.className) { 2490 if (!node || !node.tagName || !node.className) {
2484 return false; 2491 return false;
2485 } 2492 }
2486 2493
2487 function isSpanWithClass(n, cl) { 2494 function isSpanWithClass(n, cl) {
2488 return (n.tagName == 'SPAN' && 2495 return (n.tagName == 'SPAN' && n.className.split(' ').some(function(x) {
2489 n.className.split(' ').some(function(x) { 2496 return x.toLowerCase() == cl;
2490 return x.toLowerCase() == cl;})); 2497 }));
2491 } 2498 }
2492 if (isSpanWithClass(node, 'math')) { 2499 if (isSpanWithClass(node, 'math')) {
2493 var ancestors = cvox.DomUtil.getAncestors(node); 2500 var ancestors = cvox.DomUtil.getAncestors(node);
2494 return ancestors.some(function(x) {return isSpanWithClass(x, 'mathjax');}); 2501 return ancestors.some(function(x) {
2502 return isSpanWithClass(x, 'mathjax');
2503 });
2495 } 2504 }
2496 return false; 2505 return false;
2497 }; 2506 };
2498 2507
2499 2508
2500 /** 2509 /**
2501 * Computes the id of the math span in a MathJax DOM element. 2510 * Computes the id of the math span in a MathJax DOM element.
2502 * @param {string} jaxId The id of the MathJax node. 2511 * @param {string} jaxId The id of the MathJax node.
2503 * @return {string} The id of the span node. 2512 * @return {string} The id of the span node.
2504 */ 2513 */
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2539 return ''; 2548 return '';
2540 }; 2549 };
2541 2550
2542 2551
2543 /** 2552 /**
2544 * Cleaning up a list of nodes to remove empty text nodes. 2553 * Cleaning up a list of nodes to remove empty text nodes.
2545 * @param {NodeList} nodes The nodes list. 2554 * @param {NodeList} nodes The nodes list.
2546 * @return {!Array<Node|string|null>} The cleaned up list of nodes. 2555 * @return {!Array<Node|string|null>} The cleaned up list of nodes.
2547 */ 2556 */
2548 cvox.DomUtil.purgeNodes = function(nodes) { 2557 cvox.DomUtil.purgeNodes = function(nodes) {
2549 return cvox.DomUtil.toArray(nodes). 2558 return cvox.DomUtil.toArray(nodes).filter(function(node) {
2550 filter(function(node) { 2559 return node.nodeType != Node.TEXT_NODE || !node.textContent.match(/^\s+$/);
2551 return node.nodeType != Node.TEXT_NODE || 2560 });
2552 !node.textContent.match(/^\s+$/);});
2553 }; 2561 };
2554 2562
2555 2563
2556 /** 2564 /**
2557 * Calculates a hit point for a given node. 2565 * Calculates a hit point for a given node.
2558 * @param {Node} node The given node. 2566 * @param {Node} node The given node.
2559 * @return {{x:(number), y:(number)}} The position. 2567 * @return {{x:(number), y:(number)}} The position.
2560 */ 2568 */
2561 cvox.DomUtil.elementToPoint = function(node) { 2569 cvox.DomUtil.elementToPoint = function(node) {
2562 if (!node) { 2570 if (!node) {
2563 return {x: 0, y: 0}; 2571 return {x: 0, y: 0};
2564 } 2572 }
2565 if (node.constructor == Text) { 2573 if (node.constructor == Text) {
2566 node = node.parentNode; 2574 node = node.parentNode;
2567 } 2575 }
2568 var r = node.getBoundingClientRect(); 2576 var r = node.getBoundingClientRect();
2569 return { 2577 return {x: r.left + (r.width / 2), y: r.top + (r.height / 2)};
2570 x: r.left + (r.width / 2),
2571 y: r.top + (r.height / 2)
2572 };
2573 }; 2578 };
2574 2579
2575 2580
2576 /** 2581 /**
2577 * Checks if an input node supports HTML5 selection. 2582 * Checks if an input node supports HTML5 selection.
2578 * If the node is not an input element, returns false. 2583 * If the node is not an input element, returns false.
2579 * @param {Node} node The node to check. 2584 * @param {Node} node The node to check.
2580 * @return {boolean} True if HTML5 selection supported. 2585 * @return {boolean} True if HTML5 selection supported.
2581 */ 2586 */
2582 cvox.DomUtil.doesInputSupportSelection = function(node) { 2587 cvox.DomUtil.doesInputSupportSelection = function(node) {
2583 return goog.isDef(node) && 2588 return goog.isDef(node) && node.tagName == 'INPUT' && node.type != 'email' &&
2584 node.tagName == 'INPUT' &&
2585 node.type != 'email' &&
2586 node.type != 'number'; 2589 node.type != 'number';
2587 }; 2590 };
2588 2591
2589 2592
2590 /** 2593 /**
2591 * Gets the hint text for a given element. 2594 * Gets the hint text for a given element.
2592 * @param {Node} node The target node. 2595 * @param {Node} node The target node.
2593 * @return {string} The hint text. 2596 * @return {string} The hint text.
2594 */ 2597 */
2595 cvox.DomUtil.getHint = function(node) { 2598 cvox.DomUtil.getHint = function(node) {
2596 var desc = ''; 2599 var desc = '';
2597 if (node.hasAttribute) { 2600 if (node.hasAttribute) {
2598 if (node.hasAttribute('aria-describedby')) { 2601 if (node.hasAttribute('aria-describedby')) {
2599 var describedByIds = node.getAttribute('aria-describedby').split(' '); 2602 var describedByIds = node.getAttribute('aria-describedby').split(' ');
2600 for (var describedById, i = 0; describedById = describedByIds[i]; i++) { 2603 for (var describedById, i = 0; describedById = describedByIds[i]; i++) {
2601 var describedNode = document.getElementById(describedById); 2604 var describedNode = document.getElementById(describedById);
2602 if (describedNode) { 2605 if (describedNode) {
2603 desc += ' ' + cvox.DomUtil.getName( 2606 desc += ' ' + cvox.DomUtil.getName(describedNode, true, true, true);
2604 describedNode, true, true, true);
2605 } 2607 }
2606 } 2608 }
2607 } 2609 }
2608 } 2610 }
2609 return desc; 2611 return desc;
2610 }; 2612 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698