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) | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |