Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / host_setup_dialog.js
1 // Copyright (c) 2012 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 /** @suppress {duplicate} */
8 var remoting = remoting || {};
9
10 /**
11  * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of
12  *     steps for the flow.
13  * @constructor
14  */
15 remoting.HostSetupFlow = function(sequence) {
16   this.sequence_ = sequence;
17   this.currentStep_ = 0;
18   this.state_ = sequence[0];
19   this.pin = '';
20   this.consent = false;
21 };
22
23 /** @enum {number} */
24 remoting.HostSetupFlow.State = {
25   NONE: 0,
26
27   // Dialog states.
28   ASK_PIN: 1,
29
30   // Prompts the user to install the host package.
31   INSTALL_HOST: 2,
32
33   // Processing states.
34   STARTING_HOST: 3,
35   UPDATING_PIN: 4,
36   STOPPING_HOST: 5,
37
38   // Done states.
39   HOST_STARTED: 6,
40   UPDATED_PIN: 7,
41   HOST_STOPPED: 8,
42
43   // Failure states.
44   REGISTRATION_FAILED: 9,
45   START_HOST_FAILED: 10,
46   UPDATE_PIN_FAILED: 11,
47   STOP_HOST_FAILED: 12
48 };
49
50 /** @return {remoting.HostSetupFlow.State} Current state of the flow. */
51 remoting.HostSetupFlow.prototype.getState = function() {
52   return this.state_;
53 };
54
55 remoting.HostSetupFlow.prototype.switchToNextStep = function() {
56   if (this.state_ == remoting.HostSetupFlow.State.NONE) {
57     return;
58   }
59
60   if (this.currentStep_ < this.sequence_.length - 1) {
61     this.currentStep_ += 1;
62     this.state_ = this.sequence_[this.currentStep_];
63   } else {
64     this.state_ = remoting.HostSetupFlow.State.NONE;
65   }
66 };
67
68 /**
69  * @param {remoting.Error} error
70  */
71 remoting.HostSetupFlow.prototype.switchToErrorState = function(error) {
72   if (error == remoting.Error.CANCELLED) {
73     // Stop the setup flow if user rejected one of the actions.
74     this.state_ = remoting.HostSetupFlow.State.NONE;
75   } else {
76     // Current step failed, so switch to corresponding error state.
77     if (this.state_ == remoting.HostSetupFlow.State.STARTING_HOST) {
78       if (error == remoting.Error.REGISTRATION_FAILED) {
79         this.state_ = remoting.HostSetupFlow.State.REGISTRATION_FAILED;
80       } else {
81         this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
82       }
83     } else if (this.state_ == remoting.HostSetupFlow.State.UPDATING_PIN) {
84       this.state_ = remoting.HostSetupFlow.State.UPDATE_PIN_FAILED;
85     } else if (this.state_ == remoting.HostSetupFlow.State.STOPPING_HOST) {
86       this.state_ = remoting.HostSetupFlow.State.STOP_HOST_FAILED;
87     } else {
88       // TODO(sergeyu): Add other error states and use them here.
89       this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
90     }
91   }
92 };
93
94 /**
95  * @param {remoting.HostController} hostController The HostController
96  * responsible for the host daemon.
97  * @constructor
98  */
99 remoting.HostSetupDialog = function(hostController) {
100   this.hostController_ = hostController;
101   this.pinEntry_ = document.getElementById('daemon-pin-entry');
102   this.pinConfirm_ = document.getElementById('daemon-pin-confirm');
103   this.pinErrorDiv_ = document.getElementById('daemon-pin-error-div');
104   this.pinErrorMessage_ = document.getElementById('daemon-pin-error-message');
105
106   /** @type {remoting.HostSetupFlow} */
107   this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
108
109   /** @type {remoting.HostSetupDialog} */
110   var that = this;
111   /** @param {Event} event The event. */
112   var onPinSubmit = function(event) {
113     event.preventDefault();
114     that.onPinSubmit_();
115   };
116   var onPinConfirmFocus = function() {
117     that.validatePin_();
118   };
119
120   var form = document.getElementById('ask-pin-form');
121   form.addEventListener('submit', onPinSubmit, false);
122   /** @param {Event} event The event. */
123   var onDaemonPinEntryKeyPress = function(event) {
124     if (event.which == 13) {
125       document.getElementById('daemon-pin-confirm').focus();
126       event.preventDefault();
127     }
128   };
129   /** @param {Event} event A keypress event. */
130   var noDigitsInPin = function(event) {
131     if (event.which == 13) {
132       return;  // Otherwise the "submit" action can't be triggered by Enter.
133     }
134     if ((event.which >= 48) && (event.which <= 57)) {
135       return;
136     }
137     event.preventDefault();
138   };
139   this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false);
140   this.pinEntry_.addEventListener('keypress', noDigitsInPin, false);
141   this.pinConfirm_.addEventListener('focus', onPinConfirmFocus, false);
142   this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false);
143
144   this.usageStats_ = document.getElementById('usagestats-consent');
145   this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */
146       document.getElementById('usagestats-consent-checkbox');
147 };
148
149 /**
150  * Show the dialog in order to get a PIN prior to starting the daemon. When the
151  * user clicks OK, the dialog shows a spinner until the daemon has started.
152  *
153  * @return {void} Nothing.
154  */
155 remoting.HostSetupDialog.prototype.showForStart = function() {
156   /** @type {remoting.HostSetupDialog} */
157   var that = this;
158
159   /**
160    * @param {remoting.HostController.State} state
161    */
162   var onState = function(state) {
163     // Although we don't need an access token in order to start the host,
164     // using callWithToken here ensures consistent error handling in the
165     // case where the refresh token is invalid.
166     remoting.identity.callWithToken(
167         that.showForStartWithToken_.bind(that, state),
168         remoting.showErrorMessage);
169   };
170
171   this.hostController_.getLocalHostState(onState);
172 };
173
174 /**
175  * @param {remoting.HostController.State} state The current state of the local
176  *     host.
177  * @param {string} token The OAuth2 token.
178  * @private
179  */
180 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
181     function(state, token) {
182   /** @type {remoting.HostSetupDialog} */
183   var that = this;
184
185   /**
186    * @param {boolean} supported True if crash dump reporting is supported by
187    *     the host.
188    * @param {boolean} allowed True if crash dump reporting is allowed.
189    * @param {boolean} set_by_policy True if crash dump reporting is controlled
190    *     by policy.
191    */
192   function onGetConsent(supported, allowed, set_by_policy) {
193     that.usageStats_.hidden = !supported;
194     that.usageStatsCheckbox_.checked = allowed;
195     that.usageStatsCheckbox_.disabled = set_by_policy;
196   }
197
198   /** @param {remoting.Error} error */
199   function onError(error) {
200     console.error('Error getting consent status: ' + error);
201   }
202
203   this.usageStats_.hidden = false;
204   this.usageStatsCheckbox_.checked = false;
205
206   // Prevent user from ticking the box until the current consent status is
207   // known.
208   this.usageStatsCheckbox_.disabled = true;
209
210   this.hostController_.getConsent(onGetConsent, onError);
211
212   var flow = [
213       remoting.HostSetupFlow.State.INSTALL_HOST,
214       remoting.HostSetupFlow.State.ASK_PIN,
215       remoting.HostSetupFlow.State.STARTING_HOST,
216       remoting.HostSetupFlow.State.HOST_STARTED];
217
218   var installed =
219       state != remoting.HostController.State.NOT_INSTALLED &&
220       state != remoting.HostController.State.INSTALLING;
221
222   // Skip the installation step when the host is already installed or when using
223   // NPAPI plugin on Windows (because on Windows the plugin takes care of
224   // installation).
225   if (installed || (navigator.platform == 'Win32' &&
226                     this.hostController_.usingNpapiPlugin())) {
227     flow.shift();
228   }
229
230   this.startNewFlow_(flow);
231 };
232
233 /**
234  * Show the dialog in order to change the PIN associated with a running daemon.
235  *
236  * @return {void} Nothing.
237  */
238 remoting.HostSetupDialog.prototype.showForPin = function() {
239   this.usageStats_.hidden = true;
240   this.startNewFlow_(
241       [remoting.HostSetupFlow.State.ASK_PIN,
242        remoting.HostSetupFlow.State.UPDATING_PIN,
243        remoting.HostSetupFlow.State.UPDATED_PIN]);
244 };
245
246 /**
247  * Show the dialog in order to stop the daemon.
248  *
249  * @return {void} Nothing.
250  */
251 remoting.HostSetupDialog.prototype.showForStop = function() {
252   // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
253   this.startNewFlow_(
254       [remoting.HostSetupFlow.State.STOPPING_HOST,
255        remoting.HostSetupFlow.State.HOST_STOPPED]);
256 };
257
258 /**
259  * @return {void} Nothing.
260  */
261 remoting.HostSetupDialog.prototype.hide = function() {
262   remoting.setMode(remoting.AppMode.HOME);
263 };
264
265 /**
266  * Starts new flow with the specified sequence of steps.
267  * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
268  * @private
269  */
270 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
271   this.flow_ = new remoting.HostSetupFlow(sequence);
272   this.pinEntry_.value = '';
273   this.pinConfirm_.value = '';
274   this.pinErrorDiv_.hidden = true;
275   this.updateState_();
276 };
277
278 /**
279  * Updates current UI mode according to the current state of the setup
280  * flow and start the action corresponding to the current step (if
281  * any).
282  * @private
283  */
284 remoting.HostSetupDialog.prototype.updateState_ = function() {
285   remoting.updateLocalHostState();
286
287   /** @param {string} tag */
288   function showProcessingMessage(tag) {
289     var messageDiv = document.getElementById('host-setup-processing-message');
290     l10n.localizeElementFromTag(messageDiv, tag);
291     remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
292   }
293   /** @param {string} tag1
294    *  @param {string=} opt_tag2 */
295   function showDoneMessage(tag1, opt_tag2) {
296     var messageDiv = document.getElementById('host-setup-done-message');
297     l10n.localizeElementFromTag(messageDiv, tag1);
298     messageDiv = document.getElementById('host-setup-done-message-2');
299     if (opt_tag2) {
300       l10n.localizeElementFromTag(messageDiv, opt_tag2);
301     } else {
302       messageDiv.innerText = '';
303     }
304     remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
305   }
306   /** @param {string} tag */
307   function showErrorMessage(tag) {
308     var errorDiv = document.getElementById('host-setup-error-message');
309     l10n.localizeElementFromTag(errorDiv, tag);
310     remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
311   }
312
313   var state = this.flow_.getState();
314   if (state == remoting.HostSetupFlow.State.NONE) {
315     this.hide();
316   } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
317     remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
318   } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
319     this.installHost_();
320   } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
321     showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
322     this.startHost_();
323   } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
324     showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
325     this.updatePin_();
326   } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
327     showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
328     this.stopHost_();
329   } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
330     // TODO(jamiewalch): Only display the second string if the computer's power
331     // management settings indicate that it's necessary.
332     showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
333                     /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
334   } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
335     showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
336   } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
337     showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
338   } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
339     showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
340   } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
341     showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
342   } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
343     showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
344   } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
345     showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
346   }
347 };
348
349 /**
350  * Shows the prompt that asks the user to install the host.
351  */
352 remoting.HostSetupDialog.prototype.installHost_ = function() {
353   /** @type {remoting.HostSetupDialog} */
354   var that = this;
355   /** @type {remoting.HostSetupFlow} */
356   var flow = this.flow_;
357
358   var onDone = function() {
359     that.hostController_.getLocalHostState(onHostState);
360   };
361
362   /** @param {remoting.Error} error */
363   var onError = function(error) {
364     flow.switchToErrorState(error);
365   }
366
367   /** @param {remoting.HostController.State} state */
368   var onHostState = function(state) {
369     // Verify if the host has been installed. If not then try to prompt the user
370     // again.
371     var installed =
372         state != remoting.HostController.State.NOT_INSTALLED &&
373         state != remoting.HostController.State.INSTALLING;
374     if (installed) {
375       that.flow_.switchToNextStep();
376       that.updateState_();
377     } else {
378       hostInstallDialog.tryAgain();
379     }
380   }
381
382   /** @type {remoting.HostInstallDialog} */
383   var hostInstallDialog = new remoting.HostInstallDialog();
384   hostInstallDialog.show(onDone, onError);
385 }
386
387 /**
388  * Registers and starts the host.
389  */
390 remoting.HostSetupDialog.prototype.startHost_ = function() {
391   /** @type {remoting.HostSetupDialog} */
392   var that = this;
393   /** @type {remoting.HostSetupFlow} */
394   var flow = this.flow_;
395
396   /** @return {boolean} */
397   function isFlowActive() {
398     if (flow !== that.flow_ ||
399         flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
400       console.error('Host setup was interrupted when starting the host');
401       return false;
402     }
403     return true;
404   }
405
406   function onHostStarted() {
407     if (isFlowActive()) {
408       flow.switchToNextStep();
409       that.updateState_();
410     }
411   }
412
413   /** @param {remoting.Error} error */
414   function onError(error) {
415     if (isFlowActive()) {
416       flow.switchToErrorState(error);
417       that.updateState_();
418     }
419   }
420
421   this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
422                              onError);
423 };
424
425 remoting.HostSetupDialog.prototype.updatePin_ = function() {
426   /** @type {remoting.HostSetupDialog} */
427   var that = this;
428   /** @type {remoting.HostSetupFlow} */
429   var flow = this.flow_;
430
431   /** @return {boolean} */
432   function isFlowActive() {
433     if (flow !== that.flow_ ||
434         flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
435       console.error('Host setup was interrupted when updating PIN');
436       return false;
437     }
438     return true;
439   }
440
441   function onPinUpdated() {
442     if (isFlowActive()) {
443       flow.switchToNextStep();
444       that.updateState_();
445     }
446   }
447
448   /** @param {remoting.Error} error */
449   function onError(error) {
450     if (isFlowActive()) {
451       flow.switchToErrorState(error);
452       that.updateState_();
453     }
454   }
455
456   this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
457 };
458
459 /**
460  * Stops the host.
461  */
462 remoting.HostSetupDialog.prototype.stopHost_ = function() {
463   /** @type {remoting.HostSetupDialog} */
464   var that = this;
465   /** @type {remoting.HostSetupFlow} */
466   var flow = this.flow_;
467
468   /** @return {boolean} */
469   function isFlowActive() {
470     if (flow !== that.flow_ ||
471         flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
472       console.error('Host setup was interrupted when stopping the host');
473       return false;
474     }
475     return true;
476   }
477
478   function onHostStopped() {
479     if (isFlowActive()) {
480       flow.switchToNextStep();
481       that.updateState_();
482     }
483   }
484
485   /** @param {remoting.Error} error */
486   function onError(error) {
487     if (isFlowActive()) {
488       flow.switchToErrorState(error);
489       that.updateState_();
490     }
491   }
492
493   this.hostController_.stop(onHostStopped, onError);
494 };
495
496 /**
497  * Validates the PIN and shows an error message if it's invalid.
498  * @return {boolean} true if the PIN is valid, false otherwise.
499  * @private
500  */
501 remoting.HostSetupDialog.prototype.validatePin_ = function() {
502   var pin = this.pinEntry_.value;
503   var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
504   if (!pinIsValid) {
505     l10n.localizeElementFromTag(
506         this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
507   }
508   this.pinErrorDiv_.hidden = pinIsValid;
509   return pinIsValid;
510 };
511
512 /** @private */
513 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
514   if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
515     console.error('PIN submitted in an invalid state', this.flow_.getState());
516     return;
517   }
518   var pin1 = this.pinEntry_.value;
519   var pin2 = this.pinConfirm_.value;
520   if (pin1 != pin2) {
521     l10n.localizeElementFromTag(
522         this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
523     this.pinErrorDiv_.hidden = false;
524     this.prepareForPinEntry_();
525     return;
526   }
527   if (!this.validatePin_()) {
528     this.prepareForPinEntry_();
529     return;
530   }
531   this.flow_.pin = pin1;
532   this.flow_.consent = !this.usageStats_.hidden &&
533       this.usageStatsCheckbox_.checked;
534   this.flow_.switchToNextStep();
535   this.updateState_();
536 };
537
538 /** @private */
539 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
540   this.pinEntry_.value = '';
541   this.pinConfirm_.value = '';
542   this.pinEntry_.focus();
543 };
544
545 /**
546  * Returns whether a PIN is valid.
547  *
548  * @private
549  * @param {string} pin A PIN.
550  * @return {boolean} Whether the PIN is valid.
551  */
552 remoting.HostSetupDialog.validPin_ = function(pin) {
553   if (pin.length < 6) {
554     return false;
555   }
556   for (var i = 0; i < pin.length; i++) {
557     var c = pin.charAt(i);
558     if ((c < '0') || (c > '9')) {
559       return false;
560     }
561   }
562   return true;
563 };
564
565 /** @type {remoting.HostSetupDialog} */
566 remoting.hostSetupDialog = null;