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