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

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

Issue 2540553002: Implement rich editable line output (Closed)
Patch Set: Update with latest fixes. Created 3 years, 7 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 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');
11 11
12 goog.require('AutomationTreeWalker'); 12 goog.require('AutomationTreeWalker');
13 goog.require('AutomationUtil'); 13 goog.require('AutomationUtil');
14 goog.require('Output'); 14 goog.require('Output');
15 goog.require('Output.EventType'); 15 goog.require('Output.EventType');
16 goog.require('cursors.Cursor'); 16 goog.require('cursors.Cursor');
17 goog.require('cursors.Range'); 17 goog.require('cursors.Range');
18 goog.require('cvox.BrailleBackground'); 18 goog.require('cvox.BrailleBackground');
19 goog.require('cvox.ChromeVoxEditableTextBase'); 19 goog.require('cvox.ChromeVoxEditableTextBase');
20 20
21 goog.scope(function() { 21 goog.scope(function() {
22 var AutomationEvent = chrome.automation.AutomationEvent; 22 var AutomationEvent = chrome.automation.AutomationEvent;
23 var AutomationNode = chrome.automation.AutomationNode; 23 var AutomationNode = chrome.automation.AutomationNode;
24 var Cursor = cursors.Cursor; 24 var Cursor = cursors.Cursor;
25 var Dir = AutomationUtil.Dir; 25 var Dir = constants.Dir;
26 var EventType = chrome.automation.EventType; 26 var EventType = chrome.automation.EventType;
27 var Range = cursors.Range; 27 var Range = cursors.Range;
28 var RoleType = chrome.automation.RoleType; 28 var RoleType = chrome.automation.RoleType;
29 var StateType = chrome.automation.StateType; 29 var StateType = chrome.automation.StateType;
30 var Movement = cursors.Movement; 30 var Movement = cursors.Movement;
31 var Unit = cursors.Unit; 31 var Unit = cursors.Unit;
32 32
33 /** 33 /**
34 * A handler for automation events in a focused text field or editable root 34 * A handler for automation events in a focused text field or editable root
35 * such as a |contenteditable| subtree. 35 * such as a |contenteditable| subtree.
(...skipping 23 matching lines...) Expand all
59 }; 59 };
60 60
61 /** 61 /**
62 * A |TextEditHandler| suitable for text fields. 62 * A |TextEditHandler| suitable for text fields.
63 * @constructor 63 * @constructor
64 * @param {!AutomationNode} node A node with the role of |textField| 64 * @param {!AutomationNode} node A node with the role of |textField|
65 * @extends {editing.TextEditHandler} 65 * @extends {editing.TextEditHandler}
66 */ 66 */
67 function TextFieldTextEditHandler(node) { 67 function TextFieldTextEditHandler(node) {
68 editing.TextEditHandler.call(this, node); 68 editing.TextEditHandler.call(this, node);
69 /** @type {AutomationEditableText} @private */ 69
70 this.editableText_ = new AutomationEditableText(node); 70 chrome.automation.getDesktop(function(desktop) {
71 // Chrome channel is empty when building from source.
72 var useExperimentalRichText = desktop.chromeChannel == '' &&
73 node.state.richlyEditable;
74
75 /** @private {!AutomationEditableText} */
76 this.editableText_ = useExperimentalRichText ?
77 new AutomationRichEditableText(node) : new AutomationEditableText(node);
78 }.bind(this));
71 } 79 }
72 80
73 TextFieldTextEditHandler.prototype = { 81 TextFieldTextEditHandler.prototype = {
74 __proto__: editing.TextEditHandler.prototype, 82 __proto__: editing.TextEditHandler.prototype,
75 83
76 /** @override */ 84 /** @override */
77 onEvent: function(evt) { 85 onEvent: function(evt) {
78 if (evt.type !== EventType.TEXT_CHANGED && 86 if (evt.type !== EventType.TEXT_CHANGED &&
79 evt.type !== EventType.TEXT_SELECTION_CHANGED && 87 evt.type !== EventType.TEXT_SELECTION_CHANGED &&
80 evt.type !== EventType.VALUE_CHANGED && 88 evt.type !== EventType.VALUE_CHANGED &&
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
192 } else { 200 } else {
193 range = Range.fromNode(this.node_); 201 range = Range.fromNode(this.node_);
194 } 202 }
195 output.withBraille(range, null, Output.EventType.NAVIGATE); 203 output.withBraille(range, null, Output.EventType.NAVIGATE);
196 if (isFirstLine) 204 if (isFirstLine)
197 output.formatForBraille('@tag_textarea'); 205 output.formatForBraille('@tag_textarea');
198 output.go(); 206 output.go();
199 } 207 }
200 }; 208 };
201 209
210
211 /**
212 * A |ChromeVoxEditableTextBase| that implements text editing feedback
213 * for automation tree text fields using anchor and focus selection.
214 * @constructor
215 * @param {!AutomationNode} node
216 * @extends {AutomationEditableText}
217 */
218 function AutomationRichEditableText(node) {
219 AutomationEditableText.call(this, node);
220
221 var root = this.node_.root;
222 if (!root || !root.anchorObject || !root.focusObject)
223 return;
224
225 this.range = new cursors.Range(
226 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0),
227 new cursors.Cursor(root.focusObject, root.focusOffset || 0));
228 }
229
230 AutomationRichEditableText.prototype = {
231 __proto__: AutomationEditableText.prototype,
232
233 /** @override */
234 onUpdate: function() {
235 var root = this.node_.root;
236 if (!root.anchorObject || !root.focusObject)
237 return;
238
239 var cur = new cursors.Range(
240 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0),
241 new cursors.Cursor(root.focusObject, root.focusOffset || 0));
242 var prev = this.range;
243
244 if (prev.equals(cur))
dmazzoni 2017/05/09 04:49:47 What if the range stays the same but the contents
David Tseng 2017/05/09 23:13:50 Done.
245 return;
246
247 this.range = cur;
248
249 if (prev.start.node == cur.start.node &&
250 prev.end.node == cur.end.node &&
251 cur.start.node == cur.end.node) {
252 // Plain text: diff the two positions.
253 this.changed(new cvox.TextChangeEvent(
254 root.anchorObject.name || '',
255 root.anchorOffset || 0,
256 root.focusOffset || 0,
257 true));
258
259 var lineIndex = this.getLineIndex(this.start);
260 var brailleLineStart = this.getLineStart(lineIndex);
261 var buff = new Spannable(this.value);
262 buff.setSpan(new cvox.ValueSpan(0), 0, this.value.length);
263
264 var selStart = this.start - brailleLineStart;
265 var selEnd = this.end - brailleLineStart;
266 buff.setSpan(new cvox.ValueSelectionSpan(), selStart, selEnd);
267 cvox.ChromeVox.braille.write(new cvox.NavBraille({text: buff,
268 startIndex: selStart,
269 endIndex: selEnd}));
270 return;
271 } else if (cur.start.equals(prev.start)) {
dmazzoni 2017/05/09 04:49:47 Do you need a braille output rule for this case?
David Tseng 2017/05/09 23:13:50 Removed.
272 // Selection.
273 var dir = prev.end.compare(cur.end);
274 var msg = dir == Dir.FORWARD ? '@selected' : '@unselected';
275 new Output().withSpeech(
276 new cursors.Range(cur.end, prev.end), prev, Output.EventType.NAVIGATE)
277 .formatForSpeech(msg)
278 .go();
279 } else if (cur.end.equals(prev.end)) {
dmazzoni 2017/05/09 04:49:46 Same, this does this case need braille?
David Tseng 2017/05/09 23:13:50 Removed.
280 // Selection.
281 var dir = prev.start.compare(cur.start);
282 var msg = dir == Dir.FORWARD ? '@unselected' : '@selected';
283 new Output().withSpeech(new cursors.Range(
284 cur.start, prev.start), prev, Output.EventType.NAVIGATE)
285 .formatForSpeech(msg)
286 .go();
287 } else {
288 // Rich text:
289 // If the position is collapsed, expand to the current line.
290 var start = cur.start;
291 var end = cur.end;
292 if (start.equals(end)) {
293 start = start.move(Unit.LINE, Movement.BOUND, Dir.BACKWARD);
294 end = end.move(Unit.LINE, Movement.BOUND, Dir.FORWARD);
295 }
296 var range = new cursors.Range(start, end);
297 var output = new Output().withRichSpeechAndBraille(
298 range, prev, Output.EventType.NAVIGATE);
299
300 // This position is not describable.
301 if (!output.hasSpeech) {
302 cvox.ChromeVox.tts.speak('blank', cvox.QueueMode.CATEGORY_FLUSH);
303 cvox.ChromeVox.braille.write(
304 new cvox.NavBraille({text: '', startIndex: 0, endIndex: 0}));
305 } else {
306 output.go();
307 }
308 }
309
310 // Keep the other members in sync for any future editable text base state
311 // machine changes.
312 this.value = cur.start.node.name || '';
313 this.start = cur.start.index;
314 this.end = cur.start.index;
315 },
316
317 /** @override */
318 describeSelectionChanged: function(evt) {
319 // Ignore end of text announcements.
320 if ((this.start + 1) == evt.start && evt.start == this.value.length)
321 return;
322
323 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call(
324 this, evt);
325 },
326
327 /** @override */
328 getLineIndex: function(charIndex) {
329 var breaks = this.getLineBreaks_();
330 var index = 0;
331 while (index < breaks.length && breaks[index] <= charIndex)
332 ++index;
333 return index;
334 },
335
336 /** @override */
337 getLineStart: function(lineIndex) {
338 if (lineIndex == 0)
339 return 0;
340 var breaks = this.getLineBreaks_();
341 return breaks[lineIndex - 1] ||
342 this.node_.root.focusObject.value.length;
343 },
344
345 /** @override */
346 getLineEnd: function(lineIndex) {
347 var breaks = this.getLineBreaks_();
348 var value = this.node_.root.focusObject.name;
349 if (lineIndex >= breaks.length)
350 return value.length;
351 return breaks[lineIndex];
352 },
353
354 /** @override */
355 getLineBreaks_: function() {
356 return this.node_.root.focusObject.lineStartOffsets || [];
357 }
358 };
359
202 /** 360 /**
203 * @param {!AutomationNode} node The root editable node, i.e. the root of a 361 * @param {!AutomationNode} node The root editable node, i.e. the root of a
204 * contenteditable subtree or a text field. 362 * contenteditable subtree or a text field.
205 * @return {editing.TextEditHandler} 363 * @return {editing.TextEditHandler}
206 */ 364 */
207 editing.TextEditHandler.createForNode = function(node) { 365 editing.TextEditHandler.createForNode = function(node) {
208 var rootFocusedEditable = null; 366 var rootFocusedEditable = null;
209 var testNode = node; 367 var testNode = node;
210 368
211 do { 369 do {
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
245 localStorage['brailleTable']); 403 localStorage['brailleTable']);
246 } 404 }
247 }; 405 };
248 406
249 /** 407 /**
250 * @private {ChromeVoxStateObserver} 408 * @private {ChromeVoxStateObserver}
251 */ 409 */
252 editing.observer_ = new editing.EditingChromeVoxStateObserver(); 410 editing.observer_ = new editing.EditingChromeVoxStateObserver();
253 411
254 }); 412 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698