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 |