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 Handles automation from a desktop automation node. | 6 * @fileoverview Handles automation from a desktop automation node. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 goog.provide('DesktopAutomationHandler'); | 9 goog.provide('DesktopAutomationHandler'); |
| 10 | 10 |
| 11 goog.require('AutomationObjectInstaller'); | |
| 11 goog.require('BaseAutomationHandler'); | 12 goog.require('BaseAutomationHandler'); |
| 12 goog.require('ChromeVoxState'); | 13 goog.require('ChromeVoxState'); |
| 13 goog.require('editing.TextEditHandler'); | 14 goog.require('editing.TextEditHandler'); |
| 14 | 15 |
| 15 goog.scope(function() { | 16 goog.scope(function() { |
| 16 var AutomationEvent = chrome.automation.AutomationEvent; | 17 var AutomationEvent = chrome.automation.AutomationEvent; |
| 17 var AutomationNode = chrome.automation.AutomationNode; | 18 var AutomationNode = chrome.automation.AutomationNode; |
| 18 var Dir = constants.Dir; | 19 var Dir = constants.Dir; |
| 20 var EventType = chrome.automation.EventType; | |
| 19 var RoleType = chrome.automation.RoleType; | 21 var RoleType = chrome.automation.RoleType; |
| 20 | 22 |
| 21 /** | 23 /** |
| 22 * @param {!AutomationNode} node | 24 * @param {!AutomationNode} node |
| 23 * @constructor | 25 * @constructor |
| 24 * @extends {BaseAutomationHandler} | 26 * @extends {BaseAutomationHandler} |
| 25 */ | 27 */ |
| 26 DesktopAutomationHandler = function(node) { | 28 DesktopAutomationHandler = function(node) { |
| 27 BaseAutomationHandler.call(this, node); | 29 BaseAutomationHandler.call(this, node); |
| 28 | 30 |
| 29 /** | 31 /** |
| 30 * The object that speaks changes to an editable text field. | 32 * The object that speaks changes to an editable text field. |
| 31 * @type {editing.TextEditHandler} | 33 * @type {editing.TextEditHandler} |
| 32 */ | 34 */ |
| 33 this.textEditHandler_ = null; | 35 this.textEditHandler_ = null; |
| 34 | 36 |
| 35 /** | 37 /** |
| 36 * The last time we handled a value changed event. | 38 * The last time we handled a value changed event. |
| 37 * @type {!Date} | 39 * @type {!Date} |
| 38 * @private | 40 * @private |
| 39 */ | 41 */ |
| 40 this.lastValueChanged_ = new Date(0); | 42 this.lastValueChanged_ = new Date(0); |
| 41 | 43 |
| 44 var e = EventType; | |
| 45 this.addListener_(e.activedescendantchanged, this.onActiveDescendantChanged); | |
| 46 this.addListener_(e.alert, this.onAlert); | |
| 47 this.addListener_(e.ariaAttributeChanged, this.onEventIfInRange); | |
| 48 this.addListener_(e.checkedStateChanged, this.onEventIfInRange); | |
| 49 this.addListener_(e.focus, this.onFocus); | |
| 50 this.addListener_(e.hover, this.onEventWithFlushedOutput); | |
| 51 this.addListener_(e.loadComplete, this.onLoadComplete); | |
| 52 this.addListener_(e.menuEnd, this.onMenuEnd); | |
| 53 this.addListener_(e.menuListItemSelected, this.onEventDefault); | |
| 54 this.addListener_(e.menuStart, this.onMenuStart); | |
| 55 this.addListener_(e.scrollPositionChanged, this.onScrollPositionChanged); | |
| 56 this.addListener_(e.selection, this.onEventWithFlushedOutput); | |
| 57 this.addListener_(e.textChanged, this.onTextChanged); | |
| 58 this.addListener_(e.textSelectionChanged, this.onTextSelectionChanged); | |
| 59 this.addListener_(e.valueChanged, this.onValueChanged); | |
| 60 | |
| 42 // The focused state gets set on the containing webView node. | 61 // The focused state gets set on the containing webView node. |
| 43 var webView = node.find({role: RoleType.webView, state: {focused: true}}); | 62 var webView = node.find({role: RoleType.webView, state: {focused: true}}); |
| 44 if (webView) { | 63 if (webView) { |
| 45 var root = webView.find({role: RoleType.rootWebArea}); | 64 var root = webView.find({role: RoleType.rootWebArea}); |
| 46 if (root) { | 65 if (root) { |
| 47 this.onLoadComplete( | 66 this.onLoadComplete( |
| 48 {target: root, | 67 new chrome.automation.AutomationEvent(EventType.loadComplete, root)); |
|
dmazzoni
2016/05/02 22:15:48
Is there a possible race condition if the chrome.a
David Tseng
2016/05/03 20:18:27
Went with a callback as suggested below.
| |
| 49 type: chrome.automation.EventType.loadComplete}); | |
| 50 } | 68 } |
| 51 } | 69 } |
| 52 | 70 |
| 53 chrome.automation.getFocus((function(focus) { | 71 chrome.automation.getFocus((function(focus) { |
| 54 if (focus) | 72 if (focus) |
| 55 this.onFocus({target: focus, type: 'focus'}); | 73 this.onFocus(new chrome.automation.AutomationEvent(EventType.focus, focus)); |
|
dmazzoni
2016/05/02 22:15:48
nit: indentation
David Tseng
2016/05/03 20:18:27
Done.
| |
| 56 }).bind(this)); | 74 }).bind(this)); |
| 75 | |
| 76 AutomationObjectInstaller.init(node); | |
|
dmazzoni
2016/05/02 22:15:48
Perhaps this init should just take a callback argu
David Tseng
2016/05/03 20:18:27
Done.
| |
| 57 }; | 77 }; |
| 58 | 78 |
| 59 /** | 79 /** |
| 60 * Time to wait until processing more value changed events. | 80 * Time to wait until processing more value changed events. |
| 61 * @const {number} | 81 * @const {number} |
| 62 */ | 82 */ |
| 63 DesktopAutomationHandler.VMIN_VALUE_CHANGE_DELAY_MS = 500; | 83 DesktopAutomationHandler.VMIN_VALUE_CHANGE_DELAY_MS = 500; |
| 64 | 84 |
| 65 DesktopAutomationHandler.prototype = { | 85 DesktopAutomationHandler.prototype = { |
| 66 __proto__: BaseAutomationHandler.prototype, | 86 __proto__: BaseAutomationHandler.prototype, |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 111 output.withBraille( | 131 output.withBraille( |
| 112 ChromeVoxState.instance.currentRange, prevRange, evt.type); | 132 ChromeVoxState.instance.currentRange, prevRange, evt.type); |
| 113 } else { | 133 } else { |
| 114 // Delegate event handling to the text edit handler for braille. | 134 // Delegate event handling to the text edit handler for braille. |
| 115 this.textEditHandler_.onEvent(evt); | 135 this.textEditHandler_.onEvent(evt); |
| 116 } | 136 } |
| 117 output.go(); | 137 output.go(); |
| 118 }, | 138 }, |
| 119 | 139 |
| 120 /** | 140 /** |
| 141 * @param {!AutomationEvent} evt | |
| 142 */ | |
| 143 onEventIfInRange: function(evt) { | |
| 144 // TODO(dtseng): Consider the end of the current range as well. | |
| 145 if (AutomationUtil.isDescendantOf( | |
| 146 global.backgroundObj.currentRange.start.node, evt.target) || | |
| 147 evt.target.state.focused) | |
| 148 this.onEventDefault(evt); | |
| 149 }, | |
| 150 | |
| 151 /** | |
| 152 * @param {!AutomationEvent} evt | |
| 153 */ | |
| 154 onEventWithFlushedOutput: function(evt) { | |
| 155 Output.flushNextSpeechUtterance(); | |
| 156 this.onEventDefault(evt); | |
| 157 }, | |
| 158 | |
| 159 /** | |
| 121 * Makes an announcement without changing focus. | 160 * Makes an announcement without changing focus. |
| 122 * @param {Object} evt | 161 * @param {!AutomationEvent} evt |
| 162 */ | |
| 163 onActiveDescendantChanged: function(evt) { | |
| 164 if (!evt.target.activeDescendant) | |
| 165 return; | |
| 166 this.onEventDefault(new chrome.automation.AutomationEvent( | |
| 167 EventType.focus, evt.target.activeDescendant)); | |
| 168 }, | |
| 169 | |
| 170 /** | |
| 171 * Makes an announcement without changing focus. | |
| 172 * @param {!AutomationEvent} evt | |
| 123 */ | 173 */ |
| 124 onAlert: function(evt) { | 174 onAlert: function(evt) { |
| 125 var node = evt.target; | 175 var node = evt.target; |
| 126 if (!node) | 176 if (!node) |
| 127 return; | 177 return; |
| 128 | 178 |
| 129 // Don't process nodes inside of web content if ChromeVox Next is inactive. | 179 // Don't process nodes inside of web content if ChromeVox Next is inactive. |
| 130 if (node.root.role != RoleType.desktop && | 180 if (node.root.role != RoleType.desktop && |
| 131 ChromeVoxState.instance.mode === ChromeVoxMode.CLASSIC) { | 181 ChromeVoxState.instance.mode === ChromeVoxMode.CLASSIC) { |
| 132 return; | 182 return; |
| 133 } | 183 } |
| 134 | 184 |
| 135 var range = cursors.Range.fromNode(node); | 185 var range = cursors.Range.fromNode(node); |
| 136 | 186 |
| 137 new Output().withSpeechAndBraille(range, null, evt.type).go(); | 187 new Output().withSpeechAndBraille(range, null, evt.type).go(); |
| 138 }, | 188 }, |
| 139 | 189 |
| 140 /** | 190 /** |
| 141 * Provides all feedback once a focus event fires. | 191 * Provides all feedback once a focus event fires. |
| 142 * @param {Object} evt | 192 * @param {!AutomationEvent} evt |
| 143 */ | 193 */ |
| 144 onFocus: function(evt) { | 194 onFocus: function(evt) { |
| 145 // Invalidate any previous editable text handler state. | 195 // Invalidate any previous editable text handler state. |
| 146 this.textEditHandler_ = null; | 196 this.textEditHandler_ = null; |
| 147 | 197 |
| 148 var node = evt.target; | 198 var node = evt.target; |
| 149 | 199 |
| 150 // Discard focus events on embeddedObject and client nodes. | 200 // Discard focus events on embeddedObject and client nodes. |
| 151 if (node.role == RoleType.embeddedObject || node.role == RoleType.client) | 201 if (node.role == RoleType.embeddedObject || node.role == RoleType.client) |
| 152 return; | 202 return; |
| 153 | 203 |
| 154 this.createTextEditHandlerIfNeeded_(evt.target); | 204 this.createTextEditHandlerIfNeeded_(evt.target); |
| 155 | 205 |
| 156 // Since we queue output mostly for live regions support and there isn't a | 206 // Since we queue output mostly for live regions support and there isn't a |
| 157 // reliable way to know if this focus event resulted from a user's explicit | 207 // reliable way to know if this focus event resulted from a user's explicit |
| 158 // action, only flush when the focused node is not web content. | 208 // action, only flush when the focused node is not web content. |
| 159 if (node.root.role == RoleType.desktop) | 209 if (node.root.role == RoleType.desktop) |
| 160 Output.flushNextSpeechUtterance(); | 210 Output.flushNextSpeechUtterance(); |
| 161 | 211 |
| 162 this.onEventDefault( | 212 this.onEventDefault( |
| 163 /** @type {!AutomationEvent} */ | 213 new chrome.automation.AutomationEvent(EventType.focus, node)); |
| 164 ({target: node, type: chrome.automation.EventType.focus})); | |
| 165 }, | 214 }, |
| 166 | 215 |
| 167 /** | 216 /** |
| 168 * Provides all feedback once a load complete event fires. | 217 * Provides all feedback once a load complete event fires. |
| 169 * @param {Object} evt | 218 * @param {!AutomationEvent} evt |
| 170 */ | 219 */ |
| 171 onLoadComplete: function(evt) { | 220 onLoadComplete: function(evt) { |
| 172 ChromeVoxState.instance.refreshMode(evt.target.docUrl); | 221 ChromeVoxState.instance.refreshMode(evt.target.docUrl); |
| 173 | 222 |
| 174 // Don't process nodes inside of web content if ChromeVox Next is inactive. | 223 // Don't process nodes inside of web content if ChromeVox Next is inactive. |
| 175 if (evt.target.root.role != RoleType.desktop && | 224 if (evt.target.root.role != RoleType.desktop && |
| 176 ChromeVoxState.instance.mode === ChromeVoxMode.CLASSIC) | 225 ChromeVoxState.instance.mode === ChromeVoxMode.CLASSIC) |
| 177 return; | 226 return; |
| 178 | 227 |
| 179 chrome.automation.getFocus((function(focus) { | 228 chrome.automation.getFocus((function(focus) { |
| 180 if (!focus) | 229 if (!focus) |
| 181 return; | 230 return; |
| 182 | 231 |
| 183 // If initial focus was already placed on this page (e.g. if a user starts | 232 // If initial focus was already placed on this page (e.g. if a user starts |
| 184 // tabbing before load complete), then don't move ChromeVox's position on | 233 // tabbing before load complete), then don't move ChromeVox's position on |
| 185 // the page. | 234 // the page. |
| 186 if (ChromeVoxState.instance.currentRange && | 235 if (ChromeVoxState.instance.currentRange && |
| 187 ChromeVoxState.instance.currentRange.start.node.root == focus.root) | 236 ChromeVoxState.instance.currentRange.start.node.root == focus.root) |
| 188 return; | 237 return; |
| 189 | 238 |
| 190 ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(focus)); | 239 ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(focus)); |
| 191 new Output().withRichSpeechAndBraille( | 240 new Output().withRichSpeechAndBraille( |
| 192 ChromeVoxState.instance.currentRange, null, evt.type).go(); | 241 ChromeVoxState.instance.currentRange, null, evt.type).go(); |
| 193 }).bind(this)); | 242 }).bind(this)); |
| 194 }, | 243 }, |
| 195 | 244 |
| 196 /** @override */ | 245 |
| 246 /** | |
| 247 * Provides all feedback once a text changed event fires. | |
| 248 * @param {!AutomationEvent} evt | |
| 249 */ | |
| 197 onTextChanged: function(evt) { | 250 onTextChanged: function(evt) { |
| 198 if (evt.target.state.editable) | 251 if (evt.target.state.editable) |
| 199 this.onEditableChanged_(evt); | 252 this.onEditableChanged_(evt); |
| 200 }, | 253 }, |
| 201 | 254 |
| 202 /** @override */ | 255 /** |
| 256 * Provides all feedback once a text selection changed event fires. | |
| 257 * @param {!AutomationEvent} evt | |
| 258 */ | |
| 203 onTextSelectionChanged: function(evt) { | 259 onTextSelectionChanged: function(evt) { |
| 204 if (evt.target.state.editable) | 260 if (evt.target.state.editable) |
| 205 this.onEditableChanged_(evt); | 261 this.onEditableChanged_(evt); |
| 206 }, | 262 }, |
| 207 | 263 |
| 208 /** | 264 /** |
| 209 * Provides all feedback once a change event in a text field fires. | 265 * Provides all feedback once a change event in a text field fires. |
| 210 * @param {!AutomationEvent} evt | 266 * @param {!AutomationEvent} evt |
| 211 * @private | 267 * @private |
| 212 */ | 268 */ |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 259 | 315 |
| 260 this.lastValueChanged_ = new Date(); | 316 this.lastValueChanged_ = new Date(); |
| 261 | 317 |
| 262 new Output().format('$value', evt.target) | 318 new Output().format('$value', evt.target) |
| 263 .go(); | 319 .go(); |
| 264 } | 320 } |
| 265 }, | 321 }, |
| 266 | 322 |
| 267 /** | 323 /** |
| 268 * Handle updating the active indicator when the document scrolls. | 324 * Handle updating the active indicator when the document scrolls. |
| 269 * @override | 325 * @param {!AutomationEvent} evt |
| 270 */ | 326 */ |
| 271 onScrollPositionChanged: function(evt) { | 327 onScrollPositionChanged: function(evt) { |
| 272 if (ChromeVoxState.instance.mode === ChromeVoxMode.CLASSIC) | 328 if (ChromeVoxState.instance.mode === ChromeVoxMode.CLASSIC) |
| 273 return; | 329 return; |
| 274 | 330 |
| 275 var currentRange = ChromeVoxState.instance.currentRange; | 331 var currentRange = ChromeVoxState.instance.currentRange; |
| 276 if (currentRange) | 332 if (currentRange) |
| 277 new Output().withLocation(currentRange, null, evt.type).go(); | 333 new Output().withLocation(currentRange, null, evt.type).go(); |
| 278 }, | 334 }, |
| 279 | 335 |
| 280 /** @override */ | 336 /** |
| 337 * Provides all feedback once a menu start event fires. | |
| 338 * @param {!AutomationEvent} evt | |
| 339 */ | |
| 281 onMenuStart: function(evt) { | 340 onMenuStart: function(evt) { |
| 282 global.backgroundObj.startExcursion(); | 341 global.backgroundObj.startExcursion(); |
| 283 this.onEventDefault(evt); | 342 this.onEventDefault(evt); |
| 284 }, | 343 }, |
| 285 | 344 |
| 286 /** @override */ | 345 /** |
| 346 * Provides all feedback once a menu end event fires. | |
| 347 * @param {!AutomationEvent} evt | |
| 348 */ | |
| 287 onMenuEnd: function(evt) { | 349 onMenuEnd: function(evt) { |
| 288 this.onEventDefault(evt); | 350 this.onEventDefault(evt); |
| 289 global.backgroundObj.endExcursion(); | 351 global.backgroundObj.endExcursion(); |
| 290 }, | 352 }, |
| 291 | 353 |
| 292 /** | 354 /** |
| 293 * Create an editable text handler for the given node if needed. | 355 * Create an editable text handler for the given node if needed. |
| 294 * @param {!AutomationNode} node | 356 * @param {!AutomationNode} node |
| 295 */ | 357 */ |
| 296 createTextEditHandlerIfNeeded_: function(node) { | 358 createTextEditHandlerIfNeeded_: function(node) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 309 if (cvox.ChromeVox.isMac) | 371 if (cvox.ChromeVox.isMac) |
| 310 return; | 372 return; |
| 311 chrome.automation.getDesktop(function(desktop) { | 373 chrome.automation.getDesktop(function(desktop) { |
| 312 global.desktopAutomationHandler = new DesktopAutomationHandler(desktop); | 374 global.desktopAutomationHandler = new DesktopAutomationHandler(desktop); |
| 313 }); | 375 }); |
| 314 }; | 376 }; |
| 315 | 377 |
| 316 DesktopAutomationHandler.init_(); | 378 DesktopAutomationHandler.init_(); |
| 317 | 379 |
| 318 }); // goog.scope | 380 }); // goog.scope |
| OLD | NEW |