Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 Processes events related to editing text and emits the | 6 * @fileoverview Processes events related to editing text and emits the |
| 7 * appropriate spoken and braille feedback. | 7 * appropriate spoken and braille feedback. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 goog.provide('editing.TextEditHandler'); | 10 goog.provide('editing.TextEditHandler'); |
| (...skipping 209 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 220 * @param {!AutomationNode} node | 220 * @param {!AutomationNode} node |
| 221 * @extends {AutomationEditableText} | 221 * @extends {AutomationEditableText} |
| 222 */ | 222 */ |
| 223 function AutomationRichEditableText(node) { | 223 function AutomationRichEditableText(node) { |
| 224 AutomationEditableText.call(this, node); | 224 AutomationEditableText.call(this, node); |
| 225 | 225 |
| 226 var root = this.node_.root; | 226 var root = this.node_.root; |
| 227 if (!root || !root.anchorObject || !root.focusObject) | 227 if (!root || !root.anchorObject || !root.focusObject) |
| 228 return; | 228 return; |
| 229 | 229 |
| 230 this.line_ = new editing.EditableLine( | 230 this.anchorLine_ = new editing.EditableLine(root.anchorObject, |
|
dmazzoni
2017/06/19 15:11:51
Is the plan to refactor EditableLine to only take
David Tseng
2017/06/19 18:27:47
No, added comments to clarify. An editable line co
| |
| 231 root.anchorObject, root.anchorOffset, root.focusObject, root.focusOffset); | 231 root.anchorOffset, |
| 232 root.anchorObject, | |
| 233 root.anchorOffset); | |
| 234 this.focusLine_ = new editing.EditableLine(root.focusObject, | |
| 235 root.focusOffset, | |
| 236 root.focusObject, | |
| 237 root.focusOffset); | |
| 232 } | 238 } |
| 233 | 239 |
| 234 AutomationRichEditableText.prototype = { | 240 AutomationRichEditableText.prototype = { |
| 235 __proto__: AutomationEditableText.prototype, | 241 __proto__: AutomationEditableText.prototype, |
| 236 | 242 |
| 237 /** @override */ | 243 /** @override */ |
| 238 onUpdate: function() { | 244 onUpdate: function() { |
| 239 var root = this.node_.root; | 245 var root = this.node_.root; |
| 240 if (!root.anchorObject || !root.focusObject) | 246 if (!root.anchorObject || !root.focusObject) |
| 241 return; | 247 return; |
| 242 | 248 |
| 249 var anchorLine = new editing.EditableLine(root.anchorObject, | |
| 250 root.anchorOffset, | |
| 251 root.anchorObject, | |
| 252 root.anchorOffset); | |
| 253 var focusLine = new editing.EditableLine(root.focusObject, | |
| 254 root.focusOffset, | |
| 255 root.focusObject, | |
| 256 root.focusOffset); | |
| 257 | |
| 258 var prevAnchorLine = this.anchorLine_; | |
| 259 var prevFocusLine = this.focusLine_; | |
| 260 this.anchorLine_ = anchorLine; | |
| 261 this.focusLine_ = focusLine; | |
| 262 | |
| 263 // Compute the current line based upon whether the current selection was | |
| 264 // extended from anchor or focus. The default behavior is to compute lines | |
| 265 // via focus. | |
| 266 var baseLineOnStart = prevFocusLine.strictEquals(focusLine); | |
| 267 | |
| 243 var cur = new editing.EditableLine( | 268 var cur = new editing.EditableLine( |
| 244 root.anchorObject, root.anchorOffset || 0, | 269 root.anchorObject, root.anchorOffset || 0, |
| 245 root.focusObject, root.focusOffset || 0); | 270 root.focusObject, root.focusOffset || 0, |
| 246 var prev = this.line_; | 271 baseLineOnStart); |
| 247 this.line_ = cur; | 272 this.line_ = cur; |
| 248 | 273 |
| 249 if (prev.equals(cur)) { | 274 // Selection stayed within the same line(s) and didn't cross into new lines. |
| 250 // Collapsed cursor. | 275 if (anchorLine.equals(prevAnchorLine) && focusLine.equals(prevFocusLine)) { |
| 276 // Intra-line changes. | |
| 251 this.changed(new cvox.TextChangeEvent( | 277 this.changed(new cvox.TextChangeEvent( |
| 252 cur.text || '', | 278 cur.text || '', |
| 253 cur.startOffset || 0, | 279 cur.startOffset || 0, |
| 254 cur.endOffset || 0, | 280 cur.endOffset || 0, |
| 255 true)); | 281 true)); |
| 256 this.brailleCurrentRichLine_(); | 282 this.brailleCurrentRichLine_(); |
| 257 | 283 |
| 258 // Finally, queue up any text markers/styles at bounds. | 284 // Finally, queue up any text markers/styles at bounds. |
| 259 var container = cur.startContainer_; | 285 var container = cur.startContainer_; |
| 260 if (!container) | 286 if (!container) |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 288 | 314 |
| 289 // Start of the container. | 315 // Start of the container. |
| 290 if (cur.containerStartOffset == cur.startOffset) | 316 if (cur.containerStartOffset == cur.startOffset) |
| 291 this.speakTextStyle_(container); | 317 this.speakTextStyle_(container); |
| 292 else if (cur.containerEndOffset == cur.endOffset) | 318 else if (cur.containerEndOffset == cur.endOffset) |
| 293 this.speakTextStyle_(container, true); | 319 this.speakTextStyle_(container, true); |
| 294 | 320 |
| 295 return; | 321 return; |
| 296 } | 322 } |
| 297 | 323 |
| 298 // Just output the current line. | 324 if (anchorLine.equals(focusLine)) { |
| 299 cvox.ChromeVox.tts.speak(cur.text, cvox.QueueMode.CATEGORY_FLUSH); | 325 // Current selection is on a new line not part of the previous selection. |
| 300 this.brailleCurrentRichLine_(); | 326 this.line_ = focusLine; |
| 327 cvox.ChromeVox.tts.speak(focusLine.text, cvox.QueueMode.CATEGORY_FLUSH); | |
| 328 this.brailleCurrentRichLine_(); | |
| 329 } if (prevAnchorLine.equals(anchorLine)) { | |
| 330 // The selection changed in focus position. | |
| 331 this.line_ = focusLine; | |
| 332 cvox.ChromeVox.tts.speak(focusLine.text, cvox.QueueMode.CATEGORY_FLUSH); | |
| 333 this.brailleCurrentRichLine_(); | |
| 334 } else if (prevFocusLine.equals(focusLine)) { | |
| 335 // The selection changed in anchor position. | |
| 336 this.line_ = anchorLine; | |
| 337 cvox.ChromeVox.tts.speak(anchorLine.text, cvox.QueueMode.CATEGORY_FLUSH); | |
| 338 this.brailleCurrentRichLine_(); | |
| 339 } else { | |
| 340 // Describe a generic DOM selection. | |
|
dmazzoni
2017/06/19 15:11:51
Want to put some sort of placeholder here?
Or may
David Tseng
2017/06/19 18:27:47
Removed. We don't want to favor focus but favor th
| |
| 341 } | |
| 301 | 342 |
| 302 // The state in EditableTextBase needs to get updated with the new line | 343 // The state in EditableTextBase needs to get updated with the new line |
| 303 // contents, so that subsequent intra-line changes get the right state | 344 // contents, so that subsequent intra-line changes get the right state |
| 304 // transitions. | 345 // transitions. |
| 305 this.value = cur.text; | 346 this.value = cur.text; |
| 306 this.start = cur.startOffset; | 347 this.start = cur.startOffset; |
| 307 this.end = cur.endOffset; | 348 this.end = cur.endOffset; |
| 308 }, | 349 }, |
| 309 | 350 |
| 310 /** | 351 /** |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 448 }; | 489 }; |
| 449 | 490 |
| 450 /** | 491 /** |
| 451 * @private {ChromeVoxStateObserver} | 492 * @private {ChromeVoxStateObserver} |
| 452 */ | 493 */ |
| 453 editing.observer_ = new editing.EditingChromeVoxStateObserver(); | 494 editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
| 454 | 495 |
| 455 /** | 496 /** |
| 456 * An EditableLine encapsulates all data concerning a line in the automation | 497 * An EditableLine encapsulates all data concerning a line in the automation |
| 457 * tree necessary to provide output. | 498 * tree necessary to provide output. |
| 499 * @param {AutomationNode} startNode | |
| 500 * @param {number} startIndex | |
| 501 * @param {AutomationNode} endNode | |
| 502 * @param {number} endIndex | |
| 503 * @param {boolean=} opt_baseLineOnStart | |
| 458 * @constructor | 504 * @constructor |
| 459 */ | 505 */ |
| 460 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { | 506 editing.EditableLine = |
| 507 function(startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { | |
| 461 /** @private {!Cursor} */ | 508 /** @private {!Cursor} */ |
| 462 this.start_ = new Cursor(startNode, startIndex); | 509 this.start_ = new Cursor(startNode, startIndex); |
| 463 this.start_ = this.start_.deepEquivalent || this.start_; | 510 this.start_ = this.start_.deepEquivalent || this.start_; |
| 464 | 511 |
| 465 /** @private {!Cursor} */ | 512 /** @private {!Cursor} */ |
| 466 this.end_ = new Cursor(endNode, endIndex); | 513 this.end_ = new Cursor(endNode, endIndex); |
| 467 this.end_ = this.end_.deepEquivalent || this.end_; | 514 this.end_ = this.end_.deepEquivalent || this.end_; |
| 468 /** @private {number} */ | 515 /** @private {number} */ |
| 469 this.localContainerStartOffset_ = startIndex; | 516 this.localContainerStartOffset_ = startIndex; |
| 470 | 517 |
| 471 // Computed members. | 518 // Computed members. |
| 472 /** @private {Spannable} */ | 519 /** @private {Spannable} */ |
| 473 this.value_; | 520 this.value_; |
| 474 /** @private {AutomationNode} */ | 521 /** @private {AutomationNode} */ |
| 475 this.lineStart_; | 522 this.lineStart_; |
| 476 /** @private {AutomationNode} */ | 523 /** @private {AutomationNode} */ |
| 477 this.lineEnd_; | 524 this.lineEnd_; |
| 478 /** @private {AutomationNode|undefined} */ | 525 /** @private {AutomationNode|undefined} */ |
| 479 this.startContainer_; | 526 this.startContainer_; |
| 480 /** @private {AutomationNode|undefined} */ | 527 /** @private {AutomationNode|undefined} */ |
| 481 this.lineStartContainer_; | 528 this.lineStartContainer_; |
| 482 /** @private {number} */ | 529 /** @private {number} */ |
| 483 this.localLineStartContainerOffset_ = 0; | 530 this.localLineStartContainerOffset_ = 0; |
| 531 /** @private {AutomationNode|undefined} */ | |
| 532 this.lineEndContainer_; | |
| 533 /** @private {number} */ | |
| 534 this.localLineEndContainerOffset_ = 0; | |
| 484 | 535 |
| 485 this.computeLineData_(); | 536 this.computeLineData_(opt_baseLineOnStart); |
| 486 }; | 537 }; |
| 487 | 538 |
| 488 editing.EditableLine.prototype = { | 539 editing.EditableLine.prototype = { |
| 489 /** @private */ | 540 /** @private */ |
| 490 computeLineData_: function() { | 541 computeLineData_: function(opt_baseLineOnStart) { |
| 542 // Note that we calculate the line based only upon anchor or focus even if | |
| 543 // they do not fall on the same line. It is up to the caller to specify | |
| 544 // which end to base this line upon since it requires reasoning about two | |
| 545 // lines. | |
| 491 var nameLen = 0; | 546 var nameLen = 0; |
| 492 if (this.start_.node.name) | 547 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; |
| 493 nameLen = this.start_.node.name.length; | 548 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; |
| 494 | 549 |
| 495 this.value_ = new Spannable(this.start_.node.name || '', this.start_); | 550 if (lineBase.node.name) |
| 496 if (this.start_.node == this.end_.node) | 551 nameLen = lineBase.node.name.length; |
| 497 this.value_.setSpan(this.end_, 0, nameLen); | 552 |
| 553 this.value_ = new Spannable(lineBase.node.name || '', lineBase); | |
| 554 if (lineBase.node == lineExtend.node) | |
| 555 this.value_.setSpan(lineExtend, 0, nameLen); | |
| 498 | 556 |
| 499 // Initialize defaults. | 557 // Initialize defaults. |
| 500 this.lineStart_ = this.start_.node; | 558 this.lineStart_ = lineBase.node; |
| 501 this.lineEnd_ = this.lineStart_; | 559 this.lineEnd_ = this.lineStart_; |
| 502 this.startContainer_ = this.lineStart_.parent; | 560 this.startContainer_ = this.lineStart_.parent; |
| 503 this.lineStartContainer_ = this.lineStart_.parent; | 561 this.lineStartContainer_ = this.lineStart_.parent; |
| 562 this.lineEndContainer_ = this.lineStart_.parent; | |
| 504 | 563 |
| 505 // Annotate each chunk with its associated inline text box node. | 564 // Annotate each chunk with its associated inline text box node. |
| 506 this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length); | 565 this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length); |
| 507 | 566 |
| 508 // If the current selection is not on an inline text box (e.g. an image), | 567 // If the current selection is not on an inline text box (e.g. an image), |
| 509 // return early here so that the line contents are just the node. This is | 568 // return early here so that the line contents are just the node. This is |
| 510 // pending the ability to show non-text leaf inline objects. | 569 // pending the ability to show non-text leaf inline objects. |
| 511 if (this.lineStart_.role != RoleType.INLINE_TEXT_BOX) | 570 if (this.lineStart_.role != RoleType.INLINE_TEXT_BOX) |
| 512 return; | 571 return; |
| 513 | 572 |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 540 this.lineEnd_ = lineEnd; | 599 this.lineEnd_ = lineEnd; |
| 541 if (parents[parents.length - 1] != lineEnd.parent) | 600 if (parents[parents.length - 1] != lineEnd.parent) |
| 542 parents.push(this.lineEnd_.parent); | 601 parents.push(this.lineEnd_.parent); |
| 543 | 602 |
| 544 var annotation = lineEnd; | 603 var annotation = lineEnd; |
| 545 if (lineEnd == this.end_.node) | 604 if (lineEnd == this.end_.node) |
| 546 annotation = this.end_; | 605 annotation = this.end_; |
| 547 | 606 |
| 548 this.value_.append(new Spannable(lineEnd.name, annotation)); | 607 this.value_.append(new Spannable(lineEnd.name, annotation)); |
| 549 } | 608 } |
| 609 this.lineEndContainer_ = this.lineEnd_.parent; | |
| 550 | 610 |
| 551 // Finally, annotate with all parent static texts as NodeSpan's so that | 611 // Finally, annotate with all parent static texts as NodeSpan's so that |
| 552 // braille routing can key properly into the node with an offset. | 612 // braille routing can key properly into the node with an offset. |
| 553 // Note that both line start and end needs to account for | 613 // Note that both line start and end needs to account for |
| 554 // potential offsets into the static texts as follows. | 614 // potential offsets into the static texts as follows. |
| 555 var textCountBeforeLineStart = 0, textCountAfterLineEnd = 0; | 615 var textCountBeforeLineStart = 0, textCountAfterLineEnd = 0; |
| 556 var finder = this.lineStart_; | 616 var finder = this.lineStart_; |
| 557 while (finder.previousSibling) { | 617 while (finder.previousSibling) { |
| 558 finder = finder.previousSibling; | 618 finder = finder.previousSibling; |
| 559 textCountBeforeLineStart += finder.name.length; | 619 textCountBeforeLineStart += finder.name.length; |
| 560 } | 620 } |
| 561 this.localLineStartContainerOffset_ = textCountBeforeLineStart; | 621 this.localLineStartContainerOffset_ = textCountBeforeLineStart; |
| 562 | 622 |
| 563 finder = this.lineEnd_; | 623 finder = this.lineEnd_; |
| 564 while (finder.nextSibling) { | 624 while (finder.nextSibling) { |
| 565 finder = finder.nextSibling; | 625 finder = finder.nextSibling; |
| 566 textCountAfterLineEnd += finder.name.length; | 626 textCountAfterLineEnd += finder.name.length; |
| 567 } | 627 } |
| 568 | 628 |
| 629 if (this.lineEndContainer_.name) { | |
| 630 this.localLineEndContainerOffset_ = | |
| 631 this.lineEndContainer_.name.length - textCountAfterLineEnd; | |
| 632 } | |
| 633 | |
| 569 var len = 0; | 634 var len = 0; |
| 570 for (var i = 0; i < parents.length; i++) { | 635 for (var i = 0; i < parents.length; i++) { |
| 571 var parent = parents[i]; | 636 var parent = parents[i]; |
| 572 | 637 |
| 573 if (!parent.name) | 638 if (!parent.name) |
| 574 continue; | 639 continue; |
| 575 | 640 |
| 576 var prevLen = len; | 641 var prevLen = len; |
| 577 | 642 |
| 578 var currentLen = parent.name.length; | 643 var currentLen = parent.name.length; |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 598 this.value_.setSpan(parent, prevLen, len); | 663 this.value_.setSpan(parent, prevLen, len); |
| 599 } catch (e) {} | 664 } catch (e) {} |
| 600 } | 665 } |
| 601 }, | 666 }, |
| 602 | 667 |
| 603 /** | 668 /** |
| 604 * Gets the selection offset based on the text content of this line. | 669 * Gets the selection offset based on the text content of this line. |
| 605 * @return {number} | 670 * @return {number} |
| 606 */ | 671 */ |
| 607 get startOffset() { | 672 get startOffset() { |
| 608 return this.value_.getSpanStart(this.start_) + this.start_.index; | 673 // It is possible that the start cursor points to content before this line |
| 674 // (e.g. in a multi-line selection). | |
| 675 try { | |
| 676 return this.value_.getSpanStart(this.start_) + this.start_.index; | |
| 677 } catch (e) { | |
| 678 // When that happens, fall back to the start of this line. | |
| 679 return 0; | |
| 680 } | |
| 609 }, | 681 }, |
| 610 | 682 |
| 611 /** | 683 /** |
| 612 * Gets the selection offset based on the text content of this line. | 684 * Gets the selection offset based on the text content of this line. |
| 613 * @return {number} | 685 * @return {number} |
| 614 */ | 686 */ |
| 615 get endOffset() { | 687 get endOffset() { |
| 616 return this.value_.getSpanStart(this.end_) + this.end_.index; | 688 try { |
| 689 return this.value_.getSpanStart(this.end_) + this.end_.index; | |
| 690 } catch (e) { | |
| 691 return this.value_.length - 1; | |
| 692 } | |
| 617 }, | 693 }, |
| 618 | 694 |
| 619 /** | 695 /** |
| 620 * Gets the selection offset based on the parent's text. | 696 * Gets the selection offset based on the parent's text. |
| 621 * The parent is expected to be static text. | 697 * The parent is expected to be static text. |
| 622 * @return {number} | 698 * @return {number} |
| 623 */ | 699 */ |
| 624 get localStartOffset() { | 700 get localStartOffset() { |
| 625 return this.startOffset - this.containerStartOffset; | 701 return this.startOffset - this.containerStartOffset; |
| 626 }, | 702 }, |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 654 | 730 |
| 655 /** | 731 /** |
| 656 * The text content of this line. | 732 * The text content of this line. |
| 657 * @return {string} The text of this line. | 733 * @return {string} The text of this line. |
| 658 */ | 734 */ |
| 659 get text() { | 735 get text() { |
| 660 return this.value_.toString(); | 736 return this.value_.toString(); |
| 661 }, | 737 }, |
| 662 | 738 |
| 663 /** | 739 /** |
| 740 * | |
| 741 */ | |
| 742 equalsStart: function(otherLine) { | |
| 743 return otherLine.lineStartContainer_ == this.lineStartContainer_ && | |
| 744 otherLine.localLineStartContainerOffset_ == | |
| 745 this.localLineStartContainerOffset_; | |
| 746 }, | |
| 747 | |
| 748 equalsEnd: function(otherLine) { | |
| 749 return otherLine.lineEndContainer_ == this.lineEndContainer_ && | |
| 750 otherLine.localLineEndContainerOffset_ == | |
| 751 this.localLineEndContainerOffset_; | |
| 752 }, | |
| 753 | |
| 754 /** | |
| 664 * Returns true if |otherLine| surrounds the same line as |this|. Note that | 755 * Returns true if |otherLine| surrounds the same line as |this|. Note that |
| 665 * the contents of the line might be different. | 756 * the contents of the line might be different. |
| 666 * @return {boolean} | 757 * @return {boolean} |
| 667 */ | 758 */ |
| 668 equals: function(otherLine) { | 759 equals: function(otherLine) { |
| 669 // Equality is intentionally loose here as any of the state nodes can be | 760 // Equality is intentionally loose here as any of the state nodes can be |
| 670 // invalidated at any time. We rely upon the start/anchor of the line | 761 // invalidated at any time. We rely upon the start/anchor of the line |
| 671 // staying the same. | 762 // staying the same. |
| 672 return otherLine.lineStartContainer_ == this.lineStartContainer_ && | 763 return this.equalsStart(otherLine) || this.equalsEnd(otherLine); |
| 673 otherLine.localLineStartContainerOffset_ == | 764 }, |
| 674 this.localLineStartContainerOffset_; | 765 |
| 766 strictEquals: function(otherLine) { | |
| 767 return this.equals(otherLine) && | |
| 768 this.startOffset == otherLine.startOffset && | |
| 769 this.endOffset == otherLine.endOffset; | |
| 675 } | 770 } |
| 676 }; | 771 }; |
| 677 | 772 |
| 678 }); | 773 }); |
| OLD | NEW |