Chromium Code Reviews| 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 Classes related to cursors that point to and select parts of | 6 * @fileoverview Classes related to cursors that point to and select parts of |
| 7 * the automation tree. | 7 * the automation tree. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 goog.provide('cursors.Cursor'); | 10 goog.provide('cursors.Cursor'); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 64 | 64 |
| 65 /** | 65 /** |
| 66 * Represents a position within the automation tree. | 66 * Represents a position within the automation tree. |
| 67 * @constructor | 67 * @constructor |
| 68 * @param {!AutomationNode} node | 68 * @param {!AutomationNode} node |
| 69 * @param {number} index A 0-based index into this cursor node's primary | 69 * @param {number} index A 0-based index into this cursor node's primary |
| 70 * accessible name. An index of |cursors.NODE_INDEX| means the node as a whole | 70 * accessible name. An index of |cursors.NODE_INDEX| means the node as a whole |
| 71 * is pointed to and covers the case where the accessible text is empty. | 71 * is pointed to and covers the case where the accessible text is empty. |
| 72 */ | 72 */ |
| 73 cursors.Cursor = function(node, index) { | 73 cursors.Cursor = function(node, index) { |
| 74 /** @type {!AutomationNode} @private */ | |
| 75 this.node_ = node; | |
| 76 /** @type {number} @private */ | 74 /** @type {number} @private */ |
| 77 this.index_ = index; | 75 this.index_ = index; |
| 76 /** @type {Array<AutomationNode>} @private */ | |
| 77 this.ancestry_ = []; | |
| 78 var nodeWalker = node; | |
| 79 while (nodeWalker) { | |
| 80 this.ancestry_.push(nodeWalker); | |
| 81 nodeWalker = nodeWalker.parent; | |
| 82 if (nodeWalker == node.root) | |
| 83 break; | |
| 84 } | |
| 78 }; | 85 }; |
| 79 | 86 |
| 80 /** | 87 /** |
| 81 * Convenience method to construct a Cursor from a node. | 88 * Convenience method to construct a Cursor from a node. |
| 82 * @param {!AutomationNode} node | 89 * @param {!AutomationNode} node |
| 83 * @return {!cursors.Cursor} | 90 * @return {!cursors.Cursor} |
| 84 */ | 91 */ |
| 85 cursors.Cursor.fromNode = function(node) { | 92 cursors.Cursor.fromNode = function(node) { |
| 86 return new cursors.Cursor(node, cursors.NODE_INDEX); | 93 return new cursors.Cursor(node, cursors.NODE_INDEX); |
| 87 }; | 94 }; |
| 88 | 95 |
| 89 cursors.Cursor.prototype = { | 96 cursors.Cursor.prototype = { |
| 90 /** | 97 /** |
| 91 * Returns true if |rhs| is equal to this cursor. | 98 * Returns true if |rhs| is equal to this cursor. |
| 92 * @param {!cursors.Cursor} rhs | 99 * @param {!cursors.Cursor} rhs |
| 93 * @return {boolean} | 100 * @return {boolean} |
| 94 */ | 101 */ |
| 95 equals: function(rhs) { | 102 equals: function(rhs) { |
| 96 return this.node_ === rhs.node && | 103 return this.node === rhs.node && |
| 97 this.index_ === rhs.index; | 104 this.index === rhs.index; |
| 98 }, | 105 }, |
| 99 | 106 |
| 100 /** | 107 /** |
| 101 * @return {!AutomationNode} | 108 * Returns the node. If the node is invalid since the last time it |
| 109 * was accessed, moves the cursor to the nearest valid ancestor first. | |
| 110 * @return {?AutomationNode} | |
|
David Tseng
2016/05/25 20:58:10
You don't need the '?' (objects are nullable by de
dmazzoni
2016/06/01 16:36:50
Done.
| |
| 102 */ | 111 */ |
| 103 get node() { | 112 get node() { |
| 104 return this.node_; | 113 for (var index = 0; index < this.ancestry_.length; index++) { |
|
David Tseng
2016/05/25 20:58:10
The dual use of index here is confusing. Maybe use
dmazzoni
2016/06/01 16:36:50
Fixed, but the Closure compiler doesn't let us use
| |
| 114 var foo = this.ancestry_[index]; | |
|
David Tseng
2016/05/25 20:58:10
How about firstValidNode?
dmazzoni
2016/06/01 16:36:50
Acknowledged.
| |
| 115 if (foo != null && foo.role !== undefined) | |
|
David Tseng
2016/05/25 20:58:10
This won't work. There are nodes that have a role
dmazzoni
2016/06/01 16:36:50
Done.
| |
| 116 return foo; | |
|
David Tseng
2016/05/25 20:58:10
Do you need to do bookkeeping on the ancestry chai
dmazzoni
2016/06/01 16:36:50
I don't think so, because this object is basically
| |
| 117 // If we have to walk up to an ancestor, reset the index to zero. | |
| 118 this.index_ = 0; | |
|
David Tseng
2016/05/25 20:58:10
Reset to cursors.NODE_INDEX
dmazzoni
2016/06/01 16:36:50
Thanks, that made it work better.
| |
| 119 } | |
| 120 return null; | |
| 105 }, | 121 }, |
| 106 | 122 |
| 107 /** | 123 /** |
| 108 * @return {number} | 124 * @return {number} |
| 109 */ | 125 */ |
| 110 get index() { | 126 get index() { |
| 111 return this.index_; | 127 return this.index_; |
| 112 }, | 128 }, |
| 113 | 129 |
| 114 /** | 130 /** |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 134 }, | 150 }, |
| 135 | 151 |
| 136 /** | 152 /** |
| 137 * Gets the accessible text of the node associated with this cursor. | 153 * Gets the accessible text of the node associated with this cursor. |
| 138 * | 154 * |
| 139 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's | 155 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's |
| 140 * node. | 156 * node. |
| 141 * @return {string} | 157 * @return {string} |
| 142 */ | 158 */ |
| 143 getText: function(opt_node) { | 159 getText: function(opt_node) { |
| 144 var node = opt_node || this.node_; | 160 var node = opt_node || this.node; |
| 145 if (node.role === RoleType.textField) | 161 if (node.role === RoleType.textField) |
| 146 return node.value; | 162 return node.value; |
| 147 return node.name || ''; | 163 return node.name || ''; |
| 148 }, | 164 }, |
| 149 | 165 |
| 150 /** | 166 /** |
| 151 * Makes a Cursor which has been moved from this cursor by the unit in the | 167 * Makes a Cursor which has been moved from this cursor by the unit in the |
| 152 * given direction using the given movement type. | 168 * given direction using the given movement type. |
| 153 * @param {Unit} unit | 169 * @param {Unit} unit |
| 154 * @param {Movement} movement | 170 * @param {Movement} movement |
| 155 * @param {Dir} dir | 171 * @param {Dir} dir |
| 156 * @return {!cursors.Cursor} The moved cursor. | 172 * @return {!cursors.Cursor} The moved cursor. |
| 157 */ | 173 */ |
| 158 move: function(unit, movement, dir) { | 174 move: function(unit, movement, dir) { |
| 159 var newNode = this.node_; | 175 var originalNode = this.node; |
| 176 if (!originalNode) | |
| 177 return this; | |
| 178 | |
| 179 var newNode = originalNode; | |
| 160 var newIndex = this.index_; | 180 var newIndex = this.index_; |
| 161 | 181 |
| 162 if ((unit != Unit.NODE || unit != Unit.DOM_NODE) && | 182 if ((unit != Unit.NODE || unit != Unit.DOM_NODE) && |
| 163 newIndex === cursors.NODE_INDEX) | 183 newIndex === cursors.NODE_INDEX) |
| 164 newIndex = 0; | 184 newIndex = 0; |
| 165 | 185 |
| 166 switch (unit) { | 186 switch (unit) { |
| 167 case Unit.CHARACTER: | 187 case Unit.CHARACTER: |
| 168 // BOUND and DIRECTIONAL are the same for characters. | 188 // BOUND and DIRECTIONAL are the same for characters. |
| 169 var text = this.getText(); | 189 var text = this.getText(); |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 250 case Unit.NODE: | 270 case Unit.NODE: |
| 251 case Unit.DOM_NODE: | 271 case Unit.DOM_NODE: |
| 252 switch (movement) { | 272 switch (movement) { |
| 253 case Movement.BOUND: | 273 case Movement.BOUND: |
| 254 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; | 274 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; |
| 255 break; | 275 break; |
| 256 case Movement.DIRECTIONAL: | 276 case Movement.DIRECTIONAL: |
| 257 var pred = unit == Unit.NODE ? | 277 var pred = unit == Unit.NODE ? |
| 258 AutomationPredicate.leaf : AutomationPredicate.element; | 278 AutomationPredicate.leaf : AutomationPredicate.element; |
| 259 newNode = AutomationUtil.findNextNode( | 279 newNode = AutomationUtil.findNextNode( |
| 260 newNode, dir, pred) || this.node_; | 280 newNode, dir, pred) || originalNode; |
| 261 newIndex = cursors.NODE_INDEX; | 281 newIndex = cursors.NODE_INDEX; |
| 262 break; | 282 break; |
| 263 } | 283 } |
| 264 break; | 284 break; |
| 265 case Unit.LINE: | 285 case Unit.LINE: |
| 266 newIndex = 0; | 286 newIndex = 0; |
| 267 switch (movement) { | 287 switch (movement) { |
| 268 case Movement.BOUND: | 288 case Movement.BOUND: |
| 269 newNode = AutomationUtil.findNodeUntil(newNode, dir, | 289 newNode = AutomationUtil.findNodeUntil(newNode, dir, |
| 270 AutomationPredicate.linebreak, true); | 290 AutomationPredicate.linebreak, true); |
| 271 newNode = newNode || this.node_; | 291 newNode = newNode || originalNode; |
| 272 newIndex = | 292 newIndex = |
| 273 dir == Dir.FORWARD ? this.getText(newNode).length : 0; | 293 dir == Dir.FORWARD ? this.getText(newNode).length : 0; |
| 274 break; | 294 break; |
| 275 case Movement.DIRECTIONAL: | 295 case Movement.DIRECTIONAL: |
| 276 newNode = AutomationUtil.findNodeUntil( | 296 newNode = AutomationUtil.findNodeUntil( |
| 277 newNode, dir, AutomationPredicate.linebreak); | 297 newNode, dir, AutomationPredicate.linebreak); |
| 278 break; | 298 break; |
| 279 } | 299 } |
| 280 break; | 300 break; |
| 281 default: | 301 default: |
| 282 throw Error('Unrecognized unit: ' + unit); | 302 throw Error('Unrecognized unit: ' + unit); |
| 283 } | 303 } |
| 284 newNode = newNode || this.node_; | 304 newNode = newNode || originalNode; |
| 285 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; | 305 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; |
| 286 return new cursors.Cursor(newNode, newIndex); | 306 return new cursors.Cursor(newNode, newIndex); |
| 287 }, | 307 }, |
| 288 | 308 |
| 289 /** | 309 /** |
| 290 * Returns whether this cursor points to a valid position. | 310 * Returns whether this cursor points to a valid position. |
| 291 * @return {boolean} | 311 * @return {boolean} |
| 292 */ | 312 */ |
| 293 isValid: function() { | 313 isValid: function() { |
| 294 return this.node.role !== undefined; | 314 return this.node.role !== undefined; |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 317 cursors.WrappingCursor.fromNode = function(node) { | 337 cursors.WrappingCursor.fromNode = function(node) { |
| 318 return new cursors.WrappingCursor(node, cursors.NODE_INDEX); | 338 return new cursors.WrappingCursor(node, cursors.NODE_INDEX); |
| 319 }; | 339 }; |
| 320 | 340 |
| 321 cursors.WrappingCursor.prototype = { | 341 cursors.WrappingCursor.prototype = { |
| 322 __proto__: cursors.Cursor.prototype, | 342 __proto__: cursors.Cursor.prototype, |
| 323 | 343 |
| 324 /** @override */ | 344 /** @override */ |
| 325 move: function(unit, movement, dir) { | 345 move: function(unit, movement, dir) { |
| 326 var result = this; | 346 var result = this; |
| 347 if (!result.node) | |
| 348 return this; | |
| 327 | 349 |
| 328 // Regular movement. | 350 // Regular movement. |
| 329 if (!AutomationUtil.isTraversalRoot(this.node) || dir == Dir.FORWARD) | 351 if (!AutomationUtil.isTraversalRoot(this.node) || dir == Dir.FORWARD) |
| 330 result = cursors.Cursor.prototype.move.call(this, unit, movement, dir); | 352 result = cursors.Cursor.prototype.move.call(this, unit, movement, dir); |
| 331 | 353 |
| 332 // There are two cases for wrapping: | 354 // There are two cases for wrapping: |
| 333 // 1. moving forwards from the last element. | 355 // 1. moving forwards from the last element. |
| 334 // 2. moving backwards from the document root. | 356 // 2. moving backwards from the document root. |
| 335 // Both result in |move| returning the same cursor. | 357 // Both result in |move| returning the same cursor. |
| 336 // For 1, simply place the new cursor on the document node. | 358 // For 1, simply place the new cursor on the document node. |
| 337 // For 2, try to descend to the first leaf-like object. | 359 // For 2, try to descend to the first leaf-like object. |
| 338 if (movement == Movement.DIRECTIONAL && result.equals(this)) { | 360 if (movement == Movement.DIRECTIONAL && result.equals(this)) { |
| 339 var pred = unit == Unit.DOM_NODE ? | 361 var pred = unit == Unit.DOM_NODE ? |
| 340 AutomationPredicate.element : AutomationPredicate.leaf; | 362 AutomationPredicate.element : AutomationPredicate.leaf; |
| 341 var endpoint = this.node; | 363 var endpoint = this.node; |
| 364 if (!endpoint) | |
| 365 return this; | |
| 342 | 366 |
| 343 // Case 1: forwards (find the root-like node). | 367 // Case 1: forwards (find the root-like node). |
| 344 while (!AutomationUtil.isTraversalRoot(endpoint) && endpoint.parent) | 368 while (!AutomationUtil.isTraversalRoot(endpoint) && endpoint.parent) |
| 345 endpoint = endpoint.parent; | 369 endpoint = endpoint.parent; |
| 346 | 370 |
| 347 // Case 2: backward (sync downwards to a leaf). | 371 // Case 2: backward (sync downwards to a leaf). |
| 348 if (dir == Dir.BACKWARD) | 372 if (dir == Dir.BACKWARD) |
| 349 endpoint = AutomationUtil.findNodePre(endpoint, dir, pred) || endpoint; | 373 endpoint = AutomationUtil.findNodePre(endpoint, dir, pred) || endpoint; |
| 350 | 374 |
| 351 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP); | 375 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 385 * Given |rangeA| and |rangeB| in order, determine which |Dir| | 409 * Given |rangeA| and |rangeB| in order, determine which |Dir| |
| 386 * relates them. | 410 * relates them. |
| 387 * @param {!cursors.Range} rangeA | 411 * @param {!cursors.Range} rangeA |
| 388 * @param {!cursors.Range} rangeB | 412 * @param {!cursors.Range} rangeB |
| 389 * @return {Dir} | 413 * @return {Dir} |
| 390 */ | 414 */ |
| 391 cursors.Range.getDirection = function(rangeA, rangeB) { | 415 cursors.Range.getDirection = function(rangeA, rangeB) { |
| 392 if (!rangeA || !rangeB) | 416 if (!rangeA || !rangeB) |
| 393 return Dir.FORWARD; | 417 return Dir.FORWARD; |
| 394 | 418 |
| 419 if (!rangeA.start.node || !rangeA.end.node || | |
| 420 !rangeB.start.node || !rangeB.end.node) | |
| 421 return Dir.FORWARD; | |
| 422 | |
| 395 // They are the same range. | 423 // They are the same range. |
| 396 if (rangeA.start.node === rangeB.start.node && | 424 if (rangeA.start.node === rangeB.start.node && |
| 397 rangeB.end.node === rangeA.end.node) | 425 rangeB.end.node === rangeA.end.node) |
| 398 return Dir.FORWARD; | 426 return Dir.FORWARD; |
| 399 | 427 |
| 400 var testDirA = | 428 var testDirA = |
| 401 AutomationUtil.getDirection( | 429 AutomationUtil.getDirection( |
| 402 rangeA.start.node, rangeB.end.node); | 430 rangeA.start.node, rangeB.end.node); |
| 403 var testDirB = | 431 var testDirB = |
| 404 AutomationUtil.getDirection( | 432 AutomationUtil.getDirection( |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 464 | 492 |
| 465 /** | 493 /** |
| 466 * Makes a Range which has been moved from this range by the given unit and | 494 * Makes a Range which has been moved from this range by the given unit and |
| 467 * direction. | 495 * direction. |
| 468 * @param {Unit} unit | 496 * @param {Unit} unit |
| 469 * @param {Dir} dir | 497 * @param {Dir} dir |
| 470 * @return {cursors.Range} | 498 * @return {cursors.Range} |
| 471 */ | 499 */ |
| 472 move: function(unit, dir) { | 500 move: function(unit, dir) { |
| 473 var newStart = this.start_; | 501 var newStart = this.start_; |
| 502 if (!newStart.node) | |
| 503 return this; | |
| 504 | |
| 474 var newEnd; | 505 var newEnd; |
| 475 switch (unit) { | 506 switch (unit) { |
| 476 case Unit.CHARACTER: | 507 case Unit.CHARACTER: |
| 477 newStart = newStart.move(unit, Movement.BOUND, dir); | 508 newStart = newStart.move(unit, Movement.BOUND, dir); |
| 478 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD); | 509 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD); |
| 479 // Character crossed a node; collapses to the end of the node. | 510 // Character crossed a node; collapses to the end of the node. |
| 480 if (newStart.node !== newEnd.node) | 511 if (newStart.node !== newEnd.node) |
| 481 newEnd = new cursors.Cursor(newStart.node, newStart.index + 1); | 512 newEnd = new cursors.Cursor(newStart.node, newStart.index + 1); |
| 482 break; | 513 break; |
| 483 case Unit.WORD: | 514 case Unit.WORD: |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 497 return new cursors.Range(newStart, newEnd); | 528 return new cursors.Range(newStart, newEnd); |
| 498 }, | 529 }, |
| 499 | 530 |
| 500 /** | 531 /** |
| 501 * Select the text contained within this range. | 532 * Select the text contained within this range. |
| 502 */ | 533 */ |
| 503 select: function() { | 534 select: function() { |
| 504 var start = this.start.node; | 535 var start = this.start.node; |
| 505 var end = this.end.node; | 536 var end = this.end.node; |
| 506 | 537 |
| 538 if (!start || !end) | |
| 539 return; | |
| 540 | |
| 507 // Find the most common root. | 541 // Find the most common root. |
| 508 var uniqueAncestors = AutomationUtil.getUniqueAncestors(start, end); | 542 var uniqueAncestors = AutomationUtil.getUniqueAncestors(start, end); |
| 509 var mcr = start.root; | 543 var mcr = start.root; |
| 510 if (uniqueAncestors) | 544 if (uniqueAncestors) |
| 511 mcr = uniqueAncestors.pop().parent.root; | 545 mcr = uniqueAncestors.pop().parent.root; |
| 512 | 546 |
| 513 if (mcr.role == RoleType.desktop) | 547 if (mcr.role == RoleType.desktop) |
| 514 return; | 548 return; |
| 515 | 549 |
| 516 if (mcr === start.root && mcr === end.root) { | 550 if (mcr === start.root && mcr === end.root) { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 541 /** | 575 /** |
| 542 * Returns whether this range has valid start and end cursors. | 576 * Returns whether this range has valid start and end cursors. |
| 543 * @return {boolean} | 577 * @return {boolean} |
| 544 */ | 578 */ |
| 545 isValid: function() { | 579 isValid: function() { |
| 546 return this.start.isValid() && this.end.isValid(); | 580 return this.start.isValid() && this.end.isValid(); |
| 547 } | 581 } |
| 548 }; | 582 }; |
| 549 | 583 |
| 550 }); // goog.scope | 584 }); // goog.scope |
| OLD | NEW |