| 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 |