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'); | |
12 goog.require('BaseAutomationHandler'); | 11 goog.require('BaseAutomationHandler'); |
13 goog.require('ChromeVoxState'); | 12 goog.require('ChromeVoxState'); |
| 13 goog.require('CustomAutomationEvent'); |
14 goog.require('Stubs'); | 14 goog.require('Stubs'); |
15 goog.require('editing.TextEditHandler'); | 15 goog.require('editing.TextEditHandler'); |
16 | 16 |
17 goog.scope(function() { | 17 goog.scope(function() { |
18 var AutomationEvent = chrome.automation.AutomationEvent; | 18 var AutomationEvent = chrome.automation.AutomationEvent; |
19 var AutomationNode = chrome.automation.AutomationNode; | 19 var AutomationNode = chrome.automation.AutomationNode; |
20 var Dir = constants.Dir; | 20 var Dir = constants.Dir; |
21 var EventType = chrome.automation.EventType; | 21 var EventType = chrome.automation.EventType; |
22 var RoleType = chrome.automation.RoleType; | 22 var RoleType = chrome.automation.RoleType; |
23 | 23 |
(...skipping 12 matching lines...) Expand all Loading... |
36 */ | 36 */ |
37 this.textEditHandler_ = null; | 37 this.textEditHandler_ = null; |
38 | 38 |
39 /** | 39 /** |
40 * The last time we handled a value changed event. | 40 * The last time we handled a value changed event. |
41 * @type {!Date} | 41 * @type {!Date} |
42 * @private | 42 * @private |
43 */ | 43 */ |
44 this.lastValueChanged_ = new Date(0); | 44 this.lastValueChanged_ = new Date(0); |
45 | 45 |
46 var e = EventType; | 46 this.addListener_(EventType.ACTIVEDESCENDANTCHANGED, |
47 this.addListener_(e.activedescendantchanged, this.onActiveDescendantChanged); | 47 this.onActiveDescendantChanged); |
48 this.addListener_(e.alert, this.onAlert); | 48 this.addListener_(EventType.ALERT, |
49 this.addListener_(e.ariaAttributeChanged, this.onAriaAttributeChanged); | 49 this.onAlert); |
50 this.addListener_(e.autocorrectionOccured, this.onEventIfInRange); | 50 this.addListener_(EventType.ARIA_ATTRIBUTE_CHANGED, |
51 this.addListener_(e.checkedStateChanged, this.onCheckedStateChanged); | 51 this.onAriaAttributeChanged); |
52 this.addListener_(e.childrenChanged, this.onActiveDescendantChanged); | 52 this.addListener_(EventType.AUTOCORRECTION_OCCURED, |
53 this.addListener_(e.expandedChanged, this.onEventIfInRange); | 53 this.onEventIfInRange); |
54 this.addListener_(e.focus, this.onFocus); | 54 this.addListener_(EventType.CHECKED_STATE_CHANGED, |
55 this.addListener_(e.hover, this.onHover); | 55 this.onCheckedStateChanged); |
56 this.addListener_(e.invalidStatusChanged, this.onEventIfInRange); | 56 this.addListener_(EventType.CHILDREN_CHANGED, |
57 this.addListener_(e.loadComplete, this.onLoadComplete); | 57 this.onActiveDescendantChanged); |
58 this.addListener_(e.menuEnd, this.onMenuEnd); | 58 this.addListener_(EventType.EXPANDED_CHANGED, |
59 this.addListener_(e.menuListItemSelected, this.onEventIfSelected); | 59 this.onEventIfInRange); |
60 this.addListener_(e.menuStart, this.onMenuStart); | 60 this.addListener_(EventType.FOCUS, |
61 this.addListener_(e.rowCollapsed, this.onEventIfInRange); | 61 this.onFocus); |
62 this.addListener_(e.rowExpanded, this.onEventIfInRange); | 62 this.addListener_(EventType.HOVER, |
63 this.addListener_(e.scrollPositionChanged, this.onScrollPositionChanged); | 63 this.onHover); |
64 this.addListener_(e.selection, this.onSelection); | 64 this.addListener_(EventType.INVALID_STATUS_CHANGED, |
65 this.addListener_(e.textChanged, this.onTextChanged); | 65 this.onEventIfInRange); |
66 this.addListener_(e.textSelectionChanged, this.onTextSelectionChanged); | 66 this.addListener_(EventType.LOAD_COMPLETE, |
67 this.addListener_(e.valueChanged, this.onValueChanged); | 67 this.onLoadComplete); |
| 68 this.addListener_(EventType.MENU_END, |
| 69 this.onMenuEnd); |
| 70 this.addListener_(EventType.MENU_LIST_ITEM_SELECTED, |
| 71 this.onEventIfSelected); |
| 72 this.addListener_(EventType.MENU_START, |
| 73 this.onMenuStart); |
| 74 this.addListener_(EventType.ROW_COLLAPSED, |
| 75 this.onEventIfInRange); |
| 76 this.addListener_(EventType.ROW_EXPANDED, |
| 77 this.onEventIfInRange); |
| 78 this.addListener_(EventType.SCROLL_POSITION_CHANGED, |
| 79 this.onScrollPositionChanged); |
| 80 this.addListener_(EventType.SELECTION, |
| 81 this.onSelection); |
| 82 this.addListener_(EventType.TEXT_CHANGED, |
| 83 this.onTextChanged); |
| 84 this.addListener_(EventType.TEXT_SELECTION_CHANGED, |
| 85 this.onTextSelectionChanged); |
| 86 this.addListener_(EventType.VALUE_CHANGED, |
| 87 this.onValueChanged); |
68 | 88 |
69 AutomationObjectConstructorInstaller.init(node, function() { | 89 chrome.automation.getFocus((function(focus) { |
70 chrome.automation.getFocus((function(focus) { | 90 if (ChromeVoxState.instance.mode != ChromeVoxMode.FORCE_NEXT) |
71 if (ChromeVoxState.instance.mode != ChromeVoxMode.FORCE_NEXT) | 91 return; |
72 return; | |
73 | 92 |
74 if (focus) { | 93 if (focus) { |
75 this.onFocus( | 94 var event = new CustomAutomationEvent(EventType.FOCUS, focus, 'page'); |
76 new chrome.automation.AutomationEvent( | 95 this.onFocus(event); |
77 EventType.focus, focus, 'page')); | 96 } |
78 } | 97 }).bind(this)); |
79 }).bind(this)); | |
80 }.bind(this)); | |
81 }; | 98 }; |
82 | 99 |
83 /** | 100 /** |
84 * Time to wait until processing more value changed events. | 101 * Time to wait until processing more value changed events. |
85 * @const {number} | 102 * @const {number} |
86 */ | 103 */ |
87 DesktopAutomationHandler.VMIN_VALUE_CHANGE_DELAY_MS = 500; | 104 DesktopAutomationHandler.VMIN_VALUE_CHANGE_DELAY_MS = 500; |
88 | 105 |
89 /** | 106 /** |
90 * Controls announcement of non-user-initiated events. | 107 * Controls announcement of non-user-initiated events. |
91 * @type {boolean} | 108 * @type {boolean} |
92 */ | 109 */ |
93 DesktopAutomationHandler.announceActions = false; | 110 DesktopAutomationHandler.announceActions = false; |
94 | 111 |
95 DesktopAutomationHandler.prototype = { | 112 DesktopAutomationHandler.prototype = { |
96 __proto__: BaseAutomationHandler.prototype, | 113 __proto__: BaseAutomationHandler.prototype, |
97 | 114 |
98 /** @override */ | 115 /** @override */ |
99 willHandleEvent_: function(evt) { | 116 willHandleEvent_: function(evt) { |
100 return !cvox.ChromeVox.isActive; | 117 return !cvox.ChromeVox.isActive; |
101 }, | 118 }, |
102 | 119 |
103 /** | 120 /** |
104 * Provides all feedback once ChromeVox's focus changes. | 121 * Provides all feedback once ChromeVox's focus changes. |
105 * @param {!AutomationEvent} evt | 122 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
106 */ | 123 */ |
107 onEventDefault: function(evt) { | 124 onEventDefault: function(evt) { |
108 var node = evt.target; | 125 var node = evt.target; |
109 if (!node) | 126 if (!node) |
110 return; | 127 return; |
111 | 128 |
112 // Decide whether to announce and sync this event. | 129 // Decide whether to announce and sync this event. |
113 if (!DesktopAutomationHandler.announceActions && evt.eventFrom == 'action') | 130 if (!DesktopAutomationHandler.announceActions && evt.eventFrom == 'action') |
114 return; | 131 return; |
115 | 132 |
(...skipping 17 matching lines...) Expand all Loading... |
133 output.withBraille( | 150 output.withBraille( |
134 ChromeVoxState.instance.currentRange, prevRange, evt.type); | 151 ChromeVoxState.instance.currentRange, prevRange, evt.type); |
135 } else { | 152 } else { |
136 // Delegate event handling to the text edit handler for braille. | 153 // Delegate event handling to the text edit handler for braille. |
137 this.textEditHandler_.onEvent(evt); | 154 this.textEditHandler_.onEvent(evt); |
138 } | 155 } |
139 output.go(); | 156 output.go(); |
140 }, | 157 }, |
141 | 158 |
142 /** | 159 /** |
143 * @param {!AutomationEvent} evt | 160 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
144 */ | 161 */ |
145 onEventIfInRange: function(evt) { | 162 onEventIfInRange: function(evt) { |
146 if (!this.shouldOutput_(evt)) | 163 if (!this.shouldOutput_(evt)) |
147 return; | 164 return; |
148 | 165 |
149 var prev = ChromeVoxState.instance.currentRange; | 166 var prev = ChromeVoxState.instance.currentRange; |
150 if (prev.contentEquals(cursors.Range.fromNode(evt.target)) || | 167 if (prev.contentEquals(cursors.Range.fromNode(evt.target)) || |
151 evt.target.state.focused) { | 168 evt.target.state.focused) { |
152 // Category flush here since previous focus events via navigation can | 169 // Category flush here since previous focus events via navigation can |
153 // cause double speak. | 170 // cause double speak. |
154 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH); | 171 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH); |
155 | 172 |
156 // Intentionally skip setting range. | 173 // Intentionally skip setting range. |
157 new Output() | 174 new Output() |
158 .withRichSpeechAndBraille(cursors.Range.fromNode(evt.target), | 175 .withRichSpeechAndBraille(cursors.Range.fromNode(evt.target), |
159 prev, | 176 prev, |
160 Output.EventType.NAVIGATE) | 177 Output.EventType.NAVIGATE) |
161 .go(); | 178 .go(); |
162 } | 179 } |
163 }, | 180 }, |
164 | 181 |
165 /** | 182 /** |
166 * @param {!AutomationEvent} evt | 183 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
167 */ | 184 */ |
168 onEventIfSelected: function(evt) { | 185 onEventIfSelected: function(evt) { |
169 if (evt.target.state.selected) | 186 if (evt.target.state.selected) |
170 this.onEventDefault(evt); | 187 this.onEventDefault(evt); |
171 }, | 188 }, |
172 | 189 |
173 /** | 190 /** |
174 * @param {!AutomationEvent} evt | 191 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
175 */ | 192 */ |
176 onEventWithFlushedOutput: function(evt) { | 193 onEventWithFlushedOutput: function(evt) { |
177 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); | 194 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); |
178 this.onEventDefault(evt); | 195 this.onEventDefault(evt); |
179 }, | 196 }, |
180 | 197 |
181 /** | 198 /** |
182 * @param {!AutomationEvent} evt | 199 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
183 */ | 200 */ |
184 onAriaAttributeChanged: function(evt) { | 201 onAriaAttributeChanged: function(evt) { |
185 if (evt.target.state.editable) | 202 if (evt.target.state.editable) |
186 return; | 203 return; |
187 this.onEventIfInRange(evt); | 204 this.onEventIfInRange(evt); |
188 }, | 205 }, |
189 | 206 |
190 /** | 207 /** |
191 * @param {!AutomationEvent} evt | 208 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
192 */ | 209 */ |
193 onHover: function(evt) { | 210 onHover: function(evt) { |
194 if (ChromeVoxState.instance.currentRange && | 211 if (ChromeVoxState.instance.currentRange && |
195 evt.target == ChromeVoxState.instance.currentRange.start.node) | 212 evt.target == ChromeVoxState.instance.currentRange.start.node) |
196 return; | 213 return; |
197 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); | 214 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); |
198 this.onEventDefault(evt); | 215 this.onEventDefault(evt); |
199 }, | 216 }, |
200 | 217 |
201 /** | 218 /** |
202 * Makes an announcement without changing focus. | 219 * Makes an announcement without changing focus. |
203 * @param {!AutomationEvent} evt | 220 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
204 */ | 221 */ |
205 onActiveDescendantChanged: function(evt) { | 222 onActiveDescendantChanged: function(evt) { |
206 if (!evt.target.activeDescendant || !evt.target.state.focused) | 223 if (!evt.target.activeDescendant || !evt.target.state.focused) |
207 return; | 224 return; |
208 this.onEventDefault(new chrome.automation.AutomationEvent( | 225 var event = new CustomAutomationEvent( |
209 EventType.focus, evt.target.activeDescendant, evt.eventFrom)); | 226 EventType.FOCUS, evt.target.activeDescendant, evt.eventFrom); |
| 227 this.onEventDefault(event); |
210 }, | 228 }, |
211 | 229 |
212 /** | 230 /** |
213 * Makes an announcement without changing focus. | 231 * Makes an announcement without changing focus. |
214 * @param {!AutomationEvent} evt | 232 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
215 */ | 233 */ |
216 onAlert: function(evt) { | 234 onAlert: function(evt) { |
217 var node = evt.target; | 235 var node = evt.target; |
218 if (!node || !this.shouldOutput_(evt)) | 236 if (!node || !this.shouldOutput_(evt)) |
219 return; | 237 return; |
220 | 238 |
221 var range = cursors.Range.fromNode(node); | 239 var range = cursors.Range.fromNode(node); |
222 | 240 |
223 new Output().withSpeechAndBraille(range, null, evt.type).go(); | 241 new Output().withSpeechAndBraille(range, null, evt.type).go(); |
224 }, | 242 }, |
225 | 243 |
226 /** | 244 /** |
227 * Provides all feedback once a checked state changed event fires. | 245 * Provides all feedback once a checked state changed event fires. |
228 * @param {!AutomationEvent} evt | 246 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
229 */ | 247 */ |
230 onCheckedStateChanged: function(evt) { | 248 onCheckedStateChanged: function(evt) { |
231 if (!AutomationPredicate.checkable(evt.target)) | 249 if (!AutomationPredicate.checkable(evt.target)) |
232 return; | 250 return; |
233 | 251 |
234 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH); | 252 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH); |
235 this.onEventIfInRange( | 253 var event = new CustomAutomationEvent( |
236 new chrome.automation.AutomationEvent( | 254 EventType.CHECKED_STATE_CHANGED, evt.target, evt.eventFrom); |
237 EventType.checkedStateChanged, evt.target, evt.eventFrom)); | 255 this.onEventIfInRange(event); |
238 }, | 256 }, |
239 | 257 |
240 /** | 258 /** |
241 * Provides all feedback once a focus event fires. | 259 * Provides all feedback once a focus event fires. |
242 * @param {!AutomationEvent} evt | 260 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
243 */ | 261 */ |
244 onFocus: function(evt) { | 262 onFocus: function(evt) { |
245 // Invalidate any previous editable text handler state. | 263 // Invalidate any previous editable text handler state. |
246 this.textEditHandler_ = null; | 264 this.textEditHandler_ = null; |
247 | 265 |
248 var node = evt.target; | 266 var node = evt.target; |
249 | 267 |
250 // Discard focus events on embeddedObject. | 268 // Discard focus events on embeddedObject. |
251 if (node.role == RoleType.embeddedObject) | 269 if (node.role == RoleType.EMBEDDED_OBJECT) |
252 return; | 270 return; |
253 | 271 |
254 this.createTextEditHandlerIfNeeded_(evt.target); | 272 this.createTextEditHandlerIfNeeded_(evt.target); |
255 | 273 |
256 // Category flush speech triggered by events with no source. This includes | 274 // Category flush speech triggered by events with no source. This includes |
257 // views. | 275 // views. |
258 if (evt.eventFrom == '') | 276 if (evt.eventFrom == '') |
259 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH); | 277 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH); |
260 if (!node.root) | 278 if (!node.root) |
261 return; | 279 return; |
262 | 280 |
263 var root = AutomationUtil.getTopLevelRoot(node.root); | 281 var root = AutomationUtil.getTopLevelRoot(node.root); |
264 | |
265 // If we're crossing out of a root, save it in case it needs recovering. | 282 // If we're crossing out of a root, save it in case it needs recovering. |
266 var prevRange = ChromeVoxState.instance.currentRange; | 283 var prevRange = ChromeVoxState.instance.currentRange; |
267 var prevNode = prevRange ? prevRange.start.node : null; | 284 var prevNode = prevRange ? prevRange.start.node : null; |
268 if (prevNode) { | 285 if (prevNode) { |
269 var prevRoot = AutomationUtil.getTopLevelRoot(prevNode); | 286 var prevRoot = AutomationUtil.getTopLevelRoot(prevNode); |
270 if (prevRoot && prevRoot !== root) | 287 if (prevRoot && prevRoot !== root) |
271 ChromeVoxState.instance.focusRecoveryMap.set(prevRoot, prevRange); | 288 ChromeVoxState.instance.focusRecoveryMap.set(prevRoot, prevRange); |
272 } | 289 } |
273 | |
274 // If a previous node was saved for this focus, restore it. | 290 // If a previous node was saved for this focus, restore it. |
275 var savedRange = ChromeVoxState.instance.focusRecoveryMap.get(root); | 291 var savedRange = ChromeVoxState.instance.focusRecoveryMap.get(root); |
276 ChromeVoxState.instance.focusRecoveryMap.delete(root); | 292 ChromeVoxState.instance.focusRecoveryMap.delete(root); |
277 if (savedRange) { | 293 if (savedRange) { |
278 ChromeVoxState.instance.navigateToRange(savedRange, false); | 294 ChromeVoxState.instance.navigateToRange(savedRange, false); |
279 return; | 295 return; |
280 } | 296 } |
281 | 297 var event = new CustomAutomationEvent(EventType.FOCUS, node, evt.eventFrom); |
282 this.onEventDefault(new chrome.automation.AutomationEvent( | 298 this.onEventDefault(event); |
283 EventType.focus, node, evt.eventFrom)); | |
284 }, | 299 }, |
285 | 300 |
286 /** | 301 /** |
287 * Provides all feedback once a load complete event fires. | 302 * Provides all feedback once a load complete event fires. |
288 * @param {!AutomationEvent} evt | 303 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
289 */ | 304 */ |
290 onLoadComplete: function(evt) { | 305 onLoadComplete: function(evt) { |
291 chrome.automation.getFocus(function(focus) { | 306 chrome.automation.getFocus(function(focus) { |
292 if (!focus || !AutomationUtil.isDescendantOf(focus, evt.target)) | 307 if (!focus || !AutomationUtil.isDescendantOf(focus, evt.target)) |
293 return; | 308 return; |
294 | 309 |
295 // Create text edit handler, if needed, now in order not to miss initial | 310 // Create text edit handler, if needed, now in order not to miss initial |
296 // value change if text field has already been focused when initializing | 311 // value change if text field has already been focused when initializing |
297 // ChromeVox. | 312 // ChromeVox. |
298 this.createTextEditHandlerIfNeeded_(focus); | 313 this.createTextEditHandlerIfNeeded_(focus); |
299 | 314 |
300 // If auto read is set, skip focus recovery and start reading from the top
. | 315 // If auto read is set, skip focus recovery and start reading from the top
. |
301 if (localStorage['autoRead'] == 'true' && | 316 if (localStorage['autoRead'] == 'true' && |
302 AutomationUtil.getTopLevelRoot(evt.target) == evt.target) { | 317 AutomationUtil.getTopLevelRoot(evt.target) == evt.target) { |
303 ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(evt.targe
t)); | 318 ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(evt.targe
t)); |
304 cvox.ChromeVox.tts.stop(); | 319 cvox.ChromeVox.tts.stop(); |
305 CommandHandler.onCommand('readFromHere'); | 320 CommandHandler.onCommand('readFromHere'); |
306 return; | 321 return; |
307 } | 322 } |
308 | 323 |
309 // If initial focus was already placed on this page (e.g. if a user starts | 324 // If initial focus was already placed on this page (e.g. if a user starts |
310 // tabbing before load complete), then don't move ChromeVox's position on | 325 // tabbing before load complete), then don't move ChromeVox's position on |
311 // the page. | 326 // the page. |
312 if (ChromeVoxState.instance.currentRange && | 327 if (ChromeVoxState.instance.currentRange && |
313 ChromeVoxState.instance.currentRange.start.node.root == focus.root) | 328 ChromeVoxState.instance.currentRange.start.node.root == focus.root) |
314 return; | 329 return; |
315 | 330 |
316 var o = new Output(); | 331 var o = new Output(); |
317 if (focus.role == RoleType.rootWebArea) { | 332 if (focus.role == RoleType.ROOT_WEB_AREA) { |
318 // Restore to previous position. | 333 // Restore to previous position. |
319 var url = focus.docUrl; | 334 var url = focus.docUrl; |
320 url = url.substring(0, url.indexOf('#')) || url; | 335 url = url.substring(0, url.indexOf('#')) || url; |
321 var pos = cvox.ChromeVox.position[url]; | 336 var pos = cvox.ChromeVox.position[url]; |
322 if (pos) { | 337 if (pos) { |
323 focus = AutomationUtil.hitTest(focus.root, pos) || focus; | 338 focus = AutomationUtil.hitTest(focus.root, pos) || focus; |
324 if (focus != focus.root) | 339 if (focus != focus.root) |
325 o.format('$name', focus.root); | 340 o.format('$name', focus.root); |
326 } | 341 } |
327 } | 342 } |
328 ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(focus)); | 343 ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(focus)); |
329 if (!this.shouldOutput_(evt)) | 344 if (!this.shouldOutput_(evt)) |
330 return; | 345 return; |
331 | 346 |
332 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); | 347 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); |
333 o.withRichSpeechAndBraille( | 348 o.withRichSpeechAndBraille( |
334 ChromeVoxState.instance.currentRange, null, evt.type).go(); | 349 ChromeVoxState.instance.currentRange, null, evt.type).go(); |
335 }.bind(this)); | 350 }.bind(this)); |
336 }, | 351 }, |
337 | 352 |
338 /** | 353 /** |
339 * Provides all feedback once a text changed event fires. | 354 * Provides all feedback once a text changed event fires. |
340 * @param {!AutomationEvent} evt | 355 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
341 */ | 356 */ |
342 onTextChanged: function(evt) { | 357 onTextChanged: function(evt) { |
343 if (evt.target.state.editable) | 358 if (evt.target.state.editable) |
344 this.onEditableChanged_(evt); | 359 this.onEditableChanged_(evt); |
345 }, | 360 }, |
346 | 361 |
347 /** | 362 /** |
348 * Provides all feedback once a text selection changed event fires. | 363 * Provides all feedback once a text selection changed event fires. |
349 * @param {!AutomationEvent} evt | 364 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
350 */ | 365 */ |
351 onTextSelectionChanged: function(evt) { | 366 onTextSelectionChanged: function(evt) { |
352 if (evt.target.state.editable) | 367 if (evt.target.state.editable) |
353 this.onEditableChanged_(evt); | 368 this.onEditableChanged_(evt); |
354 }, | 369 }, |
355 | 370 |
356 /** | 371 /** |
357 * Provides all feedback once a change event in a text field fires. | 372 * Provides all feedback once a change event in a text field fires. |
358 * @param {!AutomationEvent} evt | 373 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
359 * @private | 374 * @private |
360 */ | 375 */ |
361 onEditableChanged_: function(evt) { | 376 onEditableChanged_: function(evt) { |
362 var topRoot = AutomationUtil.getTopLevelRoot(evt.target); | 377 var topRoot = AutomationUtil.getTopLevelRoot(evt.target); |
363 if (!evt.target.state.focused || | 378 if (!evt.target.state.focused || |
364 (topRoot && | 379 (topRoot && |
365 topRoot.parent && | 380 topRoot.parent && |
366 !topRoot.parent.state.focused)) | 381 !topRoot.parent.state.focused)) |
367 return; | 382 return; |
368 | 383 |
(...skipping 20 matching lines...) Expand all Loading... |
389 } | 404 } |
390 | 405 |
391 // TODO(plundblad): This can currently be null for contenteditables. | 406 // TODO(plundblad): This can currently be null for contenteditables. |
392 // Clean up when it can't. | 407 // Clean up when it can't. |
393 if (this.textEditHandler_) | 408 if (this.textEditHandler_) |
394 this.textEditHandler_.onEvent(evt); | 409 this.textEditHandler_.onEvent(evt); |
395 }, | 410 }, |
396 | 411 |
397 /** | 412 /** |
398 * Provides all feedback once a value changed event fires. | 413 * Provides all feedback once a value changed event fires. |
399 * @param {!AutomationEvent} evt | 414 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
400 */ | 415 */ |
401 onValueChanged: function(evt) { | 416 onValueChanged: function(evt) { |
402 // Delegate to the edit text handler if this is an editable. | 417 // Delegate to the edit text handler if this is an editable. |
403 if (evt.target.state.editable) { | 418 if (evt.target.state.editable) { |
404 this.onEditableChanged_(evt); | 419 this.onEditableChanged_(evt); |
405 return; | 420 return; |
406 } | 421 } |
407 | 422 |
408 if (!this.shouldOutput_(evt)) | 423 if (!this.shouldOutput_(evt)) |
409 return; | 424 return; |
410 | 425 |
411 var t = evt.target; | 426 var t = evt.target; |
412 if (t.state.focused || | 427 if (t.state.focused || |
413 t.root.role == RoleType.desktop || | 428 t.root.role == RoleType.DESKTOP || |
414 AutomationUtil.isDescendantOf( | 429 AutomationUtil.isDescendantOf( |
415 ChromeVoxState.instance.currentRange.start.node, t)) { | 430 ChromeVoxState.instance.currentRange.start.node, t)) { |
416 if (new Date() - this.lastValueChanged_ <= | 431 if (new Date() - this.lastValueChanged_ <= |
417 DesktopAutomationHandler.VMIN_VALUE_CHANGE_DELAY_MS) | 432 DesktopAutomationHandler.VMIN_VALUE_CHANGE_DELAY_MS) |
418 return; | 433 return; |
419 | 434 |
420 this.lastValueChanged_ = new Date(); | 435 this.lastValueChanged_ = new Date(); |
421 | 436 |
422 var output = new Output(); | 437 var output = new Output(); |
423 | 438 |
424 if (t.root.role == RoleType.desktop) | 439 if (t.root.role == RoleType.DESKTOP) |
425 output.withQueueMode(cvox.QueueMode.FLUSH); | 440 output.withQueueMode(cvox.QueueMode.FLUSH); |
426 | 441 |
427 output.format('$value', evt.target).go(); | 442 output.format('$value', evt.target).go(); |
428 } | 443 } |
429 }, | 444 }, |
430 | 445 |
431 /** | 446 /** |
432 * Handle updating the active indicator when the document scrolls. | 447 * Handle updating the active indicator when the document scrolls. |
433 * @param {!AutomationEvent} evt | 448 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
434 */ | 449 */ |
435 onScrollPositionChanged: function(evt) { | 450 onScrollPositionChanged: function(evt) { |
436 var currentRange = ChromeVoxState.instance.currentRange; | 451 var currentRange = ChromeVoxState.instance.currentRange; |
437 if (currentRange && currentRange.isValid() && this.shouldOutput_(evt)) | 452 if (currentRange && currentRange.isValid() && this.shouldOutput_(evt)) |
438 new Output().withLocation(currentRange, null, evt.type).go(); | 453 new Output().withLocation(currentRange, null, evt.type).go(); |
439 }, | 454 }, |
440 | 455 |
441 /** | 456 /** |
442 * @param {!AutomationEvent} evt | 457 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
443 */ | 458 */ |
444 onSelection: function(evt) { | 459 onSelection: function(evt) { |
445 // Invalidate any previous editable text handler state since some nodes, | 460 // Invalidate any previous editable text handler state since some nodes, |
446 // like menuitems, can receive selection while focus remains on an editable | 461 // like menuitems, can receive selection while focus remains on an editable |
447 // leading to braille output routing to the editable. | 462 // leading to braille output routing to the editable. |
448 this.textEditHandler_ = null; | 463 this.textEditHandler_ = null; |
449 | 464 |
450 chrome.automation.getFocus(function(focus) { | 465 chrome.automation.getFocus(function(focus) { |
451 // Desktop tabs get "selection" when there's a focused webview during tab | 466 // Desktop tabs get "selection" when there's a focused webview during tab |
452 // switching. | 467 // switching. |
453 if (focus.role == RoleType.webView && evt.target.role == RoleType.tab) { | 468 if (focus.role == RoleType.WEB_VIEW && evt.target.role == RoleType.TAB) { |
454 ChromeVoxState.instance.setCurrentRange( | 469 ChromeVoxState.instance.setCurrentRange( |
455 cursors.Range.fromNode(focus.firstChild)); | 470 cursors.Range.fromNode(focus.firstChild)); |
456 return; | 471 return; |
457 } | 472 } |
458 | 473 |
459 // Some cases (e.g. in overview mode), require overriding the assumption | 474 // Some cases (e.g. in overview mode), require overriding the assumption |
460 // that focus is an ancestor of a selection target. | 475 // that focus is an ancestor of a selection target. |
461 var override = evt.target.role == RoleType.menuItem || | 476 var override = evt.target.role == RoleType.MENU_ITEM || |
462 (evt.target.root == focus.root && | 477 (evt.target.root == focus.root && |
463 focus.root.role == RoleType.desktop); | 478 focus.root.role == RoleType.DESKTOP); |
464 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); | 479 Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH); |
465 if (override || AutomationUtil.isDescendantOf(evt.target, focus)) | 480 if (override || AutomationUtil.isDescendantOf(evt.target, focus)) |
466 this.onEventDefault(evt); | 481 this.onEventDefault(evt); |
467 }.bind(this)); | 482 }.bind(this)); |
468 }, | 483 }, |
469 | 484 |
470 /** | 485 /** |
471 * Provides all feedback once a menu start event fires. | 486 * Provides all feedback once a menu start event fires. |
472 * @param {!AutomationEvent} evt | 487 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
473 */ | 488 */ |
474 onMenuStart: function(evt) { | 489 onMenuStart: function(evt) { |
475 ChromeVoxState.instance.markCurrentRange(); | 490 ChromeVoxState.instance.markCurrentRange(); |
476 this.onEventDefault(evt); | 491 this.onEventDefault(evt); |
477 }, | 492 }, |
478 | 493 |
479 /** | 494 /** |
480 * Provides all feedback once a menu end event fires. | 495 * Provides all feedback once a menu end event fires. |
481 * @param {!AutomationEvent} evt | 496 * @param {!(AutomationEvent|CustomAutomationEvent)} evt |
482 */ | 497 */ |
483 onMenuEnd: function(evt) { | 498 onMenuEnd: function(evt) { |
484 this.onEventDefault(evt); | 499 this.onEventDefault(evt); |
485 | 500 |
486 // This is a work around for Chrome context menus not firing a focus event | 501 // This is a work around for Chrome context menus not firing a focus event |
487 // after you close them. | 502 // after you close them. |
488 chrome.automation.getFocus(function(focus) { | 503 chrome.automation.getFocus(function(focus) { |
489 if (focus) { | 504 if (focus) { |
490 this.onFocus( | 505 var event = new CustomAutomationEvent(EventType.FOCUS, focus, 'page'); |
491 new chrome.automation.AutomationEvent( | 506 this.onFocus(event); |
492 EventType.focus, focus, 'page')); | |
493 } | 507 } |
494 }.bind(this)); | 508 }.bind(this)); |
495 }, | 509 }, |
496 | 510 |
497 /** | 511 /** |
498 * Create an editable text handler for the given node if needed. | 512 * Create an editable text handler for the given node if needed. |
499 * @param {!AutomationNode} node | 513 * @param {!AutomationNode} node |
500 */ | 514 */ |
501 createTextEditHandlerIfNeeded_: function(node) { | 515 createTextEditHandlerIfNeeded_: function(node) { |
502 if (!this.textEditHandler_ || | 516 if (!this.textEditHandler_ || |
503 this.textEditHandler_.node !== node) { | 517 this.textEditHandler_.node !== node) { |
504 this.textEditHandler_ = editing.TextEditHandler.createForNode(node); | 518 this.textEditHandler_ = editing.TextEditHandler.createForNode(node); |
505 } | 519 } |
506 }, | 520 }, |
507 | 521 |
508 /** | 522 /** |
509 * Once an event handler updates ChromeVox's range based on |evt| | 523 * Once an event handler updates ChromeVox's range based on |evt| |
510 * which updates mode, returns whether |evt| should be outputted. | 524 * which updates mode, returns whether |evt| should be outputted. |
511 * @return {boolean} | 525 * @return {boolean} |
512 */ | 526 */ |
513 shouldOutput_: function(evt) { | 527 shouldOutput_: function(evt) { |
514 var mode = ChromeVoxState.instance.mode; | 528 var mode = ChromeVoxState.instance.mode; |
515 // Only output desktop rooted nodes or web nodes for next engine modes. | 529 // Only output desktop rooted nodes or web nodes for next engine modes. |
516 return evt.target.root.role == RoleType.desktop || | 530 return evt.target.root.role == RoleType.DESKTOP || |
517 (mode == ChromeVoxMode.NEXT || | 531 (mode == ChromeVoxMode.NEXT || |
518 mode == ChromeVoxMode.FORCE_NEXT || | 532 mode == ChromeVoxMode.FORCE_NEXT || |
519 mode == ChromeVoxMode.CLASSIC_COMPAT); | 533 mode == ChromeVoxMode.CLASSIC_COMPAT); |
520 } | 534 } |
521 }; | 535 }; |
522 | 536 |
523 /** | 537 /** |
524 * Initializes global state for DesktopAutomationHandler. | 538 * Initializes global state for DesktopAutomationHandler. |
525 * @private | 539 * @private |
526 */ | 540 */ |
527 DesktopAutomationHandler.init_ = function() { | 541 DesktopAutomationHandler.init_ = function() { |
528 chrome.automation.getDesktop(function(desktop) { | 542 chrome.automation.getDesktop(function(desktop) { |
529 ChromeVoxState.desktopAutomationHandler = | 543 ChromeVoxState.desktopAutomationHandler = |
530 new DesktopAutomationHandler(desktop); | 544 new DesktopAutomationHandler(desktop); |
531 }); | 545 }); |
532 }; | 546 }; |
533 | 547 |
534 DesktopAutomationHandler.init_(); | 548 DesktopAutomationHandler.init_(); |
535 | 549 |
536 }); // goog.scope | 550 }); // goog.scope |
OLD | NEW |