Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js

Issue 2814213002: Refactor Select-to-speak so that mouse events are forwarded to the extension. (Closed)
Patch Set: Fix closure compiler warnings Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698