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

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js

Issue 2007183002: Make ChromeVox cursor robust to deleted nodes (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 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
OLDNEW
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
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
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
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
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698