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