| 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 |