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

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js

Issue 2945703002: add support for rich text selections (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/chromeos/chromevox/chromevox/injected/externs.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698