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

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

Issue 2966273002: Provide complete, smart, spoken feedback for editable selection (Closed)
Patch Set: Fix test. Created 3 years, 5 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 | « no previous file | chrome/browser/resources/chromeos/chromevox/cvox2/background/editing_test.extjs » ('j') | 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 235 matching lines...) Expand 10 before | Expand all | Expand 10 after
246 246
247 var prevAnchorLine = this.anchorLine_; 247 var prevAnchorLine = this.anchorLine_;
248 var prevFocusLine = this.focusLine_; 248 var prevFocusLine = this.focusLine_;
249 this.anchorLine_ = anchorLine; 249 this.anchorLine_ = anchorLine;
250 this.focusLine_ = focusLine; 250 this.focusLine_ = focusLine;
251 251
252 // Compute the current line based upon whether the current selection was 252 // Compute the current line based upon whether the current selection was
253 // extended from anchor or focus. The default behavior is to compute lines 253 // extended from anchor or focus. The default behavior is to compute lines
254 // via focus. 254 // via focus.
255 var baseLineOnStart = prevFocusLine.isSameLineAndSelection(focusLine); 255 var baseLineOnStart = prevFocusLine.isSameLineAndSelection(focusLine);
256 var isSameSelection =
257 baseLineOnStart && prevAnchorLine.isSameLineAndSelection(anchorLine);
256 258
257 var cur = new editing.EditableLine( 259 var cur;
258 root.anchorObject, root.anchorOffset, root.focusObject, 260 if (isSameSelection && this.line_) {
259 root.focusOffset, baseLineOnStart); 261 // Nothing changed, return.
262 return;
263 } else {
264 cur = new editing.EditableLine(
265 root.anchorObject, root.anchorOffset, root.focusObject,
266 root.focusOffset, baseLineOnStart);
267 }
260 var prev = this.line_; 268 var prev = this.line_;
261 this.line_ = cur; 269 this.line_ = cur;
262 270
263 // Selection stayed within the same line(s) and didn't cross into new lines. 271 // Selection stayed within the same line(s) and didn't cross into new lines.
264 if (anchorLine.isSameLine(prevAnchorLine) && 272 if (anchorLine.isSameLine(prevAnchorLine) &&
265 focusLine.isSameLine(prevFocusLine)) { 273 focusLine.isSameLine(prevFocusLine)) {
266 // Intra-line changes. 274 // Intra-line changes.
267 this.changed(new cvox.TextChangeEvent( 275 this.changed(new cvox.TextChangeEvent(
268 cur.text || '', cur.startOffset, cur.endOffset, true)); 276 cur.text || '', cur.startOffset, cur.endOffset, true));
269 this.brailleCurrentRichLine_(); 277 this.brailleCurrentRichLine_();
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
310 318
311 if (cur.text == '') { 319 if (cur.text == '') {
312 // This line has no text content. Describe the DOM selection. 320 // This line has no text content. Describe the DOM selection.
313 new Output() 321 new Output()
314 .withRichSpeechAndBraille( 322 .withRichSpeechAndBraille(
315 new Range(cur.start_, cur.end_), 323 new Range(cur.start_, cur.end_),
316 new Range(prev.start_, prev.end_), Output.EventType.NAVIGATE) 324 new Range(prev.start_, prev.end_), Output.EventType.NAVIGATE)
317 .go(); 325 .go();
318 } else if (!cur.hasCollapsedSelection()) { 326 } else if (!cur.hasCollapsedSelection()) {
319 // This is a selection. 327 // This is a selection.
320 cvox.ChromeVox.tts.speak(cur.selectedText, cvox.QueueMode.CATEGORY_FLUSH); 328
321 cvox.ChromeVox.tts.speak(Msgs.getMsg('selected'), cvox.QueueMode.QUEUE); 329 // Speech requires many more states than braille.
330 // Note that base and extend are used here rather than anchor
dmazzoni 2017/07/06 21:47:01 We should use terminology correctly. Currently Bli
David Tseng 2017/07/06 22:01:36 I agree but would also like to land this and the d
331 // and focus as concepts because anchor and focus are absolute
332 // while base and extend are relative.
333 var curBase = baseLineOnStart ? focusLine : anchorLine;
334 var curExtend = baseLineOnStart ? anchorLine : focusLine;
335 var text = '';
336 var suffixMsg = '';
337 if (curBase.isBeforeLine(curExtend)) {
338 // Forward selection.
339 if (prev.isBeforeLine(curBase)) {
340 // Wrapped across the baseline. Read out the new selection.
341 suffixMsg = 'selected';
342 text = this.getTextSelection_(
343 curBase.startContainer_, curBase.localStartOffset,
344 curExtend.endContainer_, curExtend.localEndOffset);
345 } else {
346 if (prev.isBeforeLine(curExtend)) {
347 // Grew.
348 suffixMsg = 'selected';
349 text = this.getTextSelection_(
350 prev.endContainer_, prev.localEndOffset,
351 curExtend.endContainer_, curExtend.localEndOffset);
352 } else {
353 // Shrank.
354 suffixMsg = 'unselected';
355 text = this.getTextSelection_(
356 curExtend.endContainer_, curExtend.localEndOffset,
357 prev.endContainer_, prev.localEndOffset);
358 }
359 }
360 } else {
361 // Backward selection.
362 if (curBase.isBeforeLine(prev)) {
363 // Wrapped across the baseline. Read out the new selection.
364 suffixMsg = 'selected';
365 text = this.getTextSelection_(
366 curExtend.startContainer_, curExtend.localStartOffset,
367 curBase.endContainer_, curBase.localEndOffset);
368 } else {
369 if (curExtend.isBeforeLine(prev)) {
370 // Grew.
371 suffixMsg = 'selected';
372 text = this.getTextSelection_(
373 curExtend.startContainer_, curExtend.localStartOffset,
374 prev.startContainer_, prev.localStartOffset);
375 } else {
376 // Shrank.
377 suffixMsg = 'unselected';
378 text = this.getTextSelection_(
379 prev.startContainer_, prev.localStartOffset,
380 curExtend.startContainer_, curExtend.localStartOffset);
381 }
382 }
383 }
384
385 cvox.ChromeVox.tts.speak(text, cvox.QueueMode.CATEGORY_FLUSH);
386 cvox.ChromeVox.tts.speak(Msgs.getMsg(suffixMsg), cvox.QueueMode.QUEUE);
322 this.brailleCurrentRichLine_(); 387 this.brailleCurrentRichLine_();
323 } else { 388 } else {
324 // Describe the current line. This accounts for previous/current 389 // Describe the current line. This accounts for previous/current
325 // selections and picking the line edge boundary that changed (as computed 390 // selections and picking the line edge boundary that changed (as computed
326 // above). This is also the code path for describing paste. 391 // above). This is also the code path for describing paste.
327 this.speakCurrentRichLine_(prev); 392 this.speakCurrentRichLine_(prev);
328 this.brailleCurrentRichLine_(); 393 this.brailleCurrentRichLine_();
329 } 394 }
330 395
331 // The state in EditableTextBase needs to get updated with the new line 396 // The state in EditableTextBase needs to get updated with the new line
332 // contents, so that subsequent intra-line changes get the right state 397 // contents, so that subsequent intra-line changes get the right state
333 // transitions. 398 // transitions.
334 this.value = cur.text; 399 this.value = cur.text;
335 this.start = cur.startOffset; 400 this.start = cur.startOffset;
336 this.end = cur.endOffset; 401 this.end = cur.endOffset;
337 }, 402 },
338 403
339 /** 404 /**
405 * @param {AutomationNode|undefined} startNode
406 * @param {number} startOffset
407 * @param {AutomationNode|undefined} endNode
408 * @param {number} endOffset
409 * @return {string}
410 */
411 getTextSelection_: function(startNode, startOffset, endNode, endOffset) {
412 if (!startNode || !endNode)
413 return '';
414
415 if (startNode == endNode) {
416 return startNode.name ? startNode.name.substring(startOffset, endOffset) :
417 '';
418 }
419
420 var text = '';
421 if (startNode.name)
422 text = startNode.name.substring(startOffset);
423
424 for (var node = startNode;
425 (node = AutomationUtil.findNextNode(
426 node, Dir.FORWARD, AutomationPredicate.leafOrStaticText)) &&
427 node != endNode;) {
428 // Padding needs to get added to break up speech utterances.
429 if (node.name)
430 text += ' ' + node.name;
431 }
432
433 if (endNode.name)
434 text += ' ' + endNode.name.substring(0, endOffset);
435 return text;
436 },
437
438 /**
340 * @param {number} markerType 439 * @param {number} markerType
341 * @param {boolean=} opt_end 440 * @param {boolean=} opt_end
342 * @private 441 * @private
343 */ 442 */
344 speakTextMarker_: function(markerType, opt_end) { 443 speakTextMarker_: function(markerType, opt_end) {
345 // TODO(dtseng): Plumb through constants to automation. 444 // TODO(dtseng): Plumb through constants to automation.
346 var msgs = []; 445 var msgs = [];
347 if (markerType & 1) 446 if (markerType & 1)
348 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); 447 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start');
349 if (markerType & 2) 448 if (markerType & 2)
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
536 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { 635 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) {
537 /** @private {!Cursor} */ 636 /** @private {!Cursor} */
538 this.start_ = new Cursor(startNode, startIndex); 637 this.start_ = new Cursor(startNode, startIndex);
539 this.start_ = this.start_.deepEquivalent || this.start_; 638 this.start_ = this.start_.deepEquivalent || this.start_;
540 639
541 /** @private {!Cursor} */ 640 /** @private {!Cursor} */
542 this.end_ = new Cursor(endNode, endIndex); 641 this.end_ = new Cursor(endNode, endIndex);
543 this.end_ = this.end_.deepEquivalent || this.end_; 642 this.end_ = this.end_.deepEquivalent || this.end_;
544 /** @private {number} */ 643 /** @private {number} */
545 this.localContainerStartOffset_ = startIndex; 644 this.localContainerStartOffset_ = startIndex;
645 /** @private {number} */
646 this.localContainerEndOffset_ = endIndex;
546 647
547 // Computed members. 648 // Computed members.
548 /** @private {Spannable} */ 649 /** @private {Spannable} */
549 this.value_; 650 this.value_;
550 /** @private {AutomationNode|undefined} */ 651 /** @private {AutomationNode|undefined} */
551 this.lineStart_; 652 this.lineStart_;
552 /** @private {AutomationNode|undefined} */ 653 /** @private {AutomationNode|undefined} */
553 this.lineEnd_; 654 this.lineEnd_;
554 /** @private {AutomationNode|undefined} */ 655 /** @private {AutomationNode|undefined} */
555 this.startContainer_; 656 this.startContainer_;
(...skipping 20 matching lines...) Expand all
576 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; 677 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_;
577 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; 678 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_;
578 679
579 if (lineBase.node.name) 680 if (lineBase.node.name)
580 nameLen = lineBase.node.name.length; 681 nameLen = lineBase.node.name.length;
581 682
582 this.value_ = new Spannable(lineBase.node.name || '', lineBase); 683 this.value_ = new Spannable(lineBase.node.name || '', lineBase);
583 if (lineBase.node == lineExtend.node) 684 if (lineBase.node == lineExtend.node)
584 this.value_.setSpan(lineExtend, 0, nameLen); 685 this.value_.setSpan(lineExtend, 0, nameLen);
585 686
687 this.startContainer_ = this.start_.node;
688 if (this.startContainer_.role == RoleType.INLINE_TEXT_BOX)
689 this.startContainer_ = this.startContainer_.parent;
690 this.endContainer_ = this.end_.node;
691 if (this.endContainer_.role == RoleType.INLINE_TEXT_BOX)
692 this.endContainer_ = this.endContainer_.parent;
693
586 // Initialize defaults. 694 // Initialize defaults.
587 this.lineStart_ = lineBase.node; 695 this.lineStart_ = lineBase.node;
588 this.lineEnd_ = this.lineStart_; 696 this.lineEnd_ = this.lineStart_;
589 this.startContainer_ = this.lineStart_.parent;
590 this.lineStartContainer_ = this.lineStart_.parent; 697 this.lineStartContainer_ = this.lineStart_.parent;
591 this.lineEndContainer_ = this.lineStart_.parent; 698 this.lineEndContainer_ = this.lineStart_.parent;
592 699
593 // Annotate each chunk with its associated inline text box node. 700 // Annotate each chunk with its associated inline text box node.
594 this.value_.setSpan(this.lineStart_, 0, nameLen); 701 this.value_.setSpan(this.lineStart_, 0, nameLen);
595 702
596 // Also, track the nodes necessary for selection (either their parents, in 703 // Also, track the nodes necessary for selection (either their parents, in
597 // the case of inline text boxes, or the node itself). 704 // the case of inline text boxes, or the node itself).
598 var parents = [this.startContainer_]; 705 var parents = [this.startContainer_];
599 706
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after
732 return this.value_.length; 839 return this.value_.length;
733 } 840 }
734 }, 841 },
735 842
736 /** 843 /**
737 * Gets the selection offset based on the parent's text. 844 * Gets the selection offset based on the parent's text.
738 * The parent is expected to be static text. 845 * The parent is expected to be static text.
739 * @return {number} 846 * @return {number}
740 */ 847 */
741 get localStartOffset() { 848 get localStartOffset() {
742 return this.startOffset - this.containerStartOffset; 849 return this.localContainerStartOffset_;
743 }, 850 },
744 851
745 /** 852 /**
746 * Gets the selection offset based on the parent's text. 853 * Gets the selection offset based on the parent's text.
747 * The parent is expected to be static text. 854 * The parent is expected to be static text.
748 * @return {number} 855 * @return {number}
749 */ 856 */
750 get localEndOffset() { 857 get localEndOffset() {
751 return this.endOffset - this.containerStartOffset; 858 return this.localContainerEndOffset_;
752 }, 859 },
753 860
754 /** 861 /**
755 * Gets the start offset of the container, relative to the line text content. 862 * Gets the start offset of the container, relative to the line text content.
756 * The container refers to the static text parenting the inline text box. 863 * The container refers to the static text parenting the inline text box.
757 * @return {number} 864 * @return {number}
758 */ 865 */
759 get containerStartOffset() { 866 get containerStartOffset() {
760 return this.value_.getSpanStart(this.startContainer_); 867 return this.value_.getSpanStart(this.startContainer_);
761 }, 868 },
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
808 /** 915 /**
809 * Returns true if |otherLine| surrounds the same line as |this| and has the 916 * Returns true if |otherLine| surrounds the same line as |this| and has the
810 * same selection. 917 * same selection.
811 * @param {editing.EditableLine} otherLine 918 * @param {editing.EditableLine} otherLine
812 * @return {boolean} 919 * @return {boolean}
813 */ 920 */
814 isSameLineAndSelection: function(otherLine) { 921 isSameLineAndSelection: function(otherLine) {
815 return this.isSameLine(otherLine) && 922 return this.isSameLine(otherLine) &&
816 this.startOffset == otherLine.startOffset && 923 this.startOffset == otherLine.startOffset &&
817 this.endOffset == otherLine.endOffset; 924 this.endOffset == otherLine.endOffset;
925 },
926
927 /**
928 * Returns whether this line comes before |otherLine| in document order.
929 * @return {boolean}
930 */
931 isBeforeLine: function(otherLine) {
932 if (this.isSameLine(otherLine) || !this.lineStartContainer_ ||
933 !otherLine.lineStartContainer_)
934 return false;
935 return AutomationUtil.getDirection(
936 this.lineStartContainer_, otherLine.lineStartContainer_) ==
937 Dir.FORWARD;
818 } 938 }
819 }; 939 };
820 940
821 }); 941 });
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/chromeos/chromevox/cvox2/background/editing_test.extjs » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698