| 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 |