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

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: closure 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
« no previous file with comments | « chrome/browser/chromeos/accessibility/select_to_speak_event_handler_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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.isSearchKeyDown_ = false;
63
64 /** @private { !Set<number> } */
65 this.keysCurrentlyDown_ = new Set();
66
67 /** @private { !Set<number> } */
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;
122 * we always return false because we don't want any other event
123 * handlers to run.
97 */ 124 */
98 onMousePressed_: function(evt) { 125 onMouseDown_: function(evt) {
99 this.down_ = true; 126 if (!this.isSearchKeyDown_)
100 this.mouseStart_ = {x: evt.mouseX, y: evt.mouseY}; 127 return false;
101 this.startNode_ = evt.target; 128
129 this.trackingMouse_ = true;
130 this.didTrackMouse_ = true;
131 this.mouseStart_ = {x: evt.screenX, y: evt.screenY};
102 chrome.tts.stop(); 132 chrome.tts.stop();
103 this.onMouseDragged_(evt); 133
134 // Fire a hit test event on click to warm up the cache.
135 this.desktop_.hitTest(evt.screenX, evt.screenY, EventType.MOUSE_PRESSED);
136
137 this.onMouseMove_(evt);
138 return false;
104 }, 139 },
105 140
106 /** 141 /**
107 * Called when the mouse is moved or dragged and the user is in a 142 * 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 143 * mode where select-to-speak is capturing mouse events (for example
109 * holding down Search). 144 * holding down Search).
110 * 145 *
111 * @param {!AutomationEvent} evt 146 * @param {!Event} evt The DOM event
147 * @return {boolean} True if the default action should be performed.
112 */ 148 */
113 onMouseDragged_: function(evt) { 149 onMouseMove_: function(evt) {
114 if (!this.down_) 150 if (!this.trackingMouse_)
115 return; 151 return false;
116 152
117 var rect = rectFromPoints( 153 var rect = rectFromPoints(
118 this.mouseStart_.x, this.mouseStart_.y, 154 this.mouseStart_.x, this.mouseStart_.y,
119 evt.mouseX, evt.mouseY); 155 evt.screenX, evt.screenY);
120 chrome.accessibilityPrivate.setFocusRing([rect], this.color_); 156 chrome.accessibilityPrivate.setFocusRing([rect], this.color_);
157 return false;
121 }, 158 },
122 159
123 /** 160 /**
124 * Called when the mouse is released and the user is in a 161 * Called when the mouse is released and the user is in a
125 * mode where select-to-speak is capturing mouse events (for example 162 * mode where select-to-speak is capturing mouse events (for example
126 * holding down Search). 163 * holding down Search).
127 * 164 *
128 * @param {!AutomationEvent} evt 165 * @param {!Event} evt
166 * @return {boolean} True if the default action should be performed.
129 */ 167 */
130 onMouseReleased_: function(evt) { 168 onMouseUp_: function(evt) {
131 this.onMouseDragged_(evt); 169 this.onMouseMove_(evt);
132 this.down_ = false; 170 this.trackingMouse_ = false;
133 171
134 chrome.accessibilityPrivate.setFocusRing([]); 172 chrome.accessibilityPrivate.setFocusRing([]);
135 173
136 // Walk up to the nearest window, web area, or dialog that the 174 this.mouseEnd_ = {x: evt.screenX, y: evt.screenY};
175 var ctrX = Math.floor((this.mouseStart_.x + this.mouseEnd_.x) / 2);
176 var ctrY = Math.floor((this.mouseStart_.y + this.mouseEnd_.y) / 2);
177
178 // Do a hit test at the center of the area the user dragged over.
179 // This will give us some context when searching the accessibility tree.
180 // The hit test will result in a EventType.MOUSE_RELEASED event being
181 // fired on the result of that hit test, which will trigger
182 // onAutomationHitTest_.
183 this.desktop_.hitTest(ctrX, ctrY, EventType.MOUSE_RELEASED);
184 return false;
185 },
186
187 /**
188 * Called in response to our hit test after the mouse is released,
189 * when the user is in a mode where select-to-speak is capturing
190 * mouse events (for example holding down Search).
191 *
192 * @param {!AutomationEvent} evt The automation event.
193 */
194 onAutomationHitTest_: function(evt) {
195 // Walk up to the nearest window, web area, toolbar, or dialog that the
137 // hit node is contained inside. Only speak objects within that 196 // hit node is contained inside. Only speak objects within that
138 // container. In the future we might include other container-like 197 // container. In the future we might include other container-like
139 // roles here. 198 // roles here.
140 var root = this.startNode_; 199 var root = evt.target;
141 while (root.parent && 200 while (root.parent &&
142 root.role != RoleType.WINDOW && 201 root.role != RoleType.WINDOW &&
143 root.role != RoleType.ROOT_WEB_AREA && 202 root.role != RoleType.ROOT_WEB_AREA &&
144 root.role != RoleType.DESKTOP && 203 root.role != RoleType.DESKTOP &&
145 root.role != RoleType.DIALOG) { 204 root.role != RoleType.DIALOG &&
205 root.role != RoleType.ALERT_DIALOG &&
206 root.role != RoleType.TOOLBAR) {
146 root = root.parent; 207 root = root.parent;
147 } 208 }
148 209
149 var rect = rectFromPoints( 210 var rect = rectFromPoints(
150 this.mouseStart_.x, this.mouseStart_.y, 211 this.mouseStart_.x, this.mouseStart_.y,
151 evt.mouseX, evt.mouseY); 212 this.mouseEnd_.x, this.mouseEnd_.y);
152 var nodes = []; 213 var nodes = [];
153 this.findAllMatching_(root, rect, nodes); 214 this.findAllMatching_(root, rect, nodes);
154 this.startSpeechQueue_(nodes); 215 this.startSpeechQueue_(nodes);
155 }, 216 },
156 217
157 /** 218 /**
158 * Called when the user cancels select-to-speak's capturing of mouse 219 * @param {!Event} evt
159 * events (for example by releasing Search while the mouse is still down).
160 *
161 * @param {!AutomationEvent} evt
162 */ 220 */
163 onMouseCanceled_: function(evt) { 221 onKeyDown_: function(evt) {
164 this.down_ = false; 222 if (this.keysPressedTogether_.size == 0 &&
165 chrome.accessibilityPrivate.setFocusRing([]); 223 evt.keyCode == SelectToSpeak.SEARCH_KEY_CODE) {
166 chrome.tts.stop(); 224 this.isSearchKeyDown_ = true;
225 } else if (!this.trackingMouse_) {
226 this.isSearchKeyDown_ = false;
227 }
228
229 this.keysCurrentlyDown_.add(evt.keyCode);
230 this.keysPressedTogether_.add(evt.keyCode);
167 }, 231 },
168 232
169 /** 233 /**
234 * @param {!Event} evt
235 */
236 onKeyUp_: function(evt) {
237 if (evt.keyCode == SelectToSpeak.SEARCH_KEY_CODE) {
238 this.isSearchKeyDown_ = false;
239
240 // If we were in the middle of tracking the mouse, cancel it.
241 if (this.trackingMouse_) {
242 this.trackingMouse_ = false;
243 chrome.accessibilityPrivate.setFocusRing([]);
244 chrome.tts.stop();
245 }
246 }
247
248 // Stop speech when the user taps and releases Control or Search
249 // without using the mouse or pressing any other keys along the way.
250 if (!this.didTrackMouse_ &&
251 (evt.keyCode == SelectToSpeak.SEARCH_KEY_CODE ||
252 evt.keyCode == SelectToSpeak.CONTROL_KEY_CODE) &&
253 this.keysPressedTogether_.has(evt.keyCode) &&
254 this.keysPressedTogether_.size == 1) {
255 this.trackingMouse_ = false;
256 chrome.accessibilityPrivate.setFocusRing([]);
257 chrome.tts.stop();
258 }
259
260 this.keysCurrentlyDown_.delete(evt.keyCode);
261 if (this.keysCurrentlyDown_.size == 0) {
262 this.keysPressedTogether_.clear();
263 this.didTrackMouse_ = false;
264 }
265 },
266
267 /**
268 * Set up event listeners for mouse and keyboard events. These are
269 * forwarded to us from the SelectToSpeakEventHandler so they should
270 * be interpreted as global events on the whole screen, not local to
271 * any particular window.
272 */
273 setUpEventListeners_: function() {
274 document.addEventListener('keydown', this.onKeyDown_.bind(this));
275 document.addEventListener('keyup', this.onKeyUp_.bind(this));
276 document.addEventListener('mousedown', this.onMouseDown_.bind(this));
277 document.addEventListener('mousemove', this.onMouseMove_.bind(this));
278 document.addEventListener('mouseup', this.onMouseUp_.bind(this));
279 },
280
281 /**
170 * Finds all nodes within the subtree rooted at |node| that overlap 282 * Finds all nodes within the subtree rooted at |node| that overlap
171 * a given rectangle. 283 * a given rectangle.
172 * @param {AutomationNode} node The starting node. 284 * @param {AutomationNode} node The starting node.
173 * @param {{left: number, top: number, width: number, height: number}} rect 285 * @param {{left: number, top: number, width: number, height: number}} rect
174 * The bounding box to search. 286 * The bounding box to search.
175 * @param {Array<AutomationNode>} nodes The matching node array to be 287 * @param {Array<AutomationNode>} nodes The matching node array to be
176 * populated. 288 * populated.
177 * @return {boolean} True if any matches are found. 289 * @return {boolean} True if any matches are found.
178 */ 290 */
179 findAllMatching_: function(node, rect, nodes) { 291 findAllMatching_: function(node, rect, nodes) {
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
276 this.updateDefaultVoice_(); 388 this.updateDefaultVoice_();
277 window.speechSynthesis.onvoiceschanged = (function() { 389 window.speechSynthesis.onvoiceschanged = (function() {
278 this.updateDefaultVoice_(); 390 this.updateDefaultVoice_();
279 }).bind(this); 391 }).bind(this);
280 }, 392 },
281 393
282 /** 394 /**
283 * Get the list of TTS voices, and set the default voice if not already set. 395 * Get the list of TTS voices, and set the default voice if not already set.
284 */ 396 */
285 updateDefaultVoice_: function() { 397 updateDefaultVoice_: function() {
286 console.log('updateDefaultVoice_ ' + this.down_);
287 var uiLocale = chrome.i18n.getMessage('@@ui_locale'); 398 var uiLocale = chrome.i18n.getMessage('@@ui_locale');
288 uiLocale = uiLocale.replace('_', '-').toLowerCase(); 399 uiLocale = uiLocale.replace('_', '-').toLowerCase();
289 400
290 chrome.tts.getVoices((function(voices) { 401 chrome.tts.getVoices((function(voices) {
291 console.log('updateDefaultVoice_ voices: ' + voices.length); 402 console.log('updateDefaultVoice_ voices: ' + voices.length);
292 this.validVoiceNames_ = new Set(); 403 this.validVoiceNames_ = new Set();
293 404
294 if (voices.length == 0) 405 if (voices.length == 0)
295 return; 406 return;
296 407
(...skipping 17 matching lines...) Expand all
314 this.voiceNameFromLocale_ = voices[0].voiceName; 425 this.voiceNameFromLocale_ = voices[0].voiceName;
315 426
316 chrome.storage.sync.get(['voice'], (function(prefs) { 427 chrome.storage.sync.get(['voice'], (function(prefs) {
317 if (!prefs['voice']) { 428 if (!prefs['voice']) {
318 chrome.storage.sync.set({'voice': voices[0].voiceName}); 429 chrome.storage.sync.set({'voice': voices[0].voiceName});
319 } 430 }
320 }).bind(this)); 431 }).bind(this));
321 }).bind(this)); 432 }).bind(this));
322 } 433 }
323 }; 434 };
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/accessibility/select_to_speak_event_handler_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698