Chromium Code Reviews| 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. |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 }); |
| OLD | NEW |