Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_gaia_signin.js
bloba7db60831cce2ce13000ee6eb9aabcf9ec9119aa
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 /**
6  * @fileoverview Oobe signin screen implementation.
7  */
9 login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
10   // Gaia loading time after which error message must be displayed and
11   // lazy portal check should be fired.
12   /** @const */ var GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC = 7;
14   // GAIA animation guard timer. Started when GAIA page is loaded
15   // (Authenticator 'ready' event) and is intended to guard against edge cases
16   // when 'showView' message is not generated/received.
17   /** @const */ var GAIA_ANIMATION_GUARD_MILLISEC = 300;
19   // Maximum Gaia loading time in seconds.
20   /** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
22   /** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
24   // The help topic regarding user not being in the whitelist.
25   /** @const */ var HELP_CANT_ACCESS_ACCOUNT = 188036;
27   // Amount of time the user has to be idle for before showing the online login
28   // page.
29   /** @const */ var IDLE_TIME_UNTIL_EXIT_OFFLINE_IN_MILLISECONDS = 180 * 1000;
31   // Approximate amount of time between checks to see if we should go to the
32   // online login page when we're in the offline login page and the device is
33   // online.
34   /** @const */ var IDLE_TIME_CHECK_FREQUENCY = 5 * 1000;
36   return {
37     EXTERNAL_API: [
38       'loadAuthExtension',
39       'updateAuthExtension',
40       'doReload',
41       'monitorOfflineIdle',
42       'onWebviewError',
43       'onFrameError',
44       'updateCancelButtonState',
45       'showWhitelistCheckFailedError',
46     ],
48     /**
49      * Frame loading error code (0 - no error).
50      * @type {number}
51      * @private
52      */
53     error_: 0,
55     /**
56      * Saved gaia auth host load params.
57      * @type {?string}
58      * @private
59      */
60     gaiaAuthParams_: null,
62     /**
63      * Whether local version of Gaia page is used.
64      * @type {boolean}
65      * @private
66      */
67     isLocal_: false,
69     /**
70      * Whether new Gaia flow is active.
71      * @type {boolean}
72      */
73     isNewGaiaFlow: false,
75     /**
76      * Email of the user, which is logging in using offline mode.
77      * @type {string}
78      */
79     email: '',
81     /**
82      * Whether consumer management enrollment is in progress.
83      * @type {boolean}
84      * @private
85      */
86     isEnrollingConsumerManagement_: false,
88     /**
89      * Timer id of pending load.
90      * @type {number}
91      * @private
92      */
93     loadingTimer_: undefined,
95     /**
96      * Timer id of a guard timer that is fired in case 'showView' message
97      * is not received from GAIA.
98      * @type {number}
99      * @private
100      */
101     loadAnimationGuardTimer_: undefined,
103     /**
104      * Whether we've processed 'showView' message - either from GAIA or from
105      * guard timer.
106      * @type {boolean}
107      * @private
108      */
109     showViewProcessed_: undefined,
111     /**
112      * Whether user can cancel Gaia screen.
113      * @type {boolean}
114      * @private
115      */
116     cancelAllowed_: undefined,
118     /**
119      * Whether we should show user pods on the login screen.
120      * @type {boolean}
121      * @private
122      */
123     isShowUsers_: undefined,
125     /**
126      * SAML password confirmation attempt count.
127      * @type {number}
128      */
129     samlPasswordConfirmAttempt_: 0,
131     /**
132      * Do we currently have a setTimeout task running that tries to bring us
133      * back to the online login page after the user has idled for awhile? If so,
134      * then this id will be non-negative.
135      */
136     tryToGoToOnlineLoginPageCallbackId_: -1,
138     /**
139      * The most recent period of time that the user has interacted. This is
140      * only updated when the offline page is active and the device is online.
141      */
142     mostRecentUserActivity_: Date.now(),
144     /**
145      * Whether we should show webview based signin.
146      * @type {boolean}
147      * @private
148      */
149     isWebviewSignin: false,
151     /** @override */
152     decorate: function() {
153       this.isWebviewSignin = loadTimeData.getValue('isWebviewSignin');
154       if (this.isWebviewSignin) {
155         // Replace iframe with webview.
156         var webview = this.ownerDocument.createElement('webview');
157         webview.id = 'signin-frame';
158         webview.name = 'signin-frame';
159         webview.hidden = true;
160         $('signin-frame').parentNode.replaceChild(webview, $('signin-frame'));
161         this.gaiaAuthHost_ = new cr.login.GaiaAuthHost(webview);
163         $('offline-gaia').addEventListener('authCompleted',
164             this.onAuthCompletedMessage_.bind(this));
165       } else {
166         this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
167       }
168       this.gaiaAuthHost_.addEventListener(
169           'ready', this.onAuthReady_.bind(this));
170       this.gaiaAuthHost_.addEventListener(
171           'dialogShown', this.onDialogShown_.bind(this));
172       this.gaiaAuthHost_.addEventListener(
173           'dialogHidden', this.onDialogHidden_.bind(this));
174       this.gaiaAuthHost_.addEventListener(
175           'backButton', this.onBackButton_.bind(this));
176       this.gaiaAuthHost_.addEventListener(
177           'showView', this.onShowView_.bind(this));
178       this.gaiaAuthHost_.confirmPasswordCallback =
179           this.onAuthConfirmPassword_.bind(this);
180       this.gaiaAuthHost_.noPasswordCallback =
181           this.onAuthNoPassword_.bind(this);
182       this.gaiaAuthHost_.insecureContentBlockedCallback =
183           this.onInsecureContentBlocked_.bind(this);
184       this.gaiaAuthHost_.missingGaiaInfoCallback =
185           this.missingGaiaInfo_.bind(this);
186       this.gaiaAuthHost_.samlApiUsedCallback =
187           this.samlApiUsed_.bind(this);
188       this.gaiaAuthHost_.addEventListener('authDomainChange',
189           this.onAuthDomainChange_.bind(this));
190       this.gaiaAuthHost_.addEventListener('authFlowChange',
191           this.onAuthFlowChange_.bind(this));
192       this.gaiaAuthHost_.addEventListener('authCompleted',
193           this.onAuthCompletedMessage_.bind(this));
194       this.gaiaAuthHost_.addEventListener('loadAbort',
195         this.onLoadAbortMessage_.bind(this));
196       this.gaiaAuthHost_.addEventListener(
197           'identifierEntered', this.onIdentifierEnteredMessage_.bind(this));
199       $('enterprise-info-hint-link').addEventListener('click', function(e) {
200         chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
201         e.preventDefault();
202       });
204       $('back-button-item').addEventListener('click', function(e) {
205         $('back-button-item').hidden = true;
206         $('signin-frame').back();
207         e.preventDefault();
208       });
210       $('close-button-item').addEventListener('click', function(e) {
211         this.cancel();
212         e.preventDefault();
213       }.bind(this));
215       $('gaia-whitelist-error').addEventListener('buttonclick', function() {
216          this.showWhitelistCheckFailedError(false);
217       }.bind(this));
219       $('gaia-whitelist-error').addEventListener('linkclick', function() {
220         chrome.send('launchHelpApp', [HELP_CANT_ACCESS_ACCOUNT]);
221       });
223       this.updateLocalizedContent();
224     },
226     /**
227      * Header text of the screen.
228      * @type {string}
229      */
230     get header() {
231       return loadTimeData.getString('signinScreenTitle');
232     },
234     /**
235      * Returns true if local version of Gaia is used.
236      * @type {boolean}
237      */
238     get isLocal() {
239       return this.isLocal_;
240     },
242     /**
243      * Sets whether local version of Gaia is used.
244      * @param {boolean} value Whether local version of Gaia is used.
245      */
246     set isLocal(value) {
247       this.isLocal_ = value;
248       if (this.isNewGaiaFlow) {
249         $('signin-frame').hidden = this.isLocal_;
250         $('offline-gaia').hidden = !this.isLocal_;
251       }
252       chrome.send('updateOfflineLogin', [value]);
253     },
255     /**
256      * This enables or disables trying to go back to the online login page
257      * after the user is idle for a few minutes, assuming that we're currently
258      * in the offline one. This is only applicable when the offline page is
259      * currently active. It is intended that when the device goes online, this
260      * gets called with true; when it goes offline, this gets called with
261      * false.
262      */
263     monitorOfflineIdle: function(shouldMonitor) {
264       var ACTIVITY_EVENTS = ['click', 'mousemove', 'keypress'];
265       var self = this;
267       // updateActivityTime_ is used as a callback for addEventListener, so we
268       // need the exact reference for removeEventListener. Because the callback
269       // needs to access the |this| as scoped inside of this function, we create
270       // a closure that uses the appropriate |this|.
271       //
272       // Unfourtantely, we cannot define this function inside of the JSON object
273       // as then we have to no way to create to capture the correct |this|
274       // reference. We define it here instead.
275       if (!self.updateActivityTime_) {
276         self.updateActivityTime_ = function() {
277           self.mostRecentUserActivity_ = Date.now();
278         };
279       }
281       // Begin monitoring.
282       if (shouldMonitor) {
283         // If we're not using the offline login page or we're already
284         // monitoring, then we don't need to do anything.
285         if (self.isLocal === false ||
286             self.tryToGoToOnlineLoginPageCallbackId_ !== -1)
287           return;
289         self.mostRecentUserActivity_ = Date.now();
290         ACTIVITY_EVENTS.forEach(function(event) {
291           document.addEventListener(event, self.updateActivityTime_);
292         });
294         self.tryToGoToOnlineLoginPageCallbackId_ = setInterval(function() {
295           // If we're not in the offline page or the signin page, then we want
296           // to terminate monitoring.
297           if (self.isLocal === false ||
298               Oobe.getInstance().currentScreen.id != 'gaia-signin') {
299             self.monitorOfflineIdle(false);
300             return;
301           }
303           var idleDuration = Date.now() - self.mostRecentUserActivity_;
304           if (idleDuration > IDLE_TIME_UNTIL_EXIT_OFFLINE_IN_MILLISECONDS) {
305             self.isLocal = false;
306             self.monitorOfflineIdle(false);
307           }
308         }, IDLE_TIME_CHECK_FREQUENCY);
309       }
311       // Stop monitoring.
312       else {
313         // We're not monitoring, so we don't need to do anything.
314         if (self.tryToGoToOnlineLoginPageCallbackId_ === -1)
315           return;
317         ACTIVITY_EVENTS.forEach(function(event) {
318           document.removeEventListener(event, self.updateActivityTime_);
319         });
320         clearInterval(self.tryToGoToOnlineLoginPageCallbackId_);
321         self.tryToGoToOnlineLoginPageCallbackId_ = -1;
322       }
323     },
325     /**
326      * Shows/hides loading UI.
327      * @param {boolean} show True to show loading UI.
328      * @private
329      */
330     showLoadingUI_: function(show) {
331       $('gaia-loading').hidden = !show;
332       if (this.isNewGaiaFlow && this.isLocal) {
333         $('offline-gaia').hidden = show;
334       } else {
335         $('signin-frame').hidden = show;
336       }
337       $('signin-right').hidden = show;
338       $('enterprise-info-container').hidden = show;
339       $('gaia-signin-divider').hidden = show;
340       this.classList.toggle('loading', show);
341       $('signin-frame').classList.remove('show');
342       if (!show)
343         this.classList.remove('auth-completed');
344     },
346     /**
347      * Handler for Gaia loading suspiciously long timeout.
348      * @private
349      */
350     onLoadingSuspiciouslyLong_: function() {
351       if (this != Oobe.getInstance().currentScreen)
352         return;
353       chrome.send('showLoadingTimeoutError');
354       this.loadingTimer_ = setTimeout(
355           this.onLoadingTimeOut_.bind(this),
356           (MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
357           1000);
358     },
360     /**
361      * Handler for Gaia loading timeout.
362      * @private
363      */
364     onLoadingTimeOut_: function() {
365       this.loadingTimer_ = undefined;
366       chrome.send('showLoadingTimeoutError');
367     },
369     /**
370      * Clears loading timer.
371      * @private
372      */
373     clearLoadingTimer_: function() {
374       if (this.loadingTimer_) {
375         clearTimeout(this.loadingTimer_);
376         this.loadingTimer_ = undefined;
377       }
378     },
380     /**
381      * Sets up loading timer.
382      * @private
383      */
384     startLoadingTimer_: function() {
385       this.clearLoadingTimer_();
386       this.loadingTimer_ = setTimeout(
387           this.onLoadingSuspiciouslyLong_.bind(this),
388           GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
389     },
391     /**
392      * Handler for GAIA animation guard timer.
393      * @private
394      */
395     onLoadAnimationGuardTimer_: function() {
396       this.loadAnimationGuardTimer_ = undefined;
397       this.onShowView_();
398     },
400     /**
401      * Clears GAIA animation guard timer.
402      * @private
403      */
404     clearLoadAnimationGuardTimer_: function() {
405       if (this.loadAnimationGuardTimer_) {
406         clearTimeout(this.loadAnimationGuardTimer_);
407         this.loadAnimationGuardTimer_ = undefined;
408       }
409     },
411     /**
412      * Sets up GAIA animation guard timer.
413      * @private
414      */
415     startLoadAnimationGuardTimer_: function() {
416       this.clearLoadAnimationGuardTimer_();
417       this.loadAnimationGuardTimer_ = setTimeout(
418           this.onLoadAnimationGuardTimer_.bind(this),
419           GAIA_ANIMATION_GUARD_MILLISEC);
420     },
422     /**
423      * Whether Gaia is loading.
424      * @type {boolean}
425      */
426     get loading() {
427       return !$('gaia-loading').hidden;
428     },
429     set loading(loading) {
430       if (loading == this.loading)
431         return;
433       this.showLoadingUI_(loading);
434     },
436     /**
437      * Event handler that is invoked just before the frame is shown.
438      * @param {string} data Screen init payload. Url of auth extension start
439      *                      page.
440      */
441     onBeforeShow: function(data) {
442       chrome.send('loginUIStateChanged', ['gaia-signin', true]);
443       $('login-header-bar').signinUIState =
444           this.isEnrollingConsumerManagement_ ?
445               SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT :
446               SIGNIN_UI_STATE.GAIA_SIGNIN;
448       // Ensure that GAIA signin (or loading UI) is actually visible.
449       window.requestAnimationFrame(function() {
450         chrome.send('loginVisible', ['gaia-loading']);
451       });
452       $('back-button-item').disabled = false;
453       $('back-button-item').hidden = true;
454       $('close-button-item').disabled = false;
455       this.classList.toggle('loading', this.loading);
457       // Button header is always visible when sign in is presented.
458       // Header is hidden once GAIA reports on successful sign in.
459       Oobe.getInstance().headerHidden = false;
460     },
462     onAfterShow: function(data) {
463       if (!this.loading && this.isWebviewSignin) {
464         if (this.isLocal)
465           $('offline-gaia').focus();
466         else
467           $('signin-frame').focus();
468       }
469     },
471     /**
472      * Event handler that is invoked just before the screen is hidden.
473      */
474     onBeforeHide: function() {
475       chrome.send('loginUIStateChanged', ['gaia-signin', false]);
476       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
477     },
479     /**
480      * Loads the authentication extension into the iframe.
481      * @param {Object} data Extension parameters bag.
482      * @private
483      */
484     loadAuthExtension: function(data) {
485       this.isNewGaiaFlow = data.useNewGaiaFlow;
486       this.isLocal = data.isLocal;
487       this.email = '';
489       // Reset SAML
490       this.classList.toggle('full-width', false);
491       this.samlPasswordConfirmAttempt_ = 0;
493       this.updateAuthExtension(data);
495       var params = {};
496       for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
497         var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
498         if (data[name])
499           params[name] = data[name];
500       }
502       if (data.localizedStrings)
503         params.localizedStrings = data.localizedStrings;
505       if (this.isNewGaiaFlow) {
506         $('inner-container').classList.add('new-gaia-flow');
507         params.chromeType = data.chromeType;
508         params.isNewGaiaFlowChromeOS = true;
509       }
511       if (data.gaiaEndpoint)
512         params.gaiaPath = data.gaiaEndpoint;
514       $('login-header-bar').newGaiaFlow = this.isNewGaiaFlow;
516       // Screen size could have been changed because of 'new-gaia-flow' or
517       // 'full-width' classes.
518       if (Oobe.getInstance().currentScreen === this)
519         Oobe.getInstance().updateScreenSize(this);
521       if (data.forceReload ||
522           JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
523         this.error_ = 0;
525         var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
526         if (data.useOffline)
527           authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
529         this.gaiaAuthParams_ = params;
530         this.loading = true;
531         this.startLoadingTimer_();
533         if (this.isLocal && this.isNewGaiaFlow) {
534           this.loadOffline(params);
535           this.onAuthReady_();
536         } else {
537           this.gaiaAuthHost_.load(authMode,
538                                   params,
539                                   this.onAuthCompleted_.bind(this));
540         }
541       } else if (this.loading && this.error_) {
542         // An error has occurred, so trying to reload.
543         this.doReload();
544       }
545     },
547     /**
548      * Updates the authentication extension with new parameters, if needed.
549      * @param {Object} data New extension parameters bag.
550      * @private
551      */
552     updateAuthExtension: function(data) {
553       if (this.isNewGaiaFlow) {
554         $('login-header-bar').showCreateSupervisedButton =
555             data.supervisedUsersEnabled && data.supervisedUsersCanCreate;
556         $('login-header-bar').showGuestButton = data.guestSignin;
557       } else {
558         $('createAccount').hidden = !data.createAccount;
559         $('guestSignin').hidden = !data.guestSignin;
560         $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
562         $('createSupervisedUserLinkPlaceholder').hidden =
563             !data.supervisedUsersCanCreate;
564         $('createSupervisedUserNoManagerText').hidden =
565             data.supervisedUsersCanCreate;
566         $('createSupervisedUserNoManagerText').textContent =
567             data.supervisedUsersRestrictionReason;
568       }
570       var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
571       $('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
573       this.isShowUsers_ = data.isShowUsers;
574       this.updateCancelButtonState();
576       this.isEnrollingConsumerManagement_ = isEnrollingConsumerManagement;
578       // Sign-in right panel is hidden if all of its items are hidden.
579       var noRightPanel = $('createAccount').hidden &&
580                          $('guestSignin').hidden &&
581                          $('createSupervisedUserPane').hidden &&
582                          $('consumerManagementEnrollment').hidden;
583       this.classList.toggle('no-right-panel', noRightPanel);
584       this.classList.toggle('full-width', false);
585       if (Oobe.getInstance().currentScreen === this)
586         Oobe.getInstance().updateScreenSize(this);
587     },
589     /**
590      * Updates [Cancel] button state. Allow cancellation of screen only when
591      * user pods can be displayed.
592      */
593     updateCancelButtonState: function() {
594       this.cancelAllowed_ = this.isLocal ||
595                             (this.isShowUsers_ && $('pod-row').pods.length);
596       $('login-header-bar').allowCancel = this.cancelAllowed_;
597       if (this.isNewGaiaFlow)
598         $('close-button-item').hidden = !this.cancelAllowed_;
599     },
601     /**
602      * Whether the current auth flow is SAML.
603      */
604     isSAML: function() {
605        return this.gaiaAuthHost_.authFlow ==
606            cr.login.GaiaAuthHost.AuthFlow.SAML;
607     },
609     /**
610      * Invoked when the authDomain property is changed on the GAIA host.
611      */
612     onAuthDomainChange_: function() {
613       $('saml-notice-message').textContent = loadTimeData.getStringF(
614           'samlNotice',
615           this.gaiaAuthHost_.authDomain);
616     },
618     /**
619      * Invoked when the authFlow property is changed on the GAIA host.
620      * @param {Event} e Property change event.
621      */
622     onAuthFlowChange_: function(e) {
623       var isSAML = this.isSAML();
625       this.classList.toggle('no-right-panel', isSAML);
626       this.classList.toggle('full-width', isSAML);
627       $('saml-notice-container').hidden = !isSAML;
629       if (Oobe.getInstance().currentScreen === this) {
630         Oobe.getInstance().updateScreenSize(this);
631         $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
632         if (this.isNewGaiaFlow)
633           $('close-button-item').hidden = !(isSAML || this.cancelAllowed_);
634       }
635     },
637     /**
638      * Invoked when the auth host emits 'ready' event.
639      * @private
640      */
641     onAuthReady_: function() {
642       showViewProcessed_ = false;
643       if (this.isNewGaiaFlow)
644         this.startLoadAnimationGuardTimer_();
646       this.clearLoadingTimer_();
647       this.loading = false;
649       if (!this.isNewGaiaFlow)
650         this.onLoginUIVisible_();
652       // Warm up the user images screen.
653       Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
654     },
656     /**
657      * Invoked when the auth host emits 'dialogShown' event.
658      * @private
659      */
660     onDialogShown_: function() {
661       $('back-button-item').disabled = true;
662       $('close-button-item').disabled = true;
663     },
665     /**
666      * Invoked when the auth host emits 'dialogHidden' event.
667      * @private
668      */
669     onDialogHidden_: function() {
670       $('back-button-item').disabled = false;
671       $('close-button-item').disabled = false;
672     },
674     /**
675      * Invoked when the auth host emits 'backButton' event.
676      * @private
677      */
678     onBackButton_: function(e) {
679       $('back-button-item').hidden = !e.detail;
680       $('login-header-bar').updateUI_();
681       $('signin-frame').focus();
682     },
684     /**
685      * Invoked when the auth host emits 'showView' event or when corresponding
686      * guard time fires.
687      * @private
688      */
689     onShowView_: function(e) {
690       if (showViewProcessed_)
691         return;
693       showViewProcessed_ = true;
694       this.clearLoadAnimationGuardTimer_();
695       $('signin-frame').classList.add('show');
696       this.onLoginUIVisible_();
697     },
699     /**
700      * Called when UI is shown.
701      * @private
702      */
703     onLoginUIVisible_: function() {
704       // Show deferred error bubble.
705       if (this.errorBubble_) {
706         this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
707         this.errorBubble_ = undefined;
708       }
710       chrome.send('loginWebuiReady');
711       chrome.send('loginVisible', ['gaia-signin']);
712     },
714     /**
715      * Invoked when the user has successfully authenticated via SAML, the
716      * principals API was not used and the auth host needs the user to confirm
717      * the scraped password.
718      * @param {string} email The authenticated user's e-mail.
719      * @param {number} passwordCount The number of passwords that were scraped.
720      * @private
721      */
722     onAuthConfirmPassword_: function(email, passwordCount) {
723       this.loading = true;
724       Oobe.getInstance().headerHidden = false;
726       if (this.samlPasswordConfirmAttempt_ == 0)
727         chrome.send('scrapedPasswordCount', [passwordCount]);
729       if (this.samlPasswordConfirmAttempt_ < 2) {
730         login.ConfirmPasswordScreen.show(
731             email,
732             this.samlPasswordConfirmAttempt_,
733             this.onConfirmPasswordCollected_.bind(this));
734       } else {
735         chrome.send('scrapedPasswordVerificationFailed');
736         this.showFatalAuthError(
737             loadTimeData.getString('fatalErrorMessageVerificationFailed'),
738             loadTimeData.getString('fatalErrorTryAgainButton'));
739       }
740       if (this.isNewGaiaFlow) {
741         this.classList.toggle('no-right-panel', false);
742         this.classList.toggle('full-width', false);
743       }
744     },
746     /**
747      * Invoked when the confirm password screen is dismissed.
748      * @private
749      */
750     onConfirmPasswordCollected_: function(password) {
751       this.samlPasswordConfirmAttempt_++;
752       this.gaiaAuthHost_.verifyConfirmedPassword(password);
754       // Shows signin UI again without changing states.
755       Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
756     },
758     /**
759      * Inovked when the user has successfully authenticated via SAML, the
760      * principals API was not used and no passwords could be scraped.
761      * @param {string} email The authenticated user's e-mail.
762      */
763     onAuthNoPassword_: function(email) {
764       this.showFatalAuthError(
765           loadTimeData.getString('fatalErrorMessageNoPassword'),
766           loadTimeData.getString('fatalErrorTryAgainButton'));
767       chrome.send('scrapedPasswordCount', [0]);
768     },
770     /**
771      * Invoked when the authentication flow had to be aborted because content
772      * served over an unencrypted connection was detected. Shows a fatal error.
773      * This method is only called on Chrome OS, where the entire authentication
774      * flow is required to be encrypted.
775      * @param {string} url The URL that was blocked.
776      */
777     onInsecureContentBlocked_: function(url) {
778       this.showFatalAuthError(
779           loadTimeData.getStringF('fatalErrorMessageInsecureURL', url),
780           loadTimeData.getString('fatalErrorDoneButton'));
781     },
783     /**
784      * Shows the fatal auth error.
785      * @param {string} message The error message to show.
786      * @param {string} buttonLabel The label to display on dismiss button.
787      */
788     showFatalAuthError: function(message, buttonLabel) {
789       login.FatalErrorScreen.show(message, buttonLabel, Oobe.showSigninUI);
790     },
792     /**
793      * Show fatal auth error when information is missing from GAIA.
794      */
795     missingGaiaInfo_: function() {
796       this.showFatalAuthError(
797           loadTimeData.getString('fatalErrorMessageNoAccountDetails'),
798           loadTimeData.getString('fatalErrorTryAgainButton'));
799     },
801     /**
802      * Record that SAML API was used during sign-in.
803      */
804     samlApiUsed_: function() {
805       chrome.send('usingSAMLAPI');
806     },
808     /**
809      * Invoked when auth is completed successfully.
810      * @param {!Object} credentials Credentials of the completed authentication.
811      * @private
812      */
813     onAuthCompleted_: function(credentials) {
814       if (credentials.useOffline) {
815         this.email = credentials.email;
816         chrome.send('authenticateUser',
817                     [credentials.email,
818                      credentials.password]);
819       } else if (credentials.authCode) {
820         if (credentials.hasOwnProperty('authCodeOnly') &&
821             credentials.authCodeOnly) {
822           chrome.send('completeAuthenticationAuthCodeOnly',
823                       [credentials.authCode]);
824         } else {
825           chrome.send('completeAuthentication', [
826             credentials.gaiaId,
827             credentials.email,
828             credentials.password,
829             credentials.authCode,
830             credentials.usingSAML,
831             credentials.gapsCookie
832           ]);
833         }
834       } else {
835         chrome.send('completeLogin',
836                     [credentials.gaiaId,
837                      credentials.email,
838                      credentials.password,
839                      credentials.usingSAML]);
840       }
842       this.loading = true;
843       this.classList.add('auth-completed');
844       // Now that we're in logged in state header should be hidden.
845       Oobe.getInstance().headerHidden = true;
846       // Clear any error messages that were shown before login.
847       Oobe.clearErrors();
848     },
850     /**
851      * Invoked when onAuthCompleted message received.
852      * @param {!Object} e Payload of the received HTML5 message.
853      * @private
854      */
855     onAuthCompletedMessage_: function(e) {
856       this.onAuthCompleted_(e.detail);
857     },
859     /**
860      * Invoked when onLoadAbort message received.
861      * @param {!Object} e Payload of the received HTML5 message.
862      * @private
863      */
864     onLoadAbortMessage_: function(e) {
865       this.onWebviewError(e.detail);
866     },
868     /**
869      * Invoked when identifierEntered message received.
870      * @param {!Object} e Payload of the received HTML5 message.
871      * @private
872      */
873     onIdentifierEnteredMessage_: function(e) {
874       this.onIdentifierEntered(e.detail);
875     },
877     /**
878      * Clears input fields and switches to input mode.
879      * @param {boolean} takeFocus True to take focus.
880      * @param {boolean} forceOnline Whether online sign-in should be forced.
881      * If |forceOnline| is false previously used sign-in type will be used.
882      */
883     reset: function(takeFocus, forceOnline) {
884       // Reload and show the sign-in UI if needed.
885       if (takeFocus) {
886         if (!forceOnline && this.isLocal) {
887           // Show 'Cancel' button to allow user to return to the main screen
888           // (e.g. this makes sense when connection is back).
889           Oobe.getInstance().headerHidden = false;
890           $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
891           // Do nothing, since offline version is reloaded after an error comes.
892         } else {
893           Oobe.showSigninUI();
894         }
895       }
896     },
898     /**
899      * Reloads extension frame.
900      */
901     doReload: function() {
902       if (this.isLocal)
903         return;
904       this.error_ = 0;
905       this.gaiaAuthHost_.reload();
906       this.loading = true;
907       this.startLoadingTimer_();
908     },
910     /**
911      * Updates localized content of the screen that is not updated via template.
912      */
913     updateLocalizedContent: function() {
914       $('createAccount').innerHTML = loadTimeData.getStringF(
915           'createAccount',
916           '<a id="createAccountLink" class="signin-link" href="#">',
917           '</a>');
918       $('guestSignin').innerHTML = loadTimeData.getStringF(
919           'guestSignin',
920           '<a id="guestSigninLink" class="signin-link" href="#">',
921           '</a>');
922       $('createSupervisedUserLinkPlaceholder').innerHTML =
923           loadTimeData.getStringF(
924               'createSupervisedUser',
925               '<a id="createSupervisedUserLink" class="signin-link" href="#">',
926               '</a>');
927       $('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
928           'consumerManagementEnrollmentSigninMessage');
929       $('createAccountLink').addEventListener('click', function(e) {
930         chrome.send('createAccount');
931         e.preventDefault();
932       });
933       $('guestSigninLink').addEventListener('click', function(e) {
934         chrome.send('launchIncognito');
935         e.preventDefault();
936       });
937       $('createSupervisedUserLink').addEventListener('click', function(e) {
938         chrome.send('showSupervisedUserCreationScreen');
939         e.preventDefault();
940       });
941     },
943     /**
944      * Shows sign-in error bubble.
945      * @param {number} loginAttempts Number of login attemps tried.
946      * @param {HTMLElement} content Content to show in bubble.
947      */
948     showErrorBubble: function(loginAttempts, error) {
949       if (this.isLocal) {
950         $('add-user-button').hidden = true;
951         $('cancel-add-user-button').hidden = false;
952         // Reload offline version of the sign-in extension, which will show
953         // error itself.
954         chrome.send('offlineLogin', [this.email]);
955       } else if (!this.loading) {
956         // We want to show bubble near "Email" field, but we can't calculate
957         // it's position because it is located inside iframe. So we only
958         // can hardcode some constants.
959         /** @const */ var ERROR_BUBBLE_OFFSET = 84;
960         /** @const */ var ERROR_BUBBLE_PADDING = 0;
961         $('bubble').showContentForElement($('login-box'),
962                                           cr.ui.Bubble.Attachment.LEFT,
963                                           error,
964                                           ERROR_BUBBLE_OFFSET,
965                                           ERROR_BUBBLE_PADDING);
966       } else {
967         // Defer the bubble until the frame has been loaded.
968         this.errorBubble_ = [loginAttempts, error];
969       }
970     },
972     /**
973      * Called when user canceled signin.
974      */
975     cancel: function() {
976       if (!this.cancelAllowed_) {
977         // In OOBE signin screen, cancel is not allowed because there is
978         // no other screen to show. If user is in middle of a saml flow,
979         // reset signin screen to get out of the saml flow.
980         if (this.isSAML())
981           Oobe.resetSigninUI(true);
983         return;
984       }
986       $('offline-gaia').switchToEmailCard();
988       this.classList.remove('whitelist-error');
989       Oobe.showUserPods();
990     },
992     /**
993      * Handler for iframe's error notification coming from the outside.
994      * For more info see C++ class 'WebUILoginView' which calls this method.
995      * @param {number} error Error code.
996      * @param {string} url The URL that failed to load.
997      */
998     onFrameError: function(error, url) {
999       this.error_ = error;
1000       chrome.send('frameLoadingCompleted', [this.error_]);
1001     },
1003     /**
1004      * Handler for webview error handling.
1005      * @param {!Object} data Additional information about error event like:
1006      * {string} error Error code such as "ERR_INTERNET_DISCONNECTED".
1007      * {string} url The URL that failed to load.
1008      */
1009     onWebviewError: function(data) {
1010       chrome.send('webviewLoadAborted', [data.error]);
1011     },
1013     /**
1014      * Handler for identifierEntered event.
1015      * @param {!Object} data The identifier entered by user:
1016      * {string} accountIdentifier User identifier.
1017      */
1018     onIdentifierEntered: function(data) {
1019       chrome.send('identifierEntered', [data.accountIdentifier]);
1020     },
1022     /**
1023      * Sets welcome and enterpriseinfo strings for offline gaia.
1024      * Also sets callback and sends message whether we already have email and
1025      * should switch to the password screen with error.
1026      */
1027     loadOffline: function(params) {
1028       var offlineLogin = $('offline-gaia');
1029       var strings = params.localizedStrings;
1030       if ('stringEnterpriseInfo' in strings)
1031         offlineLogin.enterpriseInfo = strings['stringEnterpriseInfo'];
1032       if ('emailDomain' in params)
1033         offlineLogin.emailDomain = '@' + params['emailDomain'];
1034       offlineLogin.setEmail(params.email);
1035     },
1037     /**
1038      * Show/Hide error when user is not in whitelist. When UI is hidden
1039      * GAIA is reloaded.
1040      * @param {boolean} show Show/hide error UI.
1041      * @param {!Object} opt_data Optional additional information.
1042      */
1043     showWhitelistCheckFailedError: function(show, opt_data) {
1044       if (show) {
1045         var isManaged = opt_data && opt_data.enterpriseManaged;
1046         $('gaia-whitelist-error').textContent =
1047             loadTimeData.getValue(isManaged ? 'whitelistErrorEnterprise' :
1048                                               'whitelistErrorConsumer');
1049       }
1051       this.classList.toggle('whitelist-error', show);
1052       this.loading = !show;
1054       if (!show)
1055         Oobe.showSigninUI();
1056     }
1057   };