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

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/common/editable_text_base.js

Issue 889593002: Refactorings to reduce dependencies in ChromeVox 2. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix copyright year. Created 5 years, 10 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
OLDNEW
1 // Copyright 2014 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 goog.provide('cvox.ChromeVoxEditableContentEditable');
6 goog.provide('cvox.ChromeVoxEditableHTMLInput');
7 goog.provide('cvox.ChromeVoxEditableTextArea');
8 goog.provide('cvox.ChromeVoxEditableTextBase'); 5 goog.provide('cvox.ChromeVoxEditableTextBase');
9 goog.provide('cvox.TextChangeEvent'); 6 goog.provide('cvox.TextChangeEvent');
10 goog.provide('cvox.TextHandlerInterface');
11 goog.provide('cvox.TypingEcho'); 7 goog.provide('cvox.TypingEcho');
12 8
13 9 goog.require('cvox.ChromeVox');
14 goog.require('cvox.BrailleTextHandler');
15 goog.require('cvox.ContentEditableExtractor');
16 goog.require('cvox.DomUtil');
17 goog.require('cvox.EditableTextAreaShadow');
18 goog.require('cvox.TtsInterface'); 10 goog.require('cvox.TtsInterface');
19 goog.require('goog.i18n.MessageFormat'); 11 goog.require('goog.i18n.MessageFormat');
20 12
13
21 /** 14 /**
22 * @fileoverview Gives the user spoken feedback as they type, select text, 15 * @fileoverview Generalized logic for providing spoken feedback when editing
23 * and move the cursor in editable text controls, including multiline 16 * text fields, both single and multiline fields.
24 * controls.
25 * 17 *
26 * The majority of the code is in ChromeVoxEditableTextBase, a generalized 18 * {@code ChromeVoxEditableTextBase} is a generalized class that takes the
27 * class that takes the current state in the form of a text string, a 19 * current state in the form of a text string, a cursor start location and a
28 * cursor start location and a cursor end location, and calls a speak 20 * cursor end location, and calls a speak method with the resulting text to
29 * method with the resulting text to be spoken. If the control is multiline, 21 * be spoken. This class can be used directly for single line fields or
30 * information about line breaks (including automatic ones) is also needed. 22 * extended to override methods that extract lines for multiline fields
31 * 23 * or to provide other customizations.
32 * Two subclasses, ChromeVoxEditableHTMLInput and
33 * ChromeVoxEditableTextArea, take a HTML input (type=text) or HTML
34 * textarea node (respectively) in the constructor, and automatically
35 * handle retrieving the current state of the control, including
36 * computing line break information for a textarea using an offscreen
37 * shadow object. It is still the responsibility of the user of this
38 * class to trap key and focus events and call this class's update
39 * method.
40 *
41 */ 24 */
42 25
43 26
44 /** 27 /**
45 * A class containing the information needed to speak 28 * A class containing the information needed to speak
46 * a text change event to the user. 29 * a text change event to the user.
47 * 30 *
48 * @constructor 31 * @constructor
49 * @param {string} newValue The new string value of the editable text control. 32 * @param {string} newValue The new string value of the editable text control.
50 * @param {number} newStart The new 0-based start cursor/selection index. 33 * @param {number} newStart The new 0-based start cursor/selection index.
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
99 * @param {number} typingEcho Typing echo option. 82 * @param {number} typingEcho Typing echo option.
100 * @return {boolean} Whether the character should be spoken. 83 * @return {boolean} Whether the character should be spoken.
101 */ 84 */
102 cvox.TypingEcho.shouldSpeakChar = function(typingEcho) { 85 cvox.TypingEcho.shouldSpeakChar = function(typingEcho) {
103 return typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD || 86 return typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD ||
104 typingEcho == cvox.TypingEcho.CHARACTER; 87 typingEcho == cvox.TypingEcho.CHARACTER;
105 }; 88 };
106 89
107 90
108 /** 91 /**
109 * An interface for being notified when the text changes.
110 * @interface
111 */
112 cvox.TextHandlerInterface = function() {};
113
114
115 /**
116 * Called when text changes.
117 * @param {cvox.TextChangeEvent} evt The text change event.
118 */
119 cvox.TextHandlerInterface.prototype.changed = function(evt) {};
120
121
122 /**
123 * A class representing an abstracted editable text control. 92 * A class representing an abstracted editable text control.
124 * @param {string} value The string value of the editable text control. 93 * @param {string} value The string value of the editable text control.
125 * @param {number} start The 0-based start cursor/selection index. 94 * @param {number} start The 0-based start cursor/selection index.
126 * @param {number} end The 0-based end cursor/selection index. 95 * @param {number} end The 0-based end cursor/selection index.
127 * @param {boolean} isPassword Whether the text control if a password field. 96 * @param {boolean} isPassword Whether the text control if a password field.
128 * @param {cvox.TtsInterface} tts A TTS object. 97 * @param {cvox.TtsInterface} tts A TTS object.
129 * @constructor 98 * @constructor
130 */ 99 */
131 cvox.ChromeVoxEditableTextBase = function(value, start, end, isPassword, tts) { 100 cvox.ChromeVoxEditableTextBase = function(value, start, end, isPassword, tts) {
132 /** 101 /**
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 this.tts = tts; 134 this.tts = tts;
166 135
167 /** 136 /**
168 * Whether or not the text field is multiline. 137 * Whether or not the text field is multiline.
169 * @type {boolean} 138 * @type {boolean}
170 * @protected 139 * @protected
171 */ 140 */
172 this.multiline = false; 141 this.multiline = false;
173 142
174 /** 143 /**
175 * An optional handler for braille output. 144 * Whether or not the last update to the text and selection was described.
176 * @type {cvox.BrailleTextHandler|undefined} 145 *
177 * @private 146 * Some consumers of this flag like |ChromeVoxEventWatcher| depend on and
147 * react to when this flag is false by generating alternative feedback.
148 * @type {boolean}
178 */ 149 */
179 this.brailleHandler_ = cvox.ChromeVox.braille ? 150 this.lastChangeDescribed = false;
180 new cvox.BrailleTextHandler(cvox.ChromeVox.braille) : undefined; 151
181 }; 152 };
182 153
183 154
184 /** 155 /**
185 * Performs setup for this element. 156 * Performs setup for this element.
186 */ 157 */
187 cvox.ChromeVoxEditableTextBase.prototype.setup = function() {}; 158 cvox.ChromeVoxEditableTextBase.prototype.setup = function() {};
188 159
189 160
190 /** 161 /**
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 * to an event. For example, if the user selects "Hello", we will speak 198 * to an event. For example, if the user selects "Hello", we will speak
228 * "Hello, selected", but if the user selects 1000 characters, we will speak 199 * "Hello, selected", but if the user selects 1000 characters, we will speak
229 * "text selected" instead. 200 * "text selected" instead.
230 * 201 *
231 * @type {number} 202 * @type {number}
232 */ 203 */
233 cvox.ChromeVoxEditableTextBase.prototype.maxShortPhraseLen = 60; 204 cvox.ChromeVoxEditableTextBase.prototype.maxShortPhraseLen = 60;
234 205
235 206
236 /** 207 /**
237 * Whether or not the text control is a password.
238 *
239 * @type {boolean}
240 */
241 cvox.ChromeVoxEditableTextBase.prototype.isPassword = false;
242
243
244 /**
245 * Whether or not the last update to the text and selection was described.
246 *
247 * Some consumers of this flag like |ChromeVoxEventWatcher| depend on and
248 * react to when this flag is false by generating alternative feedback.
249 * @type {boolean}
250 */
251 cvox.ChromeVoxEditableTextBase.prototype.lastChangeDescribed = false;
252
253
254 /**
255 * Get the line number corresponding to a particular index. 208 * Get the line number corresponding to a particular index.
256 * Default implementation that can be overridden by subclasses. 209 * Default implementation that can be overridden by subclasses.
257 * @param {number} index The 0-based character index. 210 * @param {number} index The 0-based character index.
258 * @return {number} The 0-based line number corresponding to that character. 211 * @return {number} The 0-based line number corresponding to that character.
259 */ 212 */
260 cvox.ChromeVoxEditableTextBase.prototype.getLineIndex = function(index) { 213 cvox.ChromeVoxEditableTextBase.prototype.getLineIndex = function(index) {
261 return 0; 214 return 0;
262 }; 215 };
263 216
264 217
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 285
333 /** 286 /**
334 * Speak text, but if it's a single character, describe the character. 287 * Speak text, but if it's a single character, describe the character.
335 * @param {string} str The string to speak. 288 * @param {string} str The string to speak.
336 * @param {boolean=} opt_triggeredByUser True if the speech was triggered by a 289 * @param {boolean=} opt_triggeredByUser True if the speech was triggered by a
337 * user action. 290 * user action.
338 * @param {Object=} opt_personality Personality used to speak text. 291 * @param {Object=} opt_personality Personality used to speak text.
339 */ 292 */
340 cvox.ChromeVoxEditableTextBase.prototype.speak = 293 cvox.ChromeVoxEditableTextBase.prototype.speak =
341 function(str, opt_triggeredByUser, opt_personality) { 294 function(str, opt_triggeredByUser, opt_personality) {
342 // If there is a node associated with the editable text object,
343 // make sure that node has focus before speaking it.
344 if (this.node && (document.activeElement != this.node)) {
345 return;
346 }
347 var queueMode = cvox.QueueMode.QUEUE; 295 var queueMode = cvox.QueueMode.QUEUE;
348 if (opt_triggeredByUser === true) { 296 if (opt_triggeredByUser === true) {
349 queueMode = cvox.QueueMode.FLUSH; 297 queueMode = cvox.QueueMode.FLUSH;
350 } 298 }
351 this.tts.speak(str, queueMode, opt_personality || {}); 299 this.tts.speak(str, queueMode, opt_personality || {});
352 }; 300 };
353 301
354 302
355 /** 303 /**
356 * Update the state of the text and selection and describe any changes as 304 * Update the state of the text and selection and describe any changes as
(...skipping 10 matching lines...) Expand all
367 if (evt.value == this.value) { 315 if (evt.value == this.value) {
368 this.describeSelectionChanged(evt); 316 this.describeSelectionChanged(evt);
369 } else { 317 } else {
370 this.describeTextChanged(evt); 318 this.describeTextChanged(evt);
371 } 319 }
372 this.lastChangeDescribed = true; 320 this.lastChangeDescribed = true;
373 321
374 this.value = evt.value; 322 this.value = evt.value;
375 this.start = evt.start; 323 this.start = evt.start;
376 this.end = evt.end; 324 this.end = evt.end;
377
378 this.brailleCurrentLine_();
379 }; 325 };
380 326
381 327
382 /** 328 /**
383 * Shows the current line on the braille display.
384 * @private
385 */
386 cvox.ChromeVoxEditableTextBase.prototype.brailleCurrentLine_ = function() {
387 if (this.brailleHandler_) {
388 var lineIndex = this.getLineIndex(this.start);
389 var line = this.getLine(lineIndex);
390 // Collapsable whitespace inside the contenteditable is represented
391 // as non-breaking spaces. This confuses braille input (which relies on
392 // the text being added to be the same as the text in the input field).
393 // Since the non-breaking spaces are just an artifact of how
394 // contenteditable is implemented, normalize to normal spaces instead.
395 if (this instanceof cvox.ChromeVoxEditableContentEditable) {
396 line = line.replace(/\u00A0/g, ' ');
397 }
398 var lineStart = this.getLineStart(lineIndex);
399 var start = this.start - lineStart;
400 var end = Math.min(this.end - lineStart, line.length);
401 this.brailleHandler_.changed(line, start, end, this.multiline, this.node,
402 lineStart);
403 }
404 };
405
406 /**
407 * Describe a change in the selection or cursor position when the text 329 * Describe a change in the selection or cursor position when the text
408 * stays the same. 330 * stays the same.
409 * @param {cvox.TextChangeEvent} evt The text change event. 331 * @param {cvox.TextChangeEvent} evt The text change event.
410 */ 332 */
411 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged = 333 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged =
412 function(evt) { 334 function(evt) {
413 // TODO(deboer): Factor this into two function: 335 // TODO(deboer): Factor this into two function:
414 // - one to determine the selection event 336 // - one to determine the selection event
415 // - one to speak 337 // - one to speak
416 338
(...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after
780 702
781 /** 703 /**
782 * Moves the cursor backward by one paragraph. 704 * Moves the cursor backward by one paragraph.
783 * @return {boolean} True if the action was handled. 705 * @return {boolean} True if the action was handled.
784 */ 706 */
785 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousParagraph = 707 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousParagraph =
786 function() { return false; }; 708 function() { return false; };
787 709
788 710
789 /******************************************/ 711 /******************************************/
790
791
792 /**
793 * A subclass of ChromeVoxEditableTextBase a text element that's part of
794 * the webpage DOM. Contains common code shared by both EditableHTMLInput
795 * and EditableTextArea, but that might not apply to a non-DOM text box.
796 * @param {Element} node A DOM node which allows text input.
797 * @param {string} value The string value of the editable text control.
798 * @param {number} start The 0-based start cursor/selection index.
799 * @param {number} end The 0-based end cursor/selection index.
800 * @param {boolean} isPassword Whether the text control if a password field.
801 * @param {cvox.TtsInterface} tts A TTS object.
802 * @extends {cvox.ChromeVoxEditableTextBase}
803 * @constructor
804 */
805 cvox.ChromeVoxEditableElement = function(node, value, start, end, isPassword,
806 tts) {
807 goog.base(this, value, start, end, isPassword, tts);
808
809 /**
810 * The DOM node which allows text input.
811 * @type {Element}
812 * @protected
813 */
814 this.node = node;
815
816 /**
817 * True if the description was just spoken.
818 * @type {boolean}
819 * @private
820 */
821 this.justSpokeDescription_ = false;
822 };
823 goog.inherits(cvox.ChromeVoxEditableElement,
824 cvox.ChromeVoxEditableTextBase);
825
826
827 /**
828 * Update the state of the text and selection and describe any changes as
829 * appropriate.
830 *
831 * @param {cvox.TextChangeEvent} evt The text change event.
832 */
833 cvox.ChromeVoxEditableElement.prototype.changed = function(evt) {
834 // Ignore changes to the cursor and selection if they happen immediately
835 // after the description was just spoken. This avoid double-speaking when,
836 // for example, a text field is focused and then a moment later the
837 // contents are selected. If the value changes, though, this change will
838 // not be ignored.
839 if (this.justSpokeDescription_ && this.value == evt.value) {
840 this.value = evt.value;
841 this.start = evt.start;
842 this.end = evt.end;
843 this.justSpokeDescription_ = false;
844 }
845 goog.base(this, 'changed', evt);
846 };
847
848
849 /** @override */
850 cvox.ChromeVoxEditableElement.prototype.moveCursorToNextCharacter = function() {
851 var node = this.node;
852 node.selectionEnd++;
853 node.selectionStart = node.selectionEnd;
854 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
855 return true;
856 };
857
858
859 /** @override */
860 cvox.ChromeVoxEditableElement.prototype.moveCursorToPreviousCharacter =
861 function() {
862 var node = this.node;
863 node.selectionStart--;
864 node.selectionEnd = node.selectionStart;
865 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
866 return true;
867 };
868
869
870 /** @override */
871 cvox.ChromeVoxEditableElement.prototype.moveCursorToNextWord = function() {
872 var node = this.node;
873 var length = node.value.length;
874 var re = /\W+/gm;
875 var substring = node.value.substring(node.selectionEnd);
876 var match = re.exec(substring);
877 if (match !== null && match.index == 0) {
878 // Ignore word-breaking sequences right next to the cursor.
879 match = re.exec(substring);
880 }
881 var index = (match === null) ? length : match.index + node.selectionEnd;
882 node.selectionStart = node.selectionEnd = index;
883 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
884 return true;
885 };
886
887
888 /** @override */
889 cvox.ChromeVoxEditableElement.prototype.moveCursorToPreviousWord = function() {
890 var node = this.node;
891 var length = node.value.length;
892 var re = /\W+/gm;
893 var substring = node.value.substring(0, node.selectionStart);
894 var index = 0;
895 while (re.exec(substring) !== null) {
896 if (re.lastIndex < node.selectionStart) {
897 index = re.lastIndex;
898 }
899 }
900 node.selectionStart = node.selectionEnd = index;
901 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
902 return true;
903 };
904
905
906 /** @override */
907 cvox.ChromeVoxEditableElement.prototype.moveCursorToNextParagraph =
908 function() {
909 var node = this.node;
910 var length = node.value.length;
911 var index = node.selectionEnd >= length ? length :
912 node.value.indexOf('\n', node.selectionEnd);
913 if (index < 0) {
914 index = length;
915 }
916 node.selectionStart = node.selectionEnd = index + 1;
917 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
918 return true;
919 };
920
921
922 /** @override */
923 cvox.ChromeVoxEditableElement.prototype.moveCursorToPreviousParagraph =
924 function() {
925 var node = this.node;
926 var index = node.selectionStart <= 0 ? 0 :
927 node.value.lastIndexOf('\n', node.selectionStart - 2) + 1;
928 if (index < 0) {
929 index = 0;
930 }
931 node.selectionStart = node.selectionEnd = index;
932 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
933 return true;
934 };
935
936
937 /******************************************/
938
939
940 /**
941 * A subclass of ChromeVoxEditableElement for an HTMLInputElement.
942 * @param {HTMLInputElement} node The HTMLInputElement node.
943 * @param {cvox.TtsInterface} tts A TTS object.
944 * @extends {cvox.ChromeVoxEditableElement}
945 * @implements {cvox.TextHandlerInterface}
946 * @constructor
947 */
948 cvox.ChromeVoxEditableHTMLInput = function(node, tts) {
949 this.node = node;
950 this.setup();
951 goog.base(this,
952 node,
953 node.value,
954 node.selectionStart,
955 node.selectionEnd,
956 node.type === 'password',
957 tts);
958 };
959 goog.inherits(cvox.ChromeVoxEditableHTMLInput,
960 cvox.ChromeVoxEditableElement);
961
962
963 /**
964 * Performs setup for this input node.
965 * This accounts for exception-throwing behavior introduced by crbug.com/324360.
966 * @override
967 */
968 cvox.ChromeVoxEditableHTMLInput.prototype.setup = function() {
969 if (!this.node) {
970 return;
971 }
972 if (!cvox.DomUtil.doesInputSupportSelection(this.node)) {
973 this.originalType = this.node.type;
974 this.node.type = 'text';
975 }
976 };
977
978
979 /**
980 * Performs teardown for this input node.
981 * This accounts for exception-throwing behavior introduced by crbug.com/324360.
982 * @override
983 */
984 cvox.ChromeVoxEditableHTMLInput.prototype.teardown = function() {
985 if (this.node && this.originalType) {
986 this.node.type = this.originalType;
987 }
988 };
989
990
991 /**
992 * Update the state of the text and selection and describe any changes as
993 * appropriate.
994 *
995 * @param {boolean} triggeredByUser True if this was triggered by a user action.
996 */
997 cvox.ChromeVoxEditableHTMLInput.prototype.update = function(triggeredByUser) {
998 var newValue = this.node.value;
999 var textChangeEvent = new cvox.TextChangeEvent(newValue,
1000 this.node.selectionStart,
1001 this.node.selectionEnd,
1002 triggeredByUser);
1003 this.changed(textChangeEvent);
1004 };
1005
1006
1007 /******************************************/
1008
1009
1010 /**
1011 * A subclass of ChromeVoxEditableElement for an HTMLTextAreaElement.
1012 * @param {HTMLTextAreaElement} node The HTMLTextAreaElement node.
1013 * @param {cvox.TtsInterface} tts A TTS object.
1014 * @extends {cvox.ChromeVoxEditableElement}
1015 * @implements {cvox.TextHandlerInterface}
1016 * @constructor
1017 */
1018 cvox.ChromeVoxEditableTextArea = function(node, tts) {
1019 goog.base(this, node, node.value, node.selectionStart, node.selectionEnd,
1020 false /* isPassword */, tts);
1021 this.multiline = true;
1022
1023 /**
1024 * True if the shadow is up-to-date with the current value of this text area.
1025 * @type {boolean}
1026 * @private
1027 */
1028 this.shadowIsCurrent_ = false;
1029 };
1030 goog.inherits(cvox.ChromeVoxEditableTextArea,
1031 cvox.ChromeVoxEditableElement);
1032
1033
1034 /**
1035 * An offscreen div used to compute the line numbers. A single div is
1036 * shared by all instances of the class.
1037 * @type {!cvox.EditableTextAreaShadow|undefined}
1038 * @private
1039 */
1040 cvox.ChromeVoxEditableTextArea.shadow_;
1041
1042
1043 /**
1044 * Update the state of the text and selection and describe any changes as
1045 * appropriate.
1046 *
1047 * @param {boolean} triggeredByUser True if this was triggered by a user action.
1048 */
1049 cvox.ChromeVoxEditableTextArea.prototype.update = function(triggeredByUser) {
1050 if (this.node.value != this.value) {
1051 this.shadowIsCurrent_ = false;
1052 }
1053 var textChangeEvent = new cvox.TextChangeEvent(this.node.value,
1054 this.node.selectionStart, this.node.selectionEnd, triggeredByUser);
1055 this.changed(textChangeEvent);
1056 };
1057
1058
1059 /**
1060 * Get the line number corresponding to a particular index.
1061 * @param {number} index The 0-based character index.
1062 * @return {number} The 0-based line number corresponding to that character.
1063 */
1064 cvox.ChromeVoxEditableTextArea.prototype.getLineIndex = function(index) {
1065 return this.getShadow().getLineIndex(index);
1066 };
1067
1068
1069 /**
1070 * Get the start character index of a line.
1071 * @param {number} index The 0-based line index.
1072 * @return {number} The 0-based index of the first character in this line.
1073 */
1074 cvox.ChromeVoxEditableTextArea.prototype.getLineStart = function(index) {
1075 return this.getShadow().getLineStart(index);
1076 };
1077
1078
1079 /**
1080 * Get the end character index of a line.
1081 * @param {number} index The 0-based line index.
1082 * @return {number} The 0-based index of the end of this line.
1083 */
1084 cvox.ChromeVoxEditableTextArea.prototype.getLineEnd = function(index) {
1085 return this.getShadow().getLineEnd(index);
1086 };
1087
1088
1089 /**
1090 * Update the shadow object, an offscreen div used to compute line numbers.
1091 * @return {!cvox.EditableTextAreaShadow} The shadow object.
1092 */
1093 cvox.ChromeVoxEditableTextArea.prototype.getShadow = function() {
1094 var shadow = cvox.ChromeVoxEditableTextArea.shadow_;
1095 if (!shadow) {
1096 shadow = cvox.ChromeVoxEditableTextArea.shadow_ =
1097 new cvox.EditableTextAreaShadow();
1098 }
1099 if (!this.shadowIsCurrent_) {
1100 shadow.update(this.node);
1101 this.shadowIsCurrent_ = true;
1102 }
1103 return shadow;
1104 };
1105
1106
1107 /** @override */
1108 cvox.ChromeVoxEditableTextArea.prototype.moveCursorToNextLine = function() {
1109 var node = this.node;
1110 var length = node.value.length;
1111 if (node.selectionEnd >= length) {
1112 return false;
1113 }
1114 var shadow = this.getShadow();
1115 var lineIndex = shadow.getLineIndex(node.selectionEnd);
1116 var lineStart = shadow.getLineStart(lineIndex);
1117 var offset = node.selectionEnd - lineStart;
1118 var lastLine = (length == 0) ? 0 : shadow.getLineIndex(length - 1);
1119 var newCursorPosition = (lineIndex >= lastLine) ? length :
1120 Math.min(shadow.getLineStart(lineIndex + 1) + offset,
1121 shadow.getLineEnd(lineIndex + 1));
1122 node.selectionStart = node.selectionEnd = newCursorPosition;
1123 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1124 return true;
1125 };
1126
1127
1128 /** @override */
1129 cvox.ChromeVoxEditableTextArea.prototype.moveCursorToPreviousLine = function() {
1130 var node = this.node;
1131 if (node.selectionStart <= 0) {
1132 return false;
1133 }
1134 var shadow = this.getShadow();
1135 var lineIndex = shadow.getLineIndex(node.selectionStart);
1136 var lineStart = shadow.getLineStart(lineIndex);
1137 var offset = node.selectionStart - lineStart;
1138 var newCursorPosition = (lineIndex <= 0) ? 0 :
1139 Math.min(shadow.getLineStart(lineIndex - 1) + offset,
1140 shadow.getLineEnd(lineIndex - 1));
1141 node.selectionStart = node.selectionEnd = newCursorPosition;
1142 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1143 return true;
1144 };
1145
1146
1147 /******************************************/
1148
1149
1150 /**
1151 * A subclass of ChromeVoxEditableElement for elements that are contentEditable.
1152 * This is also used for a region of HTML with the ARIA role of "textbox",
1153 * so that an author can create a pure-JavaScript editable text object - this
1154 * will work the same as contentEditable as long as the DOM selection is
1155 * updated properly within the textbox when it has focus.
1156 * @param {Element} node The root contentEditable node.
1157 * @param {cvox.TtsInterface} tts A TTS object.
1158 * @extends {cvox.ChromeVoxEditableElement}
1159 * @implements {cvox.TextHandlerInterface}
1160 * @constructor
1161 */
1162 cvox.ChromeVoxEditableContentEditable = function(node, tts) {
1163 goog.base(this, node, '', 0, 0, false /* isPassword */, tts);
1164
1165
1166 /**
1167 * True if the ContentEditableExtractor is current with this field's data.
1168 * @type {boolean}
1169 * @private
1170 */
1171 this.extractorIsCurrent_ = false;
1172
1173 var extractor = this.getExtractor();
1174 this.value = extractor.getText();
1175 this.start = extractor.getStartIndex();
1176 this.end = extractor.getEndIndex();
1177 this.multiline = true;
1178 };
1179 goog.inherits(cvox.ChromeVoxEditableContentEditable,
1180 cvox.ChromeVoxEditableElement);
1181
1182 /**
1183 * A helper used to compute the line numbers. A single object is
1184 * shared by all instances of the class.
1185 * @type {!cvox.ContentEditableExtractor|undefined}
1186 * @private
1187 */
1188 cvox.ChromeVoxEditableContentEditable.extractor_;
1189
1190
1191 /**
1192 * Update the state of the text and selection and describe any changes as
1193 * appropriate.
1194 *
1195 * @param {boolean} triggeredByUser True if this was triggered by a user action.
1196 */
1197 cvox.ChromeVoxEditableContentEditable.prototype.update =
1198 function(triggeredByUser) {
1199 this.extractorIsCurrent_ = false;
1200 var textChangeEvent = new cvox.TextChangeEvent(
1201 this.getExtractor().getText(),
1202 this.getExtractor().getStartIndex(),
1203 this.getExtractor().getEndIndex(),
1204 triggeredByUser);
1205 this.changed(textChangeEvent);
1206 };
1207
1208
1209 /**
1210 * Get the line number corresponding to a particular index.
1211 * @param {number} index The 0-based character index.
1212 * @return {number} The 0-based line number corresponding to that character.
1213 */
1214 cvox.ChromeVoxEditableContentEditable.prototype.getLineIndex = function(index) {
1215 return this.getExtractor().getLineIndex(index);
1216 };
1217
1218
1219 /**
1220 * Get the start character index of a line.
1221 * @param {number} index The 0-based line index.
1222 * @return {number} The 0-based index of the first character in this line.
1223 */
1224 cvox.ChromeVoxEditableContentEditable.prototype.getLineStart = function(index) {
1225 return this.getExtractor().getLineStart(index);
1226 };
1227
1228
1229 /**
1230 * Get the end character index of a line.
1231 * @param {number} index The 0-based line index.
1232 * @return {number} The 0-based index of the end of this line.
1233 */
1234 cvox.ChromeVoxEditableContentEditable.prototype.getLineEnd = function(index) {
1235 return this.getExtractor().getLineEnd(index);
1236 };
1237
1238
1239 /**
1240 * Update the extractor object, an offscreen div used to compute line numbers.
1241 * @return {!cvox.ContentEditableExtractor} The extractor object.
1242 */
1243 cvox.ChromeVoxEditableContentEditable.prototype.getExtractor = function() {
1244 var extractor = cvox.ChromeVoxEditableContentEditable.extractor_;
1245 if (!extractor) {
1246 extractor = cvox.ChromeVoxEditableContentEditable.extractor_ =
1247 new cvox.ContentEditableExtractor();
1248 }
1249 if (!this.extractorIsCurrent_) {
1250 extractor.update(this.node);
1251 this.extractorIsCurrent_ = true;
1252 }
1253 return extractor;
1254 };
1255
1256
1257 /**
1258 * @override
1259 */
1260 cvox.ChromeVoxEditableContentEditable.prototype.changed =
1261 function(evt) {
1262 if (!evt.triggeredByUser) {
1263 return;
1264 }
1265 // Take over here if we can't describe a change; assume it's a blank line.
1266 if (!this.shouldDescribeChange(evt)) {
1267 this.speak(cvox.ChromeVox.msgs.getMsg('text_box_blank'), true);
1268 if (this.brailleHandler_) {
1269 this.brailleHandler_.changed('' /*line*/, 0 /*start*/, 0 /*end*/,
1270 true /*multiline*/, null /*element*/,
1271 evt.start /*lineStart*/);
1272 }
1273 } else {
1274 goog.base(this, 'changed', evt);
1275 }
1276 };
1277
1278
1279 /** @override */
1280 cvox.ChromeVoxEditableContentEditable.prototype.moveCursorToNextCharacter =
1281 function() {
1282 window.getSelection().modify('move', 'forward', 'character');
1283 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1284 return true;
1285 };
1286
1287
1288 /** @override */
1289 cvox.ChromeVoxEditableContentEditable.prototype.moveCursorToPreviousCharacter =
1290 function() {
1291 window.getSelection().modify('move', 'backward', 'character');
1292 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1293 return true;
1294 };
1295
1296
1297 /** @override */
1298 cvox.ChromeVoxEditableContentEditable.prototype.moveCursorToNextParagraph =
1299 function() {
1300 window.getSelection().modify('move', 'forward', 'paragraph');
1301 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1302 return true;
1303 };
1304
1305 /** @override */
1306 cvox.ChromeVoxEditableContentEditable.prototype.moveCursorToPreviousParagraph =
1307 function() {
1308 window.getSelection().modify('move', 'backward', 'paragraph');
1309 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1310 return true;
1311 };
1312
1313
1314 /**
1315 * @override
1316 */
1317 cvox.ChromeVoxEditableContentEditable.prototype.shouldDescribeChange =
1318 function(evt) {
1319 var sel = window.getSelection();
1320 var cursor = new cvox.Cursor(sel.baseNode, sel.baseOffset, '');
1321
1322 // This is a very specific work around because of our buggy content editable
1323 // support. Blank new lines are not captured in the line indexing data
1324 // structures.
1325 // Scenario: given a piece of text like:
1326 //
1327 // Some Title
1328 //
1329 // Description
1330 // Footer
1331 //
1332 // The new lines after Title are not traversed to by TraverseUtil. A root fix
1333 // would make changes there. However, considering the fickle nature of that
1334 // code, we specifically detect for new lines here.
1335 if (Math.abs(this.start - evt.start) != 1 &&
1336 this.start == this.end &&
1337 evt.start == evt.end &&
1338 sel.baseNode == sel.extentNode &&
1339 sel.baseOffset == sel.extentOffset &&
1340 sel.baseNode.nodeType == Node.ELEMENT_NODE &&
1341 sel.baseNode.querySelector('BR') &&
1342 cvox.TraverseUtil.forwardsChar(cursor, [], [])) {
1343 // This case detects if the range selection surrounds a new line,
1344 // but there is still content after the new line (like the example
1345 // above after "Title"). In these cases, we "pretend" we're the
1346 // last character so we speak "blank".
1347 return false;
1348 }
1349
1350 // Otherwise, we should never speak "blank" no matter what (even if
1351 // we're at the end of a content editable).
1352 return true;
1353 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698