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

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: Address feedback Created 4 years, 6 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)
David Tseng 2016/06/01 20:20:25 I think this should use our root predicate (curren
dmazzoni 2016/06/01 22:46:56 Done.
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}
102 */ 111 */
103 get node() { 112 get node() {
104 return this.node_; 113 for (var i = 0; i < this.ancestry_.length; i++) {
114 var firstValidNode = this.ancestry_[i];
115 if (firstValidNode != null && firstValidNode.role !== undefined &&
116 firstValidNode.root !== undefined) {
117 return firstValidNode;
118 }
119 // If we have to walk up to an ancestor, reset the index to zero.
David Tseng 2016/06/01 20:20:25 Update this as well.
dmazzoni 2016/06/01 22:46:56 Done.
120 this.index_ = cursors.NODE_INDEX;
121 }
122 return null;
105 }, 123 },
106 124
107 /** 125 /**
108 * @return {number} 126 * @return {number}
109 */ 127 */
110 get index() { 128 get index() {
111 return this.index_; 129 return this.index_;
112 }, 130 },
113 131
114 /** 132 /**
(...skipping 19 matching lines...) Expand all
134 }, 152 },
135 153
136 /** 154 /**
137 * Gets the accessible text of the node associated with this cursor. 155 * Gets the accessible text of the node associated with this cursor.
138 * 156 *
139 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's 157 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's
140 * node. 158 * node.
141 * @return {string} 159 * @return {string}
142 */ 160 */
143 getText: function(opt_node) { 161 getText: function(opt_node) {
144 var node = opt_node || this.node_; 162 var node = opt_node || this.node;
145 if (node.role === RoleType.textField) 163 if (node.role === RoleType.textField)
146 return node.value; 164 return node.value;
147 return node.name || ''; 165 return node.name || '';
148 }, 166 },
149 167
150 /** 168 /**
151 * Makes a Cursor which has been moved from this cursor by the unit in the 169 * Makes a Cursor which has been moved from this cursor by the unit in the
152 * given direction using the given movement type. 170 * given direction using the given movement type.
153 * @param {Unit} unit 171 * @param {Unit} unit
154 * @param {Movement} movement 172 * @param {Movement} movement
155 * @param {Dir} dir 173 * @param {Dir} dir
156 * @return {!cursors.Cursor} The moved cursor. 174 * @return {!cursors.Cursor} The moved cursor.
157 */ 175 */
158 move: function(unit, movement, dir) { 176 move: function(unit, movement, dir) {
159 var newNode = this.node_; 177 var originalNode = this.node;
178 if (!originalNode)
179 return this;
180
181 var newNode = originalNode;
160 var newIndex = this.index_; 182 var newIndex = this.index_;
161 183
162 if ((unit != Unit.NODE || unit != Unit.DOM_NODE) && 184 if ((unit != Unit.NODE || unit != Unit.DOM_NODE) &&
163 newIndex === cursors.NODE_INDEX) 185 newIndex === cursors.NODE_INDEX)
164 newIndex = 0; 186 newIndex = 0;
165 187
166 switch (unit) { 188 switch (unit) {
167 case Unit.CHARACTER: 189 case Unit.CHARACTER:
168 // BOUND and DIRECTIONAL are the same for characters. 190 // BOUND and DIRECTIONAL are the same for characters.
169 var text = this.getText(); 191 var text = this.getText();
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
250 case Unit.NODE: 272 case Unit.NODE:
251 case Unit.DOM_NODE: 273 case Unit.DOM_NODE:
252 switch (movement) { 274 switch (movement) {
253 case Movement.BOUND: 275 case Movement.BOUND:
254 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; 276 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0;
255 break; 277 break;
256 case Movement.DIRECTIONAL: 278 case Movement.DIRECTIONAL:
257 var pred = unit == Unit.NODE ? 279 var pred = unit == Unit.NODE ?
258 AutomationPredicate.leaf : AutomationPredicate.element; 280 AutomationPredicate.leaf : AutomationPredicate.element;
259 newNode = AutomationUtil.findNextNode( 281 newNode = AutomationUtil.findNextNode(
260 newNode, dir, pred) || this.node_; 282 newNode, dir, pred) || originalNode;
261 newIndex = cursors.NODE_INDEX; 283 newIndex = cursors.NODE_INDEX;
262 break; 284 break;
263 } 285 }
264 break; 286 break;
265 case Unit.LINE: 287 case Unit.LINE:
266 newIndex = 0; 288 newIndex = 0;
267 switch (movement) { 289 switch (movement) {
268 case Movement.BOUND: 290 case Movement.BOUND:
269 newNode = AutomationUtil.findNodeUntil(newNode, dir, 291 newNode = AutomationUtil.findNodeUntil(newNode, dir,
270 AutomationPredicate.linebreak, true); 292 AutomationPredicate.linebreak, true);
271 newNode = newNode || this.node_; 293 newNode = newNode || originalNode;
272 newIndex = 294 newIndex =
273 dir == Dir.FORWARD ? this.getText(newNode).length : 0; 295 dir == Dir.FORWARD ? this.getText(newNode).length : 0;
274 break; 296 break;
275 case Movement.DIRECTIONAL: 297 case Movement.DIRECTIONAL:
276 newNode = AutomationUtil.findNodeUntil( 298 newNode = AutomationUtil.findNodeUntil(
277 newNode, dir, AutomationPredicate.linebreak); 299 newNode, dir, AutomationPredicate.linebreak);
278 break; 300 break;
279 } 301 }
280 break; 302 break;
281 default: 303 default:
282 throw Error('Unrecognized unit: ' + unit); 304 throw Error('Unrecognized unit: ' + unit);
283 } 305 }
284 newNode = newNode || this.node_; 306 newNode = newNode || originalNode;
285 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; 307 newIndex = goog.isDef(newIndex) ? newIndex : this.index_;
286 return new cursors.Cursor(newNode, newIndex); 308 return new cursors.Cursor(newNode, newIndex);
287 }, 309 },
288 310
289 /** 311 /**
290 * Returns whether this cursor points to a valid position. 312 * Returns whether this cursor points to a valid position.
291 * @return {boolean} 313 * @return {boolean}
292 */ 314 */
293 isValid: function() { 315 isValid: function() {
294 return !!this.node.root; 316 return !!this.node.root;
(...skipping 22 matching lines...) Expand all
317 cursors.WrappingCursor.fromNode = function(node) { 339 cursors.WrappingCursor.fromNode = function(node) {
318 return new cursors.WrappingCursor(node, cursors.NODE_INDEX); 340 return new cursors.WrappingCursor(node, cursors.NODE_INDEX);
319 }; 341 };
320 342
321 cursors.WrappingCursor.prototype = { 343 cursors.WrappingCursor.prototype = {
322 __proto__: cursors.Cursor.prototype, 344 __proto__: cursors.Cursor.prototype,
323 345
324 /** @override */ 346 /** @override */
325 move: function(unit, movement, dir) { 347 move: function(unit, movement, dir) {
326 var result = this; 348 var result = this;
349 if (!result.node)
350 return this;
327 351
328 // Regular movement. 352 // Regular movement.
329 if (!AutomationUtil.isTraversalRoot(this.node) || dir == Dir.FORWARD) 353 if (!AutomationUtil.isTraversalRoot(this.node) || dir == Dir.FORWARD)
330 result = cursors.Cursor.prototype.move.call(this, unit, movement, dir); 354 result = cursors.Cursor.prototype.move.call(this, unit, movement, dir);
331 355
332 // There are two cases for wrapping: 356 // There are two cases for wrapping:
333 // 1. moving forwards from the last element. 357 // 1. moving forwards from the last element.
334 // 2. moving backwards from the document root. 358 // 2. moving backwards from the document root.
335 // Both result in |move| returning the same cursor. 359 // Both result in |move| returning the same cursor.
336 // For 1, simply place the new cursor on the document node. 360 // For 1, simply place the new cursor on the document node.
337 // For 2, try to descend to the first leaf-like object. 361 // For 2, try to descend to the first leaf-like object.
338 if (movement == Movement.DIRECTIONAL && result.equals(this)) { 362 if (movement == Movement.DIRECTIONAL && result.equals(this)) {
339 var pred = unit == Unit.DOM_NODE ? 363 var pred = unit == Unit.DOM_NODE ?
340 AutomationPredicate.element : AutomationPredicate.leaf; 364 AutomationPredicate.element : AutomationPredicate.leaf;
341 var endpoint = this.node; 365 var endpoint = this.node;
366 if (!endpoint)
367 return this;
342 368
343 // Case 1: forwards (find the root-like node). 369 // Case 1: forwards (find the root-like node).
344 while (!AutomationUtil.isTraversalRoot(endpoint) && endpoint.parent) 370 while (!AutomationUtil.isTraversalRoot(endpoint) && endpoint.parent)
345 endpoint = endpoint.parent; 371 endpoint = endpoint.parent;
346 372
347 // Case 2: backward (sync downwards to a leaf). 373 // Case 2: backward (sync downwards to a leaf).
348 if (dir == Dir.BACKWARD) 374 if (dir == Dir.BACKWARD)
349 endpoint = AutomationUtil.findNodePre(endpoint, dir, pred) || endpoint; 375 endpoint = AutomationUtil.findNodePre(endpoint, dir, pred) || endpoint;
350 376
351 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP); 377 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| 411 * Given |rangeA| and |rangeB| in order, determine which |Dir|
386 * relates them. 412 * relates them.
387 * @param {!cursors.Range} rangeA 413 * @param {!cursors.Range} rangeA
388 * @param {!cursors.Range} rangeB 414 * @param {!cursors.Range} rangeB
389 * @return {Dir} 415 * @return {Dir}
390 */ 416 */
391 cursors.Range.getDirection = function(rangeA, rangeB) { 417 cursors.Range.getDirection = function(rangeA, rangeB) {
392 if (!rangeA || !rangeB) 418 if (!rangeA || !rangeB)
393 return Dir.FORWARD; 419 return Dir.FORWARD;
394 420
421 if (!rangeA.start.node || !rangeA.end.node ||
422 !rangeB.start.node || !rangeB.end.node)
423 return Dir.FORWARD;
424
395 // They are the same range. 425 // They are the same range.
396 if (rangeA.start.node === rangeB.start.node && 426 if (rangeA.start.node === rangeB.start.node &&
397 rangeB.end.node === rangeA.end.node) 427 rangeB.end.node === rangeA.end.node)
398 return Dir.FORWARD; 428 return Dir.FORWARD;
399 429
400 var testDirA = 430 var testDirA =
401 AutomationUtil.getDirection( 431 AutomationUtil.getDirection(
402 rangeA.start.node, rangeB.end.node); 432 rangeA.start.node, rangeB.end.node);
403 var testDirB = 433 var testDirB =
404 AutomationUtil.getDirection( 434 AutomationUtil.getDirection(
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
464 494
465 /** 495 /**
466 * Makes a Range which has been moved from this range by the given unit and 496 * Makes a Range which has been moved from this range by the given unit and
467 * direction. 497 * direction.
468 * @param {Unit} unit 498 * @param {Unit} unit
469 * @param {Dir} dir 499 * @param {Dir} dir
470 * @return {cursors.Range} 500 * @return {cursors.Range}
471 */ 501 */
472 move: function(unit, dir) { 502 move: function(unit, dir) {
473 var newStart = this.start_; 503 var newStart = this.start_;
504 if (!newStart.node)
505 return this;
506
474 var newEnd; 507 var newEnd;
475 switch (unit) { 508 switch (unit) {
476 case Unit.CHARACTER: 509 case Unit.CHARACTER:
477 newStart = newStart.move(unit, Movement.BOUND, dir); 510 newStart = newStart.move(unit, Movement.BOUND, dir);
478 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD); 511 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD);
479 // Character crossed a node; collapses to the end of the node. 512 // Character crossed a node; collapses to the end of the node.
480 if (newStart.node !== newEnd.node) 513 if (newStart.node !== newEnd.node)
481 newEnd = new cursors.Cursor(newStart.node, newStart.index + 1); 514 newEnd = new cursors.Cursor(newStart.node, newStart.index + 1);
482 break; 515 break;
483 case Unit.WORD: 516 case Unit.WORD:
(...skipping 13 matching lines...) Expand all
497 return new cursors.Range(newStart, newEnd); 530 return new cursors.Range(newStart, newEnd);
498 }, 531 },
499 532
500 /** 533 /**
501 * Select the text contained within this range. 534 * Select the text contained within this range.
502 */ 535 */
503 select: function() { 536 select: function() {
504 var start = this.start.node; 537 var start = this.start.node;
505 var end = this.end.node; 538 var end = this.end.node;
506 539
540 if (!start || !end)
541 return;
542
507 // Find the most common root. 543 // Find the most common root.
508 var uniqueAncestors = AutomationUtil.getUniqueAncestors(start, end); 544 var uniqueAncestors = AutomationUtil.getUniqueAncestors(start, end);
509 var mcr = start.root; 545 var mcr = start.root;
510 if (uniqueAncestors) 546 if (uniqueAncestors)
511 mcr = uniqueAncestors.pop().parent.root; 547 mcr = uniqueAncestors.pop().parent.root;
512 548
513 if (mcr.role == RoleType.desktop) 549 if (mcr.role == RoleType.desktop)
514 return; 550 return;
515 551
516 if (mcr === start.root && mcr === end.root) { 552 if (mcr === start.root && mcr === end.root) {
(...skipping 25 matching lines...) Expand all
542 /** 578 /**
543 * Returns whether this range has valid start and end cursors. 579 * Returns whether this range has valid start and end cursors.
544 * @return {boolean} 580 * @return {boolean}
545 */ 581 */
546 isValid: function() { 582 isValid: function() {
547 return this.start.isValid() && this.end.isValid(); 583 return this.start.isValid() && this.end.isValid();
548 } 584 }
549 }; 585 };
550 586
551 }); // goog.scope 587 }); // goog.scope
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698