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

Side by Side Diff: chrome/browser/resources/access_chromevox/common/traverse_table.js

Issue 6254007: Adding ChromeVox as a component extensions (enabled only for ChromeOS, for no... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:executable
+ *
Added: svn:eol-style
+ LF
OLDNEW
(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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698