OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 }; |
OLD | NEW |