OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 var AutomationEvent = chrome.automation.AutomationEvent; | 5 var AutomationEvent = chrome.automation.AutomationEvent; |
6 var AutomationNode = chrome.automation.AutomationNode; | 6 var AutomationNode = chrome.automation.AutomationNode; |
7 var EventType = chrome.automation.EventType; | 7 var EventType = chrome.automation.EventType; |
8 var RoleType = chrome.automation.RoleType; | 8 var RoleType = chrome.automation.RoleType; |
9 | 9 |
10 /** | 10 /** |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
46 } | 46 } |
47 | 47 |
48 /** | 48 /** |
49 * @constructor | 49 * @constructor |
50 */ | 50 */ |
51 var SelectToSpeak = function() { | 51 var SelectToSpeak = function() { |
52 /** @private { AutomationNode } */ | 52 /** @private { AutomationNode } */ |
53 this.node_ = null; | 53 this.node_ = null; |
54 | 54 |
55 /** @private { boolean } */ | 55 /** @private { boolean } */ |
56 this.down_ = false; | 56 this.trackingMouse_ = false; |
57 | |
58 /** @private { boolean } */ | |
59 this.didTrackMouse_ = false; | |
60 | |
61 /** @private { boolean } */ | |
62 this.searchKeyDown_ = false; | |
David Tseng
2017/05/16 15:54:39
nit: isSearchKeyDown_
dmazzoni
2017/05/16 18:23:48
Done.
| |
63 | |
64 /** @private { Set } */ | |
David Tseng
2017/05/16 15:54:42
nit: !Set<number>
dmazzoni
2017/05/16 18:23:48
Done.
| |
65 this.keysCurrentlyDown_ = new Set(); | |
66 | |
67 /** @private { Set } */ | |
68 this.keysPressedTogether_ = new Set(); | |
57 | 69 |
58 /** @private {{x: number, y: number}} */ | 70 /** @private {{x: number, y: number}} */ |
59 this.mouseStart_ = {x: 0, y: 0}; | 71 this.mouseStart_ = {x: 0, y: 0}; |
60 | 72 |
73 /** @private {{x: number, y: number}} */ | |
74 this.mouseEnd_ = {x: 0, y: 0}; | |
75 | |
76 /** @private {AutomationRootNode} */ | |
61 chrome.automation.getDesktop(function(desktop) { | 77 chrome.automation.getDesktop(function(desktop) { |
78 this.desktop_ = desktop; | |
79 | |
80 // After the user selects a region of the screen, we do a hit test at | |
81 // the center of that box using the automation API. The result of the | |
82 // hit test is a MOUSE_RELEASED accessibility event. | |
62 desktop.addEventListener( | 83 desktop.addEventListener( |
63 EventType.MOUSE_PRESSED, this.onMousePressed_.bind(this), true); | 84 EventType.MOUSE_RELEASED, this.onAutomationHitTest_.bind(this), |
64 desktop.addEventListener( | 85 true); |
65 EventType.MOUSE_DRAGGED, this.onMouseDragged_.bind(this), true); | |
66 desktop.addEventListener( | |
67 EventType.MOUSE_RELEASED, this.onMouseReleased_.bind(this), true); | |
68 desktop.addEventListener( | |
69 EventType.MOUSE_CANCELED, this.onMouseCanceled_.bind(this), true); | |
70 }.bind(this)); | 86 }.bind(this)); |
71 | 87 |
72 /** @private { ?string } */ | 88 /** @private { ?string } */ |
73 this.voiceNameFromPrefs_ = null; | 89 this.voiceNameFromPrefs_ = null; |
74 | 90 |
75 /** @private { ?string } */ | 91 /** @private { ?string } */ |
76 this.voiceNameFromLocale_ = null; | 92 this.voiceNameFromLocale_ = null; |
77 | 93 |
78 /** @private { Set<string> } */ | 94 /** @private { Set<string> } */ |
79 this.validVoiceNames_ = new Set(); | 95 this.validVoiceNames_ = new Set(); |
80 | 96 |
81 /** @private { number } */ | 97 /** @private { number } */ |
82 this.speechRate_ = 1.0; | 98 this.speechRate_ = 1.0; |
83 | 99 |
84 /** @const { string } */ | 100 /** @const { string } */ |
85 this.color_ = "#f73a98"; | 101 this.color_ = "#f73a98"; |
86 | 102 |
87 this.initPreferences_(); | 103 this.initPreferences_(); |
104 | |
105 this.setUpEventListeners_(); | |
88 }; | 106 }; |
89 | 107 |
108 /** @const {number} */ | |
109 SelectToSpeak.SEARCH_KEY_CODE = 91; | |
110 | |
111 /** @const {number} */ | |
112 SelectToSpeak.CONTROL_KEY_CODE = 17; | |
113 | |
90 SelectToSpeak.prototype = { | 114 SelectToSpeak.prototype = { |
91 /** | 115 /** |
92 * Called when the mouse is pressed and the user is in a mode where | 116 * Called when the mouse is pressed and the user is in a mode where |
93 * select-to-speak is capturing mouse events (for example holding down | 117 * select-to-speak is capturing mouse events (for example holding down |
94 * Search). | 118 * Search). |
95 * | 119 * |
96 * @param {!AutomationEvent} evt | 120 * @param {!Event} evt The DOM event |
121 * @return {boolean} True if the default action should be performed. | |
97 */ | 122 */ |
98 onMousePressed_: function(evt) { | 123 onMouseDown_: function(evt) { |
99 this.down_ = true; | 124 if (!this.searchKeyDown_) |
100 this.mouseStart_ = {x: evt.mouseX, y: evt.mouseY}; | 125 return false; |
David Tseng
2017/05/16 15:54:43
return true
dmazzoni
2017/05/16 18:23:48
Leaving as false since we're not reinjecting
| |
101 this.startNode_ = evt.target; | 126 |
127 this.trackingMouse_ = true; | |
128 this.didTrackMouse_ = true; | |
129 this.mouseStart_ = {x: evt.screenX, y: evt.screenY}; | |
102 chrome.tts.stop(); | 130 chrome.tts.stop(); |
103 this.onMouseDragged_(evt); | 131 |
132 // Fire a hit test event on click to warm up the cache. | |
133 this.desktop_.hitTest(evt.screenX, evt.screenY, EventType.MOUSE_PRESSED); | |
134 | |
135 this.onMouseMove_(evt); | |
136 return false; | |
104 }, | 137 }, |
105 | 138 |
106 /** | 139 /** |
107 * Called when the mouse is moved or dragged and the user is in a | 140 * Called when the mouse is moved or dragged and the user is in a |
108 * mode where select-to-speak is capturing mouse events (for example | 141 * mode where select-to-speak is capturing mouse events (for example |
109 * holding down Search). | 142 * holding down Search). |
110 * | 143 * |
111 * @param {!AutomationEvent} evt | 144 * @param {!Event} evt The DOM event |
145 * @return {boolean} True if the default action should be performed. | |
112 */ | 146 */ |
113 onMouseDragged_: function(evt) { | 147 onMouseMove_: function(evt) { |
114 if (!this.down_) | 148 if (!this.trackingMouse_) |
115 return; | 149 return false; |
David Tseng
2017/05/16 15:54:39
return true
| |
116 | 150 |
117 var rect = rectFromPoints( | 151 var rect = rectFromPoints( |
118 this.mouseStart_.x, this.mouseStart_.y, | 152 this.mouseStart_.x, this.mouseStart_.y, |
119 evt.mouseX, evt.mouseY); | 153 evt.screenX, evt.screenY); |
120 chrome.accessibilityPrivate.setFocusRing([rect], this.color_); | 154 chrome.accessibilityPrivate.setFocusRing([rect], this.color_); |
155 return false; | |
121 }, | 156 }, |
122 | 157 |
123 /** | 158 /** |
124 * Called when the mouse is released and the user is in a | 159 * Called when the mouse is released and the user is in a |
125 * mode where select-to-speak is capturing mouse events (for example | 160 * mode where select-to-speak is capturing mouse events (for example |
126 * holding down Search). | 161 * holding down Search). |
127 * | 162 * |
128 * @param {!AutomationEvent} evt | 163 * @param {!Event} evt |
164 * @return {boolean} True if the default action should be performed. | |
129 */ | 165 */ |
130 onMouseReleased_: function(evt) { | 166 onMouseUp_: function(evt) { |
131 this.onMouseDragged_(evt); | 167 this.onMouseMove_(evt); |
132 this.down_ = false; | 168 this.trackingMouse_ = false; |
133 | 169 |
134 chrome.accessibilityPrivate.setFocusRing([]); | 170 chrome.accessibilityPrivate.setFocusRing([]); |
135 | 171 |
136 // Walk up to the nearest window, web area, or dialog that the | 172 this.mouseEnd_ = {x: evt.screenX, y: evt.screenY}; |
173 var ctrX = Math.floor((this.mouseStart_.x + this.mouseEnd_.x) / 2); | |
174 var ctrY = Math.floor((this.mouseStart_.y + this.mouseEnd_.y) / 2); | |
175 | |
176 // Do a hit test at the center of the area the user dragged over. | |
177 // This will give us some context when searching the accessibility tree. | |
178 // The hit test will result in a EventType.MOUSE_RELEASED event being | |
179 // fired on the result of that hit test, which will trigger | |
180 // onAutomationHitTest_. | |
181 this.desktop_.hitTest(ctrX, ctrY, EventType.MOUSE_RELEASED); | |
182 return false; | |
183 }, | |
184 | |
185 /** | |
186 * Called in response to our hit test after the mouse is released, | |
187 * when the user is in a mode where select-to-speak is capturing | |
188 * mouse events (for example holding down Search). | |
189 * | |
190 * @param {!AutomationEvent} evt The automation event. | |
191 */ | |
192 onAutomationHitTest_: function(evt) { | |
193 // Walk up to the nearest window, web area, toolbar, or dialog that the | |
137 // hit node is contained inside. Only speak objects within that | 194 // hit node is contained inside. Only speak objects within that |
138 // container. In the future we might include other container-like | 195 // container. In the future we might include other container-like |
139 // roles here. | 196 // roles here. |
140 var root = this.startNode_; | 197 var root = evt.target; |
141 while (root.parent && | 198 while (root.parent && |
142 root.role != RoleType.WINDOW && | 199 root.role != RoleType.WINDOW && |
143 root.role != RoleType.ROOT_WEB_AREA && | 200 root.role != RoleType.ROOT_WEB_AREA && |
144 root.role != RoleType.DESKTOP && | 201 root.role != RoleType.DESKTOP && |
145 root.role != RoleType.DIALOG) { | 202 root.role != RoleType.DIALOG && |
203 root.role != RoleType.ALERT_DIALOG && | |
204 root.role != RoleType.TOOLBAR) { | |
146 root = root.parent; | 205 root = root.parent; |
147 } | 206 } |
148 | 207 |
149 var rect = rectFromPoints( | 208 var rect = rectFromPoints( |
150 this.mouseStart_.x, this.mouseStart_.y, | 209 this.mouseStart_.x, this.mouseStart_.y, |
151 evt.mouseX, evt.mouseY); | 210 this.mouseEnd_.x, this.mouseEnd_.y); |
152 var nodes = []; | 211 var nodes = []; |
153 this.findAllMatching_(root, rect, nodes); | 212 this.findAllMatching_(root, rect, nodes); |
154 this.startSpeechQueue_(nodes); | 213 this.startSpeechQueue_(nodes); |
155 }, | 214 }, |
156 | 215 |
157 /** | 216 /** |
158 * Called when the user cancels select-to-speak's capturing of mouse | 217 * @param {!Event} evt |
159 * events (for example by releasing Search while the mouse is still down). | |
160 * | |
161 * @param {!AutomationEvent} evt | |
162 */ | 218 */ |
163 onMouseCanceled_: function(evt) { | 219 onKeyDown_: function(evt) { |
164 this.down_ = false; | 220 if (this.keysPressedTogether_.size == 0 && |
165 chrome.accessibilityPrivate.setFocusRing([]); | 221 evt.keyCode == SelectToSpeak.SEARCH_KEY_CODE) { |
166 chrome.tts.stop(); | 222 this.searchKeyDown_ = true; |
223 } else if (!this.trackingMouse_) { | |
224 this.searchKeyDown_ = false; | |
225 } | |
226 | |
227 this.keysCurrentlyDown_.add(evt.keyCode); | |
228 this.keysPressedTogether_.add(evt.keyCode); | |
167 }, | 229 }, |
168 | 230 |
169 /** | 231 /** |
232 * @param {!Event} evt | |
233 */ | |
234 onKeyUp_: function(evt) { | |
235 if (evt.keyCode == SelectToSpeak.SEARCH_KEY_CODE) { | |
236 this.searchKeyDown_ = false; | |
237 | |
238 // If we were in the middle of tracking the mouse, cancel it. | |
239 if (this.trackingMouse_) { | |
240 this.trackingMouse_ = false; | |
241 chrome.accessibilityPrivate.setFocusRing([]); | |
242 chrome.tts.stop(); | |
243 } | |
244 } | |
245 | |
246 // Stop speech when the user taps and releases Control or Search | |
247 // without using the mouse or pressing any other keys along the way. | |
248 if (!this.didTrackMouse_ && | |
249 (evt.keyCode == SelectToSpeak.SEARCH_KEY_CODE || | |
250 evt.keyCode == SelectToSpeak.CONTROL_KEY_CODE) && | |
251 this.keysPressedTogether_.has(evt.keyCode) && | |
252 this.keysPressedTogether_.size == 1) { | |
253 this.trackingMouse_ = false; | |
254 chrome.accessibilityPrivate.setFocusRing([]); | |
255 chrome.tts.stop(); | |
256 } | |
257 | |
258 this.keysCurrentlyDown_.delete(evt.keyCode); | |
259 if (this.keysCurrentlyDown_.size == 0) { | |
260 this.keysPressedTogether_.clear(); | |
261 this.didTrackMouse_ = false; | |
262 } | |
263 }, | |
264 | |
265 /** | |
266 * Set up event listeners for mouse ane keyboard events. These are | |
David Tseng
2017/05/16 15:54:41
s/ane/and
dmazzoni
2017/05/16 18:23:48
Done.
| |
267 * forwarded to us from the SelectToSpeakEventHandler so they should | |
268 * be interpreted as global events on the whole screen, not local to | |
269 * any particular window. | |
270 */ | |
271 setUpEventListeners_: function() { | |
272 document.addEventListener('keydown', this.onKeyDown_.bind(this)); | |
273 document.addEventListener('keyup', this.onKeyUp_.bind(this)); | |
274 document.addEventListener('mousedown', this.onMouseDown_.bind(this)); | |
275 document.addEventListener('mousemove', this.onMouseMove_.bind(this)); | |
276 document.addEventListener('mouseup', this.onMouseUp_.bind(this)); | |
277 }, | |
278 | |
279 /** | |
170 * Finds all nodes within the subtree rooted at |node| that overlap | 280 * Finds all nodes within the subtree rooted at |node| that overlap |
171 * a given rectangle. | 281 * a given rectangle. |
172 * @param {AutomationNode} node The starting node. | 282 * @param {AutomationNode} node The starting node. |
173 * @param {{left: number, top: number, width: number, height: number}} rect | 283 * @param {{left: number, top: number, width: number, height: number}} rect |
174 * The bounding box to search. | 284 * The bounding box to search. |
175 * @param {Array<AutomationNode>} nodes The matching node array to be | 285 * @param {Array<AutomationNode>} nodes The matching node array to be |
176 * populated. | 286 * populated. |
177 * @return {boolean} True if any matches are found. | 287 * @return {boolean} True if any matches are found. |
178 */ | 288 */ |
179 findAllMatching_: function(node, rect, nodes) { | 289 findAllMatching_: function(node, rect, nodes) { |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
276 this.updateDefaultVoice_(); | 386 this.updateDefaultVoice_(); |
277 window.speechSynthesis.onvoiceschanged = (function() { | 387 window.speechSynthesis.onvoiceschanged = (function() { |
278 this.updateDefaultVoice_(); | 388 this.updateDefaultVoice_(); |
279 }).bind(this); | 389 }).bind(this); |
280 }, | 390 }, |
281 | 391 |
282 /** | 392 /** |
283 * Get the list of TTS voices, and set the default voice if not already set. | 393 * Get the list of TTS voices, and set the default voice if not already set. |
284 */ | 394 */ |
285 updateDefaultVoice_: function() { | 395 updateDefaultVoice_: function() { |
286 console.log('updateDefaultVoice_ ' + this.down_); | |
287 var uiLocale = chrome.i18n.getMessage('@@ui_locale'); | 396 var uiLocale = chrome.i18n.getMessage('@@ui_locale'); |
288 uiLocale = uiLocale.replace('_', '-').toLowerCase(); | 397 uiLocale = uiLocale.replace('_', '-').toLowerCase(); |
David Tseng
2017/05/16 15:54:40
For future cl. An extjs test would be appropriate
dmazzoni
2017/05/16 18:23:48
Sounds good
| |
289 | 398 |
290 chrome.tts.getVoices((function(voices) { | 399 chrome.tts.getVoices((function(voices) { |
291 console.log('updateDefaultVoice_ voices: ' + voices.length); | 400 console.log('updateDefaultVoice_ voices: ' + voices.length); |
292 this.validVoiceNames_ = new Set(); | 401 this.validVoiceNames_ = new Set(); |
293 | 402 |
294 if (voices.length == 0) | 403 if (voices.length == 0) |
295 return; | 404 return; |
296 | 405 |
297 voices.forEach((function(voice) { | 406 voices.forEach((function(voice) { |
298 this.validVoiceNames_.add(voice.voiceName); | 407 this.validVoiceNames_.add(voice.voiceName); |
(...skipping 15 matching lines...) Expand all Loading... | |
314 this.voiceNameFromLocale_ = voices[0].voiceName; | 423 this.voiceNameFromLocale_ = voices[0].voiceName; |
315 | 424 |
316 chrome.storage.sync.get(['voice'], (function(prefs) { | 425 chrome.storage.sync.get(['voice'], (function(prefs) { |
317 if (!prefs['voice']) { | 426 if (!prefs['voice']) { |
318 chrome.storage.sync.set({'voice': voices[0].voiceName}); | 427 chrome.storage.sync.set({'voice': voices[0].voiceName}); |
319 } | 428 } |
320 }).bind(this)); | 429 }).bind(this)); |
321 }).bind(this)); | 430 }).bind(this)); |
322 } | 431 } |
323 }; | 432 }; |
OLD | NEW |