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

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: Rename. 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 // TODO(dtseng): base/extent and anchor/focus are mismatched in AX. Swap
331 // once this works.
332 var curBase = baseLineOnStart ? focusLine : anchorLine;
333 var curExtent = baseLineOnStart ? anchorLine : focusLine;
334 var text = '';
335 var suffixMsg = '';
336 if (curBase.isBeforeLine(curExtent)) {
337 // Forward selection.
338 if (prev.isBeforeLine(curBase)) {
339 // Wrapped across the baseline. Read out the new selection.
340 suffixMsg = 'selected';
341 text = this.getTextSelection_(
342 curBase.startContainer_, curBase.localStartOffset,
343 curExtent.endContainer_, curExtent.localEndOffset);
344 } else {
345 if (prev.isBeforeLine(curExtent)) {
346 // Grew.
347 suffixMsg = 'selected';
348 text = this.getTextSelection_(
349 prev.endContainer_, prev.localEndOffset,
350 curExtent.endContainer_, curExtent.localEndOffset);
351 } else {
352 // Shrank.
353 suffixMsg = 'unselected';
354 text = this.getTextSelection_(
355 curExtent.endContainer_, curExtent.localEndOffset,
356 prev.endContainer_, prev.localEndOffset);
357 }
358 }
359 } else {
360 // Backward selection.
361 if (curBase.isBeforeLine(prev)) {
362 // Wrapped across the baseline. Read out the new selection.
363 suffixMsg = 'selected';
364 text = this.getTextSelection_(
365 curExtent.startContainer_, curExtent.localStartOffset,
366 curBase.endContainer_, curBase.localEndOffset);
367 } else {
368 if (curExtent.isBeforeLine(prev)) {
369 // Grew.
370 suffixMsg = 'selected';
371 text = this.getTextSelection_(
372 curExtent.startContainer_, curExtent.localStartOffset,
373 prev.startContainer_, prev.localStartOffset);
374 } else {
375 // Shrank.
376 suffixMsg = 'unselected';
377 text = this.getTextSelection_(
378 prev.startContainer_, prev.localStartOffset,
379 curExtent.startContainer_, curExtent.localStartOffset);
380 }
381 }
382 }
383
384 cvox.ChromeVox.tts.speak(text, cvox.QueueMode.CATEGORY_FLUSH);
385 cvox.ChromeVox.tts.speak(Msgs.getMsg(suffixMsg), cvox.QueueMode.QUEUE);
322 this.brailleCurrentRichLine_(); 386 this.brailleCurrentRichLine_();
323 } else { 387 } else {
324 // Describe the current line. This accounts for previous/current 388 // Describe the current line. This accounts for previous/current
325 // selections and picking the line edge boundary that changed (as computed 389 // selections and picking the line edge boundary that changed (as computed
326 // above). This is also the code path for describing paste. 390 // above). This is also the code path for describing paste.
327 this.speakCurrentRichLine_(prev); 391 this.speakCurrentRichLine_(prev);
328 this.brailleCurrentRichLine_(); 392 this.brailleCurrentRichLine_();
329 } 393 }
330 394
331 // The state in EditableTextBase needs to get updated with the new line 395 // The state in EditableTextBase needs to get updated with the new line
332 // contents, so that subsequent intra-line changes get the right state 396 // contents, so that subsequent intra-line changes get the right state
333 // transitions. 397 // transitions.
334 this.value = cur.text; 398 this.value = cur.text;
335 this.start = cur.startOffset; 399 this.start = cur.startOffset;
336 this.end = cur.endOffset; 400 this.end = cur.endOffset;
337 }, 401 },
338 402
339 /** 403 /**
404 * @param {AutomationNode|undefined} startNode
405 * @param {number} startOffset
406 * @param {AutomationNode|undefined} endNode
407 * @param {number} endOffset
408 * @return {string}
409 */
410 getTextSelection_: function(startNode, startOffset, endNode, endOffset) {
411 if (!startNode || !endNode)
412 return '';
413
414 if (startNode == endNode) {
415 return startNode.name ? startNode.name.substring(startOffset, endOffset) :
416 '';
417 }
418
419 var text = '';
420 if (startNode.name)
421 text = startNode.name.substring(startOffset);
422
423 for (var node = startNode;
424 (node = AutomationUtil.findNextNode(
425 node, Dir.FORWARD, AutomationPredicate.leafOrStaticText)) &&
426 node != endNode;) {
427 // Padding needs to get added to break up speech utterances.
428 if (node.name)
429 text += ' ' + node.name;
430 }
431
432 if (endNode.name)
433 text += ' ' + endNode.name.substring(0, endOffset);
434 return text;
435 },
436
437 /**
340 * @param {number} markerType 438 * @param {number} markerType
341 * @param {boolean=} opt_end 439 * @param {boolean=} opt_end
342 * @private 440 * @private
343 */ 441 */
344 speakTextMarker_: function(markerType, opt_end) { 442 speakTextMarker_: function(markerType, opt_end) {
345 // TODO(dtseng): Plumb through constants to automation. 443 // TODO(dtseng): Plumb through constants to automation.
346 var msgs = []; 444 var msgs = [];
347 if (markerType & 1) 445 if (markerType & 1)
348 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); 446 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start');
349 if (markerType & 2) 447 if (markerType & 2)
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
536 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { 634 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) {
537 /** @private {!Cursor} */ 635 /** @private {!Cursor} */
538 this.start_ = new Cursor(startNode, startIndex); 636 this.start_ = new Cursor(startNode, startIndex);
539 this.start_ = this.start_.deepEquivalent || this.start_; 637 this.start_ = this.start_.deepEquivalent || this.start_;
540 638
541 /** @private {!Cursor} */ 639 /** @private {!Cursor} */
542 this.end_ = new Cursor(endNode, endIndex); 640 this.end_ = new Cursor(endNode, endIndex);
543 this.end_ = this.end_.deepEquivalent || this.end_; 641 this.end_ = this.end_.deepEquivalent || this.end_;
544 /** @private {number} */ 642 /** @private {number} */
545 this.localContainerStartOffset_ = startIndex; 643 this.localContainerStartOffset_ = startIndex;
644 /** @private {number} */
645 this.localContainerEndOffset_ = endIndex;
546 646
547 // Computed members. 647 // Computed members.
548 /** @private {Spannable} */ 648 /** @private {Spannable} */
549 this.value_; 649 this.value_;
550 /** @private {AutomationNode|undefined} */ 650 /** @private {AutomationNode|undefined} */
551 this.lineStart_; 651 this.lineStart_;
552 /** @private {AutomationNode|undefined} */ 652 /** @private {AutomationNode|undefined} */
553 this.lineEnd_; 653 this.lineEnd_;
554 /** @private {AutomationNode|undefined} */ 654 /** @private {AutomationNode|undefined} */
555 this.startContainer_; 655 this.startContainer_;
(...skipping 20 matching lines...) Expand all
576 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; 676 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_;
577 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; 677 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_;
578 678
579 if (lineBase.node.name) 679 if (lineBase.node.name)
580 nameLen = lineBase.node.name.length; 680 nameLen = lineBase.node.name.length;
581 681
582 this.value_ = new Spannable(lineBase.node.name || '', lineBase); 682 this.value_ = new Spannable(lineBase.node.name || '', lineBase);
583 if (lineBase.node == lineExtend.node) 683 if (lineBase.node == lineExtend.node)
584 this.value_.setSpan(lineExtend, 0, nameLen); 684 this.value_.setSpan(lineExtend, 0, nameLen);
585 685
686 this.startContainer_ = this.start_.node;
687 if (this.startContainer_.role == RoleType.INLINE_TEXT_BOX)
688 this.startContainer_ = this.startContainer_.parent;
689 this.endContainer_ = this.end_.node;
690 if (this.endContainer_.role == RoleType.INLINE_TEXT_BOX)
691 this.endContainer_ = this.endContainer_.parent;
692
586 // Initialize defaults. 693 // Initialize defaults.
587 this.lineStart_ = lineBase.node; 694 this.lineStart_ = lineBase.node;
588 this.lineEnd_ = this.lineStart_; 695 this.lineEnd_ = this.lineStart_;
589 this.startContainer_ = this.lineStart_.parent;
590 this.lineStartContainer_ = this.lineStart_.parent; 696 this.lineStartContainer_ = this.lineStart_.parent;
591 this.lineEndContainer_ = this.lineStart_.parent; 697 this.lineEndContainer_ = this.lineStart_.parent;
592 698
593 // Annotate each chunk with its associated inline text box node. 699 // Annotate each chunk with its associated inline text box node.
594 this.value_.setSpan(this.lineStart_, 0, nameLen); 700 this.value_.setSpan(this.lineStart_, 0, nameLen);
595 701
596 // Also, track the nodes necessary for selection (either their parents, in 702 // Also, track the nodes necessary for selection (either their parents, in
597 // the case of inline text boxes, or the node itself). 703 // the case of inline text boxes, or the node itself).
598 var parents = [this.startContainer_]; 704 var parents = [this.startContainer_];
599 705
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after
732 return this.value_.length; 838 return this.value_.length;
733 } 839 }
734 }, 840 },
735 841
736 /** 842 /**
737 * Gets the selection offset based on the parent's text. 843 * Gets the selection offset based on the parent's text.
738 * The parent is expected to be static text. 844 * The parent is expected to be static text.
739 * @return {number} 845 * @return {number}
740 */ 846 */
741 get localStartOffset() { 847 get localStartOffset() {
742 return this.startOffset - this.containerStartOffset; 848 return this.localContainerStartOffset_;
743 }, 849 },
744 850
745 /** 851 /**
746 * Gets the selection offset based on the parent's text. 852 * Gets the selection offset based on the parent's text.
747 * The parent is expected to be static text. 853 * The parent is expected to be static text.
748 * @return {number} 854 * @return {number}
749 */ 855 */
750 get localEndOffset() { 856 get localEndOffset() {
751 return this.endOffset - this.containerStartOffset; 857 return this.localContainerEndOffset_;
752 }, 858 },
753 859
754 /** 860 /**
755 * Gets the start offset of the container, relative to the line text content. 861 * 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. 862 * The container refers to the static text parenting the inline text box.
757 * @return {number} 863 * @return {number}
758 */ 864 */
759 get containerStartOffset() { 865 get containerStartOffset() {
760 return this.value_.getSpanStart(this.startContainer_); 866 return this.value_.getSpanStart(this.startContainer_);
761 }, 867 },
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
808 /** 914 /**
809 * Returns true if |otherLine| surrounds the same line as |this| and has the 915 * Returns true if |otherLine| surrounds the same line as |this| and has the
810 * same selection. 916 * same selection.
811 * @param {editing.EditableLine} otherLine 917 * @param {editing.EditableLine} otherLine
812 * @return {boolean} 918 * @return {boolean}
813 */ 919 */
814 isSameLineAndSelection: function(otherLine) { 920 isSameLineAndSelection: function(otherLine) {
815 return this.isSameLine(otherLine) && 921 return this.isSameLine(otherLine) &&
816 this.startOffset == otherLine.startOffset && 922 this.startOffset == otherLine.startOffset &&
817 this.endOffset == otherLine.endOffset; 923 this.endOffset == otherLine.endOffset;
924 },
925
926 /**
927 * Returns whether this line comes before |otherLine| in document order.
928 * @return {boolean}
929 */
930 isBeforeLine: function(otherLine) {
931 if (this.isSameLine(otherLine) || !this.lineStartContainer_ ||
932 !otherLine.lineStartContainer_)
933 return false;
934 return AutomationUtil.getDirection(
935 this.lineStartContainer_, otherLine.lineStartContainer_) ==
936 Dir.FORWARD;
818 } 937 }
819 }; 938 };
820 939
821 }); 940 });
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