OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 The entry point for all ChromeVox2 related code for the | 6 * @fileoverview The entry point for all ChromeVox2 related code for the |
7 * background page. | 7 * background page. |
8 */ | 8 */ |
9 | 9 |
10 goog.provide('Background'); | 10 goog.provide('Background'); |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
58 /** | 58 /** |
59 * Which variant of ChromeVox is active. | 59 * Which variant of ChromeVox is active. |
60 * @type {ChromeVoxMode} | 60 * @type {ChromeVoxMode} |
61 * @private | 61 * @private |
62 */ | 62 */ |
63 this.mode_ = ChromeVoxMode.CLASSIC; | 63 this.mode_ = ChromeVoxMode.CLASSIC; |
64 | 64 |
65 /** @type {!ClassicCompatibility} @private */ | 65 /** @type {!ClassicCompatibility} @private */ |
66 this.compat_ = new ClassicCompatibility(); | 66 this.compat_ = new ClassicCompatibility(); |
67 | 67 |
68 /** @type {Array<Object>} A queue of incoming events. @private */ | |
69 this.eventQueue_ = []; | |
Peter Lundblad
2015/08/20 08:30:05
I suggest putting the event queue into its own cla
| |
70 | |
71 /** | |
72 * @type {?number} | |
73 * The window.setTimeout timer ID of the event queue timer. | |
74 * @private | |
75 */ | |
76 this.eventQueueTimerId_ = null; | |
Peter Lundblad
2015/08/20 08:30:05
Since your code already relies on timer ids being
| |
77 | |
78 /** | |
79 * @type {number} | |
80 * How long to hold events in the event queue before executing them. | |
81 * @const | |
82 */ | |
83 this.EVENT_QUEUE_DELAY_MS = 10; | |
David Tseng
2015/08/19 17:19:25
nit: make private.
Peter Lundblad
2015/08/20 08:30:05
Put constants outside of the constructor, directly
| |
84 | |
68 // Manually bind all functions to |this|. | 85 // Manually bind all functions to |this|. |
69 for (var func in this) { | 86 for (var func in this) { |
70 if (typeof(this[func]) == 'function') | 87 if (typeof(this[func]) == 'function') |
71 this[func] = this[func].bind(this); | 88 this[func] = this[func].bind(this); |
72 } | 89 } |
73 | 90 |
74 /** | 91 /** |
75 * Maps an automation event to its listener. | 92 * Maps an automation event to its listener. |
76 * @type {!Object<EventType, function(Object) : void>} | 93 * @type {!Object<EventType, function(Object) : void>} |
77 */ | 94 */ |
78 this.listeners_ = { | 95 this.listeners_ = { |
79 alert: this.onEventDefault, | 96 alert: this.onEventDefault, |
80 focus: this.onFocus, | 97 focus: this.onFocus, |
81 hover: this.onEventDefault, | 98 hover: this.onEventDefault, |
82 loadComplete: this.onLoadComplete, | 99 loadComplete: this.onLoadComplete, |
83 menuStart: this.onEventDefault, | 100 menuStart: this.onEventDefault, |
84 menuEnd: this.onEventDefault, | 101 menuEnd: this.onEventDefault, |
85 menuListValueChanged: this.onEventDefault, | 102 menuListValueChanged: this.onEventDefault, |
86 textChanged: this.onTextOrTextSelectionChanged, | 103 textChanged: this.onTextOrTextSelectionChanged, |
87 textSelectionChanged: this.onTextOrTextSelectionChanged, | 104 textSelectionChanged: this.onTextOrTextSelectionChanged, |
88 valueChanged: this.onValueChanged | 105 valueChanged: this.onValueChanged |
89 }; | 106 }; |
90 | 107 |
108 /** | |
109 * The object that speaks changes to an editable text field. | |
110 * @type {?cvox.ChromeVoxEditableTextBase} | |
Peter Lundblad
2015/08/20 08:30:05
Document when this is null.
| |
111 */ | |
112 this.editableTextHandler_ = null; | |
113 | |
91 chrome.automation.getDesktop(this.onGotDesktop); | 114 chrome.automation.getDesktop(this.onGotDesktop); |
92 | 115 |
93 // Handle messages directed to the Next background page. | 116 // Handle messages directed to the Next background page. |
94 cvox.ExtensionBridge.addMessageListener(function(msg, port) { | 117 cvox.ExtensionBridge.addMessageListener(function(msg, port) { |
95 var target = msg['target']; | 118 var target = msg['target']; |
96 var action = msg['action']; | 119 var action = msg['action']; |
97 | 120 |
98 switch (target) { | 121 switch (target) { |
99 case 'next': | 122 case 'next': |
100 if (action == 'getIsClassicEnabled') { | 123 if (action == 'getIsClassicEnabled') { |
(...skipping 15 matching lines...) Expand all Loading... | |
116 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); | 139 this.setChromeVoxMode(ChromeVoxMode.FORCE_NEXT); |
117 }, | 140 }, |
118 | 141 |
119 /** | 142 /** |
120 * Handles all setup once a new automation tree appears. | 143 * Handles all setup once a new automation tree appears. |
121 * @param {chrome.automation.AutomationNode} desktop | 144 * @param {chrome.automation.AutomationNode} desktop |
122 */ | 145 */ |
123 onGotDesktop: function(desktop) { | 146 onGotDesktop: function(desktop) { |
124 // Register all automation event listeners. | 147 // Register all automation event listeners. |
125 for (var eventType in this.listeners_) | 148 for (var eventType in this.listeners_) |
126 desktop.addEventListener(eventType, this.listeners_[eventType], true); | 149 desktop.addEventListener(eventType, this.addToEventQueue_, true); |
127 | 150 |
128 // Register a tree change observer. | 151 // Register a tree change observer. |
129 chrome.automation.addTreeChangeObserver(this.onTreeChange); | 152 chrome.automation.addTreeChangeObserver(this.onTreeChange); |
130 | 153 |
131 // The focused state gets set on the containing webView node. | 154 // The focused state gets set on the containing webView node. |
132 var webView = desktop.find({role: chrome.automation.RoleType.webView, | 155 var webView = desktop.find({role: chrome.automation.RoleType.webView, |
133 state: {focused: true}}); | 156 state: {focused: true}}); |
134 if (webView) { | 157 if (webView) { |
135 var root = webView.find({role: chrome.automation.RoleType.rootWebArea}); | 158 var root = webView.find({role: chrome.automation.RoleType.rootWebArea}); |
136 if (root) { | 159 if (root) { |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
394 case cvox.BrailleKeyCommand.BOTTOM: | 417 case cvox.BrailleKeyCommand.BOTTOM: |
395 this.onGotCommand('goToEnd', true); | 418 this.onGotCommand('goToEnd', true); |
396 break; | 419 break; |
397 default: | 420 default: |
398 return false; | 421 return false; |
399 } | 422 } |
400 return true; | 423 return true; |
401 }, | 424 }, |
402 | 425 |
403 /** | 426 /** |
427 * Add an event to the event queue so that we can avoid ignore | |
David Tseng
2015/08/19 17:19:25
avoid or ignore
| |
428 * transient states - for example if focus moves to one node and | |
429 * then immediately another, or if a node gains focus and | |
430 * immediately changes its selection. | |
431 * | |
432 * @param {Object} evt | |
433 */ | |
David Tseng
2015/08/19 17:19:25
nit: @private
| |
434 addToEventQueue_: function(evt) { | |
435 evt.enqueueTime = new Date(); | |
Peter Lundblad
2015/08/20 08:30:05
Should we extend the automation api with a time fi
| |
436 this.eventQueue_.push(evt); | |
437 if (!this.eventQueueTimerId_) { | |
438 this.eventQueueTimerId_ = window.setTimeout( | |
439 this.executeNextEventFromQueue_, this.EVENT_QUEUE_DELAY_MS); | |
440 } | |
441 }, | |
442 | |
443 /** | |
444 * Filter duplicates from the event queue, then pop the first item | |
445 * off and if it's time to execute it, do so, otherwise set the timer | |
446 * to wake up again. | |
David Tseng
2015/08/19 17:19:25
nit: @private
| |
447 */ | |
448 executeNextEventFromQueue_: function() { | |
Peter Lundblad
2015/08/20 08:30:05
nit: s/execute/handle/ ?
| |
449 this.eventQueueTimerId_ = null; | |
450 | |
451 if (this.eventQueue_.length == 0) { | |
452 return; | |
453 } | |
David Tseng
2015/08/19 17:19:25
nit: remove braces.
| |
454 | |
455 // Assuming the queue is in increasing order of enqueue time, remove all exc ept the | |
456 // last item of each type. | |
457 var lastTimeForEventType = {}; | |
Peter Lundblad
2015/08/20 08:30:05
Why not just store the object reference instead of
| |
458 this.eventQueue_.forEach(function(evt) { | |
459 lastTimeForEventType[evt.type] = evt.enqueueTime; | |
460 }); | |
461 this.eventQueue_ = this.eventQueue_.filter(function(evt) { | |
462 return evt.enqueueTime == lastTimeForEventType[evt.type]; | |
David Tseng
2015/08/19 17:19:25
Any issues doing this generically? For example, do
| |
463 }); | |
464 | |
465 while (this.eventQueue_.length) { | |
466 var now = new Date(); | |
467 var delta = now - this.eventQueue_[0].enqueueTime; | |
468 if (delta < this.EVENT_QUEUE_DELAY_MS) { | |
469 this.eventQueueTimerId_ = window.setTimeout( | |
470 this.executeNextEventFromQueue_, this.EVENT_QUEUE_DELAY_MS - delta); | |
471 return; | |
472 } | |
473 | |
474 var evt = this.eventQueue_.shift(); | |
475 this.listeners_[evt.type](evt); | |
476 } | |
477 }, | |
478 | |
479 /** | |
404 * Provides all feedback once ChromeVox's focus changes. | 480 * Provides all feedback once ChromeVox's focus changes. |
405 * @param {Object} evt | 481 * @param {Object} evt |
406 */ | 482 */ |
407 onEventDefault: function(evt) { | 483 onEventDefault: function(evt) { |
408 var node = evt.target; | 484 var node = evt.target; |
409 | 485 |
410 if (!node) | 486 if (!node) |
411 return; | 487 return; |
412 | 488 |
413 var prevRange = this.currentRange_; | 489 var prevRange = this.currentRange_; |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
447 onFocus: function(evt) { | 523 onFocus: function(evt) { |
448 var node = evt.target; | 524 var node = evt.target; |
449 | 525 |
450 // It doesn't make sense to focus the containing web area if a descendant | 526 // It doesn't make sense to focus the containing web area if a descendant |
451 // has focused state. | 527 // has focused state. |
452 if (node.role == 'rootWebArea') { | 528 if (node.role == 'rootWebArea') { |
453 node = AutomationUtil.findNodePost(node, | 529 node = AutomationUtil.findNodePost(node, |
454 Dir.FORWARD, | 530 Dir.FORWARD, |
455 AutomationPredicate.focused) || node; | 531 AutomationPredicate.focused) || node; |
456 } | 532 } |
533 | |
534 if (evt.target.role == 'textField' || evt.target.role == 'textBox') { | |
David Tseng
2015/08/19 17:19:25
nit: please use RoleType.textField and RoleType.te
| |
535 this.createEditableTextHandlerIfNeeded_(evt.target); | |
536 } | |
537 | |
457 this.onEventDefault({target: node, type: 'focus'}); | 538 this.onEventDefault({target: node, type: 'focus'}); |
458 }, | 539 }, |
459 | 540 |
460 /** | 541 /** |
461 * Provides all feedback once a load complete event fires. | 542 * Provides all feedback once a load complete event fires. |
462 * @param {Object} evt | 543 * @param {Object} evt |
463 */ | 544 */ |
464 onLoadComplete: function(evt) { | 545 onLoadComplete: function(evt) { |
465 this.setupChromeVoxVariants_(evt.target.docUrl); | 546 this.setupChromeVoxVariants_(evt.target.docUrl); |
466 | 547 |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
504 return; | 585 return; |
505 | 586 |
506 if (!evt.target.state.focused) | 587 if (!evt.target.state.focused) |
507 return; | 588 return; |
508 | 589 |
509 if (!this.currentRange_) { | 590 if (!this.currentRange_) { |
510 this.onEventDefault(evt); | 591 this.onEventDefault(evt); |
511 this.currentRange_ = cursors.Range.fromNode(evt.target); | 592 this.currentRange_ = cursors.Range.fromNode(evt.target); |
512 } | 593 } |
513 | 594 |
595 this.createEditableTextHandlerIfNeeded_(evt.target); | |
514 var textChangeEvent = new cvox.TextChangeEvent( | 596 var textChangeEvent = new cvox.TextChangeEvent( |
515 evt.target.value, | 597 evt.target.value, |
516 evt.target.textSelStart, | 598 evt.target.textSelStart, |
517 evt.target.textSelEnd, | 599 evt.target.textSelEnd, |
518 true); // triggered by user | 600 true); // triggered by user |
519 if (!this.editableTextHandler || | 601 this.editableTextHandler_.changed(textChangeEvent); |
520 evt.target != this.currentRange_.start.node) { | |
521 this.editableTextHandler = | |
522 new cvox.ChromeVoxEditableTextBase( | |
523 textChangeEvent.value, | |
524 textChangeEvent.start, | |
525 textChangeEvent.end, | |
526 evt.target.state['protected'], | |
527 cvox.ChromeVox.tts); | |
528 } | |
529 | 602 |
530 this.editableTextHandler.changed(textChangeEvent); | |
531 new Output().withBraille( | 603 new Output().withBraille( |
532 this.currentRange_, null, evt.type) | 604 this.currentRange_, null, evt.type) |
533 .go(); | 605 .go(); |
534 }, | 606 }, |
535 | 607 |
536 /** | 608 /** |
537 * Provides all feedback once a value changed event fires. | 609 * Provides all feedback once a value changed event fires. |
538 * @param {Object} evt | 610 * @param {Object} evt |
539 */ | 611 */ |
540 onValueChanged: function(evt) { | 612 onValueChanged: function(evt) { |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
682 // When in compat mode, if the focus is within the desktop tree proper, | 754 // When in compat mode, if the focus is within the desktop tree proper, |
683 // then do not disable content scripts. | 755 // then do not disable content scripts. |
684 if (this.currentRange_.start.node.root.role == 'desktop') | 756 if (this.currentRange_.start.node.root.role == 'desktop') |
685 return; | 757 return; |
686 | 758 |
687 this.disableClassicChromeVox_(); | 759 this.disableClassicChromeVox_(); |
688 } | 760 } |
689 }.bind(this)); | 761 }.bind(this)); |
690 | 762 |
691 this.mode_ = mode; | 763 this.mode_ = mode; |
692 } | 764 }, |
765 | |
766 /** | |
767 * Create an editable text handler for the given node if needed. | |
768 * @param {Object} node | |
David Tseng
2015/08/19 17:19:25
s/Object/AutomationNode
| |
769 */ | |
David Tseng
2015/08/19 17:19:25
@private
| |
770 createEditableTextHandlerIfNeeded_: function(node) { | |
771 if (!this.editableTextHandler_ || node != this.currentRange_.start.node) { | |
772 this.editableTextHandler_ = | |
773 new cvox.ChromeVoxEditableTextBase( | |
774 node.value, | |
775 node.textSelStart, | |
776 node.textSelEnd, | |
777 node.state['protected'], | |
778 cvox.ChromeVox.tts); | |
779 } | |
780 }, | |
693 }; | 781 }; |
694 | 782 |
695 /** @type {Background} */ | 783 /** @type {Background} */ |
696 global.backgroundObj = new Background(); | 784 global.backgroundObj = new Background(); |
697 | 785 |
698 }); // goog.scope | 786 }); // goog.scope |
OLD | NEW |