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 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
81 // Re-interpret this case as the beginning of the next node. | 81 // Re-interpret this case as the beginning of the next node. |
82 var nextNode = AutomationUtil.findNextNode( | 82 var nextNode = AutomationUtil.findNextNode( |
83 node, Dir.FORWARD, AutomationPredicate.leafOrStaticText); | 83 node, Dir.FORWARD, AutomationPredicate.leafOrStaticText); |
84 | 84 |
85 // The exception is when a user types at the end of a line. In that case, | 85 // The exception is when a user types at the end of a line. In that case, |
86 // staying on the current node is appropriate. | 86 // staying on the current node is appropriate. |
87 if (node && node.nextOnLine && nextNode) { | 87 if (node && node.nextOnLine && nextNode) { |
88 node = nextNode; | 88 node = nextNode; |
89 index = 0; | 89 index = 0; |
90 } | 90 } |
91 } else if (node.role == RoleType.GENERIC_CONTAINER && | 91 } else if ( |
92 node.state.richlyEditable && | 92 node.role == RoleType.GENERIC_CONTAINER && node.state.richlyEditable && |
93 (node.firstChild && (node.firstChild.role == RoleType.LINE_BREAK || | 93 (node.firstChild && |
94 node.firstChild.role == RoleType.STATIC_TEXT))) { | 94 (node.firstChild.role == RoleType.LINE_BREAK || |
| 95 node.firstChild.role == RoleType.STATIC_TEXT))) { |
95 // Re-interpret this case as pointing to the text under the div. | 96 // Re-interpret this case as pointing to the text under the div. |
96 node = node.find({ role: RoleType.INLINE_TEXT_BOX }) || node; | 97 node = node.find({role: RoleType.INLINE_TEXT_BOX}) || node; |
97 } | 98 } |
98 | 99 |
99 /** @type {number} @private */ | 100 /** @type {number} @private */ |
100 this.index_ = index; | 101 this.index_ = index; |
101 /** @type {Array<AutomationNode>} @private */ | 102 /** @type {Array<AutomationNode>} @private */ |
102 this.ancestry_ = []; | 103 this.ancestry_ = []; |
103 var nodeWalker = node; | 104 var nodeWalker = node; |
104 while (nodeWalker) { | 105 while (nodeWalker) { |
105 this.ancestry_.push(nodeWalker); | 106 this.ancestry_.push(nodeWalker); |
106 nodeWalker = nodeWalker.parent; | 107 nodeWalker = nodeWalker.parent; |
(...skipping 12 matching lines...) Expand all Loading... |
119 }; | 120 }; |
120 | 121 |
121 cursors.Cursor.prototype = { | 122 cursors.Cursor.prototype = { |
122 /** | 123 /** |
123 * Returns true if |rhs| is equal to this cursor. | 124 * Returns true if |rhs| is equal to this cursor. |
124 * Use this for strict equality between cursors. | 125 * Use this for strict equality between cursors. |
125 * @param {!cursors.Cursor} rhs | 126 * @param {!cursors.Cursor} rhs |
126 * @return {boolean} | 127 * @return {boolean} |
127 */ | 128 */ |
128 equals: function(rhs) { | 129 equals: function(rhs) { |
129 return this.node === rhs.node && | 130 return this.node === rhs.node && this.index === rhs.index; |
130 this.index === rhs.index; | |
131 }, | 131 }, |
132 | 132 |
133 /** | 133 /** |
134 * Returns true if |rhs| is equal to this cursor. | 134 * Returns true if |rhs| is equal to this cursor. |
135 * Use this for loose equality between cursors where specific character-based | 135 * Use this for loose equality between cursors where specific character-based |
136 * indicies do not matter such as when processing node-targeted events. | 136 * indicies do not matter such as when processing node-targeted events. |
137 * @param {!cursors.Cursor} rhs | 137 * @param {!cursors.Cursor} rhs |
138 * @return {boolean} | 138 * @return {boolean} |
139 */ | 139 */ |
140 contentEquals: function(rhs) { | 140 contentEquals: function(rhs) { |
141 // First, normalize the two nodes so they both point to the first non-text | 141 // First, normalize the two nodes so they both point to the first non-text |
142 // node. | 142 // node. |
143 var lNode = this.node; | 143 var lNode = this.node; |
144 var rNode = rhs.node; | 144 var rNode = rhs.node; |
145 while (lNode && (lNode.role == RoleType.INLINE_TEXT_BOX || | 145 while (lNode && |
146 lNode.role == RoleType.STATIC_TEXT)) | 146 (lNode.role == RoleType.INLINE_TEXT_BOX || |
| 147 lNode.role == RoleType.STATIC_TEXT)) |
147 lNode = lNode.parent; | 148 lNode = lNode.parent; |
148 while (rNode && (rNode.role == RoleType.INLINE_TEXT_BOX || | 149 while (rNode && |
149 rNode.role == RoleType.STATIC_TEXT)) | 150 (rNode.role == RoleType.INLINE_TEXT_BOX || |
| 151 rNode.role == RoleType.STATIC_TEXT)) |
150 rNode = rNode.parent; | 152 rNode = rNode.parent; |
151 | 153 |
152 // Ignore indicies for now. | 154 // Ignore indicies for now. |
153 | 155 |
154 return lNode === rNode && lNode != undefined; | 156 return lNode === rNode && lNode != undefined; |
155 }, | 157 }, |
156 | 158 |
157 /** | 159 /** |
158 * Compares this cursor with |rhs|. | 160 * Compares this cursor with |rhs|. |
159 * @param {cursors.Cursor} rhs | 161 * @param {cursors.Cursor} rhs |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
209 if (adjustedNode.state.editable) | 211 if (adjustedNode.state.editable) |
210 return adjustedNode; | 212 return adjustedNode; |
211 | 213 |
212 // Selections over line break nodes are broken. | 214 // Selections over line break nodes are broken. |
213 var parent = adjustedNode.parent; | 215 var parent = adjustedNode.parent; |
214 var grandparent = parent && parent.parent; | 216 var grandparent = parent && parent.parent; |
215 if (parent && parent.role == RoleType.LINE_BREAK) { | 217 if (parent && parent.role == RoleType.LINE_BREAK) { |
216 adjustedNode = grandparent; | 218 adjustedNode = grandparent; |
217 } else if (grandparent && grandparent.role == RoleType.LINE_BREAK) { | 219 } else if (grandparent && grandparent.role == RoleType.LINE_BREAK) { |
218 adjustedNode = grandparent.parent; | 220 adjustedNode = grandparent.parent; |
219 } else if (this.index_ == cursors.NODE_INDEX || | 221 } else if ( |
| 222 this.index_ == cursors.NODE_INDEX || |
220 adjustedNode.role == RoleType.INLINE_TEXT_BOX || | 223 adjustedNode.role == RoleType.INLINE_TEXT_BOX || |
221 adjustedNode.nameFrom != chrome.automation.NameFromType.CONTENTS) { | 224 adjustedNode.nameFrom != chrome.automation.NameFromType.CONTENTS) { |
222 // A node offset or unselectable character offset. | 225 // A node offset or unselectable character offset. |
223 adjustedNode = parent; | 226 adjustedNode = parent; |
224 } else { | 227 } else { |
225 // A character offset into content. | 228 // A character offset into content. |
226 adjustedNode = | 229 adjustedNode = |
227 adjustedNode.find({role: RoleType.STATIC_TEXT}) || adjustedNode; | 230 adjustedNode.find({role: RoleType.STATIC_TEXT}) || adjustedNode; |
228 } | 231 } |
229 | 232 |
230 return adjustedNode || null; | 233 return adjustedNode || null; |
231 }, | 234 }, |
232 | 235 |
233 /** | 236 /** |
234 * An index appropriate for making selections. If this cursor has a | 237 * An index appropriate for making selections. If this cursor has a |
235 * cursors.NODE_INDEX index, the selection index is a node offset e.g. the | 238 * cursors.NODE_INDEX index, the selection index is a node offset e.g. the |
236 * index in parent. If not, the index is a character offset. | 239 * index in parent. If not, the index is a character offset. |
237 * @return {number} | 240 * @return {number} |
238 * @private | 241 * @private |
239 */ | 242 */ |
240 get selectionIndex_() { | 243 get selectionIndex_() { |
241 var adjustedIndex = this.index_; | 244 var adjustedIndex = this.index_; |
242 | 245 |
243 if (!this.node) | 246 if (!this.node) |
244 return -1; | 247 return -1; |
245 | 248 |
246 if (this.node.state.editable) { | 249 if (this.node.state.editable) { |
247 return this.index_ == cursors.NODE_INDEX ? 0 : this.index_; | 250 return this.index_ == cursors.NODE_INDEX ? 0 : this.index_; |
248 } else if (this.node.role == RoleType.INLINE_TEXT_BOX && | 251 } else if ( |
249 // Selections under a line break are broken. | 252 this.node.role == RoleType.INLINE_TEXT_BOX && |
| 253 // Selections under a line break are broken. |
250 this.node.parent && this.node.parent.role != RoleType.LINE_BREAK) { | 254 this.node.parent && this.node.parent.role != RoleType.LINE_BREAK) { |
251 if (adjustedIndex == cursors.NODE_INDEX) | 255 if (adjustedIndex == cursors.NODE_INDEX) |
252 adjustedIndex = 0; | 256 adjustedIndex = 0; |
253 | 257 |
254 var sibling = this.node.previousSibling; | 258 var sibling = this.node.previousSibling; |
255 while (sibling) { | 259 while (sibling) { |
256 adjustedIndex += sibling.name.length; | 260 adjustedIndex += sibling.name.length; |
257 sibling = sibling.previousSibling; | 261 sibling = sibling.previousSibling; |
258 } | 262 } |
259 } else if (this.index_ == cursors.NODE_INDEX || | 263 } else if ( |
| 264 this.index_ == cursors.NODE_INDEX || |
260 this.node.nameFrom != chrome.automation.NameFromType.CONTENTS) { | 265 this.node.nameFrom != chrome.automation.NameFromType.CONTENTS) { |
261 // A node offset or unselectable character offset. | 266 // A node offset or unselectable character offset. |
262 | 267 |
263 // The selected node could have been adjusted upwards in the tree. | 268 // The selected node could have been adjusted upwards in the tree. |
264 var childOfSelection = this.node; | 269 var childOfSelection = this.node; |
265 do { | 270 do { |
266 adjustedIndex = childOfSelection.indexInParent || 0; | 271 adjustedIndex = childOfSelection.indexInParent || 0; |
267 childOfSelection = childOfSelection.parent; | 272 childOfSelection = childOfSelection.parent; |
268 } while (childOfSelection && childOfSelection != this.selectionNode_); | 273 } while (childOfSelection && childOfSelection != this.selectionNode_); |
269 } | 274 } |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 // BOUND and DIRECTIONAL are the same for characters. | 310 // BOUND and DIRECTIONAL are the same for characters. |
306 var text = this.getText(); | 311 var text = this.getText(); |
307 newIndex = dir == Dir.FORWARD ? | 312 newIndex = dir == Dir.FORWARD ? |
308 StringUtil.nextCodePointOffset(text, newIndex) : | 313 StringUtil.nextCodePointOffset(text, newIndex) : |
309 StringUtil.previousCodePointOffset(text, newIndex); | 314 StringUtil.previousCodePointOffset(text, newIndex); |
310 if (newIndex < 0 || newIndex >= text.length) { | 315 if (newIndex < 0 || newIndex >= text.length) { |
311 newNode = AutomationUtil.findNextNode( | 316 newNode = AutomationUtil.findNextNode( |
312 newNode, dir, AutomationPredicate.leafWithText); | 317 newNode, dir, AutomationPredicate.leafWithText); |
313 if (newNode) { | 318 if (newNode) { |
314 var newText = AutomationUtil.getText(newNode); | 319 var newText = AutomationUtil.getText(newNode); |
315 newIndex = | 320 newIndex = dir == Dir.FORWARD ? |
316 dir == Dir.FORWARD ? 0 : | 321 0 : |
317 StringUtil.previousCodePointOffset(newText, newText.length); | 322 StringUtil.previousCodePointOffset(newText, newText.length); |
318 newIndex = Math.max(newIndex, 0); | 323 newIndex = Math.max(newIndex, 0); |
319 } else { | 324 } else { |
320 newIndex = this.index_; | 325 newIndex = this.index_; |
321 } | 326 } |
322 } | 327 } |
323 break; | 328 break; |
324 case Unit.WORD: | 329 case Unit.WORD: |
325 if (newNode.role != RoleType.INLINE_TEXT_BOX) { | 330 if (newNode.role != RoleType.INLINE_TEXT_BOX) { |
326 newNode = AutomationUtil.findNextNode(newNode, | 331 newNode = AutomationUtil.findNextNode( |
327 Dir.FORWARD, | 332 newNode, Dir.FORWARD, AutomationPredicate.inlineTextBox, |
328 AutomationPredicate.inlineTextBox, | 333 {skipInitialSubtree: false}) || |
329 {skipInitialSubtree: false}) || newNode; | 334 newNode; |
330 } | 335 } |
331 switch (movement) { | 336 switch (movement) { |
332 case Movement.BOUND: | 337 case Movement.BOUND: |
333 if (newNode.role == RoleType.INLINE_TEXT_BOX) { | 338 if (newNode.role == RoleType.INLINE_TEXT_BOX) { |
334 var start, end; | 339 var start, end; |
335 for (var i = 0; i < newNode.wordStarts.length; i++) { | 340 for (var i = 0; i < newNode.wordStarts.length; i++) { |
336 if (newIndex >= newNode.wordStarts[i] && | 341 if (newIndex >= newNode.wordStarts[i] && |
337 newIndex <= newNode.wordEnds[i]) { | 342 newIndex <= newNode.wordEnds[i]) { |
338 start = newNode.wordStarts[i]; | 343 start = newNode.wordStarts[i]; |
339 end = newNode.wordEnds[i]; | 344 end = newNode.wordEnds[i]; |
(...skipping 18 matching lines...) Expand all Loading... |
358 break; | 363 break; |
359 } | 364 } |
360 } | 365 } |
361 if (goog.isDef(start)) { | 366 if (goog.isDef(start)) { |
362 newIndex = start; | 367 newIndex = start; |
363 } else { | 368 } else { |
364 // The backward case is special at the beginning of nodes. | 369 // The backward case is special at the beginning of nodes. |
365 if (dir == Dir.BACKWARD && newIndex != 0) { | 370 if (dir == Dir.BACKWARD && newIndex != 0) { |
366 newIndex = 0; | 371 newIndex = 0; |
367 } else { | 372 } else { |
368 newNode = AutomationUtil.findNextNode(newNode, dir, | 373 newNode = AutomationUtil.findNextNode( |
369 AutomationPredicate.leaf); | 374 newNode, dir, AutomationPredicate.leaf); |
370 if (newNode) { | 375 if (newNode) { |
371 newIndex = 0; | 376 newIndex = 0; |
372 if (dir == Dir.BACKWARD && | 377 if (dir == Dir.BACKWARD && |
373 newNode.role == RoleType.INLINE_TEXT_BOX) { | 378 newNode.role == RoleType.INLINE_TEXT_BOX) { |
374 var starts = newNode.wordStarts; | 379 var starts = newNode.wordStarts; |
375 newIndex = starts[starts.length - 1] || 0; | 380 newIndex = starts[starts.length - 1] || 0; |
376 } else { | 381 } else { |
377 // TODO(dtseng): Figure out what to do for general nodes. | 382 // TODO(dtseng): Figure out what to do for general nodes. |
378 } | 383 } |
379 } | 384 } |
380 } | 385 } |
381 } | 386 } |
382 } else { | 387 } else { |
383 // TODO(dtseng): Figure out what to do in this case. | 388 // TODO(dtseng): Figure out what to do in this case. |
384 } | 389 } |
385 } | 390 } |
386 break; | 391 break; |
387 case Unit.TEXT: | 392 case Unit.TEXT: |
388 case Unit.NODE: | 393 case Unit.NODE: |
389 switch (movement) { | 394 switch (movement) { |
390 case Movement.BOUND: | 395 case Movement.BOUND: |
391 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; | 396 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; |
392 break; | 397 break; |
393 case Movement.DIRECTIONAL: | 398 case Movement.DIRECTIONAL: |
394 var pred = unit == Unit.TEXT ? | 399 var pred = unit == Unit.TEXT ? AutomationPredicate.leaf : |
395 AutomationPredicate.leaf : AutomationPredicate.object; | 400 AutomationPredicate.object; |
396 newNode = AutomationUtil.findNextNode(newNode, dir, pred) || | 401 newNode = |
397 originalNode; | 402 AutomationUtil.findNextNode(newNode, dir, pred) || originalNode; |
398 newIndex = cursors.NODE_INDEX; | 403 newIndex = cursors.NODE_INDEX; |
399 break; | 404 break; |
400 } | 405 } |
401 break; | 406 break; |
402 case Unit.LINE: | 407 case Unit.LINE: |
403 var deepEquivalent = this.deepEquivalent; | 408 var deepEquivalent = this.deepEquivalent; |
404 newNode = deepEquivalent.node || newNode; | 409 newNode = deepEquivalent.node || newNode; |
405 newIndex = deepEquivalent.index || 0; | 410 newIndex = deepEquivalent.index || 0; |
406 | 411 |
407 switch (movement) { | 412 switch (movement) { |
408 case Movement.BOUND: | 413 case Movement.BOUND: |
409 newNode = AutomationUtil.findNodeUntil(newNode, dir, | 414 newNode = AutomationUtil.findNodeUntil( |
410 AutomationPredicate.linebreak, true); | 415 newNode, dir, AutomationPredicate.linebreak, true); |
411 newNode = newNode || originalNode; | 416 newNode = newNode || originalNode; |
412 newIndex = | 417 newIndex = |
413 dir == Dir.FORWARD ? AutomationUtil.getText(newNode).length : 0; | 418 dir == Dir.FORWARD ? AutomationUtil.getText(newNode).length : 0; |
414 break; | 419 break; |
415 case Movement.DIRECTIONAL: | 420 case Movement.DIRECTIONAL: |
416 newNode = AutomationUtil.findNodeUntil( | 421 newNode = AutomationUtil.findNodeUntil( |
417 newNode, dir, AutomationPredicate.linebreak); | 422 newNode, dir, AutomationPredicate.linebreak); |
418 break; | 423 break; |
419 } | 424 } |
420 break; | 425 break; |
421 default: | 426 default: |
422 throw Error('Unrecognized unit: ' + unit); | 427 throw Error('Unrecognized unit: ' + unit); |
423 } | 428 } |
424 newNode = newNode || originalNode; | 429 newNode = newNode || originalNode; |
425 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; | 430 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; |
426 return new cursors.Cursor(newNode, newIndex); | 431 return new cursors.Cursor(newNode, newIndex); |
427 }, | 432 }, |
428 | 433 |
429 /** | 434 /** |
430 * Returns the deepest equivalent cursor. | 435 * Returns the deepest equivalent cursor. |
(...skipping 12 matching lines...) Expand all Loading... |
443 if (length <= newIndex && newIndex < (length + target.name.length)) | 448 if (length <= newIndex && newIndex < (length + target.name.length)) |
444 break; | 449 break; |
445 length += target.name.length; | 450 length += target.name.length; |
446 target = target.nextSibling; | 451 target = target.nextSibling; |
447 } | 452 } |
448 if (target) { | 453 if (target) { |
449 newNode = target; | 454 newNode = target; |
450 newIndex = newIndex - length; | 455 newIndex = newIndex - length; |
451 } | 456 } |
452 break; | 457 break; |
453 } else if (newNode.role != RoleType.INLINE_TEXT_BOX && | 458 } else if ( |
| 459 newNode.role != RoleType.INLINE_TEXT_BOX && |
454 newNode.children[newIndex]) { | 460 newNode.children[newIndex]) { |
455 // Valid node offset. | 461 // Valid node offset. |
456 newNode = newNode.children[newIndex]; | 462 newNode = newNode.children[newIndex]; |
457 newIndex = 0; | 463 newIndex = 0; |
458 } else { | 464 } else { |
459 // Invalid offset. | 465 // Invalid offset. |
460 break; | 466 break; |
461 } | 467 } |
462 } | 468 } |
463 | 469 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
499 cursors.WrappingCursor.prototype = { | 505 cursors.WrappingCursor.prototype = { |
500 __proto__: cursors.Cursor.prototype, | 506 __proto__: cursors.Cursor.prototype, |
501 | 507 |
502 /** @override */ | 508 /** @override */ |
503 move: function(unit, movement, dir) { | 509 move: function(unit, movement, dir) { |
504 var result = this; | 510 var result = this; |
505 if (!result.node) | 511 if (!result.node) |
506 return this; | 512 return this; |
507 | 513 |
508 // Regular movement. | 514 // Regular movement. |
509 if (!AutomationPredicate.root(this.node) || | 515 if (!AutomationPredicate.root(this.node) || dir == Dir.FORWARD || |
510 dir == Dir.FORWARD || | |
511 movement == Movement.BOUND) | 516 movement == Movement.BOUND) |
512 result = cursors.Cursor.prototype.move.call(this, unit, movement, dir); | 517 result = cursors.Cursor.prototype.move.call(this, unit, movement, dir); |
513 | 518 |
514 // Moving to the bounds of a unit never wraps. | 519 // Moving to the bounds of a unit never wraps. |
515 if (movement == Movement.BOUND) | 520 if (movement == Movement.BOUND) |
516 return new cursors.WrappingCursor(result.node, result.index); | 521 return new cursors.WrappingCursor(result.node, result.index); |
517 | 522 |
518 // There are two cases for wrapping: | 523 // There are two cases for wrapping: |
519 // 1. moving forwards from the last element. | 524 // 1. moving forwards from the last element. |
520 // 2. moving backwards from the first element. | 525 // 2. moving backwards from the first element. |
521 // Both result in |move| returning the same cursor. | 526 // Both result in |move| returning the same cursor. |
522 // For 1, simply place the new cursor on the document node. | 527 // For 1, simply place the new cursor on the document node. |
523 // For 2, place range on the root (if not already there). If at root, | 528 // For 2, place range on the root (if not already there). If at root, |
524 // try to descend to the first leaf-like object. | 529 // try to descend to the first leaf-like object. |
525 if (movement == Movement.DIRECTIONAL && result.equals(this)) { | 530 if (movement == Movement.DIRECTIONAL && result.equals(this)) { |
526 var pred = unit == Unit.NODE ? | 531 var pred = unit == Unit.NODE ? AutomationPredicate.object : |
527 AutomationPredicate.object : AutomationPredicate.leaf; | 532 AutomationPredicate.leaf; |
528 var endpoint = this.node; | 533 var endpoint = this.node; |
529 if (!endpoint) | 534 if (!endpoint) |
530 return this; | 535 return this; |
531 | 536 |
532 // Case 1: forwards (find the root-like node). | 537 // Case 1: forwards (find the root-like node). |
533 while (!AutomationPredicate.root(endpoint) && endpoint.parent) | 538 while (!AutomationPredicate.root(endpoint) && endpoint.parent) |
534 endpoint = endpoint.parent; | 539 endpoint = endpoint.parent; |
535 | 540 |
536 // Always play a wrap earcon when moving forward. | 541 // Always play a wrap earcon when moving forward. |
537 var playEarcon = dir == Dir.FORWARD; | 542 var playEarcon = dir == Dir.FORWARD; |
538 | 543 |
539 // Case 2: backward (sync downwards to a leaf), if already on the root. | 544 // Case 2: backward (sync downwards to a leaf), if already on the root. |
540 if (dir == Dir.BACKWARD && endpoint == this.node) { | 545 if (dir == Dir.BACKWARD && endpoint == this.node) { |
541 playEarcon = true; | 546 playEarcon = true; |
542 endpoint = AutomationUtil.findNodePre(endpoint, | 547 endpoint = AutomationUtil.findNodePre(endpoint, dir, function(n) { |
543 dir, | 548 return pred(n) && !AutomationPredicate.shouldIgnoreNode(n); |
544 function(n) { | 549 }) || endpoint; |
545 return pred(n) && !AutomationPredicate.shouldIgnoreNode(n); | |
546 }) || endpoint; | |
547 } | 550 } |
548 | 551 |
549 if (playEarcon) | 552 if (playEarcon) |
550 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP); | 553 cvox.ChromeVox.earcons.playEarcon(cvox.Earcon.WRAP); |
551 | 554 |
552 return new cursors.WrappingCursor(endpoint, cursors.NODE_INDEX); | 555 return new cursors.WrappingCursor(endpoint, cursors.NODE_INDEX); |
553 } | 556 } |
554 return new cursors.WrappingCursor(result.node, result.index); | 557 return new cursors.WrappingCursor(result.node, result.index); |
555 } | 558 } |
556 }; | 559 }; |
(...skipping 16 matching lines...) Expand all Loading... |
573 /** | 576 /** |
574 * Convenience method to construct a Range surrounding one node. | 577 * Convenience method to construct a Range surrounding one node. |
575 * @param {!AutomationNode} node | 578 * @param {!AutomationNode} node |
576 * @return {!cursors.Range} | 579 * @return {!cursors.Range} |
577 */ | 580 */ |
578 cursors.Range.fromNode = function(node) { | 581 cursors.Range.fromNode = function(node) { |
579 var cursor = cursors.WrappingCursor.fromNode(node); | 582 var cursor = cursors.WrappingCursor.fromNode(node); |
580 return new cursors.Range(cursor, cursor); | 583 return new cursors.Range(cursor, cursor); |
581 }; | 584 }; |
582 | 585 |
583 /** | 586 /** |
584 * Given |rangeA| and |rangeB| in order, determine which |Dir| | 587 * Given |rangeA| and |rangeB| in order, determine which |Dir| |
585 * relates them. | 588 * relates them. |
586 * @param {!cursors.Range} rangeA | 589 * @param {!cursors.Range} rangeA |
587 * @param {!cursors.Range} rangeB | 590 * @param {!cursors.Range} rangeB |
588 * @return {Dir} | 591 * @return {Dir} |
589 */ | 592 */ |
590 cursors.Range.getDirection = function(rangeA, rangeB) { | 593 cursors.Range.getDirection = function(rangeA, rangeB) { |
591 if (!rangeA || !rangeB) | 594 if (!rangeA || !rangeB) |
592 return Dir.FORWARD; | 595 return Dir.FORWARD; |
593 | 596 |
594 if (!rangeA.start.node || !rangeA.end.node || | 597 if (!rangeA.start.node || !rangeA.end.node || !rangeB.start.node || |
595 !rangeB.start.node || !rangeB.end.node) | 598 !rangeB.end.node) |
596 return Dir.FORWARD; | 599 return Dir.FORWARD; |
597 | 600 |
598 // They are the same range. | 601 // They are the same range. |
599 if (rangeA.start.node === rangeB.start.node && | 602 if (rangeA.start.node === rangeB.start.node && |
600 rangeB.end.node === rangeA.end.node) | 603 rangeB.end.node === rangeA.end.node) |
601 return Dir.FORWARD; | 604 return Dir.FORWARD; |
602 | 605 |
603 var testDirA = | 606 var testDirA = |
604 AutomationUtil.getDirection( | 607 AutomationUtil.getDirection(rangeA.start.node, rangeB.end.node); |
605 rangeA.start.node, rangeB.end.node); | |
606 var testDirB = | 608 var testDirB = |
607 AutomationUtil.getDirection( | 609 AutomationUtil.getDirection(rangeB.start.node, rangeA.end.node); |
608 rangeB.start.node, rangeA.end.node); | |
609 | 610 |
610 // The two ranges are either partly overlapping or non overlapping. | 611 // The two ranges are either partly overlapping or non overlapping. |
611 if (testDirA == Dir.FORWARD && testDirB == Dir.BACKWARD) | 612 if (testDirA == Dir.FORWARD && testDirB == Dir.BACKWARD) |
612 return Dir.FORWARD; | 613 return Dir.FORWARD; |
613 else if (testDirA == Dir.BACKWARD && testDirB == Dir.FORWARD) | 614 else if (testDirA == Dir.BACKWARD && testDirB == Dir.FORWARD) |
614 return Dir.BACKWARD; | 615 return Dir.BACKWARD; |
615 else | 616 else |
616 return testDirA; | 617 return testDirA; |
617 }; | 618 }; |
618 | 619 |
619 cursors.Range.prototype = { | 620 cursors.Range.prototype = { |
620 /** | 621 /** |
621 * Returns true if |rhs| is equal to this range. | 622 * Returns true if |rhs| is equal to this range. |
622 * Use this for strict equality between ranges. | 623 * Use this for strict equality between ranges. |
623 * @param {!cursors.Range} rhs | 624 * @param {!cursors.Range} rhs |
624 * @return {boolean} | 625 * @return {boolean} |
625 */ | 626 */ |
626 equals: function(rhs) { | 627 equals: function(rhs) { |
627 return this.start_.equals(rhs.start) && | 628 return this.start_.equals(rhs.start) && this.end_.equals(rhs.end); |
628 this.end_.equals(rhs.end); | |
629 }, | 629 }, |
630 | 630 |
631 /** | 631 /** |
632 * Returns true if |rhs| is equal to this range. | 632 * Returns true if |rhs| is equal to this range. |
633 * Use this for loose equality between ranges. | 633 * Use this for loose equality between ranges. |
634 * @param {!cursors.Range} rhs | 634 * @param {!cursors.Range} rhs |
635 * @return {boolean} | 635 * @return {boolean} |
636 */ | 636 */ |
637 contentEquals: function(rhs) { | 637 contentEquals: function(rhs) { |
638 return this.start_.contentEquals(rhs.start) && | 638 return this.start_.contentEquals(rhs.start) && |
(...skipping 22 matching lines...) Expand all Loading... |
661 */ | 661 */ |
662 get end() { | 662 get end() { |
663 return this.end_; | 663 return this.end_; |
664 }, | 664 }, |
665 | 665 |
666 /** | 666 /** |
667 * Returns true if this range covers a single node's text content or less. | 667 * Returns true if this range covers a single node's text content or less. |
668 * @return {boolean} | 668 * @return {boolean} |
669 */ | 669 */ |
670 isSubNode: function() { | 670 isSubNode: function() { |
671 return this.start.node === this.end.node && | 671 return this.start.node === this.end.node && this.start.index > -1 && |
672 this.start.index > -1 && | |
673 this.end.index > -1; | 672 this.end.index > -1; |
674 }, | 673 }, |
675 | 674 |
676 /** | 675 /** |
677 * Returns true if this range covers inline text (i.e. each end points to an | 676 * Returns true if this range covers inline text (i.e. each end points to an |
678 * inlineTextBox). | 677 * inlineTextBox). |
679 * @return {boolean?} | 678 * @return {boolean?} |
680 */ | 679 */ |
681 isInlineText: function() { | 680 isInlineText: function() { |
682 return this.start.node && | 681 return this.start.node && this.end.node && |
683 this.end.node && | |
684 this.start.node.role == this.end.node.role && | 682 this.start.node.role == this.end.node.role && |
685 this.start.node.role == RoleType.INLINE_TEXT_BOX; | 683 this.start.node.role == RoleType.INLINE_TEXT_BOX; |
686 }, | 684 }, |
687 | 685 |
688 /** | 686 /** |
689 * Makes a Range which has been moved from this range by the given unit and | 687 * Makes a Range which has been moved from this range by the given unit and |
690 * direction. | 688 * direction. |
691 * @param {Unit} unit | 689 * @param {Unit} unit |
692 * @param {Dir} dir | 690 * @param {Dir} dir |
693 * @return {cursors.Range} | 691 * @return {cursors.Range} |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
726 * Select the text contained within this range. | 724 * Select the text contained within this range. |
727 */ | 725 */ |
728 select: function() { | 726 select: function() { |
729 var startNode = this.start.selectionNode_; | 727 var startNode = this.start.selectionNode_; |
730 var endNode = this.end.selectionNode_; | 728 var endNode = this.end.selectionNode_; |
731 | 729 |
732 if (!startNode || !endNode) | 730 if (!startNode || !endNode) |
733 return; | 731 return; |
734 | 732 |
735 // Only allow selections within the same web tree. | 733 // Only allow selections within the same web tree. |
736 if (startNode.root && | 734 if (startNode.root && startNode.root.role == RoleType.ROOT_WEB_AREA && |
737 startNode.root.role == RoleType.ROOT_WEB_AREA && | |
738 startNode.root == endNode.root) { | 735 startNode.root == endNode.root) { |
739 // We want to adjust to select the entire node for node offsets; | 736 // We want to adjust to select the entire node for node offsets; |
740 // otherwise, use the plain character offset. | 737 // otherwise, use the plain character offset. |
741 var startIndex = this.start.selectionIndex_; | 738 var startIndex = this.start.selectionIndex_; |
742 var endIndex = this.end.index_ == cursors.NODE_INDEX ? | 739 var endIndex = this.end.index_ == cursors.NODE_INDEX ? |
743 this.end.selectionIndex_ + 1 : this.end.selectionIndex_; | 740 this.end.selectionIndex_ + 1 : |
| 741 this.end.selectionIndex_; |
744 | 742 |
745 chrome.automation.setDocumentSelection( | 743 chrome.automation.setDocumentSelection({ |
746 { anchorObject: startNode, | 744 anchorObject: startNode, |
747 anchorOffset: startIndex, | 745 anchorOffset: startIndex, |
748 focusObject: endNode, | 746 focusObject: endNode, |
749 focusOffset: endIndex } | 747 focusOffset: endIndex |
750 ); | 748 }); |
751 } | 749 } |
752 }, | 750 }, |
753 | 751 |
754 /** | 752 /** |
755 * Returns true if this range has either cursor end on web content. | 753 * Returns true if this range has either cursor end on web content. |
756 * @return {boolean} | 754 * @return {boolean} |
757 */ | 755 */ |
758 isWebRange: function() { | 756 isWebRange: function() { |
759 return this.isValid() && | 757 return this.isValid() && |
760 (this.start.node.root.role != RoleType.DESKTOP || | 758 (this.start.node.root.role != RoleType.DESKTOP || |
761 this.end.node.root.role != RoleType.DESKTOP); | 759 this.end.node.root.role != RoleType.DESKTOP); |
762 }, | 760 }, |
763 | 761 |
764 /** | 762 /** |
765 * Returns whether this range has valid start and end cursors. | 763 * Returns whether this range has valid start and end cursors. |
766 * @return {boolean} | 764 * @return {boolean} |
767 */ | 765 */ |
768 isValid: function() { | 766 isValid: function() { |
769 return this.start.isValid() && this.end.isValid(); | 767 return this.start.isValid() && this.end.isValid(); |
770 } | 768 } |
771 }; | 769 }; |
772 | 770 |
773 }); // goog.scope | 771 }); // goog.scope |
OLD | NEW |