Loosen up heuristics for detecting account creation forms.
[chromium-blink-merge.git] / remoting / webapp / host_setup_dialog.js
blob422384263e16ae47c87b71ce05bba7694a9fc2a8
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 /**
56 * @param {remoting.HostController.AsyncResult} result Result of the
57 * current step.
58 * @return {remoting.HostSetupFlow.State} New state.
60 remoting.HostSetupFlow.prototype.switchToNextStep = function(result) {
61 if (this.state_ == remoting.HostSetupFlow.State.NONE) {
62 return this.state_;
64 if (result == remoting.HostController.AsyncResult.OK) {
65 // If the current step was successful then switch to the next
66 // step in the sequence.
67 if (this.currentStep_ < this.sequence_.length - 1) {
68 this.currentStep_ += 1;
69 this.state_ = this.sequence_[this.currentStep_];
70 } else {
71 this.state_ = remoting.HostSetupFlow.State.NONE;
73 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
74 // Stop the setup flow if user rejected one of the actions.
75 this.state_ = remoting.HostSetupFlow.State.NONE;
76 } else {
77 // Current step failed, so switch to corresponding error state.
78 if (this.state_ == remoting.HostSetupFlow.State.STARTING_HOST) {
79 if (result == remoting.HostController.AsyncResult.FAILED_DIRECTORY) {
80 this.state_ = remoting.HostSetupFlow.State.REGISTRATION_FAILED;
81 } else {
82 this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
84 } else if (this.state_ == remoting.HostSetupFlow.State.UPDATING_PIN) {
85 this.state_ = remoting.HostSetupFlow.State.UPDATE_PIN_FAILED;
86 } else if (this.state_ == remoting.HostSetupFlow.State.STOPPING_HOST) {
87 this.state_ = remoting.HostSetupFlow.State.STOP_HOST_FAILED;
88 } else {
89 // TODO(sergeyu): Add other error states and use them here.
90 this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
93 return this.state_;
96 /**
97 * @param {remoting.HostController} hostController The HostController
98 * responsible for the host daemon.
99 * @constructor
101 remoting.HostSetupDialog = function(hostController) {
102 this.hostController_ = hostController;
103 this.pinEntry_ = document.getElementById('daemon-pin-entry');
104 this.pinConfirm_ = document.getElementById('daemon-pin-confirm');
105 this.pinErrorDiv_ = document.getElementById('daemon-pin-error-div');
106 this.pinErrorMessage_ = document.getElementById('daemon-pin-error-message');
108 /** @type {remoting.HostSetupFlow} */
109 this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
111 /** @type {remoting.HostSetupDialog} */
112 var that = this;
113 /** @param {Event} event The event. */
114 var onPinSubmit = function(event) {
115 event.preventDefault();
116 that.onPinSubmit_();
118 var form = document.getElementById('ask-pin-form');
119 form.addEventListener('submit', onPinSubmit, false);
120 /** @param {Event} event The event. */
121 var onDaemonPinEntryKeyPress = function(event) {
122 if (event.which == 13) {
123 document.getElementById('daemon-pin-confirm').focus();
124 event.preventDefault();
127 /** @param {Event} event A keypress event. */
128 var noDigitsInPin = function(event) {
129 if (event.which == 13) {
130 return; // Otherwise the "submit" action can't be triggered by Enter.
132 if ((event.which >= 48) && (event.which <= 57)) {
133 return;
135 event.preventDefault();
137 this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false);
138 this.pinEntry_.addEventListener('keypress', noDigitsInPin, false);
139 this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false);
141 this.usageStats_ = document.getElementById('usagestats-consent');
142 this.usageStatsCheckbox_ =
143 document.getElementById('usagestats-consent-checkbox');
147 * Show the dialog in order to get a PIN prior to starting the daemon. When the
148 * user clicks OK, the dialog shows a spinner until the daemon has started.
150 * @return {void} Nothing.
152 remoting.HostSetupDialog.prototype.showForStart = function() {
153 // Although we don't need an access token in order to start the host,
154 // using callWithToken here ensures consistent error handling in the
155 // case where the refresh token is invalid.
156 remoting.oauth2.callWithToken(this.showForStartWithToken_.bind(this),
157 remoting.showErrorMessage);
161 * @param {string} token The OAuth2 token.
162 * @private
164 remoting.HostSetupDialog.prototype.showForStartWithToken_ = function(token) {
165 /** @type {remoting.HostSetupDialog} */
166 var that = this;
169 * @param {boolean} supported True if crash dump reporting is supported by
170 * the host.
171 * @param {boolean} allowed True if crash dump reporting is allowed.
172 * @param {boolean} set_by_policy True if crash dump reporting is controlled
173 * by policy.
175 var onGetConsent = function(supported, allowed, set_by_policy) {
176 that.usageStats_.hidden = !supported;
177 that.usageStatsCheckbox_.checked = allowed;
178 that.usageStatsCheckbox_.disabled = set_by_policy;
180 this.usageStats_.hidden = false;
181 this.usageStatsCheckbox_.checked = true;
182 this.hostController_.getConsent(onGetConsent);
184 var flow = [
185 remoting.HostSetupFlow.State.ASK_PIN,
186 remoting.HostSetupFlow.State.STARTING_HOST,
187 remoting.HostSetupFlow.State.HOST_STARTED];
189 if (navigator.platform.indexOf('Mac') != -1 &&
190 this.hostController_.state() ==
191 remoting.HostController.State.NOT_INSTALLED) {
192 flow.unshift(remoting.HostSetupFlow.State.INSTALL_HOST);
195 this.startNewFlow_(flow);
199 * Show the dialog in order to change the PIN associated with a running daemon.
201 * @return {void} Nothing.
203 remoting.HostSetupDialog.prototype.showForPin = function() {
204 this.usageStats_.hidden = true;
205 this.startNewFlow_(
206 [remoting.HostSetupFlow.State.ASK_PIN,
207 remoting.HostSetupFlow.State.UPDATING_PIN,
208 remoting.HostSetupFlow.State.UPDATED_PIN]);
212 * Show the dialog in order to stop the daemon.
214 * @return {void} Nothing.
216 remoting.HostSetupDialog.prototype.showForStop = function() {
217 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
218 this.startNewFlow_(
219 [remoting.HostSetupFlow.State.STOPPING_HOST,
220 remoting.HostSetupFlow.State.HOST_STOPPED]);
224 * @return {void} Nothing.
226 remoting.HostSetupDialog.prototype.hide = function() {
227 remoting.setMode(remoting.AppMode.HOME);
231 * Starts new flow with the specified sequence of steps.
232 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
233 * @private
235 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
236 this.flow_ = new remoting.HostSetupFlow(sequence);
237 this.pinEntry_.value = '';
238 this.pinConfirm_.value = '';
239 this.pinErrorDiv_.hidden = true;
240 this.updateState_();
244 * Updates current UI mode according to the current state of the setup
245 * flow and start the action corresponding to the current step (if
246 * any).
247 * @private
249 remoting.HostSetupDialog.prototype.updateState_ = function() {
250 remoting.hostController.updateDom();
252 /** @param {string} tag */
253 function showProcessingMessage(tag) {
254 var messageDiv = document.getElementById('host-setup-processing-message');
255 l10n.localizeElementFromTag(messageDiv, tag);
256 remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
258 /** @param {string} tag1
259 * @param {string=} opt_tag2 */
260 function showDoneMessage(tag1, opt_tag2) {
261 var messageDiv = document.getElementById('host-setup-done-message');
262 l10n.localizeElementFromTag(messageDiv, tag1);
263 messageDiv = document.getElementById('host-setup-done-message-2');
264 if (opt_tag2) {
265 l10n.localizeElementFromTag(messageDiv, opt_tag2);
266 } else {
267 messageDiv.innerText = '';
269 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
271 /** @param {string} tag */
272 function showErrorMessage(tag) {
273 var errorDiv = document.getElementById('host-setup-error-message');
274 l10n.localizeElementFromTag(errorDiv, tag);
275 remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
278 var state = this.flow_.getState();
279 if (state == remoting.HostSetupFlow.State.NONE) {
280 this.hide();
281 } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
282 remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
283 } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
284 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
285 window.location =
286 'https://dl.google.com/chrome-remote-desktop/chromeremotedesktop.dmg';
287 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
288 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
289 this.startHost_();
290 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
291 showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
292 this.updatePin_();
293 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
294 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
295 this.stopHost_();
296 } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
297 // TODO(jamiewalch): Only display the second string if the computer's power
298 // management settings indicate that it's necessary.
299 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
300 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
301 } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
302 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
303 } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
304 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
305 } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
306 showErrorMessage(/*i18n-content*/'HOST_SETUP_REGISTRATION_FAILED');
307 } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
308 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
309 } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
310 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
311 } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
312 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
317 * Registers and starts the host.
319 remoting.HostSetupDialog.prototype.startHost_ = function() {
320 /** @type {remoting.HostSetupDialog} */
321 var that = this;
322 /** @type {remoting.HostSetupFlow} */
323 var flow = this.flow_;
325 /** @param {remoting.HostController.AsyncResult} result */
326 function onHostStarted(result) {
327 if (flow !== that.flow_ ||
328 flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
329 console.error('Host setup was interrupted when starting the host');
330 return;
333 flow.switchToNextStep(result);
334 that.updateState_();
336 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted);
339 remoting.HostSetupDialog.prototype.updatePin_ = function() {
340 /** @type {remoting.HostSetupDialog} */
341 var that = this;
342 /** @type {remoting.HostSetupFlow} */
343 var flow = this.flow_;
345 /** @param {remoting.HostController.AsyncResult} result */
346 function onPinUpdated(result) {
347 if (flow !== that.flow_ ||
348 flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
349 console.error('Host setup was interrupted when updating PIN');
350 return;
353 flow.switchToNextStep(result);
354 that.updateState_();
357 this.hostController_.updatePin(flow.pin, onPinUpdated);
361 * Stops the host.
363 remoting.HostSetupDialog.prototype.stopHost_ = function() {
364 /** @type {remoting.HostSetupDialog} */
365 var that = this;
366 /** @type {remoting.HostSetupFlow} */
367 var flow = this.flow_;
369 /** @param {remoting.HostController.AsyncResult} result */
370 function onHostStopped(result) {
371 if (flow !== that.flow_ ||
372 flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
373 console.error('Host setup was interrupted when stopping the host');
374 return;
377 flow.switchToNextStep(result);
378 that.updateState_();
380 this.hostController_.stop(onHostStopped);
383 /** @private */
384 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
385 if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
386 console.error('PIN submitted in an invalid state', this.flow_.getState());
387 return;
389 var pin1 = this.pinEntry_.value;
390 var pin2 = this.pinConfirm_.value;
391 if (pin1 != pin2) {
392 l10n.localizeElementFromTag(
393 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
394 this.pinErrorDiv_.hidden = false;
395 this.prepareForPinEntry_();
396 return;
398 if (!remoting.HostSetupDialog.validPin_(pin1)) {
399 l10n.localizeElementFromTag(
400 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
401 this.pinErrorDiv_.hidden = false;
402 this.prepareForPinEntry_();
403 return;
405 this.pinErrorDiv_.hidden = true;
406 this.flow_.pin = pin1;
407 this.flow_.consent = !this.usageStats_.hidden &&
408 (this.usageStatsCheckbox_.value == "on");
409 this.flow_.switchToNextStep(remoting.HostController.AsyncResult.OK);
410 this.updateState_();
413 /** @private */
414 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
415 this.pinEntry_.value = '';
416 this.pinConfirm_.value = '';
417 this.pinEntry_.focus();
421 * Returns whether a PIN is valid.
423 * @private
424 * @param {string} pin A PIN.
425 * @return {boolean} Whether the PIN is valid.
427 remoting.HostSetupDialog.validPin_ = function(pin) {
428 if (pin.length < 6) {
429 return false;
431 for (var i = 0; i < pin.length; i++) {
432 var c = pin.charAt(i);
433 if ((c < '0') || (c > '9')) {
434 return false;
437 return true;
441 * @return {void} Nothing.
443 remoting.HostSetupDialog.prototype.onInstallDialogOk = function() {
444 var state = this.hostController_.state();
445 if (state == remoting.HostController.State.STOPPED) {
446 this.flow_.switchToNextStep(remoting.HostController.AsyncResult.OK);
447 this.updateState_();
448 } else {
449 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL_PENDING);
454 * @return {void} Nothing.
456 remoting.HostSetupDialog.prototype.onInstallDialogRetry = function() {
457 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
460 /** @type {remoting.HostSetupDialog} */
461 remoting.hostSetupDialog = null;