OLD | NEW |
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. |
36 * @constructor | 36 * @constructor |
37 * @param {!AutomationNode} node | 37 * @param {!AutomationNode} node |
38 */ | 38 */ |
39 editing.TextEditHandler = function(node) { | 39 editing.TextEditHandler = function(node) { |
40 /** @const {!AutomationNode} @private */ | 40 /** @const {!AutomationNode} @private */ |
41 this.node_ = node; | 41 this.node_ = node; |
42 }; | 42 }; |
43 | 43 |
| 44 /** |
| 45 * Flag set to indicate whether ChromeVox uses experimental rich text support. |
| 46 * @type {boolean} |
| 47 */ |
| 48 editing.useRichText = false; |
| 49 |
44 editing.TextEditHandler.prototype = { | 50 editing.TextEditHandler.prototype = { |
45 /** @return {!AutomationNode} */ | 51 /** @return {!AutomationNode} */ |
46 get node() { | 52 get node() { |
47 return this.node_; | 53 return this.node_; |
48 }, | 54 }, |
49 | 55 |
50 /** | 56 /** |
51 * Receives the following kinds of events when the node provided to the | 57 * Receives the following kinds of events when the node provided to the |
52 * constructor is focuse: |focus|, |textChanged|, |textSelectionChanged| and | 58 * constructor is focuse: |focus|, |textChanged|, |textSelectionChanged| and |
53 * |valueChanged|. | 59 * |valueChanged|. |
54 * An implementation of this method should emit the appropritate braille and | 60 * An implementation of this method should emit the appropritate braille and |
55 * spoken feedback for the event. | 61 * spoken feedback for the event. |
56 * @param {!(AutomationEvent|CustomAutomationEvent)} evt | 62 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
57 */ | 63 */ |
58 onEvent: goog.abstractMethod, | 64 onEvent: goog.abstractMethod, |
59 }; | 65 }; |
60 | 66 |
61 /** | 67 /** |
62 * A |TextEditHandler| suitable for text fields. | 68 * A |TextEditHandler| suitable for text fields. |
63 * @constructor | 69 * @constructor |
64 * @param {!AutomationNode} node A node with the role of |textField| | 70 * @param {!AutomationNode} node A node with the role of |textField| |
65 * @extends {editing.TextEditHandler} | 71 * @extends {editing.TextEditHandler} |
66 */ | 72 */ |
67 function TextFieldTextEditHandler(node) { | 73 function TextFieldTextEditHandler(node) { |
68 editing.TextEditHandler.call(this, node); | 74 editing.TextEditHandler.call(this, node); |
69 /** @type {AutomationEditableText} @private */ | 75 |
70 this.editableText_ = new AutomationEditableText(node); | 76 chrome.automation.getDesktop(function(desktop) { |
| 77 var useRichText = editing.useRichText && |
| 78 node.state.richlyEditable; |
| 79 |
| 80 /** @private {!AutomationEditableText} */ |
| 81 this.editableText_ = useRichText ? |
| 82 new AutomationRichEditableText(node) : new AutomationEditableText(node); |
| 83 }.bind(this)); |
71 } | 84 } |
72 | 85 |
73 TextFieldTextEditHandler.prototype = { | 86 TextFieldTextEditHandler.prototype = { |
74 __proto__: editing.TextEditHandler.prototype, | 87 __proto__: editing.TextEditHandler.prototype, |
75 | 88 |
76 /** @override */ | 89 /** @override */ |
77 onEvent: function(evt) { | 90 onEvent: function(evt) { |
78 if (evt.type !== EventType.TEXT_CHANGED && | 91 if (evt.type !== EventType.TEXT_CHANGED && |
79 evt.type !== EventType.TEXT_SELECTION_CHANGED && | 92 evt.type !== EventType.TEXT_SELECTION_CHANGED && |
80 evt.type !== EventType.VALUE_CHANGED && | 93 evt.type !== EventType.VALUE_CHANGED && |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
192 } else { | 205 } else { |
193 range = Range.fromNode(this.node_); | 206 range = Range.fromNode(this.node_); |
194 } | 207 } |
195 output.withBraille(range, null, Output.EventType.NAVIGATE); | 208 output.withBraille(range, null, Output.EventType.NAVIGATE); |
196 if (isFirstLine) | 209 if (isFirstLine) |
197 output.formatForBraille('@tag_textarea'); | 210 output.formatForBraille('@tag_textarea'); |
198 output.go(); | 211 output.go(); |
199 } | 212 } |
200 }; | 213 }; |
201 | 214 |
| 215 |
| 216 /** |
| 217 * A |ChromeVoxEditableTextBase| that implements text editing feedback |
| 218 * for automation tree text fields using anchor and focus selection. |
| 219 * @constructor |
| 220 * @param {!AutomationNode} node |
| 221 * @extends {AutomationEditableText} |
| 222 */ |
| 223 function AutomationRichEditableText(node) { |
| 224 AutomationEditableText.call(this, node); |
| 225 |
| 226 var root = this.node_.root; |
| 227 if (!root || !root.anchorObject || !root.focusObject) |
| 228 return; |
| 229 |
| 230 this.range = new cursors.Range( |
| 231 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), |
| 232 new cursors.Cursor(root.focusObject, root.focusOffset || 0)); |
| 233 } |
| 234 |
| 235 AutomationRichEditableText.prototype = { |
| 236 __proto__: AutomationEditableText.prototype, |
| 237 |
| 238 /** @override */ |
| 239 onUpdate: function() { |
| 240 var root = this.node_.root; |
| 241 if (!root.anchorObject || !root.focusObject) |
| 242 return; |
| 243 |
| 244 var cur = new cursors.Range( |
| 245 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), |
| 246 new cursors.Cursor(root.focusObject, root.focusOffset || 0)); |
| 247 var prev = this.range; |
| 248 |
| 249 this.range = cur; |
| 250 |
| 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( |
| 256 root.anchorObject.name || '', |
| 257 root.anchorOffset || 0, |
| 258 root.focusOffset || 0, |
| 259 true)); |
| 260 |
| 261 var lineIndex = this.getLineIndex(this.start); |
| 262 var brailleLineStart = this.getLineStart(lineIndex); |
| 263 var brailleLineEnd = this.getLineEnd(lineIndex); |
| 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; |
| 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 } |
| 296 |
| 297 // Keep the other members in sync for any future editable text base state |
| 298 // machine changes. |
| 299 this.value = cur.start.node.name || ''; |
| 300 this.start = cur.start.index; |
| 301 this.end = cur.start.index; |
| 302 }, |
| 303 |
| 304 /** @override */ |
| 305 describeSelectionChanged: function(evt) { |
| 306 // Ignore end of text announcements. |
| 307 if ((this.start + 1) == evt.start && evt.start == this.value.length) |
| 308 return; |
| 309 |
| 310 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( |
| 311 this, evt); |
| 312 }, |
| 313 |
| 314 /** @override */ |
| 315 getLineIndex: function(charIndex) { |
| 316 var breaks = this.getLineBreaks_(); |
| 317 var index = 0; |
| 318 while (index < breaks.length && breaks[index] <= charIndex) |
| 319 ++index; |
| 320 return index; |
| 321 }, |
| 322 |
| 323 /** @override */ |
| 324 getLineStart: function(lineIndex) { |
| 325 if (lineIndex == 0) |
| 326 return 0; |
| 327 var breaks = this.getLineBreaks_(); |
| 328 return breaks[lineIndex - 1] || |
| 329 this.node_.root.focusObject.value.length; |
| 330 }, |
| 331 |
| 332 /** @override */ |
| 333 getLineEnd: function(lineIndex) { |
| 334 var breaks = this.getLineBreaks_(); |
| 335 var value = this.node_.root.focusObject.name; |
| 336 if (lineIndex >= breaks.length) |
| 337 return value.length; |
| 338 return breaks[lineIndex]; |
| 339 }, |
| 340 |
| 341 /** @override */ |
| 342 getLineBreaks_: function() { |
| 343 return this.node_.root.focusObject.lineStartOffsets || []; |
| 344 } |
| 345 }; |
| 346 |
202 /** | 347 /** |
203 * @param {!AutomationNode} node The root editable node, i.e. the root of a | 348 * @param {!AutomationNode} node The root editable node, i.e. the root of a |
204 * contenteditable subtree or a text field. | 349 * contenteditable subtree or a text field. |
205 * @return {editing.TextEditHandler} | 350 * @return {editing.TextEditHandler} |
206 */ | 351 */ |
207 editing.TextEditHandler.createForNode = function(node) { | 352 editing.TextEditHandler.createForNode = function(node) { |
208 var rootFocusedEditable = null; | 353 var rootFocusedEditable = null; |
209 var testNode = node; | 354 var testNode = node; |
210 | 355 |
211 do { | 356 do { |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
245 localStorage['brailleTable']); | 390 localStorage['brailleTable']); |
246 } | 391 } |
247 }; | 392 }; |
248 | 393 |
249 /** | 394 /** |
250 * @private {ChromeVoxStateObserver} | 395 * @private {ChromeVoxStateObserver} |
251 */ | 396 */ |
252 editing.observer_ = new editing.EditingChromeVoxStateObserver(); | 397 editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
253 | 398 |
254 }); | 399 }); |
OLD | NEW |