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