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

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

Issue 2903973002: Rich editable text implementation using spannables (Closed)
Patch Set: wip 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
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.EditableLine');
11
10 goog.provide('editing.TextEditHandler'); 12 goog.provide('editing.TextEditHandler');
11 13
12 goog.require('AutomationTreeWalker'); 14 goog.require('AutomationTreeWalker');
13 goog.require('AutomationUtil'); 15 goog.require('AutomationUtil');
14 goog.require('Output'); 16 goog.require('Output');
15 goog.require('Output.EventType'); 17 goog.require('Output.EventType');
16 goog.require('cursors.Cursor'); 18 goog.require('cursors.Cursor');
17 goog.require('cursors.Range'); 19 goog.require('cursors.Range');
18 goog.require('cvox.BrailleBackground'); 20 goog.require('cvox.BrailleBackground');
19 goog.require('cvox.ChromeVoxEditableTextBase'); 21 goog.require('cvox.ChromeVoxEditableTextBase');
(...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after
220 * @param {!AutomationNode} node 222 * @param {!AutomationNode} node
221 * @extends {AutomationEditableText} 223 * @extends {AutomationEditableText}
222 */ 224 */
223 function AutomationRichEditableText(node) { 225 function AutomationRichEditableText(node) {
224 AutomationEditableText.call(this, node); 226 AutomationEditableText.call(this, node);
225 227
226 var root = this.node_.root; 228 var root = this.node_.root;
227 if (!root || !root.anchorObject || !root.focusObject) 229 if (!root || !root.anchorObject || !root.focusObject)
228 return; 230 return;
229 231
230 this.range = new cursors.Range( 232 this.line_ = new editing.EditableLine(root.anchorObject, root.anchorOffset, ro ot.focusObject, root.focusOffset);
231 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0),
232 new cursors.Cursor(root.focusObject, root.focusOffset || 0));
233 } 233 }
234 234
235 AutomationRichEditableText.prototype = { 235 AutomationRichEditableText.prototype = {
236 __proto__: AutomationEditableText.prototype, 236 __proto__: AutomationEditableText.prototype,
237 237
238 /** @override */ 238 /** @override */
239 onUpdate: function() { 239 onUpdate: function() {
240 var root = this.node_.root; 240 var root = this.node_.root;
241 if (!root.anchorObject || !root.focusObject) 241 if (!root.anchorObject || !root.focusObject)
242 return; 242 return;
243 243
244 var cur = new cursors.Range( 244 var cur = new editing.EditableLine(
245 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), 245 root.anchorObject, root.anchorOffset || 0,
246 new cursors.Cursor(root.focusObject, root.focusOffset || 0)); 246 root.focusObject, root.focusOffset || 0);
247 var prev = this.range; 247 var prev = this.line_;
248 this.line_ = cur;
248 249
249 this.range = cur; 250 if (prev.equals(cur)) {
250 251 // Collapsed cursor.
251 if (prev.start.node == cur.start.node &&
252 prev.end.node == cur.end.node &&
253 cur.start.node == cur.end.node) {
254 // Plain text: diff the two positions.
255 this.changed(new cvox.TextChangeEvent( 252 this.changed(new cvox.TextChangeEvent(
256 root.anchorObject.name || '', 253 cur.text || '',
257 root.anchorOffset || 0, 254 cur.startOffset || 0,
258 root.focusOffset || 0, 255 cur.endOffset || 0,
259 true)); 256 true));
260 257
261 var lineIndex = this.getLineIndex(this.start); 258 cvox.ChromeVox.braille.write(new cvox.NavBraille({text: cur.value_,
262 var brailleLineStart = this.getLineStart(lineIndex); 259 startIndex: 0,
263 var brailleLineEnd = this.getLineEnd(lineIndex); 260 endIndex: 0}));
264 var buff = new Spannable(this.value);
265 buff.setSpan(new cvox.ValueSpan(0), brailleLineStart, brailleLineEnd);
266
267 var selStart = this.start - brailleLineStart;
268 var selEnd = this.end - brailleLineStart;
269 buff.setSpan(new cvox.ValueSelectionSpan(), selStart, selEnd);
270 cvox.ChromeVox.braille.write(new cvox.NavBraille({text: buff,
271 startIndex: selStart,
272 endIndex: selEnd}));
273 return; 261 return;
274 } else {
275 // Rich text:
276 // If the position is collapsed, expand to the current line.
277 var start = cur.start;
278 var end = cur.end;
279 if (start.equals(end)) {
280 start = start.move(Unit.LINE, Movement.BOUND, Dir.BACKWARD);
281 end = end.move(Unit.LINE, Movement.BOUND, Dir.FORWARD);
282 }
283 var range = new cursors.Range(start, end);
284 var output = new Output().withRichSpeechAndBraille(
285 range, prev, Output.EventType.NAVIGATE);
286
287 // This position is not describable.
288 if (!output.hasSpeech) {
289 cvox.ChromeVox.tts.speak('blank', cvox.QueueMode.CATEGORY_FLUSH);
290 cvox.ChromeVox.braille.write(
291 new cvox.NavBraille({text: '', startIndex: 0, endIndex: 0}));
292 } else {
293 output.go();
294 }
295 } 262 }
296 263
297 // Keep the other members in sync for any future editable text base state 264 // Just output the current line.
298 // machine changes. 265 new Output().withString(cur.text).go();
299 this.value = cur.start.node.name || '';
300 this.start = cur.start.index;
301 this.end = cur.start.index;
302 }, 266 },
303 267
304 /** @override */ 268 /** @override */
305 describeSelectionChanged: function(evt) { 269 describeSelectionChanged: function(evt) {
306 // Ignore end of text announcements. 270 // Ignore end of text announcements.
307 if ((this.start + 1) == evt.start && evt.start == this.value.length) 271 if ((this.start + 1) == evt.start && evt.start == this.value.length)
308 return; 272 return;
309 273
310 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( 274 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call(
311 this, evt); 275 this, evt);
312 }, 276 },
313 277
314 /** @override */ 278 /** @override */
315 getLineIndex: function(charIndex) { 279 getLineIndex: function(charIndex) {
316 var breaks = this.getLineBreaks_(); 280 return 0;
317 var index = 0;
318 while (index < breaks.length && breaks[index] <= charIndex)
319 ++index;
320 return index;
321 }, 281 },
322 282
323 /** @override */ 283 /** @override */
324 getLineStart: function(lineIndex) { 284 getLineStart: function(lineIndex) {
325 if (lineIndex == 0) 285 return 0;
326 return 0;
327 var breaks = this.getLineBreaks_();
328 return breaks[lineIndex - 1] ||
329 this.node_.root.focusObject.value.length;
330 }, 286 },
331 287
332 /** @override */ 288 /** @override */
333 getLineEnd: function(lineIndex) { 289 getLineEnd: function(lineIndex) {
334 var breaks = this.getLineBreaks_(); 290 return this.value.length;
335 var value = this.node_.root.focusObject.name;
336 if (lineIndex >= breaks.length)
337 return value.length;
338 return breaks[lineIndex];
339 }, 291 },
340 292
341 /** @override */ 293 /** @override */
342 getLineBreaks_: function() { 294 getLineBreaks_: function() {
343 return this.node_.root.focusObject.lineStartOffsets || []; 295 return [];
344 } 296 }
345 }; 297 };
346 298
347 /** 299 /**
348 * @param {!AutomationNode} node The root editable node, i.e. the root of a 300 * @param {!AutomationNode} node The root editable node, i.e. the root of a
349 * contenteditable subtree or a text field. 301 * contenteditable subtree or a text field.
350 * @return {editing.TextEditHandler} 302 * @return {editing.TextEditHandler}
351 */ 303 */
352 editing.TextEditHandler.createForNode = function(node) { 304 editing.TextEditHandler.createForNode = function(node) {
353 var rootFocusedEditable = null; 305 var rootFocusedEditable = null;
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
389 cvox.BrailleBackground.getInstance().getTranslatorManager().refresh( 341 cvox.BrailleBackground.getInstance().getTranslatorManager().refresh(
390 localStorage['brailleTable']); 342 localStorage['brailleTable']);
391 } 343 }
392 }; 344 };
393 345
394 /** 346 /**
395 * @private {ChromeVoxStateObserver} 347 * @private {ChromeVoxStateObserver}
396 */ 348 */
397 editing.observer_ = new editing.EditingChromeVoxStateObserver(); 349 editing.observer_ = new editing.EditingChromeVoxStateObserver();
398 350
351 /**
352 * An EditableLine encapsulates all data concerning a line in the automation
353 * tree necessary to provide output.
354 * @constructor
355 */
356 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) {
357 /** @private {!Cursor} */
358 this.start_ = new Cursor(startNode, startIndex).deepEquivalent;
359 /** @private {!Cursor} */
360 this.end_ = new Cursor(endNode, endIndex).deepEquivalent;
361
362 // Computed members.
363 /** @private {Spannable} */
364 this.value_;
365 /** @private {AutomationNode} */
366 this.lineStart_;
367 /** @private {AutomationNode} */
368 this.lineEnd_;
369 /** @private {AutomationNode} */
370 this.lineContainer_;
371
372 this.computeLineData_();
373 };
374
375 editing.EditableLine.prototype = {
376 /** @private */
377 computeLineData_: function() {
378 this.value_ = new Spannable(this.start_.node.name, this.start_);
379 if (this.start_.node == this.end_.node)
380 this.value_.setSpan(this.end_, 0, this.start_.node.name.length);
381
382 this.lineStart_ = this.start_.node;
383 this.lineEnd_ = this.lineStart_;
384 this.lineContainer_ = this.lineStart_.parent;
385
386 // Annotate each chunk with its associated node.
387 this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length);
388
389 while (this.lineStart_.previousOnLine) {
390 this.lineStart_ = this.lineStart_.previousOnLine;
391 var oldValue = this.value_;
392 var prepend = new Spannable(this.lineStart_.name, this.lineStart_);
393 prepend.append(this.value_);
394 this.value_ = prepend;
395 }
396
397 while (this.lineEnd_.nextOnLine) {
398 this.lineEnd_ = this.lineEnd_.nextOnLine;
399
400 var annotation = this.lineEnd_;
401 if (this.lineEnd_ == this.end_.node)
402 annotation = this.end_;
403
404 this.value_.append(new Spannable(this.lineEnd_.name, annotation));
405 }
406 },
407
408 /**
409 * Gets the selection offset based on the text content of this line.
410 * @return {number}
411 */
412 get startOffset() {
413 return this.value_.getSpanStart(this.start_) + this.start_.index;
414 },
415
416 /**
417 * Gets the selection offset based on the text content of this line.
418 * @return {number}
419 */
420 get endOffset() {
421 return this.value_.getSpanStart(this.end_) + this.end_.index;
422 },
423
424 /**
425 * Gets the selection offset based on the text content of this line.
426 * @return {number}
427 */
428 get lineStartOffset() {
429 return this.value_.getSpanStart(this.lineStart_);
430 },
431
432 /**
433 * @return {string} The text of this line.
434 */
435 get text() {
436 return this.value_.toString();
437 },
438
439 /**
440 * Returns true if |otherLine| surrounds the same line as |this|. Note that
441 * the contents of the line might be different.
442 */
443 equals: function(otherLine) {
444 // Equality is intentionally loose here as any of the state nodes can be inv alidated at any time.
445 return (otherLine.lineStart_ == this.lineStart_ && otherLine.lineEnd_ == thi s.lineEnd_) ||
446 (otherLine.lineContainer_ == this.lineContainer_ &&
447 otherLine.lineStartOffset == this.lineStartOffset);
448 }
449 };
450
399 }); 451 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698