1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview A bunch of predicates that take as input an array of 7 * nodes with the unique ancestors of a node. They output true if a 8 * certain category of node has been found. 9 * 10 */ 11 12 goog.provide('cvox.DomPredicates'); 13 14 15 /** 16 * Checkbox. 17 * @param {Array.<Node>} nodes An array of nodes to check. 18 * @return {?Node} Node in the array that is a checkbox. 19 */ 20 cvox.DomPredicates.checkboxPredicate = function(nodes) { 21 for (var i = 0; i < nodes.length; i++) { 22 if ((nodes[i].getAttribute && 23 nodes[i].getAttribute('role') == 'checkbox') || 24 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'checkbox')) { 25 return nodes[i]; 26 } 27 } 28 return null; 29 }; 30 31 32 /** 33 * Radio button. 34 * @param {Array.<Node>} nodes An array of nodes to check. 35 * @return {?Node} Node in the array that is a radio button. 36 */ 37 cvox.DomPredicates.radioPredicate = function(nodes) { 38 for (var i = 0; i < nodes.length; i++) { 39 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'radio') || 40 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'radio')) { 41 return nodes[i]; 42 } 43 } 44 return null; 45 }; 46 47 48 /** 49 * Slider. 50 * @param {Array.<Node>} nodes An array of nodes to check. 51 * @return {?Node} Node in the array that is a slider. 52 */ 53 cvox.DomPredicates.sliderPredicate = function(nodes) { 54 for (var i = 0; i < nodes.length; i++) { 55 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'slider') || 56 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'range')) { 57 return nodes[i]; 58 } 59 } 60 return null; 61 }; 62 63 64 /** 65 * Graphic. 66 * @param {Array.<Node>} nodes An array of nodes to check. 67 * @return {?Node} Node in the array that is a graphic. 68 */ 69 cvox.DomPredicates.graphicPredicate = function(nodes) { 70 for (var i = 0; i < nodes.length; i++) { 71 if (nodes[i].tagName == 'IMG' || 72 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'img')) { 73 return nodes[i]; 74 } 75 } 76 return null; 77 }; 78 79 80 /** 81 * Button. 82 * @param {Array.<Node>} nodes An array of nodes to check. 83 * @return {?Node} Node in the array that is a button. 84 */ 85 cvox.DomPredicates.buttonPredicate = function(nodes) { 86 for (var i = 0; i < nodes.length; i++) { 87 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'button') || 88 nodes[i].tagName == 'BUTTON' || 89 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'submit') || 90 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'button') || 91 (nodes[i].tagName == 'INPUT' && nodes[i].type == 'reset')) { 92 return nodes[i]; 93 } 94 } 95 return null; 96 }; 97 98 99 /** 100 * Combo box. 101 * @param {Array.<Node>} nodes An array of nodes to check. 102 * @return {?Node} Node in the array that is a combo box. 103 */ 104 cvox.DomPredicates.comboBoxPredicate = function(nodes) { 105 for (var i = 0; i < nodes.length; i++) { 106 if ((nodes[i].getAttribute && 107 nodes[i].getAttribute('role') == 'combobox') || 108 nodes[i].tagName == 'SELECT') { 109 return nodes[i]; 110 } 111 } 112 return null; 113 }; 114 115 116 /** 117 * Editable text field. 118 * @param {Array.<Node>} nodes An array of nodes to check. 119 * @return {?Node} Node in the array that is an editable text field. 120 */ 121 cvox.DomPredicates.editTextPredicate = function(nodes) { 122 for (var i = 0; i < nodes.length; i++) { 123 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'textbox') || 124 nodes[i].tagName == 'TEXTAREA' || 125 nodes[i].isContentEditable || 126 (nodes[i].tagName == 'INPUT' && 127 cvox.DomUtil.isInputTypeText(nodes[i]))) { 128 return nodes[i]; 129 } 130 } 131 return null; 132 }; 133 134 135 /** 136 * Heading. 137 * @param {Array.<Node>} nodes An array of nodes to check. 138 * @return {?Node} Node in the array that is a heading. 139 */ 140 cvox.DomPredicates.headingPredicate = function(nodes) { 141 for (var i = 0; i < nodes.length; i++) { 142 if (nodes[i].getAttribute && 143 nodes[i].getAttribute('role') == 'heading') { 144 return nodes[i]; 145 } 146 switch (nodes[i].tagName) { 147 case 'H1': 148 case 'H2': 149 case 'H3': 150 case 'H4': 151 case 'H5': 152 case 'H6': 153 return nodes[i]; 154 } 155 } 156 return null; 157 }; 158 159 160 /** 161 * Heading level 1. 162 * @param {Array.<Node>} nodes An array of nodes to check. 163 * @return {?Node} Node in the array that is a heading level 1. 164 * TODO: handle ARIA headings with ARIA heading levels? 165 */ 166 cvox.DomPredicates.heading1Predicate = function(nodes) { 167 return cvox.DomPredicates.containsTagName_(nodes, 'H1'); 168 }; 169 170 171 /** 172 * Heading level 2. 173 * @param {Array.<Node>} nodes An array of nodes to check. 174 * @return {?Node} Node in the array that is a heading level 2. 175 */ 176 cvox.DomPredicates.heading2Predicate = function(nodes) { 177 return cvox.DomPredicates.containsTagName_(nodes, 'H2'); 178 }; 179 180 181 /** 182 * Heading level 3. 183 * @param {Array.<Node>} nodes An array of nodes to check. 184 * @return {?Node} Node in the array that is a heading level 3. 185 */ 186 cvox.DomPredicates.heading3Predicate = function(nodes) { 187 return cvox.DomPredicates.containsTagName_(nodes, 'H3'); 188 }; 189 190 191 /** 192 * Heading level 4. 193 * @param {Array.<Node>} nodes An array of nodes to check. 194 * @return {?Node} Node in the array that is a heading level 4. 195 */ 196 cvox.DomPredicates.heading4Predicate = function(nodes) { 197 return cvox.DomPredicates.containsTagName_(nodes, 'H4'); 198 }; 199 200 201 /** 202 * Heading level 5. 203 * @param {Array.<Node>} nodes An array of nodes to check. 204 * @return {?Node} Node in the array that is a heading level 5. 205 */ 206 cvox.DomPredicates.heading5Predicate = function(nodes) { 207 return cvox.DomPredicates.containsTagName_(nodes, 'H5'); 208 }; 209 210 211 /** 212 * Heading level 6. 213 * @param {Array.<Node>} nodes An array of nodes to check. 214 * @return {?Node} Node in the array that is a heading level 6. 215 */ 216 cvox.DomPredicates.heading6Predicate = function(nodes) { 217 return cvox.DomPredicates.containsTagName_(nodes, 'H6'); 218 }; 219 220 221 /** 222 * Link. 223 * @param {Array.<Node>} nodes An array of nodes to check. 224 * @return {?Node} Node in the array that is a link. 225 */ 226 cvox.DomPredicates.linkPredicate = function(nodes) { 227 for (var i = 0; i < nodes.length; i++) { 228 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'link') || 229 (nodes[i].tagName == 'A' && nodes[i].href)) { 230 return nodes[i]; 231 } 232 } 233 return null; 234 }; 235 236 237 /** 238 * Table. 239 * @param {Array.<Node>} nodes An array of nodes to check. 240 * @return {?Node} Node in the array that is a data table. 241 */ 242 cvox.DomPredicates.tablePredicate = function(nodes) { 243 // TODO(stoarca): Captions should always be allowed!! 244 var node = cvox.DomUtil.findTableNodeInList(nodes, {allowCaptions: true}); 245 if (node && !cvox.DomUtil.isLayoutTable(node)) { 246 return node; 247 } else { 248 return null; 249 } 250 }; 251 252 /** 253 * Table Cell. 254 * @param {Array.<Node>} nodes An array of nodes to check. 255 * @return {?Node} Node in the array that is a table cell. 256 */ 257 cvox.DomPredicates.cellPredicate = function(nodes) { 258 for (var i = nodes.length - 1; i >= 0; --i) { 259 var node = nodes[i]; 260 if (node.tagName == 'TD' || 261 node.tagName == 'TH' || 262 (node.getAttribute && node.getAttribute('role') == 'gridcell')) { 263 return node; 264 } 265 } 266 return null; 267 }; 268 269 270 /** 271 * Visited link. 272 * @param {Array.<Node>} nodes An array of nodes to check. 273 * @return {?Node} Node in the array that is a visited link. 274 */ 275 cvox.DomPredicates.visitedLinkPredicate = function(nodes) { 276 for (var i = nodes.length - 1; i >= 0; --i) { 277 if (cvox.DomPredicates.linkPredicate([nodes[i]]) && 278 cvox.ChromeVox.visitedUrls[nodes[i].href]) { 279 return nodes[i]; 280 } 281 } 282 }; 283 284 285 /** 286 * List. 287 * @param {Array.<Node>} nodes An array of nodes to check. 288 * @return {?Node} Node in the array that is a list. 289 */ 290 cvox.DomPredicates.listPredicate = function(nodes) { 291 for (var i = 0; i < nodes.length; i++) { 292 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'list') || 293 nodes[i].tagName == 'UL' || 294 nodes[i].tagName == 'OL') { 295 return nodes[i]; 296 } 297 } 298 return null; 299 }; 300 301 302 /** 303 * List item. 304 * @param {Array.<Node>} nodes An array of nodes to check. 305 * @return {?Node} Node in the array that is a list item. 306 */ 307 cvox.DomPredicates.listItemPredicate = function(nodes) { 308 for (var i = 0; i < nodes.length; i++) { 309 if ((nodes[i].getAttribute && 310 nodes[i].getAttribute('role') == 'listitem') || 311 nodes[i].tagName == 'LI') { 312 return nodes[i]; 313 } 314 } 315 return null; 316 }; 317 318 319 /** 320 * Blockquote. 321 * @param {Array.<Node>} nodes An array of nodes to check. 322 * @return {?Node} Node in the array that is a blockquote. 323 */ 324 cvox.DomPredicates.blockquotePredicate = function(nodes) { 325 return cvox.DomPredicates.containsTagName_(nodes, 'BLOCKQUOTE'); 326 }; 327 328 329 /** 330 * Form field. 331 * @param {Array.<Node>} nodes An array of nodes to check. 332 * @return {?Node} Node in the array that is any type of form field. 333 */ 334 cvox.DomPredicates.formFieldPredicate = function(nodes) { 335 for (var i = 0; i < nodes.length; i++) { 336 if (cvox.DomUtil.isControl(nodes[i])) { 337 return nodes[i]; 338 } 339 } 340 return null; 341 }; 342 343 344 /** 345 * ARIA landmark. 346 * @param {Array.<Node>} nodes An array of nodes to check. 347 * @return {?Node} Node in the array that is an ARIA landmark. 348 */ 349 cvox.DomPredicates.landmarkPredicate = function(nodes) { 350 for (var i = 0; i < nodes.length; i++) { 351 if (cvox.AriaUtil.isLandmark(nodes[i])) { 352 return nodes[i]; 353 } 354 } 355 return null; 356 }; 357 358 359 /** 360 * @param {Array} arr Array of nodes. 361 * @param {string} tagName The name of the tag. 362 * @return {?Node} Node if obj is in the array. 363 * @private 364 */ 365 cvox.DomPredicates.containsTagName_ = function(arr, tagName) { 366 var i = arr.length; 367 while (i--) { 368 if (arr[i].tagName == tagName) { 369 return arr[i]; 370 } 371 } 372 return null; 373 }; 374 375 376 /** 377 * MathML expression 378 * @param {Array.<Node>} nodes An array of nodes to check. 379 * @return {?Node} Node in the array that is a math expression. 380 */ 381 cvox.DomPredicates.mathPredicate = function(nodes) { 382 return cvox.DomUtil.findMathNodeInList(nodes); 383 }; 384 385 /** 386 * SECTION: A section is anything that indicates a new section. This includes 387 * headings and landmarks. 388 * @param {Array.<Node>} nodes An array of nodes to check. 389 * @return {?Node} Node in the array that is considered a section marker. 390 */ 391 cvox.DomPredicates.sectionPredicate = function(nodes) { 392 for (var i = 0; i < nodes.length; i++) { 393 if (cvox.DomUtil.isSemanticElt(nodes[i])) { 394 return nodes[i]; 395 } 396 if (cvox.AriaUtil.isLandmark(nodes[i])) { 397 return nodes[i]; 398 } 399 if (nodes[i].getAttribute && 400 nodes[i].getAttribute('role') == 'heading') { 401 return nodes[i]; 402 } 403 switch (nodes[i].tagName) { 404 case 'H1': 405 case 'H2': 406 case 'H3': 407 case 'H4': 408 case 'H5': 409 case 'H6': 410 return nodes[i]; 411 } 412 } 413 return null; 414 }; 415 416 /** 417 * CONTROL: A control is anything that the user can interact with. This includes 418 * form fields and links. 419 * @param {Array.<Node>} nodes An array of nodes to check. 420 * @return {?Node} Node in the array that is considered a control. 421 */ 422 cvox.DomPredicates.controlPredicate = function(nodes) { 423 for (var i = 0; i < nodes.length; i++) { 424 if (cvox.DomUtil.isControl(nodes[i])) { 425 return nodes[i]; 426 } 427 if ((nodes[i].getAttribute && nodes[i].getAttribute('role') == 'link') || 428 (nodes[i].tagName == 'A' && nodes[i].href)) { 429 return nodes[i]; 430 } 431 } 432 return null; 433 }; 434 435 /** 436 * Caption. 437 * @param {Array.<Node>} nodes An array of nodes to check. 438 * @return {?Node} Node in the array that is a caption. 439 */ 440 cvox.DomPredicates.captionPredicate = function(nodes) { 441 for (var i = 0; i < nodes.length; i++) { 442 if (nodes[i].tagName == 'CAPTION') { 443 return nodes[i]; 444 } 445 } 446 return null; 447 }; 448 449 /** 450 * Article. 451 * @param {Array.<Node>} nodes An array of nodes to check. 452 * @return {?Node} Node in the array that is a article. 453 */ 454 cvox.DomPredicates.articlePredicate = function(nodes) { 455 for (var i = 0; i < nodes.length; i++) { 456 if ((nodes[i].getAttribute && 457 nodes[i].getAttribute('role') == 'article') || 458 nodes[i].tagName == 'ARTICLE') { 459 return nodes[i]; 460 } 461 } 462 return null; 463 }; 464 465 /** 466 * Media. 467 * @param {Array.<Node>} nodes An array of nodes to check. 468 * @return {?Node} Node in the array that is a media widget (video or audio). 469 */ 470 cvox.DomPredicates.mediaPredicate = function(nodes) { 471 for (var i = 0; i < nodes.length; i++) { 472 if (nodes[i].tagName == 'AUDIO' || 473 nodes[i].tagName == 'VIDEO') { 474 return nodes[i]; 475 } 476 } 477 return null; 478 }; 479 480 481 /** 482 * Ordered List. 483 * @param {Array.<Node>} nodes An array of nodes to check. 484 * @return {?Node} Node in the array that is a ordered list. 485 */ 486 cvox.DomPredicates.orderedListPredicate = function(nodes) { 487 for (var i = 0; i < nodes.length; i++) { 488 if (nodes[i].tagName == 'OL') { 489 return nodes[i]; 490 } 491 } 492 return null; 493 }; 494