Ignore non-active fullscreen windows for shelf state.
[chromium-blink-merge.git] / remoting / webapp / host_setup_dialog.js
blobeb52b7c14c06b775bc6bfc75e269e71698f56dbd
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /**
11 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of
12 * steps for the flow.
13 * @constructor
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;
23 /** @enum {number} */
24 remoting.HostSetupFlow.State = {
25 NONE: 0,
27 // Dialog states.
28 ASK_PIN: 1,
30 // Used on Mac OS X to prompt the user to manually install a .dmg package.
31 INSTALL_HOST: 2,
33 // Processing states.
34 STARTING_HOST: 3,
35 UPDATING_PIN: 4,
36 STOPPING_HOST: 5,
38 // Done states.
39 HOST_STARTED: 6,
40 UPDATED_PIN: 7,
41 HOST_STOPPED: 8,
43 // Failure states.
44 REGISTRATION_FAILED: 9,
45 START_HOST_FAILED: 10,
46 UPDATE_PIN_FAILED: 11,
47 STOP_HOST_FAILED: 12
50 /** @return {remoting.HostSetupFlow.State} Current state of the flow. */
51 remoting.HostSetupFlow.prototype.getState = function() {
52 return this.state_;
55 remoting.HostSetupFlow.prototype.switchToNextStep = function() {
56 if (this.state_ == remoting.HostSetupFlow.State.NONE) {
57 return;
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;
68 /**
69 * @param {remoting.Error} error
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;
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;
94 /**
95 * @param {remoting.HostController} hostController The HostController
96 * responsible for the host daemon.
97 * @constructor
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 this.continueInstallButton_ = document.getElementById(
106 'host-config-install-continue');
107 this.cancelInstallButton_ = document.getElementById(
108 'host-config-install-dismiss');
109 this.retryInstallButton_ = document.getElementById(
110 'host-config-install-retry');
112 this.continueInstallButton_.addEventListener(
113 'click', this.onInstallDialogOk.bind(this), false);
114 this.cancelInstallButton_.addEventListener(
115 'click', this.hide.bind(this), false);
116 this.retryInstallButton_.addEventListener(
117 'click', this.onInstallDialogRetry.bind(this), false);
119 /** @type {remoting.HostSetupFlow} */
120 this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
122 /** @type {remoting.HostSetupDialog} */
123 var that = this;
124 /** @param {Event} event The event. */
125 var onPinSubmit = function(event) {
126 event.preventDefault();
127 that.onPinSubmit_();
129 var onPinConfirmFocus = function() {
130 that.validatePin_();
133 var form = document.getElementById('ask-pin-form');
134 form.addEventListener('submit', onPinSubmit, false);
135 /** @param {Event} event The event. */
136 var onDaemonPinEntryKeyPress = function(event) {
137 if (event.which == 13) {
138 document.getElementById('daemon-pin-confirm').focus();
139 event.preventDefault();
142 /** @param {Event} event A keypress event. */
143 var noDigitsInPin = function(event) {
144 if (event.which == 13) {
145 return; // Otherwise the "submit" action can't be triggered by Enter.
147 if ((event.which >= 48) && (event.which <= 57)) {
148 return;
150 event.preventDefault();
152 this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false);
153 this.pinEntry_.addEventListener('keypress', noDigitsInPin, false);
154 this.pinConfirm_.addEventListener('focus', onPinConfirmFocus, false);
155 this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false);
157 this.usageStats_ = document.getElementById('usagestats-consent');
158 this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */
159 document.getElementById('usagestats-consent-checkbox');
163 * Show the dialog in order to get a PIN prior to starting the daemon. When the
164 * user clicks OK, the dialog shows a spinner until the daemon has started.
166 * @return {void} Nothing.
168 remoting.HostSetupDialog.prototype.showForStart = function() {
169 /** @type {remoting.HostSetupDialog} */
170 var that = this;
173 * @param {remoting.HostController.State} state
175 var onState = function(state) {
176 // Although we don't need an access token in order to start the host,
177 // using callWithToken here ensures consistent error handling in the
178 // case where the refresh token is invalid.
179 remoting.identity.callWithToken(
180 that.showForStartWithToken_.bind(that, state),
181 remoting.showErrorMessage);
184 this.hostController_.getLocalHostState(onState);
188 * @param {remoting.HostController.State} state The current state of the local
189 * host.
190 * @param {string} token The OAuth2 token.
191 * @private
193 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
194 function(state, token) {
195 /** @type {remoting.HostSetupDialog} */
196 var that = this;
199 * @param {boolean} supported True if crash dump reporting is supported by
200 * the host.
201 * @param {boolean} allowed True if crash dump reporting is allowed.
202 * @param {boolean} set_by_policy True if crash dump reporting is controlled
203 * by policy.
205 function onGetConsent(supported, allowed, set_by_policy) {
206 that.usageStats_.hidden = !supported;
207 that.usageStatsCheckbox_.checked = allowed;
208 that.usageStatsCheckbox_.disabled = set_by_policy;
211 /** @param {remoting.Error} error */
212 function onError(error) {
213 console.error('Error getting consent status: ' + error);
216 this.usageStats_.hidden = false;
217 this.usageStatsCheckbox_.checked = false;
219 // Prevent user from ticking the box until the current consent status is
220 // known.
221 this.usageStatsCheckbox_.disabled = true;
223 this.hostController_.getConsent(onGetConsent, onError);
225 var flow = [
226 remoting.HostSetupFlow.State.ASK_PIN,
227 remoting.HostSetupFlow.State.STARTING_HOST,
228 remoting.HostSetupFlow.State.HOST_STARTED];
230 var installed =
231 state != remoting.HostController.State.NOT_INSTALLED &&
232 state != remoting.HostController.State.INSTALLING;
234 if (navigator.platform.indexOf('Mac') != -1 && !installed) {
235 flow.unshift(remoting.HostSetupFlow.State.INSTALL_HOST);
238 this.startNewFlow_(flow);
242 * Show the dialog in order to change the PIN associated with a running daemon.
244 * @return {void} Nothing.
246 remoting.HostSetupDialog.prototype.showForPin = function() {
247 this.usageStats_.hidden = true;
248 this.startNewFlow_(
249 [remoting.HostSetupFlow.State.ASK_PIN,
250 remoting.HostSetupFlow.State.UPDATING_PIN,
251 remoting.HostSetupFlow.State.UPDATED_PIN]);
255 * Show the dialog in order to stop the daemon.
257 * @return {void} Nothing.
259 remoting.HostSetupDialog.prototype.showForStop = function() {
260 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
261 this.startNewFlow_(
262 [remoting.HostSetupFlow.State.STOPPING_HOST,
263 remoting.HostSetupFlow.State.HOST_STOPPED]);
267 * @return {void} Nothing.
269 remoting.HostSetupDialog.prototype.hide = function() {
270 remoting.setMode(remoting.AppMode.HOME);
274 * Starts new flow with the specified sequence of steps.
275 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
276 * @private
278 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
279 this.flow_ = new remoting.HostSetupFlow(sequence);
280 this.pinEntry_.value = '';
281 this.pinConfirm_.value = '';
282 this.pinErrorDiv_.hidden = true;
283 this.updateState_();
287 * Updates current UI mode according to the current state of the setup
288 * flow and start the action corresponding to the current step (if
289 * any).
290 * @private
292 remoting.HostSetupDialog.prototype.updateState_ = function() {
293 remoting.updateLocalHostState();
295 /** @param {string} tag */
296 function showProcessingMessage(tag) {
297 var messageDiv = document.getElementById('host-setup-processing-message');
298 l10n.localizeElementFromTag(messageDiv, tag);
299 remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
301 /** @param {string} tag1
302 * @param {string=} opt_tag2 */
303 function showDoneMessage(tag1, opt_tag2) {
304 var messageDiv = document.getElementById('host-setup-done-message');
305 l10n.localizeElementFromTag(messageDiv, tag1);
306 messageDiv = document.getElementById('host-setup-done-message-2');
307 if (opt_tag2) {
308 l10n.localizeElementFromTag(messageDiv, opt_tag2);
309 } else {
310 messageDiv.innerText = '';
312 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
314 /** @param {string} tag */
315 function showErrorMessage(tag) {
316 var errorDiv = document.getElementById('host-setup-error-message');
317 l10n.localizeElementFromTag(errorDiv, tag);
318 remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
321 var state = this.flow_.getState();
322 if (state == remoting.HostSetupFlow.State.NONE) {
323 this.hide();
324 } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
325 remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
326 } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
327 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
328 window.location =
329 'https://dl.google.com/chrome-remote-desktop/chromeremotedesktop.dmg';
330 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
331 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
332 this.startHost_();
333 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
334 showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
335 this.updatePin_();
336 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
337 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
338 this.stopHost_();
339 } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
340 // TODO(jamiewalch): Only display the second string if the computer's power
341 // management settings indicate that it's necessary.
342 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
343 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
344 } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
345 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
346 } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
347 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
348 } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
349 showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
350 } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
351 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
352 } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
353 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
354 } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
355 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
360 * Registers and starts the host.
362 remoting.HostSetupDialog.prototype.startHost_ = function() {
363 /** @type {remoting.HostSetupDialog} */
364 var that = this;
365 /** @type {remoting.HostSetupFlow} */
366 var flow = this.flow_;
368 /** @return {boolean} */
369 function isFlowActive() {
370 if (flow !== that.flow_ ||
371 flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
372 console.error('Host setup was interrupted when starting the host');
373 return false;
375 return true;
378 function onHostStarted() {
379 if (isFlowActive()) {
380 flow.switchToNextStep();
381 that.updateState_();
385 /** @param {remoting.Error} error */
386 function onError(error) {
387 if (isFlowActive()) {
388 flow.switchToErrorState(error);
389 that.updateState_();
393 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
394 onError);
397 remoting.HostSetupDialog.prototype.updatePin_ = function() {
398 /** @type {remoting.HostSetupDialog} */
399 var that = this;
400 /** @type {remoting.HostSetupFlow} */
401 var flow = this.flow_;
403 /** @return {boolean} */
404 function isFlowActive() {
405 if (flow !== that.flow_ ||
406 flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
407 console.error('Host setup was interrupted when updating PIN');
408 return false;
410 return true;
413 function onPinUpdated() {
414 if (isFlowActive()) {
415 flow.switchToNextStep();
416 that.updateState_();
420 /** @param {remoting.Error} error */
421 function onError(error) {
422 if (isFlowActive()) {
423 flow.switchToErrorState(error);
424 that.updateState_();
428 this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
432 * Stops the host.
434 remoting.HostSetupDialog.prototype.stopHost_ = function() {
435 /** @type {remoting.HostSetupDialog} */
436 var that = this;
437 /** @type {remoting.HostSetupFlow} */
438 var flow = this.flow_;
440 /** @return {boolean} */
441 function isFlowActive() {
442 if (flow !== that.flow_ ||
443 flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
444 console.error('Host setup was interrupted when stopping the host');
445 return false;
447 return true;
450 function onHostStopped() {
451 if (isFlowActive()) {
452 flow.switchToNextStep();
453 that.updateState_();
457 /** @param {remoting.Error} error */
458 function onError(error) {
459 if (isFlowActive()) {
460 flow.switchToErrorState(error);
461 that.updateState_();
465 this.hostController_.stop(onHostStopped, onError);
469 * Validates the PIN and shows an error message if it's invalid.
470 * @return {boolean} true if the PIN is valid, false otherwise.
471 * @private
473 remoting.HostSetupDialog.prototype.validatePin_ = function() {
474 var pin = this.pinEntry_.value;
475 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
476 if (!pinIsValid) {
477 l10n.localizeElementFromTag(
478 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
480 this.pinErrorDiv_.hidden = pinIsValid;
481 return pinIsValid;
484 /** @private */
485 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
486 if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
487 console.error('PIN submitted in an invalid state', this.flow_.getState());
488 return;
490 var pin1 = this.pinEntry_.value;
491 var pin2 = this.pinConfirm_.value;
492 if (pin1 != pin2) {
493 l10n.localizeElementFromTag(
494 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
495 this.pinErrorDiv_.hidden = false;
496 this.prepareForPinEntry_();
497 return;
499 if (!this.validatePin_()) {
500 this.prepareForPinEntry_();
501 return;
503 this.flow_.pin = pin1;
504 this.flow_.consent = !this.usageStats_.hidden &&
505 this.usageStatsCheckbox_.checked;
506 this.flow_.switchToNextStep();
507 this.updateState_();
510 /** @private */
511 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
512 this.pinEntry_.value = '';
513 this.pinConfirm_.value = '';
514 this.pinEntry_.focus();
518 * Returns whether a PIN is valid.
520 * @private
521 * @param {string} pin A PIN.
522 * @return {boolean} Whether the PIN is valid.
524 remoting.HostSetupDialog.validPin_ = function(pin) {
525 if (pin.length < 6) {
526 return false;
528 for (var i = 0; i < pin.length; i++) {
529 var c = pin.charAt(i);
530 if ((c < '0') || (c > '9')) {
531 return false;
534 return true;
538 * @return {void} Nothing.
540 remoting.HostSetupDialog.prototype.onInstallDialogOk = function() {
541 this.continueInstallButton_.disabled = true;
542 this.cancelInstallButton_.disabled = true;
544 /** @type {remoting.HostSetupDialog} */
545 var that = this;
547 /** @param {remoting.HostController.State} state */
548 var onHostState = function(state) {
549 that.continueInstallButton_.disabled = false;
550 that.cancelInstallButton_.disabled = false;
551 var installed =
552 state != remoting.HostController.State.NOT_INSTALLED &&
553 state != remoting.HostController.State.INSTALLING;
554 if (installed) {
555 that.flow_.switchToNextStep();
556 that.updateState_();
557 } else {
558 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL_PENDING);
562 this.hostController_.getLocalHostState(onHostState);
566 * @return {void} Nothing.
568 remoting.HostSetupDialog.prototype.onInstallDialogRetry = function() {
569 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
572 /** @type {remoting.HostSetupDialog} */
573 remoting.hostSetupDialog = null;