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 /** |
11 * Return the rect that encloses two points. | 11 * Return the rect that encloses two points. |
12 * @param {number} x1 The first x coordinate. | 12 * @param {number} x1 The first x coordinate. |
13 * @param {number} y1 The first y coordinate. | 13 * @param {number} y1 The first y coordinate. |
14 * @param {number} x2 The second x coordinate. | 14 * @param {number} x2 The second x coordinate. |
15 * @param {number} y2 The second x coordinate. | 15 * @param {number} y2 The second x coordinate. |
16 * @return {{left: number, top: number, width: number, height: number}} | 16 * @return {{left: number, top: number, width: number, height: number}} |
17 */ | 17 */ |
18 function rectFromPoints(x1, y1, x2, y2) { | 18 function rectFromPoints(x1, y1, x2, y2) { |
19 var left = Math.min(x1, x2); | 19 var left = Math.min(x1, x2); |
20 var right = Math.max(x1, x2); | 20 var right = Math.max(x1, x2); |
21 var top = Math.min(y1, y2); | 21 var top = Math.min(y1, y2); |
22 var bottom = Math.max(y1, y2); | 22 var bottom = Math.max(y1, y2); |
23 return {left: left, | 23 return {left: left, top: top, width: right - left, height: bottom - top}; |
24 top: top, | |
25 width: right - left, | |
26 height: bottom - top}; | |
27 } | 24 } |
28 | 25 |
29 /** | 26 /** |
30 * Returns true if |rect1| and |rect2| overlap. The rects must define | 27 * Returns true if |rect1| and |rect2| overlap. The rects must define |
31 * left, top, width, and height. | 28 * left, top, width, and height. |
32 * @param {{left: number, top: number, width: number, height: number}} rect1 | 29 * @param {{left: number, top: number, width: number, height: number}} rect1 |
33 * @param {{left: number, top: number, width: number, height: number}} rect2 | 30 * @param {{left: number, top: number, width: number, height: number}} rect2 |
34 * @return {boolean} True if the rects overlap. | 31 * @return {boolean} True if the rects overlap. |
35 */ | 32 */ |
36 function overlaps(rect1, rect2) { | 33 function overlaps(rect1, rect2) { |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
74 this.mouseEnd_ = {x: 0, y: 0}; | 71 this.mouseEnd_ = {x: 0, y: 0}; |
75 | 72 |
76 /** @private {AutomationRootNode} */ | 73 /** @private {AutomationRootNode} */ |
77 chrome.automation.getDesktop(function(desktop) { | 74 chrome.automation.getDesktop(function(desktop) { |
78 this.desktop_ = desktop; | 75 this.desktop_ = desktop; |
79 | 76 |
80 // After the user selects a region of the screen, we do a hit test at | 77 // 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 | 78 // the center of that box using the automation API. The result of the |
82 // hit test is a MOUSE_RELEASED accessibility event. | 79 // hit test is a MOUSE_RELEASED accessibility event. |
83 desktop.addEventListener( | 80 desktop.addEventListener( |
84 EventType.MOUSE_RELEASED, this.onAutomationHitTest_.bind(this), | 81 EventType.MOUSE_RELEASED, this.onAutomationHitTest_.bind(this), true); |
85 true); | |
86 }.bind(this)); | 82 }.bind(this)); |
87 | 83 |
88 /** @private { ?string } */ | 84 /** @private { ?string } */ |
89 this.voiceNameFromPrefs_ = null; | 85 this.voiceNameFromPrefs_ = null; |
90 | 86 |
91 /** @private { ?string } */ | 87 /** @private { ?string } */ |
92 this.voiceNameFromLocale_ = null; | 88 this.voiceNameFromLocale_ = null; |
93 | 89 |
94 /** @private { Set<string> } */ | 90 /** @private { Set<string> } */ |
95 this.validVoiceNames_ = new Set(); | 91 this.validVoiceNames_ = new Set(); |
96 | 92 |
97 /** @private { number } */ | 93 /** @private { number } */ |
98 this.speechRate_ = 1.0; | 94 this.speechRate_ = 1.0; |
99 | 95 |
100 /** @const { string } */ | 96 /** @const { string } */ |
101 this.color_ = "#f73a98"; | 97 this.color_ = '#f73a98'; |
102 | 98 |
103 this.initPreferences_(); | 99 this.initPreferences_(); |
104 | 100 |
105 this.setUpEventListeners_(); | 101 this.setUpEventListeners_(); |
106 }; | 102 }; |
107 | 103 |
108 /** @const {number} */ | 104 /** @const {number} */ |
109 SelectToSpeak.SEARCH_KEY_CODE = 91; | 105 SelectToSpeak.SEARCH_KEY_CODE = 91; |
110 | 106 |
111 /** @const {number} */ | 107 /** @const {number} */ |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 * holding down Search). | 140 * holding down Search). |
145 * | 141 * |
146 * @param {!Event} evt The DOM event | 142 * @param {!Event} evt The DOM event |
147 * @return {boolean} True if the default action should be performed. | 143 * @return {boolean} True if the default action should be performed. |
148 */ | 144 */ |
149 onMouseMove_: function(evt) { | 145 onMouseMove_: function(evt) { |
150 if (!this.trackingMouse_) | 146 if (!this.trackingMouse_) |
151 return false; | 147 return false; |
152 | 148 |
153 var rect = rectFromPoints( | 149 var rect = rectFromPoints( |
154 this.mouseStart_.x, this.mouseStart_.y, | 150 this.mouseStart_.x, this.mouseStart_.y, evt.screenX, evt.screenY); |
155 evt.screenX, evt.screenY); | |
156 chrome.accessibilityPrivate.setFocusRing([rect], this.color_); | 151 chrome.accessibilityPrivate.setFocusRing([rect], this.color_); |
157 return false; | 152 return false; |
158 }, | 153 }, |
159 | 154 |
160 /** | 155 /** |
161 * Called when the mouse is released and the user is in a | 156 * Called when the mouse is released and the user is in a |
162 * mode where select-to-speak is capturing mouse events (for example | 157 * mode where select-to-speak is capturing mouse events (for example |
163 * holding down Search). | 158 * holding down Search). |
164 * | 159 * |
165 * @param {!Event} evt | 160 * @param {!Event} evt |
(...skipping 24 matching lines...) Expand all Loading... |
190 * mouse events (for example holding down Search). | 185 * mouse events (for example holding down Search). |
191 * | 186 * |
192 * @param {!AutomationEvent} evt The automation event. | 187 * @param {!AutomationEvent} evt The automation event. |
193 */ | 188 */ |
194 onAutomationHitTest_: function(evt) { | 189 onAutomationHitTest_: function(evt) { |
195 // Walk up to the nearest window, web area, toolbar, or dialog that the | 190 // Walk up to the nearest window, web area, toolbar, or dialog that the |
196 // hit node is contained inside. Only speak objects within that | 191 // hit node is contained inside. Only speak objects within that |
197 // container. In the future we might include other container-like | 192 // container. In the future we might include other container-like |
198 // roles here. | 193 // roles here. |
199 var root = evt.target; | 194 var root = evt.target; |
200 while (root.parent && | 195 while (root.parent && root.role != RoleType.WINDOW && |
201 root.role != RoleType.WINDOW && | 196 root.role != RoleType.ROOT_WEB_AREA && |
202 root.role != RoleType.ROOT_WEB_AREA && | 197 root.role != RoleType.DESKTOP && root.role != RoleType.DIALOG && |
203 root.role != RoleType.DESKTOP && | 198 root.role != RoleType.ALERT_DIALOG && |
204 root.role != RoleType.DIALOG && | 199 root.role != RoleType.TOOLBAR) { |
205 root.role != RoleType.ALERT_DIALOG && | |
206 root.role != RoleType.TOOLBAR) { | |
207 root = root.parent; | 200 root = root.parent; |
208 } | 201 } |
209 | 202 |
210 var rect = rectFromPoints( | 203 var rect = rectFromPoints( |
211 this.mouseStart_.x, this.mouseStart_.y, | 204 this.mouseStart_.x, this.mouseStart_.y, this.mouseEnd_.x, |
212 this.mouseEnd_.x, this.mouseEnd_.y); | 205 this.mouseEnd_.y); |
213 var nodes = []; | 206 var nodes = []; |
214 this.findAllMatching_(root, rect, nodes); | 207 this.findAllMatching_(root, rect, nodes); |
215 this.startSpeechQueue_(nodes); | 208 this.startSpeechQueue_(nodes); |
216 }, | 209 }, |
217 | 210 |
218 /** | 211 /** |
219 * @param {!Event} evt | 212 * @param {!Event} evt |
220 */ | 213 */ |
221 onKeyDown_: function(evt) { | 214 onKeyDown_: function(evt) { |
222 if (this.keysPressedTogether_.size == 0 && | 215 if (this.keysPressedTogether_.size == 0 && |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 */ | 308 */ |
316 startSpeechQueue_: function(nodes) { | 309 startSpeechQueue_: function(nodes) { |
317 chrome.tts.stop(); | 310 chrome.tts.stop(); |
318 for (var i = 0; i < nodes.length; i++) { | 311 for (var i = 0; i < nodes.length; i++) { |
319 var node = nodes[i]; | 312 var node = nodes[i]; |
320 var isLast = (i == nodes.length - 1); | 313 var isLast = (i == nodes.length - 1); |
321 | 314 |
322 var options = { | 315 var options = { |
323 rate: this.rate_, | 316 rate: this.rate_, |
324 'enqueue': true, | 317 'enqueue': true, |
325 onEvent: (function(node, isLast, event) { | 318 onEvent: |
326 if (event.type == 'start') { | 319 (function(node, isLast, event) { |
327 chrome.accessibilityPrivate.setFocusRing( | 320 if (event.type == 'start') { |
328 [node.location], this.color_); | 321 chrome.accessibilityPrivate.setFocusRing( |
329 } else if (event.type == 'interrupted' || | 322 [node.location], this.color_); |
330 event.type == 'cancelled') { | 323 } else if ( |
331 chrome.accessibilityPrivate.setFocusRing([]); | 324 event.type == 'interrupted' || event.type == 'cancelled') { |
332 } else if (event.type == 'end') { | 325 chrome.accessibilityPrivate.setFocusRing([]); |
333 if (isLast) { | 326 } else if (event.type == 'end') { |
334 chrome.accessibilityPrivate.setFocusRing([]); | 327 if (isLast) { |
335 } | 328 chrome.accessibilityPrivate.setFocusRing([]); |
336 } | 329 } |
337 }).bind(this, node, isLast) | 330 } |
| 331 }).bind(this, node, isLast) |
338 }; | 332 }; |
339 | 333 |
340 // Pick the voice name from prefs first, or the one that matches | 334 // Pick the voice name from prefs first, or the one that matches |
341 // the locale next, but don't pick a voice that isn't currently | 335 // the locale next, but don't pick a voice that isn't currently |
342 // loaded. If no voices are found, leave the voiceName option | 336 // loaded. If no voices are found, leave the voiceName option |
343 // unset to let the browser try to route the speech request | 337 // unset to let the browser try to route the speech request |
344 // anyway if possible. | 338 // anyway if possible. |
345 console.log('Pref: ' + this.voiceNameFromPrefs_); | 339 console.log('Pref: ' + this.voiceNameFromPrefs_); |
346 console.log('Locale: ' + this.voiceNameFromLocale_); | 340 console.log('Locale: ' + this.voiceNameFromLocale_); |
347 var valid = ''; | 341 var valid = ''; |
348 this.validVoiceNames_.forEach(function(voiceName) { | 342 this.validVoiceNames_.forEach(function(voiceName) { |
349 if (valid) | 343 if (valid) |
350 valid += ','; | 344 valid += ','; |
351 valid += voiceName; | 345 valid += voiceName; |
352 }); | 346 }); |
353 console.log('Valid: ' + valid); | 347 console.log('Valid: ' + valid); |
354 if (this.voiceNameFromPrefs_ && | 348 if (this.voiceNameFromPrefs_ && |
355 this.validVoiceNames_.has(this.voiceNameFromPrefs_)) { | 349 this.validVoiceNames_.has(this.voiceNameFromPrefs_)) { |
356 options['voiceName'] = this.voiceNameFromPrefs_; | 350 options['voiceName'] = this.voiceNameFromPrefs_; |
357 } else if (this.voiceNameFromLocale_ && | 351 } else if ( |
| 352 this.voiceNameFromLocale_ && |
358 this.validVoiceNames_.has(this.voiceNameFromLocale_)) { | 353 this.validVoiceNames_.has(this.voiceNameFromLocale_)) { |
359 options['voiceName'] = this.voiceNameFromLocale_; | 354 options['voiceName'] = this.voiceNameFromLocale_; |
360 } | 355 } |
361 | 356 |
362 chrome.tts.speak(node.name || '', options); | 357 chrome.tts.speak(node.name || '', options); |
363 } | 358 } |
364 }, | 359 }, |
365 | 360 |
366 /** | 361 /** |
367 * Loads preferences from chrome.storage, sets default values if | 362 * Loads preferences from chrome.storage, sets default values if |
368 * necessary, and registers a listener to update prefs when they | 363 * necessary, and registers a listener to update prefs when they |
369 * change. | 364 * change. |
370 */ | 365 */ |
371 initPreferences_: function() { | 366 initPreferences_: function() { |
372 var updatePrefs = (function() { | 367 var updatePrefs = (function() { |
373 chrome.storage.sync.get(['voice', 'rate'], (function(prefs) { | 368 chrome.storage.sync.get( |
374 if (prefs['voice']) { | 369 ['voice', 'rate'], |
375 this.voiceNameFromPrefs_ = prefs['voice']; | 370 (function(prefs) { |
376 } | 371 if (prefs['voice']) { |
377 if (prefs['rate']) { | 372 this.voiceNameFromPrefs_ = prefs['voice']; |
378 this.rate_ = parseFloat(prefs['rate']); | 373 } |
379 } else { | 374 if (prefs['rate']) { |
380 chrome.storage.sync.set({'rate': this.rate_}); | 375 this.rate_ = parseFloat(prefs['rate']); |
381 } | 376 } else { |
382 }).bind(this)); | 377 chrome.storage.sync.set({'rate': this.rate_}); |
383 }).bind(this); | 378 } |
| 379 }).bind(this)); |
| 380 }).bind(this); |
384 | 381 |
385 updatePrefs(); | 382 updatePrefs(); |
386 chrome.storage.onChanged.addListener(updatePrefs); | 383 chrome.storage.onChanged.addListener(updatePrefs); |
387 | 384 |
388 this.updateDefaultVoice_(); | 385 this.updateDefaultVoice_(); |
389 window.speechSynthesis.onvoiceschanged = (function() { | 386 window.speechSynthesis.onvoiceschanged = (function() { |
390 this.updateDefaultVoice_(); | 387 this.updateDefaultVoice_(); |
391 }).bind(this); | 388 }).bind(this); |
392 }, | 389 }, |
393 | 390 |
394 /** | 391 /** |
395 * Get the list of TTS voices, and set the default voice if not already set. | 392 * Get the list of TTS voices, and set the default voice if not already set. |
396 */ | 393 */ |
397 updateDefaultVoice_: function() { | 394 updateDefaultVoice_: function() { |
398 var uiLocale = chrome.i18n.getMessage('@@ui_locale'); | 395 var uiLocale = chrome.i18n.getMessage('@@ui_locale'); |
399 uiLocale = uiLocale.replace('_', '-').toLowerCase(); | 396 uiLocale = uiLocale.replace('_', '-').toLowerCase(); |
400 | 397 |
401 chrome.tts.getVoices((function(voices) { | 398 chrome.tts.getVoices( |
402 console.log('updateDefaultVoice_ voices: ' + voices.length); | 399 (function(voices) { |
403 this.validVoiceNames_ = new Set(); | 400 console.log('updateDefaultVoice_ voices: ' + voices.length); |
| 401 this.validVoiceNames_ = new Set(); |
404 | 402 |
405 if (voices.length == 0) | 403 if (voices.length == 0) |
406 return; | 404 return; |
407 | 405 |
408 voices.forEach((function(voice) { | 406 voices.forEach((function(voice) { |
409 this.validVoiceNames_.add(voice.voiceName); | 407 this.validVoiceNames_.add(voice.voiceName); |
410 }).bind(this)); | 408 }).bind(this)); |
411 | 409 |
412 voices.sort(function(a, b) { | 410 voices.sort(function(a, b) { |
413 function score(voice) { | 411 function score(voice) { |
414 var lang = voice.lang.toLowerCase(); | 412 var lang = voice.lang.toLowerCase(); |
415 var s = 0; | 413 var s = 0; |
416 if (lang == uiLocale) | 414 if (lang == uiLocale) |
417 s += 2; | 415 s += 2; |
418 if (lang.substr(0, 2) == uiLocale.substr(0, 2)) | 416 if (lang.substr(0, 2) == uiLocale.substr(0, 2)) |
419 s += 1; | 417 s += 1; |
420 return s; | 418 return s; |
421 } | 419 } |
422 return score(b) - score(a); | 420 return score(b) - score(a); |
423 }); | 421 }); |
424 | 422 |
425 this.voiceNameFromLocale_ = voices[0].voiceName; | 423 this.voiceNameFromLocale_ = voices[0].voiceName; |
426 | 424 |
427 chrome.storage.sync.get(['voice'], (function(prefs) { | 425 chrome.storage.sync.get( |
428 if (!prefs['voice']) { | 426 ['voice'], |
429 chrome.storage.sync.set({'voice': voices[0].voiceName}); | 427 (function(prefs) { |
430 } | 428 if (!prefs['voice']) { |
431 }).bind(this)); | 429 chrome.storage.sync.set({'voice': voices[0].voiceName}); |
432 }).bind(this)); | 430 } |
| 431 }).bind(this)); |
| 432 }).bind(this)); |
433 } | 433 } |
434 }; | 434 }; |
OLD | NEW |