[Extensions] Make extension message bubble factory platform-abstract
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_gaia_signin.js
blobe6ed735e1db0d02436bd5a47b102f182285bac63
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   // Maximum Gaia loading time in seconds.
15   /** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
17   /** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
19   return {
20     EXTERNAL_API: [
21       'loadAuthExtension',
22       'updateAuthExtension',
23       'doReload',
24       'onWebviewError',
25       'onFrameError',
26       'updateCancelButtonState',
27       'showWhitelistCheckFailedError'
28     ],
30     /**
31      * Frame loading error code (0 - no error).
32      * @type {number}
33      * @private
34      */
35     error_: 0,
37     /**
38      * Saved gaia auth host load params.
39      * @type {?string}
40      * @private
41      */
42     gaiaAuthParams_: null,
44     /**
45      * Whether local version of Gaia page is used.
46      * @type {boolean}
47      * @private
48      */
49     isLocal_: false,
51     /**
52      * Whether new Gaia flow is active.
53      * @type {boolean}
54      */
55     isNewGaiaFlow: false,
57     /**
58      * Email of the user, which is logging in using offline mode.
59      * @type {string}
60      */
61     email: '',
63     /**
64      * Whether consumer management enrollment is in progress.
65      * @type {boolean}
66      * @private
67      */
68     isEnrollingConsumerManagement_: false,
70     /**
71      * Timer id of pending load.
72      * @type {number}
73      * @private
74      */
75     loadingTimer_: undefined,
77     /**
78      * Whether user can cancel Gaia screen.
79      * @type {boolean}
80      * @private
81      */
82     cancelAllowed_: undefined,
84     /**
85      * Whether we should show user pods on the login screen.
86      * @type {boolean}
87      * @private
88      */
89     isShowUsers_: undefined,
91     /**
92      * SAML password confirmation attempt count.
93      * @type {number}
94      */
95     samlPasswordConfirmAttempt_: 0,
97     /**
98      * Whether we should show webview based signin.
99      * @type {boolean}
100      * @private
101      */
102     isWebviewSignin: false,
104     /** @override */
105     decorate: function() {
106       this.isWebviewSignin = loadTimeData.getValue('isWebviewSignin');
107       if (this.isWebviewSignin) {
108         // Replace iframe with webview.
109         var webview = this.ownerDocument.createElement('webview');
110         webview.id = 'signin-frame';
111         webview.name = 'signin-frame';
112         webview.hidden = true;
113         $('signin-frame').parentNode.replaceChild(webview, $('signin-frame'));
114         this.gaiaAuthHost_ = new cr.login.GaiaAuthHost(webview);
115       } else {
116         this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
117       }
118       this.gaiaAuthHost_.addEventListener(
119           'ready', this.onAuthReady_.bind(this));
120       this.gaiaAuthHost_.addEventListener(
121           'dialogShown', this.onDialogShown_.bind(this));
122       this.gaiaAuthHost_.addEventListener(
123           'dialogHidden', this.onDialogHidden_.bind(this));
124       this.gaiaAuthHost_.addEventListener(
125           'backButton', this.onBackButton_.bind(this));
126       this.gaiaAuthHost_.addEventListener(
127           'showView', this.onShowView_.bind(this));
128       this.gaiaAuthHost_.confirmPasswordCallback =
129           this.onAuthConfirmPassword_.bind(this);
130       this.gaiaAuthHost_.noPasswordCallback =
131           this.onAuthNoPassword_.bind(this);
132       this.gaiaAuthHost_.insecureContentBlockedCallback =
133           this.onInsecureContentBlocked_.bind(this);
134       this.gaiaAuthHost_.missingGaiaInfoCallback =
135           this.missingGaiaInfo_.bind(this);
136       this.gaiaAuthHost_.samlApiUsedCallback =
137           this.samlApiUsed_.bind(this);
138       this.gaiaAuthHost_.addEventListener('authFlowChange',
139           this.onAuthFlowChange_.bind(this));
140       this.gaiaAuthHost_.addEventListener('authCompleted',
141           this.onAuthCompletedMessage_.bind(this));
142       this.gaiaAuthHost_.addEventListener('loadAbort',
143         this.onLoadAbortMessage_.bind(this));
145       $('enterprise-info-hint-link').addEventListener('click', function(e) {
146         chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
147         e.preventDefault();
148       });
150       $('back-button-item').addEventListener('click', function(e) {
151         $('back-button-item').hidden = true;
152         $('signin-frame').back();
153         e.preventDefault();
154       }.bind(this));
155       $('close-button-item').addEventListener('click', function(e) {
156         this.cancel();
157         e.preventDefault();
158       }.bind(this));
160       this.updateLocalizedContent();
161     },
163     /**
164      * Header text of the screen.
165      * @type {string}
166      */
167     get header() {
168       return loadTimeData.getString('signinScreenTitle');
169     },
171     /**
172      * Returns true if local version of Gaia is used.
173      * @type {boolean}
174      */
175     get isLocal() {
176       return this.isLocal_;
177     },
179     /**
180      * Sets whether local version of Gaia is used.
181      * @param {boolean} value Whether local version of Gaia is used.
182      */
183     set isLocal(value) {
184       this.isLocal_ = value;
185       chrome.send('updateOfflineLogin', [value]);
186     },
188     /**
189      * Shows/hides loading UI.
190      * @param {boolean} show True to show loading UI.
191      * @private
192      */
193     showLoadingUI_: function(show) {
194       $('gaia-loading').hidden = !show;
195       $('signin-frame').hidden = show;
196       $('signin-right').hidden = show;
197       $('enterprise-info-container').hidden = show;
198       $('gaia-signin-divider').hidden = show;
199       this.classList.toggle('loading', show);
200       if (!show)
201         this.classList.remove('auth-completed');
202     },
204     /**
205      * Handler for Gaia loading suspiciously long timeout.
206      * @private
207      */
208     onLoadingSuspiciouslyLong_: function() {
209       if (this != Oobe.getInstance().currentScreen)
210         return;
211       chrome.send('showLoadingTimeoutError');
212       this.loadingTimer_ = window.setTimeout(
213           this.onLoadingTimeOut_.bind(this),
214           (MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
215           1000);
216     },
218     /**
219      * Handler for Gaia loading timeout.
220      * @private
221      */
222     onLoadingTimeOut_: function() {
223       this.loadingTimer_ = undefined;
224       chrome.send('showLoadingTimeoutError');
225     },
227     /**
228      * Clears loading timer.
229      * @private
230      */
231     clearLoadingTimer_: function() {
232       if (this.loadingTimer_) {
233         window.clearTimeout(this.loadingTimer_);
234         this.loadingTimer_ = undefined;
235       }
236     },
238     /**
239      * Sets up loading timer.
240      * @private
241      */
242     startLoadingTimer_: function() {
243       this.clearLoadingTimer_();
244       this.loadingTimer_ = window.setTimeout(
245           this.onLoadingSuspiciouslyLong_.bind(this),
246           GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
247     },
249     /**
250      * Whether Gaia is loading.
251      * @type {boolean}
252      */
253     get loading() {
254       return !$('gaia-loading').hidden;
255     },
256     set loading(loading) {
257       if (loading == this.loading)
258         return;
260       this.showLoadingUI_(loading);
261     },
263     /**
264      * Event handler that is invoked just before the frame is shown.
265      * @param {string} data Screen init payload. Url of auth extension start
266      *                      page.
267      */
268     onBeforeShow: function(data) {
269       chrome.send('loginUIStateChanged', ['gaia-signin', true]);
270       $('login-header-bar').signinUIState =
271           this.isEnrollingConsumerManagement_ ?
272               SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT :
273               SIGNIN_UI_STATE.GAIA_SIGNIN;
275       // Ensure that GAIA signin (or loading UI) is actually visible.
276       window.requestAnimationFrame(function() {
277         chrome.send('loginVisible', ['gaia-loading']);
278       });
279       $('back-button-item').disabled = false;
280       $('back-button-item').hidden = true;
281       $('close-button-item').disabled = false;
282       this.classList.toggle('loading', this.loading);
284       // Button header is always visible when sign in is presented.
285       // Header is hidden once GAIA reports on successful sign in.
286       Oobe.getInstance().headerHidden = false;
287     },
289     onAfterShow: function(data) {
290       if (!this.loading && this.isWebviewSignin)
291         $('signin-frame').focus();
292     },
294     /**
295      * Event handler that is invoked just before the screen is hidden.
296      */
297     onBeforeHide: function() {
298       chrome.send('loginUIStateChanged', ['gaia-signin', false]);
299       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
300     },
302     /**
303      * Loads the authentication extension into the iframe.
304      * @param {Object} data Extension parameters bag.
305      * @private
306      */
307     loadAuthExtension: function(data) {
308       this.isLocal = data.isLocal;
309       this.email = '';
310       this.isNewGaiaFlow = data.useNewGaiaFlow;
312       // Reset SAML
313       this.classList.toggle('full-width', false);
314       this.samlPasswordConfirmAttempt_ = 0;
316       this.updateAuthExtension(data);
318       var params = {};
319       for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
320         var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
321         if (data[name])
322           params[name] = data[name];
323       }
325       if (data.localizedStrings)
326         params.localizedStrings = data.localizedStrings;
328       if (this.isNewGaiaFlow) {
329         $('inner-container').classList.add('new-gaia-flow');
330         $('progress-dots').hidden = true;
331         if (data.enterpriseDomain)
332           params.enterpriseDomain = data.enterpriseDomain;
333         params.chromeType = data.chromeType;
334         params.isNewGaiaFlowChromeOS = true;
335         $('login-header-bar').showGuestButton = true;
336       }
338       if (data.gaiaEndpoint)
339         params.gaiaPath = data.gaiaEndpoint;
341       $('login-header-bar').newGaiaFlow = this.isNewGaiaFlow;
343       if (data.forceReload ||
344           JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
345         this.error_ = 0;
347         var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
348         if (data.useOffline)
349           authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
351         this.gaiaAuthHost_.load(authMode,
352                                 params,
353                                 this.onAuthCompleted_.bind(this));
354         this.gaiaAuthParams_ = params;
356         this.loading = true;
357         this.startLoadingTimer_();
358       } else if (this.loading && this.error_) {
359         // An error has occurred, so trying to reload.
360         this.doReload();
361       }
362     },
364     /**
365      * Updates the authentication extension with new parameters, if needed.
366      * @param {Object} data New extension parameters bag.
367      * @private
368      */
369     updateAuthExtension: function(data) {
370       var reasonLabel = $('gaia-signin-reason');
371       if (data.passwordChanged) {
372         reasonLabel.textContent =
373             loadTimeData.getString('signinScreenPasswordChanged');
374         reasonLabel.hidden = false;
375       } else {
376         reasonLabel.hidden = true;
377       }
379       if (this.isNewGaiaFlow) {
380         $('login-header-bar').showCreateSupervisedButton =
381             data.supervisedUsersCanCreate;
382       } else {
383         $('createAccount').hidden = !data.createAccount;
384         $('guestSignin').hidden = !data.guestSignin;
385         $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
387         $('createSupervisedUserLinkPlaceholder').hidden =
388             !data.supervisedUsersCanCreate;
389         $('createSupervisedUserNoManagerText').hidden =
390             data.supervisedUsersCanCreate;
391         $('createSupervisedUserNoManagerText').textContent =
392             data.supervisedUsersRestrictionReason;
393       }
395       var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
396       $('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
398       this.isShowUsers_ = data.isShowUsers;
399       this.updateCancelButtonState();
401       this.isEnrollingConsumerManagement_ = isEnrollingConsumerManagement;
403       // Sign-in right panel is hidden if all of its items are hidden.
404       var noRightPanel = $('gaia-signin-reason').hidden &&
405                          $('createAccount').hidden &&
406                          $('guestSignin').hidden &&
407                          $('createSupervisedUserPane').hidden &&
408                          $('consumerManagementEnrollment').hidden;
409       this.classList.toggle('no-right-panel', noRightPanel);
410       this.classList.toggle('full-width', false);
411       if (Oobe.getInstance().currentScreen === this)
412         Oobe.getInstance().updateScreenSize(this);
413     },
415     /**
416      * Updates [Cancel] button state. Allow cancellation of screen only when
417      * user pods can be displayed.
418      */
419     updateCancelButtonState: function() {
420       this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
421       $('login-header-bar').allowCancel = this.cancelAllowed_;
422       if (this.isNewGaiaFlow)
423         $('close-button-item').hidden = !this.cancelAllowed_;
424     },
426     /**
427      * Whether the current auth flow is SAML.
428      */
429     isSAML: function() {
430        return this.gaiaAuthHost_.authFlow ==
431            cr.login.GaiaAuthHost.AuthFlow.SAML;
432     },
434     /**
435      * Invoked when the authFlow property is changed no the gaia host.
436      */
437     onAuthFlowChange_: function() {
438       var isSAML = this.isSAML();
440       if (isSAML) {
441         $('saml-notice-message').textContent = loadTimeData.getStringF(
442             'samlNotice',
443             this.gaiaAuthHost_.authDomain);
444       }
446       this.classList.toggle('no-right-panel', isSAML);
447       this.classList.toggle('full-width', isSAML);
448       $('saml-notice-container').hidden = !isSAML;
450       if (Oobe.getInstance().currentScreen === this) {
451         Oobe.getInstance().updateScreenSize(this);
452         $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
453         if (this.isNewGaiaFlow)
454           $('close-button-item').hidden = !(isSAML || this.cancelAllowed_);
455       }
456     },
458     /**
459      * Invoked when the auth host emits 'ready' event.
460      * @private
461      */
462     onAuthReady_: function() {
463       this.loading = false;
464       this.clearLoadingTimer_();
466       // Show deferred error bubble.
467       if (this.errorBubble_) {
468         this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
469         this.errorBubble_ = undefined;
470       }
472       chrome.send('loginWebuiReady');
473       chrome.send('loginVisible', ['gaia-signin']);
475       // Warm up the user images screen.
476       Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
477     },
479     /**
480      * Invoked when the auth host emits 'dialogShown' event.
481      * @private
482      */
483     onDialogShown_: function() {
484       $('back-button-item').disabled = true;
485       $('close-button-item').disabled = true;
486     },
488     /**
489      * Invoked when the auth host emits 'dialogHidden' event.
490      * @private
491      */
492     onDialogHidden_: function() {
493       $('back-button-item').disabled = false;
494       $('close-button-item').disabled = false;
495     },
497     /**
498      * Invoked when the auth host emits 'backButton' event.
499      * @private
500      */
501     onBackButton_: function(e) {
502       $('back-button-item').hidden = !e.detail;
503     },
505     /**
506      * Invoked when the auth host emits 'showView' event.
507      * @private
508      */
509     onShowView_: function(e) {
510       $('signin-frame').classList.add('show');
511     },
513     /**
514      * Invoked when the user has successfully authenticated via SAML, the
515      * principals API was not used and the auth host needs the user to confirm
516      * the scraped password.
517      * @param {number} passwordCount The number of passwords that were scraped.
518      * @private
519      */
520     onAuthConfirmPassword_: function(passwordCount) {
521       this.loading = true;
522       Oobe.getInstance().headerHidden = false;
524       if (this.samlPasswordConfirmAttempt_ == 0)
525         chrome.send('scrapedPasswordCount', [passwordCount]);
527       if (this.samlPasswordConfirmAttempt_ < 2) {
528         login.ConfirmPasswordScreen.show(
529             this.samlPasswordConfirmAttempt_,
530             this.onConfirmPasswordCollected_.bind(this));
531       } else {
532         chrome.send('scrapedPasswordVerificationFailed');
533         this.showFatalAuthError(
534             loadTimeData.getString('fatalErrorMessageVerificationFailed'));
535       }
536     },
538     /**
539      * Invoked when the confirm password screen is dismissed.
540      * @private
541      */
542     onConfirmPasswordCollected_: function(password) {
543       this.samlPasswordConfirmAttempt_++;
544       this.gaiaAuthHost_.verifyConfirmedPassword(password);
546       // Shows signin UI again without changing states.
547       Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
548     },
550     /**
551      * Inovked when the user has successfully authenticated via SAML, the
552      * principals API was not used and no passwords could be scraped.
553      * @param {string} email The authenticated user's e-mail.
554      */
555     onAuthNoPassword_: function(email) {
556       this.showFatalAuthError(loadTimeData.getString(
557           'fatalErrorMessageNoPassword'));
558       chrome.send('scrapedPasswordCount', [0]);
559     },
561     /**
562      * Invoked when the authentication flow had to be aborted because content
563      * served over an unencrypted connection was detected. Shows a fatal error.
564      * This method is only called on Chrome OS, where the entire authentication
565      * flow is required to be encrypted.
566      * @param {string} url The URL that was blocked.
567      */
568     onInsecureContentBlocked_: function(url) {
569       this.showFatalAuthError(loadTimeData.getStringF(
570           'fatalErrorMessageInsecureURL',
571           url));
572     },
574     /**
575      * Shows the fatal auth error.
576      * @param {string} message The error message to show.
577      */
578     showFatalAuthError: function(message) {
579       login.FatalErrorScreen.show(message, Oobe.showSigninUI);
580     },
582     /**
583      * Show fatal auth error when information is missing from GAIA.
584      */
585     missingGaiaInfo_: function() {
586       this.showFatalAuthError(
587           loadTimeData.getString('fatalErrorMessageNoAccountDetails'));
588     },
590     /**
591      * Record that SAML API was used during sign-in.
592      */
593     samlApiUsed_: function() {
594       chrome.send('usingSAMLAPI');
595     },
597     /**
598      * Invoked when auth is completed successfully.
599      * @param {!Object} credentials Credentials of the completed authentication.
600      * @private
601      */
602     onAuthCompleted_: function(credentials) {
603       if (credentials.useOffline) {
604         this.email = credentials.email;
605         chrome.send('authenticateUser',
606                     [credentials.email,
607                      credentials.password]);
608       } else if (credentials.authCode) {
609         if (credentials.hasOwnProperty('authCodeOnly') &&
610             credentials.authCodeOnly) {
611           chrome.send('completeAuthenticationAuthCodeOnly',
612                       [credentials.authCode]);
613         } else {
614           chrome.send('completeAuthentication',
615                       [credentials.gaiaId,
616                        credentials.email,
617                        credentials.password,
618                        credentials.authCode,
619                        credentials.usingSAML]);
620         }
621       } else {
622         chrome.send('completeLogin',
623                     [credentials.gaiaId,
624                      credentials.email,
625                      credentials.password,
626                      credentials.usingSAML]);
627       }
629       this.loading = true;
630       this.classList.add('auth-completed');
631       // Now that we're in logged in state header should be hidden.
632       Oobe.getInstance().headerHidden = true;
633       // Clear any error messages that were shown before login.
634       Oobe.clearErrors();
635     },
637     /**
638      * Invoked when onAuthCompleted message received.
639      * @param {!Object} e Payload of the received HTML5 message.
640      * @private
641      */
642     onAuthCompletedMessage_: function(e) {
643       this.onAuthCompleted_(e.detail);
644     },
646     /**
647      * Invoked when onLoadAbort message received.
648      * @param {!Object} e Payload of the received HTML5 message.
649      * @private
650      */
651     onLoadAbortMessage_: function(e) {
652       this.onWebviewError(e.detail);
653     },
655     /**
656      * Clears input fields and switches to input mode.
657      * @param {boolean} takeFocus True to take focus.
658      * @param {boolean} forceOnline Whether online sign-in should be forced.
659      * If |forceOnline| is false previously used sign-in type will be used.
660      */
661     reset: function(takeFocus, forceOnline) {
662       // Reload and show the sign-in UI if needed.
663       if (takeFocus) {
664         if (!forceOnline && this.isLocal) {
665           // Show 'Cancel' button to allow user to return to the main screen
666           // (e.g. this makes sense when connection is back).
667           Oobe.getInstance().headerHidden = false;
668           $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
669           // Do nothing, since offline version is reloaded after an error comes.
670         } else {
671           Oobe.showSigninUI();
672         }
673       }
674     },
676     /**
677      * Reloads extension frame.
678      */
679     doReload: function() {
680       this.error_ = 0;
681       this.gaiaAuthHost_.reload();
682       this.loading = true;
683       this.startLoadingTimer_();
684     },
686     /**
687      * Updates localized content of the screen that is not updated via template.
688      */
689     updateLocalizedContent: function() {
690       $('createAccount').innerHTML = loadTimeData.getStringF(
691           'createAccount',
692           '<a id="createAccountLink" class="signin-link" href="#">',
693           '</a>');
694       $('guestSignin').innerHTML = loadTimeData.getStringF(
695           'guestSignin',
696           '<a id="guestSigninLink" class="signin-link" href="#">',
697           '</a>');
698       $('createSupervisedUserLinkPlaceholder').innerHTML =
699           loadTimeData.getStringF(
700               'createSupervisedUser',
701               '<a id="createSupervisedUserLink" class="signin-link" href="#">',
702               '</a>');
703       $('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
704           'consumerManagementEnrollmentSigninMessage');
705       $('createAccountLink').addEventListener('click', function(e) {
706         chrome.send('createAccount');
707         e.preventDefault();
708       });
709       $('guestSigninLink').addEventListener('click', function(e) {
710         chrome.send('launchIncognito');
711         e.preventDefault();
712       });
713       $('createSupervisedUserLink').addEventListener('click', function(e) {
714         chrome.send('showSupervisedUserCreationScreen');
715         e.preventDefault();
716       });
717     },
719     /**
720      * Shows sign-in error bubble.
721      * @param {number} loginAttempts Number of login attemps tried.
722      * @param {HTMLElement} content Content to show in bubble.
723      */
724     showErrorBubble: function(loginAttempts, error) {
725       if (this.isLocal) {
726         $('add-user-button').hidden = true;
727         $('cancel-add-user-button').hidden = false;
728         // Reload offline version of the sign-in extension, which will show
729         // error itself.
730         chrome.send('offlineLogin', [this.email]);
731       } else if (!this.loading) {
732         // We want to show bubble near "Email" field, but we can't calculate
733         // it's position because it is located inside iframe. So we only
734         // can hardcode some constants.
735         /** @const */ var ERROR_BUBBLE_OFFSET = 84;
736         /** @const */ var ERROR_BUBBLE_PADDING = 0;
737         $('bubble').showContentForElement($('login-box'),
738                                           cr.ui.Bubble.Attachment.LEFT,
739                                           error,
740                                           ERROR_BUBBLE_OFFSET,
741                                           ERROR_BUBBLE_PADDING);
742       } else {
743         // Defer the bubble until the frame has been loaded.
744         this.errorBubble_ = [loginAttempts, error];
745       }
746     },
748     /**
749      * Called when user canceled signin.
750      */
751     cancel: function() {
752       if (!this.cancelAllowed_) {
753         // In OOBE signin screen, cancel is not allowed because there is
754         // no other screen to show. If user is in middle of a saml flow,
755         // reset signin screen to get out of the saml flow.
756         if (this.isSAML())
757           Oobe.resetSigninUI(true);
759         return;
760       }
762       $('pod-row').loadLastWallpaper();
763       Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
764       this.classList.remove('whitelist-error');
765       Oobe.resetSigninUI(true);
766     },
768     /**
769      * Handler for iframe's error notification coming from the outside.
770      * For more info see C++ class 'WebUILoginView' which calls this method.
771      * @param {number} error Error code.
772      * @param {string} url The URL that failed to load.
773      */
774     onFrameError: function(error, url) {
775       this.error_ = error;
776       chrome.send('frameLoadingCompleted', [this.error_]);
777     },
779     /**
780      * Handler for webview error handling.
781      * @param {!Object} data Additional information about error event like:
782      * {string} error Error code such as "ERR_INTERNET_DISCONNECTED".
783      * {string} url The URL that failed to load.
784      */
785     onWebviewError: function(data) {
786       chrome.send('webviewLoadAborted', [data.error]);
787     },
789     /**
790      * Show/Hide error when user is not in whitelist. When UI is hidden
791      * GAIA is reloaded.
792      * @param {boolean} show Show/hide error UI.
793      * @param {!Object} opt_data Optional additional information.
794      */
795     showWhitelistCheckFailedError: function(show, opt_data) {
796       if (opt_data) {
797         $('gaia-whitelist-error').enterpriseManaged =
798             opt_data.enterpriseManaged;
799       }
801       this.classList.toggle('whitelist-error', show);
802       this.loading = !show;
804       if (!show)
805         Oobe.showSigninUI();
806     },
807   };