| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 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 DOM traversal interface for navigating data in tables. |
| 7 */ |
| 8 |
| 9 goog.provide('cvox.TraverseTable'); |
| 10 |
| 11 goog.require('cvox.SelectionUtil'); |
| 12 goog.require('cvox.TraverseUtil'); |
| 13 goog.require('cvox.XpathUtil'); |
| 14 |
| 15 /** |
| 16 * An object that represents an active table cell inside the shadow table. |
| 17 * @constructor |
| 18 */ |
| 19 function ShadowTableNode() {} |
| 20 |
| 21 /** |
| 22 * Whether or not the active cell is spanned by a preceding cell. |
| 23 * @type {?boolean} |
| 24 */ |
| 25 ShadowTableNode.prototype.spanned; |
| 26 |
| 27 /** |
| 28 * Whether or not this cell is spanned by a rowSpan. |
| 29 * @type {?boolean} |
| 30 */ |
| 31 ShadowTableNode.prototype.rowSpan; |
| 32 |
| 33 /** |
| 34 * Whether or not this cell is spanned by a colspan |
| 35 * @type {?boolean} |
| 36 */ |
| 37 ShadowTableNode.prototype.colSpan; |
| 38 |
| 39 /** |
| 40 * The row index of the corresponding active table cell |
| 41 * @type {?number} |
| 42 */ |
| 43 ShadowTableNode.prototype.i; |
| 44 |
| 45 /** |
| 46 * The column index of the corresponding active table cell |
| 47 * @type {?number} |
| 48 */ |
| 49 ShadowTableNode.prototype.j; |
| 50 |
| 51 /** |
| 52 * The corresponding <TD> or <TH> node in the active table. |
| 53 * @type {?Node} |
| 54 */ |
| 55 ShadowTableNode.prototype.activeCell; |
| 56 |
| 57 /** |
| 58 * Initializes the traversal with the provided table node. |
| 59 * |
| 60 * @constructor |
| 61 * @param {Node} tableNode The table to be traversed. |
| 62 */ |
| 63 cvox.TraverseTable = function(tableNode) { |
| 64 |
| 65 /** |
| 66 * The active table <TABLE> node. In this context, "active" means that this is |
| 67 * the table the TraverseTable object is navigating. |
| 68 * @type {Node} |
| 69 * @private |
| 70 */ |
| 71 this.activeTable_ = []; |
| 72 |
| 73 /** |
| 74 * A 2D array "shadow table" that contains pointers to nodes in the active |
| 75 * table. More specifically, each cell of the shadow table contains a special |
| 76 * object ShadowTableNode that has as one of its member variables the |
| 77 * corresponding cell in the active table. |
| 78 * |
| 79 * The shadow table will allow us efficient navigation of tables with |
| 80 * rowspans and colspans without needing to repeatedly scan the table. For |
| 81 * example, if someone requests a cell at (1,3), predecessor cells with |
| 82 * rowspans/colspans mean the cell you eventually return could actually be |
| 83 * one located at (0,2) that spans out to (1,3). |
| 84 * |
| 85 * This shadow table will contain a ShadowTableNode with the (0, 2) index at |
| 86 * the (1,3) position, eliminating the need to check for predecessor cells |
| 87 * with rowspan/colspan every time we traverse the table. |
| 88 * |
| 89 * @type {Array.<Array.<ShadowTableNode>>} |
| 90 * @private |
| 91 */ |
| 92 this.shadowTable_ = []; |
| 93 |
| 94 this.initialize(tableNode); |
| 95 }; |
| 96 |
| 97 |
| 98 /** |
| 99 * The active row <TR> node. In this context, "active" means that this is the |
| 100 * row that contains the cell the user is currently looking at. |
| 101 * @type {Node} |
| 102 */ |
| 103 cvox.TraverseTable.prototype.currentRow; |
| 104 |
| 105 |
| 106 /** |
| 107 * The active column, represented as an array of <TH> or <TD> nodes that make |
| 108 * up a column. In this context, "active" means that this is the column that |
| 109 * contains the cell the user is currently looking at. |
| 110 * @type {Array} |
| 111 */ |
| 112 cvox.TraverseTable.prototype.currentCol; |
| 113 |
| 114 |
| 115 /** |
| 116 * The cell cursor, represented by an array that stores the row and |
| 117 * column location [i, j] of the active cell. These numbers are 0-based. |
| 118 * In this context, "active" means that this is the cell the user is |
| 119 * currently looking at. |
| 120 * @type {Array} |
| 121 */ |
| 122 cvox.TraverseTable.prototype.currentCellCursor; |
| 123 |
| 124 |
| 125 /** |
| 126 * The number of columns in the active table. This is calculated at |
| 127 * initialization and then only recalculated if the table changes. |
| 128 * |
| 129 * Please Note: We have chosen to use the number of columns in the shadow |
| 130 * table as the canonical column count. This is important for tables that |
| 131 * have colspans - the number of columns in the active table will always be |
| 132 * less than the true number of columns. |
| 133 * @type {?number} |
| 134 */ |
| 135 cvox.TraverseTable.prototype.colCount = null; |
| 136 |
| 137 /** |
| 138 * The number of rows in the active table. This is calculated at |
| 139 * initialization and then only recalculated if the table changes. |
| 140 * @type {?number} |
| 141 */ |
| 142 cvox.TraverseTable.prototype.rowCount = null; |
| 143 |
| 144 |
| 145 /** |
| 146 * Initializes the class member variables. |
| 147 * @param {Node} tableNode The table to be traversed. |
| 148 */ |
| 149 cvox.TraverseTable.prototype.initialize = function(tableNode) { |
| 150 this.activeTable_ = tableNode; |
| 151 this.currentRow = null; |
| 152 this.currentCol = null; |
| 153 this.currentCellCursor = null; |
| 154 |
| 155 this.buildShadowTable_(); |
| 156 |
| 157 this.colCount = this.shadowColCount_(); |
| 158 this.rowCount = this.countRows_(); |
| 159 |
| 160 var self = this; |
| 161 // Listen for changes to the active table. If the active table changes, |
| 162 // rebuild the shadow table. |
| 163 this.activeTable_.addEventListener('DOMSubtreeModified', |
| 164 function() { |
| 165 self.buildShadowTable_(); |
| 166 self.colCount = self.shadowColCount_(); |
| 167 self.rowCount = self.countRows_(); |
| 168 }, false); |
| 169 }; |
| 170 |
| 171 /** |
| 172 * Builds or rebuilds the shadow table by iterating through all of the cells |
| 173 * ( <TD> or <TH> nodes) of the active table. |
| 174 * @private |
| 175 */ |
| 176 cvox.TraverseTable.prototype.buildShadowTable_ = function() { |
| 177 // Clear shadow table |
| 178 this.shadowTable_ = []; |
| 179 |
| 180 // Build shadow table structure. Initialize it as a 2D array. |
| 181 var allRows = this.getChildRows_(); |
| 182 for (var ctr = 0; ctr < allRows.length; ctr++) { |
| 183 this.shadowTable_.push([]); |
| 184 } |
| 185 |
| 186 // Iterate through active table by row |
| 187 for (var i = 0; i < allRows.length; i++) { |
| 188 var childCells = this.getChildCells_(allRows[i]); |
| 189 |
| 190 // Keep track of position in active table |
| 191 var activeTableCol = 0; |
| 192 // Keep track of position in shadow table |
| 193 var shadowTableCol = 0; |
| 194 |
| 195 while (activeTableCol < childCells.length) { |
| 196 |
| 197 // Check to make sure we haven't already filled this cell. |
| 198 if (this.shadowTable_[i][shadowTableCol] == null) { |
| 199 |
| 200 // Default value for colspan and rowspan is 1 |
| 201 var colsSpanned = 1; |
| 202 var rowsSpanned = 1; |
| 203 |
| 204 if (childCells[activeTableCol].hasAttribute('colspan')) { |
| 205 |
| 206 colsSpanned = |
| 207 parseInt(childCells[activeTableCol].getAttribute('colspan')); |
| 208 |
| 209 if ((isNaN(colsSpanned)) || (colsSpanned <= 0)) { |
| 210 // The HTML5 spec defines colspan MUST be greater than 0: |
| 211 // http://dev.w3.org/html5/spec/Overview.html#attr-tdth-colspan |
| 212 // |
| 213 // This is a change from the HTML4 spec: |
| 214 // http://www.w3.org/TR/html401/struct/tables.html#adef-colspan |
| 215 // |
| 216 // We will degrade gracefully by treating a colspan=0 as |
| 217 // equivalent to a colspan=1. |
| 218 // Tested in method testColSpan0 in rowColSpanTable_test.js |
| 219 colsSpanned = 1; |
| 220 } |
| 221 } |
| 222 if (childCells[activeTableCol].hasAttribute('rowspan')) { |
| 223 rowsSpanned = |
| 224 parseInt(childCells[activeTableCol].getAttribute('rowspan')); |
| 225 |
| 226 if ((isNaN(rowsSpanned)) || (rowsSpanned <= 0)) { |
| 227 // The HTML5 spec defines that rowspan can be any non-negative |
| 228 // integer, including 0: |
| 229 // http://dev.w3.org/html5/spec/Overview.html#attr-tdth-rowspan |
| 230 // |
| 231 // However, Chromium treats rowspan=0 as rowspan=1. This appears |
| 232 // to be a bug from WebKit: |
| 233 // https://bugs.webkit.org/show_bug.cgi?id=10300 |
| 234 // Inherited from a bug (since fixed) in KDE: |
| 235 // http://bugs.kde.org/show_bug.cgi?id=41063 |
| 236 // |
| 237 // We will follow Chromium and treat rowspan=0 as equivalent to |
| 238 // rowspan=1. |
| 239 // |
| 240 // Tested in method testRowSpan0 in rowColSpanTable_test.js |
| 241 // |
| 242 // Filed as a bug in Chromium: http://crbug.com/58223 |
| 243 rowsSpanned = 1; |
| 244 } |
| 245 } |
| 246 for (var r = 0; r < rowsSpanned; r++) { |
| 247 for (var c = 0; c < colsSpanned; c++) { |
| 248 var shadowNode = new ShadowTableNode(); |
| 249 if ((r == 0) && (c == 0)) { |
| 250 // This position is not spanned. |
| 251 shadowNode.spanned = false; |
| 252 shadowNode.rowSpan = false; |
| 253 shadowNode.colSpan = false; |
| 254 shadowNode.i = i; |
| 255 shadowNode.j = shadowTableCol; |
| 256 shadowNode.activeCell = childCells[activeTableCol]; |
| 257 } else { |
| 258 // This position is spanned. |
| 259 shadowNode.spanned = true; |
| 260 shadowNode.rowSpan = (rowsSpanned > 1); |
| 261 shadowNode.colSpan = (colsSpanned > 1); |
| 262 shadowNode.i = i; |
| 263 shadowNode.j = shadowTableCol; |
| 264 shadowNode.activeCell = childCells[activeTableCol]; |
| 265 } |
| 266 this.shadowTable_[i + r][shadowTableCol + c] = shadowNode; |
| 267 } |
| 268 } |
| 269 shadowTableCol += colsSpanned; |
| 270 activeTableCol++; |
| 271 } else { |
| 272 // This position has already been filled (by a previous cell that has |
| 273 // a colspan or a rowspan) |
| 274 shadowTableCol += 1; |
| 275 } |
| 276 } |
| 277 } |
| 278 }; |
| 279 |
| 280 |
| 281 /** |
| 282 * Gets the current cell. |
| 283 * @return {?Node} The cell <TD> or <TH> node. |
| 284 */ |
| 285 cvox.TraverseTable.prototype.getCell = function() { |
| 286 var shadowEntry = |
| 287 this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]]; |
| 288 |
| 289 return shadowEntry.activeCell; |
| 290 }; |
| 291 |
| 292 |
| 293 /** |
| 294 * Gets the table summary text. |
| 295 * |
| 296 * @return {?string} Either: |
| 297 * 1) The table summary text |
| 298 * 2) Null if the table does not contain a summary attribute. |
| 299 */ |
| 300 cvox.TraverseTable.prototype.summaryText = function() { |
| 301 // see http://code.google.com/p/chromium/issues/detail?id=46567 |
| 302 // for information why this is necessary |
| 303 if (!this.activeTable_.hasAttribute('summary')) { |
| 304 return null; |
| 305 } |
| 306 return this.activeTable_.getAttribute('summary'); |
| 307 }; |
| 308 |
| 309 |
| 310 /** |
| 311 * Gets the table caption text. |
| 312 * |
| 313 * @return {?string} Either: |
| 314 * 1) The table caption text |
| 315 * 2) Null if the table does not include a caption tag. |
| 316 */ |
| 317 cvox.TraverseTable.prototype.captionText = function() { |
| 318 // If there's more than one outer <caption> element, choose the first one. |
| 319 var captionNodes = cvox.XpathUtil.evalXPath('caption\[1]', |
| 320 this.activeTable_); |
| 321 if (captionNodes.length > 0) { |
| 322 return captionNodes[0].innerHTML; |
| 323 } else { |
| 324 return null; |
| 325 } |
| 326 }; |
| 327 |
| 328 |
| 329 /** |
| 330 * Calculates the number of columns in the shadow table. |
| 331 * @return {number} The number of columns in the shadow table. |
| 332 * @private |
| 333 */ |
| 334 cvox.TraverseTable.prototype.shadowColCount_ = function() { |
| 335 // As the shadow table is a 2D array, the number of columns is the |
| 336 // max number of elements in the second-level arrays. |
| 337 var max = 0; |
| 338 for (var i = 0; i < this.shadowTable_.length; i++) { |
| 339 if (this.shadowTable_[i].length > max) { |
| 340 max = this.shadowTable_[i].length; |
| 341 } |
| 342 } |
| 343 return max; |
| 344 }; |
| 345 |
| 346 |
| 347 /** |
| 348 * Calculates the number of rows in the table. |
| 349 * @return {number} The number of rows in the table. |
| 350 * @private |
| 351 */ |
| 352 cvox.TraverseTable.prototype.countRows_ = function() { |
| 353 // Number of rows in a table is equal to the number of TR elements contained |
| 354 // by the (outer) TBODY elements. |
| 355 var rowCount = this.getChildRows_(); |
| 356 return rowCount.length; |
| 357 }; |
| 358 |
| 359 |
| 360 /** |
| 361 * Calculates the number of columns in the table. |
| 362 * This uses the W3C recommended algorithm for calculating number of |
| 363 * columns, but it does not take rowspans or colspans into account. This means |
| 364 * that the number of columns calculated here might be lower than the actual |
| 365 * number of columns in the table if columns are indicated by colspans. |
| 366 * @return {number} The number of columns in the table. |
| 367 * @private |
| 368 */ |
| 369 cvox.TraverseTable.prototype.getW3CColumnCount_ = function() { |
| 370 // See http://www.w3.org/TR/html401/struct/tables.html#h-11.2.4.3 |
| 371 |
| 372 var colgroupNodes = cvox.XpathUtil.evalXPath('child::colgroup', |
| 373 this.activeTable_); |
| 374 var colNodes = cvox.XpathUtil.evalXPath('child::col', this.activeTable_); |
| 375 |
| 376 if ((colgroupNodes.length == 0) && (colNodes.length == 0)) { |
| 377 var maxcols = 0; |
| 378 var outerChildren = this.getChildRows_(); |
| 379 for (var i = 0; i < outerChildren.length; i++) { |
| 380 var childrenCount = this.getChildCells_(outerChildren[i]); |
| 381 if (childrenCount.length > maxcols) { |
| 382 maxcols = childrenCount.length; |
| 383 } |
| 384 } |
| 385 return maxcols; |
| 386 } else { |
| 387 var sum = 0; |
| 388 for (var i = 0; i < colNodes.length; i++) { |
| 389 if (colNodes[i].hasAttribute('span')) { |
| 390 sum += colNodes[i].getAttribute('span'); |
| 391 } else { |
| 392 sum += 1; |
| 393 } |
| 394 } |
| 395 for (i = 0; i < colgroupNodes.length; i++) { |
| 396 var colChildren = cvox.XpathUtil.evalXPath('child::col', |
| 397 colgroupNodes[i]); |
| 398 if (colChildren.length == 0) { |
| 399 if (colgroupNodes[i].hasAttribute('span')) { |
| 400 sum += colgroupNodes[i].getAttribute('span'); |
| 401 } else { |
| 402 sum += 1; |
| 403 } |
| 404 } |
| 405 } |
| 406 } |
| 407 return sum; |
| 408 }; |
| 409 |
| 410 |
| 411 /** |
| 412 * Moves to the next row in the table. Updates the cell cursor. |
| 413 * |
| 414 * @return {boolean} Either: |
| 415 * 1) True if the update has been made. |
| 416 * 2) False if the end of the table has been reached and the update has not |
| 417 * happened. |
| 418 */ |
| 419 cvox.TraverseTable.prototype.nextRow = function() { |
| 420 if (!this.currentCellCursor) { |
| 421 // We have not started moving through the table yet |
| 422 return this.goToRow(0); |
| 423 } else { |
| 424 return this.goToRow(this.currentCellCursor[0] + 1); |
| 425 } |
| 426 |
| 427 }; |
| 428 |
| 429 |
| 430 /** |
| 431 * Moves to the previous row in the table. Updates the cell cursor. |
| 432 * |
| 433 * @return {boolean} Either: |
| 434 * 1) True if the update has been made. |
| 435 * 2) False if the end of the table has been reached and the update has not |
| 436 * happened. |
| 437 */ |
| 438 cvox.TraverseTable.prototype.prevRow = function() { |
| 439 if (!this.currentCellCursor) { |
| 440 // We have not started moving through the table yet |
| 441 return this.goToRow(this.rowCount - 1); |
| 442 } else { |
| 443 return this.goToRow(this.currentCellCursor[0] - 1); |
| 444 } |
| 445 }; |
| 446 |
| 447 |
| 448 /** |
| 449 * Moves to the next column in the table. Updates the cell cursor. |
| 450 * |
| 451 * @return {boolean} Either: |
| 452 * 1) True if the update has been made. |
| 453 * 2) False if the end of the table has been reached and the update has not |
| 454 * happened. |
| 455 */ |
| 456 cvox.TraverseTable.prototype.nextCol = function() { |
| 457 if (!this.currentCellCursor) { |
| 458 // We have not started moving through the table yet |
| 459 return this.goToCol(0); |
| 460 } else { |
| 461 return this.goToCol(this.currentCellCursor[1] + 1); |
| 462 } |
| 463 }; |
| 464 |
| 465 |
| 466 /** |
| 467 * Moves to the previous column in the table. Updates the cell cursor. |
| 468 * |
| 469 * @return {boolean} Either: |
| 470 * 1) True if the update has been made. |
| 471 * 2) False if the end of the table has been reached and the update has not |
| 472 * happened. |
| 473 */ |
| 474 cvox.TraverseTable.prototype.prevCol = function() { |
| 475 if (!this.currentCellCursor) { |
| 476 // We have not started moving through the table yet |
| 477 return this.goToCol(this.shadowColCount_() - 1); |
| 478 } else { |
| 479 return this.goToCol(this.currentCellCursor[1] - 1); |
| 480 } |
| 481 }; |
| 482 |
| 483 |
| 484 /** |
| 485 * Moves to the row at the specified index in the table. Updates the cell |
| 486 * cursor. |
| 487 * @param {number} index The index of the required row. |
| 488 * @return {boolean} Either: |
| 489 * 1) True if the index is valid and the update has been made. |
| 490 * 2) False if the index is not valid (either less than 0 or greater than |
| 491 * the number of rows in the table). |
| 492 */ |
| 493 cvox.TraverseTable.prototype.goToRow = function(index) { |
| 494 var childRows = this.getChildRows_(); |
| 495 if ((index < this.shadowTable_.length) && |
| 496 (index < childRows.length) && (index >= 0)) { |
| 497 this.currentRow = childRows[index]; |
| 498 if (this.currentCellCursor == null) { |
| 499 // We haven't started moving through the table yet |
| 500 this.currentCellCursor = [index, 0]; |
| 501 } else { |
| 502 this.currentCellCursor = [index, this.currentCellCursor[1]]; |
| 503 } |
| 504 return true; |
| 505 } else { |
| 506 return false; |
| 507 } |
| 508 }; |
| 509 |
| 510 |
| 511 /** |
| 512 * Moves to the column at the specified index in the table. Updates the cell |
| 513 * cursor. |
| 514 * @param {number} index The index of the required column. |
| 515 * @return {boolean} Either: |
| 516 * 1) True if the index is valid and the update has been made. |
| 517 * 2) False if the index is not valid (either less than 0 or greater than |
| 518 * the number of rows in the table). |
| 519 */ |
| 520 cvox.TraverseTable.prototype.goToCol = function(index) { |
| 521 if (index < 0 || index >= this.colCount) { |
| 522 return false; |
| 523 } |
| 524 |
| 525 var colArray = []; |
| 526 for (var i = 0; i < this.shadowTable_.length; i++) { |
| 527 |
| 528 if (this.shadowTable_[i][index]) { |
| 529 var shadowEntry = this.shadowTable_[i][index]; |
| 530 |
| 531 if (shadowEntry.colSpan && shadowEntry.rowSpan) { |
| 532 // Look at the last element in the column cell aray. |
| 533 var prev = colArray[colArray.length - 1]; |
| 534 if (prev != |
| 535 shadowEntry.activeCell) { |
| 536 // Watch out for positions spanned by a cell with rowspan and |
| 537 // colspan. We don't want the same cell showing up multiple times |
| 538 // in per-column cell lists. |
| 539 colArray.push( |
| 540 shadowEntry.activeCell); |
| 541 } |
| 542 } else if ((shadowEntry.colSpan) || (!shadowEntry.rowSpan)) { |
| 543 colArray.push( |
| 544 shadowEntry.activeCell); |
| 545 } |
| 546 } |
| 547 } |
| 548 this.currentCol = colArray; |
| 549 if (this.currentCellCursor == null) { |
| 550 // We haven't started moving through the table yet |
| 551 this.currentCellCursor = [0, index]; |
| 552 } else { |
| 553 this.currentCellCursor = [this.currentCellCursor[0], index]; |
| 554 } |
| 555 return true; |
| 556 }; |
| 557 |
| 558 |
| 559 /** |
| 560 * Moves to the cell at the specified index <i, j> in the table. Updates the |
| 561 * cell cursor. |
| 562 * @param {Array.<number>} index The index <i, j> of the required cell. |
| 563 * @return {boolean} Either: |
| 564 * 1) True if the index is valid and the update has been made. |
| 565 * 2) False if the index is not valid (either less than 0, greater than |
| 566 * the number of rows or columns in the table, or there is no cell |
| 567 * at that location). |
| 568 */ |
| 569 cvox.TraverseTable.prototype.goToCell = function(index) { |
| 570 var prevIndex = this.currentCellCursor; |
| 571 if (!this.goToRow(index[0])) { |
| 572 return false; |
| 573 } |
| 574 if (!this.goToCol(index[1])) { |
| 575 this.goToRow(prevIndex[0]); |
| 576 return false; |
| 577 } else { |
| 578 // The row and column exist, but the cell may still not if we're dealing |
| 579 // with a partial column. |
| 580 var cell = |
| 581 this.shadowTable_[this.currentCellCursor[0]][this.currentCellCursor[1]]; |
| 582 if (cell) { |
| 583 return true; |
| 584 } else { |
| 585 this.goToRow(prevIndex[0]); |
| 586 this.goToCol(prevIndex[1]); |
| 587 return false; |
| 588 } |
| 589 } |
| 590 }; |
| 591 |
| 592 |
| 593 /** |
| 594 * Resets the table cursors. |
| 595 * |
| 596 */ |
| 597 cvox.TraverseTable.prototype.resetCursor = function() { |
| 598 this.currentRow = null; |
| 599 this.currentCellCursor = null; |
| 600 this.currentCol = null; |
| 601 }; |
| 602 |
| 603 |
| 604 /** |
| 605 * Returns a JavaScript array of all the non-nested rows in the active table. |
| 606 * |
| 607 * @return {Array} An array of all the child rows of the active table. |
| 608 * @private |
| 609 */ |
| 610 cvox.TraverseTable.prototype.getChildRows_ = function() { |
| 611 return cvox.XpathUtil.evalXPath('child::tbody/tr', this.activeTable_); |
| 612 }; |
| 613 |
| 614 |
| 615 /** |
| 616 * Returns a JavaScript array of all the child cell <TD> or <TH> nodes of |
| 617 * the given row. |
| 618 * |
| 619 * @param {Node} rowNode The specified row node. |
| 620 * @return {Array} An array of all the child cells of the given row node. |
| 621 * @private |
| 622 */ |
| 623 cvox.TraverseTable.prototype.getChildCells_ = function(rowNode) { |
| 624 return cvox.XpathUtil.evalXPath('child::td | child::th', rowNode); |
| 625 }; |
| OLD | NEW |