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 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /* eslint-disable no-restricted-properties */ | 7 /* eslint-disable no-restricted-properties */ |
8 | 8 |
9 /** | 9 /** |
10 * @fileoverview This is the audio client content script injected into eligible | 10 * @fileoverview This is the audio client content script injected into eligible |
11 * Google.com and New tab pages for interaction between the Webpage and the | 11 * Google.com and New tab pages for interaction between the Webpage and the |
12 * Hotword extension. | 12 * Hotword extension. |
13 */ | 13 */ |
14 | 14 |
15 (function() { | 15 (function() { |
| 16 /** |
| 17 * @constructor |
| 18 */ |
| 19 var AudioClient = function() { |
| 20 /** @private {Element} */ |
| 21 this.speechOverlay_ = null; |
| 22 |
| 23 /** @private {number} */ |
| 24 this.checkSpeechUiRetries_ = 0; |
| 25 |
16 /** | 26 /** |
17 * @constructor | 27 * Port used to communicate with the audio manager. |
| 28 * @private {?Port} |
18 */ | 29 */ |
19 var AudioClient = function() { | 30 this.port_ = null; |
20 /** @private {Element} */ | 31 |
21 this.speechOverlay_ = null; | 32 /** |
22 | 33 * Keeps track of the effects of different commands. Used to verify that |
23 /** @private {number} */ | 34 * proper UIs are shown to the user. |
| 35 * @private {Object<AudioClient.CommandToPage, Object>} |
| 36 */ |
| 37 this.uiStatus_ = null; |
| 38 |
| 39 /** |
| 40 * Bound function used to handle commands sent from the page to this script. |
| 41 * @private {Function} |
| 42 */ |
| 43 this.handleCommandFromPageFunc_ = null; |
| 44 }; |
| 45 |
| 46 /** |
| 47 * Messages sent to the page to control the voice search UI. |
| 48 * @enum {string} |
| 49 */ |
| 50 AudioClient.CommandToPage = { |
| 51 HOTWORD_VOICE_TRIGGER: 'vt', |
| 52 HOTWORD_STARTED: 'hs', |
| 53 HOTWORD_ENDED: 'hd', |
| 54 HOTWORD_TIMEOUT: 'ht', |
| 55 HOTWORD_ERROR: 'he' |
| 56 }; |
| 57 |
| 58 /** |
| 59 * Messages received from the page used to indicate voice search state. |
| 60 * @enum {string} |
| 61 */ |
| 62 AudioClient.CommandFromPage = { |
| 63 SPEECH_START: 'ss', |
| 64 SPEECH_END: 'se', |
| 65 SPEECH_RESET: 'sr', |
| 66 SHOWING_HOTWORD_START: 'shs', |
| 67 SHOWING_ERROR_MESSAGE: 'sem', |
| 68 SHOWING_TIMEOUT_MESSAGE: 'stm', |
| 69 CLICKED_RESUME: 'hcc', |
| 70 CLICKED_RESTART: 'hcr', |
| 71 CLICKED_DEBUG: 'hcd' |
| 72 }; |
| 73 |
| 74 /** |
| 75 * Errors that are sent to the hotword extension. |
| 76 * @enum {string} |
| 77 */ |
| 78 AudioClient.Error = { |
| 79 NO_SPEECH_UI: 'ac1', |
| 80 NO_HOTWORD_STARTED_UI: 'ac2', |
| 81 NO_HOTWORD_TIMEOUT_UI: 'ac3', |
| 82 NO_HOTWORD_ERROR_UI: 'ac4' |
| 83 }; |
| 84 |
| 85 /** |
| 86 * @const {string} |
| 87 * @private |
| 88 */ |
| 89 AudioClient.HOTWORD_EXTENSION_ID_ = 'nbpagnldghgfoolbancepceaanlmhfmd'; |
| 90 |
| 91 /** |
| 92 * Number of times to retry checking a transient error. |
| 93 * @const {number} |
| 94 * @private |
| 95 */ |
| 96 AudioClient.MAX_RETRIES = 3; |
| 97 |
| 98 /** |
| 99 * Delay to wait in milliseconds before rechecking for any transient errors. |
| 100 * @const {number} |
| 101 * @private |
| 102 */ |
| 103 AudioClient.RETRY_TIME_MS_ = 2000; |
| 104 |
| 105 /** |
| 106 * DOM ID for the speech UI overlay. |
| 107 * @const {string} |
| 108 * @private |
| 109 */ |
| 110 AudioClient.SPEECH_UI_OVERLAY_ID_ = 'spch'; |
| 111 |
| 112 /** |
| 113 * @const {string} |
| 114 * @private |
| 115 */ |
| 116 AudioClient.HELP_CENTER_URL_ = |
| 117 'https://support.google.com/chrome/?p=ui_hotword_search'; |
| 118 |
| 119 /** |
| 120 * @const {string} |
| 121 * @private |
| 122 */ |
| 123 AudioClient.CLIENT_PORT_NAME_ = 'chwcpn'; |
| 124 |
| 125 /** |
| 126 * Existence of the Audio Client. |
| 127 * @const {string} |
| 128 * @private |
| 129 */ |
| 130 AudioClient.EXISTS_ = 'chwace'; |
| 131 |
| 132 /** |
| 133 * Checks for the presence of speech overlay UI DOM elements. |
| 134 * @private |
| 135 */ |
| 136 AudioClient.prototype.checkSpeechOverlayUi_ = function() { |
| 137 if (!this.speechOverlay_) { |
| 138 window.setTimeout( |
| 139 this.delayedCheckSpeechOverlayUi_.bind(this), |
| 140 AudioClient.RETRY_TIME_MS_); |
| 141 } else { |
24 this.checkSpeechUiRetries_ = 0; | 142 this.checkSpeechUiRetries_ = 0; |
25 | 143 } |
26 /** | 144 }; |
27 * Port used to communicate with the audio manager. | 145 |
28 * @private {?Port} | 146 /** |
29 */ | 147 * Function called to check for the speech UI overlay after some time has |
30 this.port_ = null; | 148 * passed since an initial check. Will either retry triggering the speech |
31 | 149 * or sends an error message depending on the number of retries. |
32 /** | 150 * @private |
33 * Keeps track of the effects of different commands. Used to verify that | 151 */ |
34 * proper UIs are shown to the user. | 152 AudioClient.prototype.delayedCheckSpeechOverlayUi_ = function() { |
35 * @private {Object<AudioClient.CommandToPage, Object>} | 153 this.speechOverlay_ = |
36 */ | 154 document.getElementById(AudioClient.SPEECH_UI_OVERLAY_ID_); |
37 this.uiStatus_ = null; | 155 if (!this.speechOverlay_) { |
38 | 156 if (this.checkSpeechUiRetries_++ < AudioClient.MAX_RETRIES) { |
39 /** | 157 this.sendCommandToPage_(AudioClient.CommandToPage.VOICE_TRIGGER); |
40 * Bound function used to handle commands sent from the page to this script. | 158 this.checkSpeechOverlayUi_(); |
41 * @private {Function} | 159 } else { |
42 */ | 160 this.sendCommandToExtension_(AudioClient.Error.NO_SPEECH_UI); |
43 this.handleCommandFromPageFunc_ = null; | 161 } |
| 162 } else { |
| 163 this.checkSpeechUiRetries_ = 0; |
| 164 } |
| 165 }; |
| 166 |
| 167 /** |
| 168 * Checks that the triggered UI is actually displayed. |
| 169 * @param {AudioClient.CommandToPage} command Command that was send. |
| 170 * @private |
| 171 */ |
| 172 AudioClient.prototype.checkUi_ = function(command) { |
| 173 this.uiStatus_[command].timeoutId = window.setTimeout( |
| 174 this.failedCheckUi_.bind(this, command), AudioClient.RETRY_TIME_MS_); |
| 175 }; |
| 176 |
| 177 /** |
| 178 * Function called when the UI verification is not called in time. Will either |
| 179 * retry the command or sends an error message, depending on the number of |
| 180 * retries for the command. |
| 181 * @param {AudioClient.CommandToPage} command Command that was sent. |
| 182 * @private |
| 183 */ |
| 184 AudioClient.prototype.failedCheckUi_ = function(command) { |
| 185 if (this.uiStatus_[command].tries++ < AudioClient.MAX_RETRIES) { |
| 186 this.sendCommandToPage_(command); |
| 187 this.checkUi_(command); |
| 188 } else { |
| 189 this.sendCommandToExtension_(this.uiStatus_[command].error); |
| 190 } |
| 191 }; |
| 192 |
| 193 /** |
| 194 * Confirm that an UI element has been shown. |
| 195 * @param {AudioClient.CommandToPage} command UI to confirm. |
| 196 * @private |
| 197 */ |
| 198 AudioClient.prototype.verifyUi_ = function(command) { |
| 199 if (this.uiStatus_[command].timeoutId) { |
| 200 window.clearTimeout(this.uiStatus_[command].timeoutId); |
| 201 this.uiStatus_[command].timeoutId = null; |
| 202 this.uiStatus_[command].tries = 0; |
| 203 } |
| 204 }; |
| 205 |
| 206 /** |
| 207 * Sends a command to the audio manager. |
| 208 * @param {string} commandStr command to send to plugin. |
| 209 * @private |
| 210 */ |
| 211 AudioClient.prototype.sendCommandToExtension_ = function(commandStr) { |
| 212 if (this.port_) |
| 213 this.port_.postMessage({'cmd': commandStr}); |
| 214 }; |
| 215 |
| 216 /** |
| 217 * Handles a message from the audio manager. |
| 218 * @param {{cmd: string}} commandObj Command from the audio manager. |
| 219 * @private |
| 220 */ |
| 221 AudioClient.prototype.handleCommandFromExtension_ = function(commandObj) { |
| 222 var command = commandObj['cmd']; |
| 223 if (command) { |
| 224 switch (command) { |
| 225 case AudioClient.CommandToPage.HOTWORD_VOICE_TRIGGER: |
| 226 this.sendCommandToPage_(command); |
| 227 this.checkSpeechOverlayUi_(); |
| 228 break; |
| 229 case AudioClient.CommandToPage.HOTWORD_STARTED: |
| 230 this.sendCommandToPage_(command); |
| 231 this.checkUi_(command); |
| 232 break; |
| 233 case AudioClient.CommandToPage.HOTWORD_ENDED: |
| 234 this.sendCommandToPage_(command); |
| 235 break; |
| 236 case AudioClient.CommandToPage.HOTWORD_TIMEOUT: |
| 237 this.sendCommandToPage_(command); |
| 238 this.checkUi_(command); |
| 239 break; |
| 240 case AudioClient.CommandToPage.HOTWORD_ERROR: |
| 241 this.sendCommandToPage_(command); |
| 242 this.checkUi_(command); |
| 243 break; |
| 244 } |
| 245 } |
| 246 }; |
| 247 |
| 248 /** |
| 249 * @param {AudioClient.CommandToPage} commandStr Command to send. |
| 250 * @private |
| 251 */ |
| 252 AudioClient.prototype.sendCommandToPage_ = function(commandStr) { |
| 253 window.postMessage({'type': commandStr}, '*'); |
| 254 }; |
| 255 |
| 256 /** |
| 257 * Handles a message from the html window. |
| 258 * @param {!MessageEvent} messageEvent Message event from the window. |
| 259 * @private |
| 260 */ |
| 261 AudioClient.prototype.handleCommandFromPage_ = function(messageEvent) { |
| 262 if (messageEvent.source == window && messageEvent.data.type) { |
| 263 var command = messageEvent.data.type; |
| 264 switch (command) { |
| 265 case AudioClient.CommandFromPage.SPEECH_START: |
| 266 this.speechActive_ = true; |
| 267 this.sendCommandToExtension_(command); |
| 268 break; |
| 269 case AudioClient.CommandFromPage.SPEECH_END: |
| 270 this.speechActive_ = false; |
| 271 this.sendCommandToExtension_(command); |
| 272 break; |
| 273 case AudioClient.CommandFromPage.SPEECH_RESET: |
| 274 this.speechActive_ = false; |
| 275 this.sendCommandToExtension_(command); |
| 276 break; |
| 277 case 'SPEECH_RESET': // Legacy, for embedded NTP. |
| 278 this.speechActive_ = false; |
| 279 this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_END); |
| 280 break; |
| 281 case AudioClient.CommandFromPage.CLICKED_RESUME: |
| 282 this.sendCommandToExtension_(command); |
| 283 break; |
| 284 case AudioClient.CommandFromPage.CLICKED_RESTART: |
| 285 this.sendCommandToExtension_(command); |
| 286 break; |
| 287 case AudioClient.CommandFromPage.CLICKED_DEBUG: |
| 288 window.open(AudioClient.HELP_CENTER_URL_, '_blank'); |
| 289 break; |
| 290 case AudioClient.CommandFromPage.SHOWING_HOTWORD_START: |
| 291 this.verifyUi_(AudioClient.CommandToPage.HOTWORD_STARTED); |
| 292 break; |
| 293 case AudioClient.CommandFromPage.SHOWING_ERROR_MESSAGE: |
| 294 this.verifyUi_(AudioClient.CommandToPage.HOTWORD_ERROR); |
| 295 break; |
| 296 case AudioClient.CommandFromPage.SHOWING_TIMEOUT_MESSAGE: |
| 297 this.verifyUi_(AudioClient.CommandToPage.HOTWORD_TIMEOUT); |
| 298 break; |
| 299 } |
| 300 } |
| 301 }; |
| 302 |
| 303 /** |
| 304 * Initialize the content script. |
| 305 */ |
| 306 AudioClient.prototype.initialize = function() { |
| 307 if (AudioClient.EXISTS_ in window) |
| 308 return; |
| 309 window[AudioClient.EXISTS_] = true; |
| 310 |
| 311 // UI verification object. |
| 312 this.uiStatus_ = {}; |
| 313 this.uiStatus_[AudioClient.CommandToPage.HOTWORD_STARTED] = { |
| 314 timeoutId: null, |
| 315 tries: 0, |
| 316 error: AudioClient.Error.NO_HOTWORD_STARTED_UI |
44 }; | 317 }; |
45 | 318 this.uiStatus_[AudioClient.CommandToPage.HOTWORD_TIMEOUT] = { |
46 /** | 319 timeoutId: null, |
47 * Messages sent to the page to control the voice search UI. | 320 tries: 0, |
48 * @enum {string} | 321 error: AudioClient.Error.NO_HOTWORD_TIMEOUT_UI |
49 */ | |
50 AudioClient.CommandToPage = { | |
51 HOTWORD_VOICE_TRIGGER: 'vt', | |
52 HOTWORD_STARTED: 'hs', | |
53 HOTWORD_ENDED: 'hd', | |
54 HOTWORD_TIMEOUT: 'ht', | |
55 HOTWORD_ERROR: 'he' | |
56 }; | 322 }; |
57 | 323 this.uiStatus_[AudioClient.CommandToPage.HOTWORD_ERROR] = { |
58 /** | 324 timeoutId: null, |
59 * Messages received from the page used to indicate voice search state. | 325 tries: 0, |
60 * @enum {string} | 326 error: AudioClient.Error.NO_HOTWORD_ERROR_UI |
61 */ | |
62 AudioClient.CommandFromPage = { | |
63 SPEECH_START: 'ss', | |
64 SPEECH_END: 'se', | |
65 SPEECH_RESET: 'sr', | |
66 SHOWING_HOTWORD_START: 'shs', | |
67 SHOWING_ERROR_MESSAGE: 'sem', | |
68 SHOWING_TIMEOUT_MESSAGE: 'stm', | |
69 CLICKED_RESUME: 'hcc', | |
70 CLICKED_RESTART: 'hcr', | |
71 CLICKED_DEBUG: 'hcd' | |
72 }; | 327 }; |
73 | 328 |
74 /** | 329 this.handleCommandFromPageFunc_ = this.handleCommandFromPage_.bind(this); |
75 * Errors that are sent to the hotword extension. | 330 window.addEventListener('message', this.handleCommandFromPageFunc_, false); |
76 * @enum {string} | 331 this.initPort_(); |
77 */ | 332 }; |
78 AudioClient.Error = { | 333 |
79 NO_SPEECH_UI: 'ac1', | 334 /** |
80 NO_HOTWORD_STARTED_UI: 'ac2', | 335 * Initialize the communications port with the audio manager. This |
81 NO_HOTWORD_TIMEOUT_UI: 'ac3', | 336 * function will be also be called again if the audio-manager |
82 NO_HOTWORD_ERROR_UI: 'ac4' | 337 * disconnects for some reason (such as the extension |
83 }; | 338 * background.html page being reloaded). |
84 | 339 * @private |
85 /** | 340 */ |
86 * @const {string} | 341 AudioClient.prototype.initPort_ = function() { |
87 * @private | 342 this.port_ = chrome.runtime.connect( |
88 */ | 343 AudioClient.HOTWORD_EXTENSION_ID_, |
89 AudioClient.HOTWORD_EXTENSION_ID_ = 'nbpagnldghgfoolbancepceaanlmhfmd'; | 344 {'name': AudioClient.CLIENT_PORT_NAME_}); |
90 | 345 // Note that this listen may have to be destroyed manually if AudioClient |
91 /** | 346 // is ever destroyed on this tab. |
92 * Number of times to retry checking a transient error. | 347 this.port_.onDisconnect.addListener( |
93 * @const {number} | 348 (function(e) { |
94 * @private | 349 if (this.handleCommandFromPageFunc_) { |
95 */ | 350 window.removeEventListener( |
96 AudioClient.MAX_RETRIES = 3; | 351 'message', this.handleCommandFromPageFunc_, false); |
97 | 352 } |
98 /** | 353 delete window[AudioClient.EXISTS_]; |
99 * Delay to wait in milliseconds before rechecking for any transient errors. | 354 }).bind(this)); |
100 * @const {number} | 355 |
101 * @private | 356 // See note above. |
102 */ | 357 this.port_.onMessage.addListener(this.handleCommandFromExtension_.bind(this)); |
103 AudioClient.RETRY_TIME_MS_ = 2000; | 358 |
104 | 359 if (this.speechActive_) { |
105 /** | 360 this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_START); |
106 * DOM ID for the speech UI overlay. | 361 } else { |
107 * @const {string} | 362 // It's possible for this script to be injected into the page after it has |
108 * @private | 363 // completed loaded (i.e. when prerendering). In this case, this script |
109 */ | 364 // won't receive a SPEECH_RESET from the page to forward onto the |
110 AudioClient.SPEECH_UI_OVERLAY_ID_ = 'spch'; | 365 // extension. To make up for this, always send a SPEECH_RESET. This means |
111 | 366 // in most cases, the extension will receive SPEECH_RESET twice, one from |
112 /** | 367 // this sendCommandToExtension_ and the one forwarded from the page. But |
113 * @const {string} | 368 // that's OK and the extension can handle it. |
114 * @private | 369 this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_RESET); |
115 */ | 370 } |
116 AudioClient.HELP_CENTER_URL_ = | 371 }; |
117 'https://support.google.com/chrome/?p=ui_hotword_search'; | 372 |
118 | 373 // Initializes as soon as the code is ready, do not wait for the page. |
119 /** | 374 new AudioClient().initialize(); |
120 * @const {string} | |
121 * @private | |
122 */ | |
123 AudioClient.CLIENT_PORT_NAME_ = 'chwcpn'; | |
124 | |
125 /** | |
126 * Existence of the Audio Client. | |
127 * @const {string} | |
128 * @private | |
129 */ | |
130 AudioClient.EXISTS_ = 'chwace'; | |
131 | |
132 /** | |
133 * Checks for the presence of speech overlay UI DOM elements. | |
134 * @private | |
135 */ | |
136 AudioClient.prototype.checkSpeechOverlayUi_ = function() { | |
137 if (!this.speechOverlay_) { | |
138 window.setTimeout( | |
139 this.delayedCheckSpeechOverlayUi_.bind(this), | |
140 AudioClient.RETRY_TIME_MS_); | |
141 } else { | |
142 this.checkSpeechUiRetries_ = 0; | |
143 } | |
144 }; | |
145 | |
146 /** | |
147 * Function called to check for the speech UI overlay after some time has | |
148 * passed since an initial check. Will either retry triggering the speech | |
149 * or sends an error message depending on the number of retries. | |
150 * @private | |
151 */ | |
152 AudioClient.prototype.delayedCheckSpeechOverlayUi_ = function() { | |
153 this.speechOverlay_ = | |
154 document.getElementById(AudioClient.SPEECH_UI_OVERLAY_ID_); | |
155 if (!this.speechOverlay_) { | |
156 if (this.checkSpeechUiRetries_++ < AudioClient.MAX_RETRIES) { | |
157 this.sendCommandToPage_(AudioClient.CommandToPage.VOICE_TRIGGER); | |
158 this.checkSpeechOverlayUi_(); | |
159 } else { | |
160 this.sendCommandToExtension_(AudioClient.Error.NO_SPEECH_UI); | |
161 } | |
162 } else { | |
163 this.checkSpeechUiRetries_ = 0; | |
164 } | |
165 }; | |
166 | |
167 /** | |
168 * Checks that the triggered UI is actually displayed. | |
169 * @param {AudioClient.CommandToPage} command Command that was send. | |
170 * @private | |
171 */ | |
172 AudioClient.prototype.checkUi_ = function(command) { | |
173 this.uiStatus_[command].timeoutId = window.setTimeout( | |
174 this.failedCheckUi_.bind(this, command), AudioClient.RETRY_TIME_MS_); | |
175 }; | |
176 | |
177 /** | |
178 * Function called when the UI verification is not called in time. Will either | |
179 * retry the command or sends an error message, depending on the number of | |
180 * retries for the command. | |
181 * @param {AudioClient.CommandToPage} command Command that was sent. | |
182 * @private | |
183 */ | |
184 AudioClient.prototype.failedCheckUi_ = function(command) { | |
185 if (this.uiStatus_[command].tries++ < AudioClient.MAX_RETRIES) { | |
186 this.sendCommandToPage_(command); | |
187 this.checkUi_(command); | |
188 } else { | |
189 this.sendCommandToExtension_(this.uiStatus_[command].error); | |
190 } | |
191 }; | |
192 | |
193 /** | |
194 * Confirm that an UI element has been shown. | |
195 * @param {AudioClient.CommandToPage} command UI to confirm. | |
196 * @private | |
197 */ | |
198 AudioClient.prototype.verifyUi_ = function(command) { | |
199 if (this.uiStatus_[command].timeoutId) { | |
200 window.clearTimeout(this.uiStatus_[command].timeoutId); | |
201 this.uiStatus_[command].timeoutId = null; | |
202 this.uiStatus_[command].tries = 0; | |
203 } | |
204 }; | |
205 | |
206 /** | |
207 * Sends a command to the audio manager. | |
208 * @param {string} commandStr command to send to plugin. | |
209 * @private | |
210 */ | |
211 AudioClient.prototype.sendCommandToExtension_ = function(commandStr) { | |
212 if (this.port_) | |
213 this.port_.postMessage({'cmd': commandStr}); | |
214 }; | |
215 | |
216 /** | |
217 * Handles a message from the audio manager. | |
218 * @param {{cmd: string}} commandObj Command from the audio manager. | |
219 * @private | |
220 */ | |
221 AudioClient.prototype.handleCommandFromExtension_ = function(commandObj) { | |
222 var command = commandObj['cmd']; | |
223 if (command) { | |
224 switch (command) { | |
225 case AudioClient.CommandToPage.HOTWORD_VOICE_TRIGGER: | |
226 this.sendCommandToPage_(command); | |
227 this.checkSpeechOverlayUi_(); | |
228 break; | |
229 case AudioClient.CommandToPage.HOTWORD_STARTED: | |
230 this.sendCommandToPage_(command); | |
231 this.checkUi_(command); | |
232 break; | |
233 case AudioClient.CommandToPage.HOTWORD_ENDED: | |
234 this.sendCommandToPage_(command); | |
235 break; | |
236 case AudioClient.CommandToPage.HOTWORD_TIMEOUT: | |
237 this.sendCommandToPage_(command); | |
238 this.checkUi_(command); | |
239 break; | |
240 case AudioClient.CommandToPage.HOTWORD_ERROR: | |
241 this.sendCommandToPage_(command); | |
242 this.checkUi_(command); | |
243 break; | |
244 } | |
245 } | |
246 }; | |
247 | |
248 /** | |
249 * @param {AudioClient.CommandToPage} commandStr Command to send. | |
250 * @private | |
251 */ | |
252 AudioClient.prototype.sendCommandToPage_ = function(commandStr) { | |
253 window.postMessage({'type': commandStr}, '*'); | |
254 }; | |
255 | |
256 /** | |
257 * Handles a message from the html window. | |
258 * @param {!MessageEvent} messageEvent Message event from the window. | |
259 * @private | |
260 */ | |
261 AudioClient.prototype.handleCommandFromPage_ = function(messageEvent) { | |
262 if (messageEvent.source == window && messageEvent.data.type) { | |
263 var command = messageEvent.data.type; | |
264 switch (command) { | |
265 case AudioClient.CommandFromPage.SPEECH_START: | |
266 this.speechActive_ = true; | |
267 this.sendCommandToExtension_(command); | |
268 break; | |
269 case AudioClient.CommandFromPage.SPEECH_END: | |
270 this.speechActive_ = false; | |
271 this.sendCommandToExtension_(command); | |
272 break; | |
273 case AudioClient.CommandFromPage.SPEECH_RESET: | |
274 this.speechActive_ = false; | |
275 this.sendCommandToExtension_(command); | |
276 break; | |
277 case 'SPEECH_RESET': // Legacy, for embedded NTP. | |
278 this.speechActive_ = false; | |
279 this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_END); | |
280 break; | |
281 case AudioClient.CommandFromPage.CLICKED_RESUME: | |
282 this.sendCommandToExtension_(command); | |
283 break; | |
284 case AudioClient.CommandFromPage.CLICKED_RESTART: | |
285 this.sendCommandToExtension_(command); | |
286 break; | |
287 case AudioClient.CommandFromPage.CLICKED_DEBUG: | |
288 window.open(AudioClient.HELP_CENTER_URL_, '_blank'); | |
289 break; | |
290 case AudioClient.CommandFromPage.SHOWING_HOTWORD_START: | |
291 this.verifyUi_(AudioClient.CommandToPage.HOTWORD_STARTED); | |
292 break; | |
293 case AudioClient.CommandFromPage.SHOWING_ERROR_MESSAGE: | |
294 this.verifyUi_(AudioClient.CommandToPage.HOTWORD_ERROR); | |
295 break; | |
296 case AudioClient.CommandFromPage.SHOWING_TIMEOUT_MESSAGE: | |
297 this.verifyUi_(AudioClient.CommandToPage.HOTWORD_TIMEOUT); | |
298 break; | |
299 } | |
300 } | |
301 }; | |
302 | |
303 /** | |
304 * Initialize the content script. | |
305 */ | |
306 AudioClient.prototype.initialize = function() { | |
307 if (AudioClient.EXISTS_ in window) | |
308 return; | |
309 window[AudioClient.EXISTS_] = true; | |
310 | |
311 // UI verification object. | |
312 this.uiStatus_ = {}; | |
313 this.uiStatus_[AudioClient.CommandToPage.HOTWORD_STARTED] = { | |
314 timeoutId: null, | |
315 tries: 0, | |
316 error: AudioClient.Error.NO_HOTWORD_STARTED_UI | |
317 }; | |
318 this.uiStatus_[AudioClient.CommandToPage.HOTWORD_TIMEOUT] = { | |
319 timeoutId: null, | |
320 tries: 0, | |
321 error: AudioClient.Error.NO_HOTWORD_TIMEOUT_UI | |
322 }; | |
323 this.uiStatus_[AudioClient.CommandToPage.HOTWORD_ERROR] = { | |
324 timeoutId: null, | |
325 tries: 0, | |
326 error: AudioClient.Error.NO_HOTWORD_ERROR_UI | |
327 }; | |
328 | |
329 this.handleCommandFromPageFunc_ = this.handleCommandFromPage_.bind(this); | |
330 window.addEventListener('message', this.handleCommandFromPageFunc_, false); | |
331 this.initPort_(); | |
332 }; | |
333 | |
334 /** | |
335 * Initialize the communications port with the audio manager. This | |
336 * function will be also be called again if the audio-manager | |
337 * disconnects for some reason (such as the extension | |
338 * background.html page being reloaded). | |
339 * @private | |
340 */ | |
341 AudioClient.prototype.initPort_ = function() { | |
342 this.port_ = chrome.runtime.connect( | |
343 AudioClient.HOTWORD_EXTENSION_ID_, | |
344 {'name': AudioClient.CLIENT_PORT_NAME_}); | |
345 // Note that this listen may have to be destroyed manually if AudioClient | |
346 // is ever destroyed on this tab. | |
347 this.port_.onDisconnect.addListener( | |
348 (function(e) { | |
349 if (this.handleCommandFromPageFunc_) { | |
350 window.removeEventListener( | |
351 'message', this.handleCommandFromPageFunc_, false); | |
352 } | |
353 delete window[AudioClient.EXISTS_]; | |
354 }).bind(this)); | |
355 | |
356 // See note above. | |
357 this.port_.onMessage.addListener( | |
358 this.handleCommandFromExtension_.bind(this)); | |
359 | |
360 if (this.speechActive_) { | |
361 this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_START); | |
362 } else { | |
363 // It's possible for this script to be injected into the page after it has | |
364 // completed loaded (i.e. when prerendering). In this case, this script | |
365 // won't receive a SPEECH_RESET from the page to forward onto the | |
366 // extension. To make up for this, always send a SPEECH_RESET. This means | |
367 // in most cases, the extension will receive SPEECH_RESET twice, one from | |
368 // this sendCommandToExtension_ and the one forwarded from the page. But | |
369 // that's OK and the extension can handle it. | |
370 this.sendCommandToExtension_(AudioClient.CommandFromPage.SPEECH_RESET); | |
371 } | |
372 }; | |
373 | |
374 // Initializes as soon as the code is ready, do not wait for the page. | |
375 new AudioClient().initialize(); | |
376 })(); | 375 })(); |
OLD | NEW |