Make AppLifetimeHelper a member of ApplicationImpl.
[chromium-blink-merge.git] / ui / login / display_manager.js
blob7fb2a61ea6a8ebabeaa535893df252d79de6e835
1 // Copyright 2014 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 Display manager for WebUI OOBE and login.
7  */
9 // TODO(xiyuan): Find a better to share those constants.
10 /** @const */ var SCREEN_OOBE_NETWORK = 'connect';
11 /** @const */ var SCREEN_OOBE_HID_DETECTION = 'hid-detection';
12 /** @const */ var SCREEN_OOBE_EULA = 'eula';
13 /** @const */ var SCREEN_OOBE_ENABLE_DEBUGGING = 'debugging';
14 /** @const */ var SCREEN_OOBE_UPDATE = 'update';
15 /** @const */ var SCREEN_OOBE_RESET = 'reset';
16 /** @const */ var SCREEN_OOBE_ENROLLMENT = 'oauth-enrollment';
17 /** @const */ var SCREEN_OOBE_KIOSK_ENABLE = 'kiosk-enable';
18 /** @const */ var SCREEN_OOBE_AUTO_ENROLLMENT_CHECK = 'auto-enrollment-check';
19 /** @const */ var SCREEN_GAIA_SIGNIN = 'gaia-signin';
20 /** @const */ var SCREEN_ACCOUNT_PICKER = 'account-picker';
21 /** @const */ var SCREEN_USER_IMAGE_PICKER = 'user-image';
22 /** @const */ var SCREEN_ERROR_MESSAGE = 'error-message';
23 /** @const */ var SCREEN_TPM_ERROR = 'tpm-error-message';
24 /** @const */ var SCREEN_PASSWORD_CHANGED = 'password-changed';
25 /** @const */ var SCREEN_CREATE_SUPERVISED_USER_FLOW =
26     'supervised-user-creation';
27 /** @const */ var SCREEN_APP_LAUNCH_SPLASH = 'app-launch-splash';
28 /** @const */ var SCREEN_CONFIRM_PASSWORD = 'confirm-password';
29 /** @const */ var SCREEN_FATAL_ERROR = 'fatal-error';
30 /** @const */ var SCREEN_KIOSK_ENABLE = 'kiosk-enable';
31 /** @const */ var SCREEN_TERMS_OF_SERVICE = 'terms-of-service';
32 /** @const */ var SCREEN_WRONG_HWID = 'wrong-hwid';
33 /** @const */ var SCREEN_DEVICE_DISABLED = 'device-disabled';
35 /* Accelerator identifiers. Must be kept in sync with webui_login_view.cc. */
36 /** @const */ var ACCELERATOR_CANCEL = 'cancel';
37 /** @const */ var ACCELERATOR_ENABLE_DEBBUGING = 'debugging';
38 /** @const */ var ACCELERATOR_TOGGLE_EASY_BOOTSTRAP = 'toggle_easy_bootstrap';
39 /** @const */ var ACCELERATOR_ENROLLMENT = 'enrollment';
40 /** @const */ var ACCELERATOR_KIOSK_ENABLE = 'kiosk_enable';
41 /** @const */ var ACCELERATOR_VERSION = 'version';
42 /** @const */ var ACCELERATOR_RESET = 'reset';
43 /** @const */ var ACCELERATOR_FOCUS_PREV = 'focus_prev';
44 /** @const */ var ACCELERATOR_FOCUS_NEXT = 'focus_next';
45 /** @const */ var ACCELERATOR_DEVICE_REQUISITION = 'device_requisition';
46 /** @const */ var ACCELERATOR_DEVICE_REQUISITION_REMORA =
47     'device_requisition_remora';
48 /** @const */ var ACCELERATOR_DEVICE_REQUISITION_SHARK =
49     'device_requisition_shark';
50 /** @const */ var ACCELERATOR_APP_LAUNCH_BAILOUT = 'app_launch_bailout';
51 /** @const */ var ACCELERATOR_APP_LAUNCH_NETWORK_CONFIG =
52     'app_launch_network_config';
53 /** @const */ var ACCELERATOR_NEW_OOBE = 'new_oobe';
54 /** @const */ var ACCELERATOR_TOGGLE_WEBVIEW_SIGNIN = 'toggle_webview_signin';
55 /** @const */ var ACCELERATOR_TOGGLE_NEW_LOGIN_UI = 'toggle_new_login_ui';
57 /* Signin UI state constants. Used to control header bar UI. */
58 /** @const */ var SIGNIN_UI_STATE = {
59   HIDDEN: 0,
60   GAIA_SIGNIN: 1,
61   ACCOUNT_PICKER: 2,
62   WRONG_HWID_WARNING: 3,
63   SUPERVISED_USER_CREATION_FLOW: 4,
64   SAML_PASSWORD_CONFIRM: 5,
65   CONSUMER_MANAGEMENT_ENROLLMENT: 6,
66   PASSWORD_CHANGED: 7,
69 /* Possible UI states of the error screen. */
70 /** @const */ var ERROR_SCREEN_UI_STATE = {
71   UNKNOWN: 'ui-state-unknown',
72   UPDATE: 'ui-state-update',
73   SIGNIN: 'ui-state-signin',
74   SUPERVISED_USER_CREATION_FLOW: 'ui-state-supervised',
75   KIOSK_MODE: 'ui-state-kiosk-mode',
76   LOCAL_STATE_ERROR: 'ui-state-local-state-error',
77   AUTO_ENROLLMENT_ERROR: 'ui-state-auto-enrollment-error',
78   ROLLBACK_ERROR: 'ui-state-rollback-error'
81 /* Possible types of UI. */
82 /** @const */ var DISPLAY_TYPE = {
83   UNKNOWN: 'unknown',
84   OOBE: 'oobe',
85   LOGIN: 'login',
86   LOCK: 'lock',
87   USER_ADDING: 'user-adding',
88   APP_LAUNCH_SPLASH: 'app-launch-splash',
89   DESKTOP_USER_MANAGER: 'login-add-user'
92 /** @const */ var USER_ACTION_ROLLBACK_TOGGLED = 'rollback-toggled';
94 cr.define('cr.ui.login', function() {
95   var Bubble = cr.ui.Bubble;
97   /**
98    * Maximum time in milliseconds to wait for step transition to finish.
99    * The value is used as the duration for ensureTransitionEndEvent below.
100    * It needs to be inline with the step screen transition duration time
101    * defined in css file. The current value in css is 200ms. To avoid emulated
102    * webkitTransitionEnd fired before real one, 250ms is used.
103    * @const
104    */
105   var MAX_SCREEN_TRANSITION_DURATION = 250;
107   /**
108    * Groups of screens (screen IDs) that should have the same dimensions.
109    * @type Array.<Array.<string>>
110    * @const
111    */
112   var SCREEN_GROUPS = [[SCREEN_OOBE_NETWORK,
113                         SCREEN_OOBE_EULA,
114                         SCREEN_OOBE_UPDATE,
115                         SCREEN_OOBE_AUTO_ENROLLMENT_CHECK]
116                       ];
117   /**
118    * Group of screens (screen IDs) where factory-reset screen invocation is
119    * available.
120    * @type Array.<string>
121    * @const
122    */
123   var RESET_AVAILABLE_SCREEN_GROUP = [
124     SCREEN_OOBE_NETWORK,
125     SCREEN_OOBE_EULA,
126     SCREEN_OOBE_UPDATE,
127     SCREEN_OOBE_ENROLLMENT,
128     SCREEN_OOBE_AUTO_ENROLLMENT_CHECK,
129     SCREEN_GAIA_SIGNIN,
130     SCREEN_ACCOUNT_PICKER,
131     SCREEN_KIOSK_ENABLE,
132     SCREEN_ERROR_MESSAGE,
133     SCREEN_USER_IMAGE_PICKER,
134     SCREEN_TPM_ERROR,
135     SCREEN_PASSWORD_CHANGED,
136     SCREEN_TERMS_OF_SERVICE,
137     SCREEN_WRONG_HWID,
138     SCREEN_CONFIRM_PASSWORD,
139     SCREEN_FATAL_ERROR
140   ];
142   /**
143    * Group of screens (screen IDs) where enable debuggingscreen invocation is
144    * available.
145    * @type Array.<string>
146    * @const
147    */
148   var ENABLE_DEBUGGING_AVAILABLE_SCREEN_GROUP = [
149     SCREEN_OOBE_HID_DETECTION,
150     SCREEN_OOBE_NETWORK,
151     SCREEN_OOBE_EULA,
152     SCREEN_OOBE_UPDATE,
153     SCREEN_TERMS_OF_SERVICE
154   ];
156   /**
157    * Group of screens (screen IDs) that are not participating in
158    * left-current-right animation.
159    * @type Array.<string>
160    * @const
161    */
162   var NOT_ANIMATED_SCREEN_GROUP = [
163     SCREEN_OOBE_ENABLE_DEBUGGING,
164     SCREEN_OOBE_RESET,
165   ];
168   /**
169    * OOBE screens group index.
170    */
171   var SCREEN_GROUP_OOBE = 0;
173   /**
174    * Constructor a display manager that manages initialization of screens,
175    * transitions, error messages display.
176    *
177    * @constructor
178    */
179   function DisplayManager() {
180   }
182   DisplayManager.prototype = {
183     /**
184      * Registered screens.
185      */
186     screens_: [],
188     /**
189      * Current OOBE step, index in the screens array.
190      * @type {number}
191      */
192     currentStep_: 0,
194     /**
195      * Whether version label can be toggled by ACCELERATOR_VERSION.
196      * @type {boolean}
197      */
198     allowToggleVersion_: false,
200     /**
201      * Whether keyboard navigation flow is enforced.
202      * @type {boolean}
203      */
204     forceKeyboardFlow_: false,
206     /**
207      * Whether virtual keyboard is shown.
208      * @type {boolean}
209      */
210     virtualKeyboardShown_: false,
212     /**
213      * Virtual keyboard width.
214      * @type {number}
215      */
216     virtualKeyboardWidth_: 0,
218     /**
219      * Virtual keyboard height.
220      * @type {number}
221      */
222     virtualKeyboardHeight_: 0,
224     /**
225      * Type of UI.
226      * @type {string}
227      */
228     displayType_: DISPLAY_TYPE.UNKNOWN,
230     /**
231      * Error message (bubble) was shown. This is checked in tests.
232      */
233     errorMessageWasShownForTesting_: false,
235     get displayType() {
236       return this.displayType_;
237     },
239     set displayType(displayType) {
240       this.displayType_ = displayType;
241       document.documentElement.setAttribute('screen', displayType);
242     },
244     get newKioskUI() {
245       return loadTimeData.getString('newKioskUI') == 'on';
246     },
248     /**
249      * Returns dimensions of screen exluding header bar.
250      * @type {Object}
251      */
252     get clientAreaSize() {
253       var container = $('outer-container');
254       return {width: container.offsetWidth, height: container.offsetHeight};
255     },
257     /**
258      * Gets current screen element.
259      * @type {HTMLElement}
260      */
261     get currentScreen() {
262       return $(this.screens_[this.currentStep_]);
263     },
265     /**
266      * Hides/shows header (Shutdown/Add User/Cancel buttons).
267      * @param {boolean} hidden Whether header is hidden.
268      */
269     get headerHidden() {
270       return $('login-header-bar').hidden;
271     },
273     set headerHidden(hidden) {
274       $('login-header-bar').hidden = hidden;
275     },
277     /**
278      * Virtual keyboard state (hidden/shown).
279      * @param {boolean} hidden Whether keyboard is shown.
280      */
281     get virtualKeyboardShown() {
282       return this.virtualKeyboardShown_;
283     },
285     set virtualKeyboardShown(shown) {
286       this.virtualKeyboardShown_ = shown;
287     },
289     /**
290      * Sets the current size of the virtual keyboard.
291      * @param {number} width keyboard width
292      * @param {number} height keyboard height
293      */
294     setVirtualKeyboardSize: function(width, height) {
295       this.virtualKeyboardWidth_ = width;
296       this.virtualKeyboardHeight_ = height;
297     },
299     /**
300      * Sets the current size of the client area (display size).
301      * @param {number} width client area width
302      * @param {number} height client area height
303      */
304     setClientAreaSize: function(width, height) {
305       var clientArea = $('outer-container');
306       var bottom = parseInt(window.getComputedStyle(clientArea).bottom);
307       clientArea.style.minHeight = cr.ui.toCssPx(height - bottom);
308     },
310     /**
311      * Toggles background of main body between transparency and solid.
312      * @param {boolean} solid Whether to show a solid background.
313      */
314     set solidBackground(solid) {
315       if (solid)
316         document.body.classList.add('solid');
317       else
318         document.body.classList.remove('solid');
319     },
321     /**
322      * Forces keyboard based OOBE navigation.
323      * @param {boolean} value True if keyboard navigation flow is forced.
324      */
325     set forceKeyboardFlow(value) {
326       this.forceKeyboardFlow_ = value;
327       if (value) {
328         keyboard.initializeKeyboardFlow();
329         cr.ui.DropDown.enableKeyboardFlow();
330         for (var i = 0; i < this.screens_.length; ++i) {
331           var screen = $(this.screens_[i]);
332           if (screen.enableKeyboardFlow)
333             screen.enableKeyboardFlow();
334         }
335       }
336     },
338     /**
339      * Shows/hides version labels.
340      * @param {boolean} show Whether labels should be visible by default. If
341      *     false, visibility can be toggled by ACCELERATOR_VERSION.
342      */
343     showVersion: function(show) {
344       $('version-labels').hidden = !show;
345       this.allowToggleVersion_ = !show;
346     },
348     /**
349      * Handle accelerators.
350      * @param {string} name Accelerator name.
351      */
352     handleAccelerator: function(name) {
353       if (this.currentScreen.ignoreAccelerators) {
354         return;
355       }
356       var currentStepId = this.screens_[this.currentStep_];
357       if (name == ACCELERATOR_CANCEL) {
358         if (this.currentScreen.cancel) {
359           this.currentScreen.cancel();
360         }
361       } else if (name == ACCELERATOR_ENABLE_DEBBUGING) {
362         if (ENABLE_DEBUGGING_AVAILABLE_SCREEN_GROUP.indexOf(
363                 currentStepId) != -1) {
364           chrome.send('toggleEnableDebuggingScreen');
365         }
366       } else if (name == ACCELERATOR_ENROLLMENT) {
367         if (currentStepId == SCREEN_GAIA_SIGNIN ||
368             currentStepId == SCREEN_ACCOUNT_PICKER) {
369           chrome.send('toggleEnrollmentScreen');
370         } else if (currentStepId == SCREEN_OOBE_NETWORK ||
371                    currentStepId == SCREEN_OOBE_EULA) {
372           // In this case update check will be skipped and OOBE will
373           // proceed straight to enrollment screen when EULA is accepted.
374           chrome.send('skipUpdateEnrollAfterEula');
375         }
376       } else if (name == ACCELERATOR_KIOSK_ENABLE) {
377         if (currentStepId == SCREEN_GAIA_SIGNIN ||
378             currentStepId == SCREEN_ACCOUNT_PICKER) {
379           chrome.send('toggleKioskEnableScreen');
380         }
381       } else if (name == ACCELERATOR_VERSION) {
382         if (this.allowToggleVersion_)
383           $('version-labels').hidden = !$('version-labels').hidden;
384       } else if (name == ACCELERATOR_RESET) {
385         if (currentStepId == SCREEN_OOBE_RESET)
386           $('reset').send(login.Screen.CALLBACK_USER_ACTED,
387                           USER_ACTION_ROLLBACK_TOGGLED);
388         else if (RESET_AVAILABLE_SCREEN_GROUP.indexOf(currentStepId) != -1)
389           chrome.send('toggleResetScreen');
390       } else if (name == ACCELERATOR_DEVICE_REQUISITION) {
391         if (this.isOobeUI())
392           this.showDeviceRequisitionPrompt_();
393       } else if (name == ACCELERATOR_DEVICE_REQUISITION_REMORA) {
394         if (this.isOobeUI())
395           this.showDeviceRequisitionRemoraPrompt_(
396               'deviceRequisitionRemoraPromptText', 'remora');
397       } else if (name == ACCELERATOR_DEVICE_REQUISITION_SHARK) {
398         if (this.isOobeUI())
399           this.showDeviceRequisitionRemoraPrompt_(
400               'deviceRequisitionSharkPromptText', 'shark');
401       } else if (name == ACCELERATOR_APP_LAUNCH_BAILOUT) {
402         if (currentStepId == SCREEN_APP_LAUNCH_SPLASH)
403           chrome.send('cancelAppLaunch');
404       } else if (name == ACCELERATOR_APP_LAUNCH_NETWORK_CONFIG) {
405         if (currentStepId == SCREEN_APP_LAUNCH_SPLASH)
406           chrome.send('networkConfigRequest');
407       } else if (name == ACCELERATOR_NEW_OOBE) {
408         chrome.send('switchToNewOobe');
409       } else if (name == ACCELERATOR_TOGGLE_WEBVIEW_SIGNIN) {
410         if (currentStepId == SCREEN_GAIA_SIGNIN ||
411             currentStepId == SCREEN_OOBE_ENROLLMENT)
412           chrome.send('toggleWebviewSignin');
413       } else if (name == ACCELERATOR_TOGGLE_NEW_LOGIN_UI) {
414         if (currentStepId == SCREEN_OOBE_NETWORK)
415           chrome.send('toggleNewLoginUI');
416       } else if (name == ACCELERATOR_TOGGLE_EASY_BOOTSTRAP) {
417         if (currentStepId == SCREEN_GAIA_SIGNIN)
418           chrome.send('toggleEasyBootstrap');
419       }
421       if (!this.forceKeyboardFlow_)
422         return;
424       // Handle special accelerators for keyboard enhanced navigation flow.
425       if (name == ACCELERATOR_FOCUS_PREV)
426         keyboard.raiseKeyFocusPrevious(document.activeElement);
427       else if (name == ACCELERATOR_FOCUS_NEXT)
428         keyboard.raiseKeyFocusNext(document.activeElement);
429     },
431     /**
432      * Appends buttons to the button strip.
433      * @param {Array.<HTMLElement>} buttons Array with the buttons to append.
434      * @param {string} screenId Id of the screen that buttons belong to.
435      */
436     appendButtons_: function(buttons, screenId) {
437       if (buttons) {
438         var buttonStrip = $(screenId + '-controls');
439         if (buttonStrip) {
440           for (var i = 0; i < buttons.length; ++i)
441             buttonStrip.appendChild(buttons[i]);
442         }
443       }
444     },
446     /**
447      * Disables or enables control buttons on the specified screen.
448      * @param {HTMLElement} screen Screen which controls should be affected.
449      * @param {boolean} disabled Whether to disable controls.
450      */
451     disableButtons_: function(screen, disabled) {
452       var buttons = document.querySelectorAll(
453           '#' + screen.id + '-controls button:not(.preserve-disabled-state)');
454       for (var i = 0; i < buttons.length; ++i) {
455         buttons[i].disabled = disabled;
456       }
457     },
459     screenIsAnimated_: function(screenId) {
460       return NOT_ANIMATED_SCREEN_GROUP.indexOf(screenId) != -1;
461     },
463     /**
464      * Updates a step's css classes to reflect left, current, or right position.
465      * @param {number} stepIndex step index.
466      * @param {string} state one of 'left', 'current', 'right'.
467      */
468     updateStep_: function(stepIndex, state) {
469       var stepId = this.screens_[stepIndex];
470       var step = $(stepId);
471       var header = $('header-' + stepId);
472       var states = ['left', 'right', 'current'];
473       for (var i = 0; i < states.length; ++i) {
474         if (states[i] != state) {
475           step.classList.remove(states[i]);
476           header.classList.remove(states[i]);
477         }
478       }
480       step.classList.add(state);
481       header.classList.add(state);
482     },
484     /**
485      * Switches to the next OOBE step.
486      * @param {number} nextStepIndex Index of the next step.
487      */
488     toggleStep_: function(nextStepIndex, screenData) {
489       var currentStepId = this.screens_[this.currentStep_];
490       var nextStepId = this.screens_[nextStepIndex];
491       var oldStep = $(currentStepId);
492       var newStep = $(nextStepId);
493       var newHeader = $('header-' + nextStepId);
495       // Disable controls before starting animation.
496       this.disableButtons_(oldStep, true);
498       if (oldStep.onBeforeHide)
499         oldStep.onBeforeHide();
501       $('oobe').className = nextStepId;
503       // Need to do this before calling newStep.onBeforeShow() so that new step
504       // is back in DOM tree and has correct offsetHeight / offsetWidth.
505       newStep.hidden = false;
507       if (newStep.onBeforeShow)
508         newStep.onBeforeShow(screenData);
510       newStep.classList.remove('hidden');
512       if (this.isOobeUI() &&
513           this.screenIsAnimated_(nextStepId) &&
514           this.screenIsAnimated_(currentStepId)) {
515         // Start gliding animation for OOBE steps.
516         if (nextStepIndex > this.currentStep_) {
517           for (var i = this.currentStep_; i < nextStepIndex; ++i)
518             this.updateStep_(i, 'left');
519           this.updateStep_(nextStepIndex, 'current');
520         } else if (nextStepIndex < this.currentStep_) {
521           for (var i = this.currentStep_; i > nextStepIndex; --i)
522             this.updateStep_(i, 'right');
523           this.updateStep_(nextStepIndex, 'current');
524         }
525       } else {
526         // Start fading animation for login display or reset screen.
527         oldStep.classList.add('faded');
528         newStep.classList.remove('faded');
529         if (!this.screenIsAnimated_(nextStepId)) {
530           newStep.classList.remove('left');
531           newStep.classList.remove('right');
532         }
533       }
535       this.disableButtons_(newStep, false);
537       // Adjust inner container height based on new step's height.
538       this.updateScreenSize(newStep);
540       if (newStep.onAfterShow)
541         newStep.onAfterShow(screenData);
543       // Workaround for gaia and network screens.
544       // Due to other origin iframe and long ChromeVox focusing correspondingly
545       // passive aria-label title is not pronounced.
546       // Gaia hack can be removed on fixed crbug.com/316726.
547       if (nextStepId == SCREEN_GAIA_SIGNIN) {
548         newStep.setAttribute(
549             'aria-label',
550             loadTimeData.getString('signinScreenTitle'));
551       } else if (nextStepId == SCREEN_OOBE_NETWORK) {
552         newStep.setAttribute(
553             'aria-label',
554             loadTimeData.getString('networkScreenAccessibleTitle'));
555       }
557       // Default control to be focused (if specified).
558       var defaultControl = newStep.defaultControl;
560       var outerContainer = $('outer-container');
561       var innerContainer = $('inner-container');
562       var isOOBE = this.isOobeUI();
563       if (this.currentStep_ != nextStepIndex &&
564           !oldStep.classList.contains('hidden')) {
565         if (oldStep.classList.contains('animated')) {
566           innerContainer.classList.add('animation');
567           oldStep.addEventListener('webkitTransitionEnd', function f(e) {
568             oldStep.removeEventListener('webkitTransitionEnd', f);
569             if (oldStep.classList.contains('faded') ||
570                 oldStep.classList.contains('left') ||
571                 oldStep.classList.contains('right')) {
572               innerContainer.classList.remove('animation');
573               oldStep.classList.add('hidden');
574               if (!isOOBE)
575                 oldStep.hidden = true;
576             }
577             // Refresh defaultControl. It could have changed.
578             var defaultControl = newStep.defaultControl;
579             if (defaultControl)
580               defaultControl.focus();
581           });
582           ensureTransitionEndEvent(oldStep, MAX_SCREEN_TRANSITION_DURATION);
583         } else {
584           oldStep.classList.add('hidden');
585           oldStep.hidden = true;
586           if (defaultControl)
587             defaultControl.focus();
588         }
589       } else {
590         // First screen on OOBE launch.
591         if (this.isOobeUI() && innerContainer.classList.contains('down')) {
592           innerContainer.classList.remove('down');
593           innerContainer.addEventListener(
594               'webkitTransitionEnd', function f(e) {
595                 innerContainer.removeEventListener('webkitTransitionEnd', f);
596                 outerContainer.classList.remove('down');
597                 $('progress-dots').classList.remove('down');
598                 chrome.send('loginVisible', ['oobe']);
599                 // Refresh defaultControl. It could have changed.
600                 var defaultControl = newStep.defaultControl;
601                 if (defaultControl)
602                   defaultControl.focus();
603               });
604           ensureTransitionEndEvent(innerContainer,
605                                    MAX_SCREEN_TRANSITION_DURATION);
606         } else {
607           if (defaultControl)
608             defaultControl.focus();
609           chrome.send('loginVisible', ['oobe']);
610         }
611       }
612       this.currentStep_ = nextStepIndex;
614       $('step-logo').hidden = newStep.classList.contains('no-logo');
616       chrome.send('updateCurrentScreen', [this.currentScreen.id]);
617     },
619     /**
620      * Make sure that screen is initialized and decorated.
621      * @param {Object} screen Screen params dict, e.g. {id: screenId, data: {}}.
622      */
623     preloadScreen: function(screen) {
624       var screenEl = $(screen.id);
625       if (screenEl.deferredInitialization !== undefined) {
626         screenEl.deferredInitialization();
627         delete screenEl.deferredInitialization;
628       }
629     },
631     /**
632      * Show screen of given screen id.
633      * @param {Object} screen Screen params dict, e.g. {id: screenId, data: {}}.
634      */
635     showScreen: function(screen) {
636       // Do not allow any other screen to clobber the device disabled screen.
637       if (this.currentScreen.id == SCREEN_DEVICE_DISABLED)
638         return;
640       var screenId = screen.id;
642       // Make sure the screen is decorated.
643       this.preloadScreen(screen);
645       if (screen.data !== undefined && screen.data.disableAddUser)
646         DisplayManager.updateAddUserButtonStatus(true);
649       // Show sign-in screen instead of account picker if pod row is empty.
650       if (screenId == SCREEN_ACCOUNT_PICKER && $('pod-row').pods.length == 0) {
651         // Manually hide 'add-user' header bar, because of the case when
652         // 'Cancel' button is used on the offline login page.
653         $('add-user-header-bar-item').hidden = true;
654         Oobe.showSigninUI(true);
655         return;
656       }
658       var data = screen.data;
659       var index = this.getScreenIndex_(screenId);
660       if (index >= 0)
661         this.toggleStep_(index, data);
662     },
664     /**
665      * Gets index of given screen id in screens_.
666      * @param {string} screenId Id of the screen to look up.
667      * @private
668      */
669     getScreenIndex_: function(screenId) {
670       for (var i = 0; i < this.screens_.length; ++i) {
671         if (this.screens_[i] == screenId)
672           return i;
673       }
674       return -1;
675     },
677     /**
678      * Register an oobe screen.
679      * @param {Element} el Decorated screen element.
680      */
681     registerScreen: function(el) {
682       var screenId = el.id;
683       this.screens_.push(screenId);
685       var header = document.createElement('span');
686       header.id = 'header-' + screenId;
687       header.textContent = el.header ? el.header : '';
688       header.className = 'header-section';
689       $('header-sections').appendChild(header);
691       var dot = document.createElement('div');
692       dot.id = screenId + '-dot';
693       dot.className = 'progdot';
694       var progressDots = $('progress-dots');
695       if (progressDots)
696         progressDots.appendChild(dot);
698       this.appendButtons_(el.buttons, screenId);
699     },
701     /**
702      * Updates inner container size based on the size of the current screen and
703      * other screens in the same group.
704      * Should be executed on screen change / screen size change.
705      * @param {!HTMLElement} screen Screen that is being shown.
706      */
707     updateScreenSize: function(screen) {
708       // Have to reset any previously predefined screen size first
709       // so that screen contents would define it instead.
710       $('inner-container').style.height = '';
711       $('inner-container').style.width = '';
712       screen.style.width = '';
713       screen.style.height = '';
715       $('outer-container').classList.toggle(
716         'fullscreen', screen.classList.contains('fullscreen'));
718       var width = screen.getPreferredSize().width;
719       var height = screen.getPreferredSize().height;
720       for (var i = 0, screenGroup; screenGroup = SCREEN_GROUPS[i]; i++) {
721         if (screenGroup.indexOf(screen.id) != -1) {
722           // Set screen dimensions to maximum dimensions within this group.
723           for (var j = 0, screen2; screen2 = $(screenGroup[j]); j++) {
724             width = Math.max(width, screen2.getPreferredSize().width);
725             height = Math.max(height, screen2.getPreferredSize().height);
726           }
727           break;
728         }
729       }
730       $('inner-container').style.height = height + 'px';
731       $('inner-container').style.width = width + 'px';
732       // This requires |screen| to have 'box-sizing: border-box'.
733       screen.style.width = width + 'px';
734       screen.style.height = height + 'px';
735     },
737     /**
738      * Updates localized content of the screens like headers, buttons and links.
739      * Should be executed on language change.
740      */
741     updateLocalizedContent_: function() {
742       for (var i = 0, screenId; screenId = this.screens_[i]; ++i) {
743         var screen = $(screenId);
744         var buttonStrip = $(screenId + '-controls');
745         if (buttonStrip)
746           buttonStrip.innerHTML = '';
747         // TODO(nkostylev): Update screen headers for new OOBE design.
748         this.appendButtons_(screen.buttons, screenId);
749         if (screen.updateLocalizedContent)
750           screen.updateLocalizedContent();
751       }
753       var currentScreenId = this.screens_[this.currentStep_];
754       var currentScreen = $(currentScreenId);
755       this.updateScreenSize(currentScreen);
757       // Trigger network drop-down to reload its state
758       // so that strings are reloaded.
759       // Will be reloaded if drowdown is actually shown.
760       cr.ui.DropDown.refresh();
761     },
763     /**
764      * Initialized first group of OOBE screens.
765      */
766     initializeOOBEScreens: function() {
767       if (this.isOobeUI() && $('inner-container').classList.contains('down')) {
768         for (var i = 0, screen;
769              screen = $(SCREEN_GROUPS[SCREEN_GROUP_OOBE][i]); i++) {
770           screen.hidden = false;
771         }
772       }
773     },
775     /**
776      * Prepares screens to use in login display.
777      */
778     prepareForLoginDisplay_: function() {
779       for (var i = 0, screenId; screenId = this.screens_[i]; ++i) {
780         var screen = $(screenId);
781         screen.classList.add('faded');
782         screen.classList.remove('right');
783         screen.classList.remove('left');
784       }
785     },
787     /**
788      * Shows the device requisition prompt.
789      */
790     showDeviceRequisitionPrompt_: function() {
791       if (!this.deviceRequisitionDialog_) {
792         this.deviceRequisitionDialog_ =
793             new cr.ui.dialogs.PromptDialog(document.body);
794         this.deviceRequisitionDialog_.setOkLabel(
795             loadTimeData.getString('deviceRequisitionPromptOk'));
796         this.deviceRequisitionDialog_.setCancelLabel(
797             loadTimeData.getString('deviceRequisitionPromptCancel'));
798       }
799       this.deviceRequisitionDialog_.show(
800           loadTimeData.getString('deviceRequisitionPromptText'),
801           this.deviceRequisition_,
802           this.onConfirmDeviceRequisitionPrompt_.bind(this));
803     },
805     /**
806      * Confirmation handle for the device requisition prompt.
807      * @param {string} value The value entered by the user.
808      * @private
809      */
810     onConfirmDeviceRequisitionPrompt_: function(value) {
811       this.deviceRequisition_ = value;
812       chrome.send('setDeviceRequisition', [value == '' ? 'none' : value]);
813     },
815     /**
816      * Called when window size changed. Notifies current screen about change.
817      * @private
818      */
819     onWindowResize_: function() {
820       var currentScreenId = this.screens_[this.currentStep_];
821       var currentScreen = $(currentScreenId);
822       if (currentScreen)
823         currentScreen.onWindowResize();
824     },
826     /*
827      * Updates the device requisition string shown in the requisition prompt.
828      * @param {string} requisition The device requisition.
829      */
830     updateDeviceRequisition: function(requisition) {
831       this.deviceRequisition_ = requisition;
832     },
834     /**
835      * Shows the special remora/shark device requisition prompt.
836      * @private
837      */
838     showDeviceRequisitionRemoraPrompt_: function(promptText, requisition) {
839       if (!this.deviceRequisitionRemoraDialog_) {
840         this.deviceRequisitionRemoraDialog_ =
841             new cr.ui.dialogs.ConfirmDialog(document.body);
842         this.deviceRequisitionRemoraDialog_.setOkLabel(
843             loadTimeData.getString('deviceRequisitionRemoraPromptOk'));
844         this.deviceRequisitionRemoraDialog_.setCancelLabel(
845             loadTimeData.getString('deviceRequisitionRemoraPromptCancel'));
846       }
847       this.deviceRequisitionRemoraDialog_.show(
848           loadTimeData.getString(promptText),
849           function() {  // onShow
850             chrome.send('setDeviceRequisition', [requisition]);
851           },
852           function() {  // onCancel
853             chrome.send('setDeviceRequisition', ['none']);
854           });
855     },
857     /**
858      * Returns true if Oobe UI is shown.
859      */
860     isOobeUI: function() {
861       return document.body.classList.contains('oobe-display');
862     },
864     /**
865      * Sets or unsets given |className| for top-level container. Useful for
866      * customizing #inner-container with CSS rules. All classes set with with
867      * this method will be removed after screen change.
868      * @param {string} className Class to toggle.
869      * @param {boolean} enabled Whether class should be enabled or disabled.
870      */
871     toggleClass: function(className, enabled) {
872       $('oobe').classList.toggle(className, enabled);
873     }
874   };
876   /**
877    * Initializes display manager.
878    */
879   DisplayManager.initialize = function() {
880     var givenDisplayType = DISPLAY_TYPE.UNKNOWN;
881     if (document.documentElement.hasAttribute('screen')) {
882       // Display type set in HTML property.
883       givenDisplayType = document.documentElement.getAttribute('screen');
884     } else {
885       // Extracting display type from URL.
886       givenDisplayType = window.location.pathname.substr(1);
887     }
888     var instance = Oobe.getInstance();
889     Object.getOwnPropertyNames(DISPLAY_TYPE).forEach(function(type) {
890       if (DISPLAY_TYPE[type] == givenDisplayType) {
891         instance.displayType = givenDisplayType;
892       }
893     });
894     if (instance.displayType == DISPLAY_TYPE.UNKNOWN) {
895       console.error("Unknown display type '" + givenDisplayType +
896           "'. Setting default.");
897       instance.displayType = DISPLAY_TYPE.LOGIN;
898     }
900     instance.initializeOOBEScreens();
902     window.addEventListener('resize', instance.onWindowResize_.bind(instance));
903   };
905   /**
906    * Returns offset (top, left) of the element.
907    * @param {!Element} element HTML element.
908    * @return {!Object} The offset (top, left).
909    */
910   DisplayManager.getOffset = function(element) {
911     var x = 0;
912     var y = 0;
913     while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
914       x += element.offsetLeft - element.scrollLeft;
915       y += element.offsetTop - element.scrollTop;
916       element = element.offsetParent;
917     }
918     return { top: y, left: x };
919   };
921   /**
922    * Returns position (top, left, right, bottom) of the element.
923    * @param {!Element} element HTML element.
924    * @return {!Object} Element position (top, left, right, bottom).
925    */
926   DisplayManager.getPosition = function(element) {
927     var offset = DisplayManager.getOffset(element);
928     return { top: offset.top,
929              right: window.innerWidth - element.offsetWidth - offset.left,
930              bottom: window.innerHeight - element.offsetHeight - offset.top,
931              left: offset.left };
932   };
934   /**
935    * Disables signin UI.
936    */
937   DisplayManager.disableSigninUI = function() {
938     $('login-header-bar').disabled = true;
939     $('pod-row').disabled = true;
940   };
942   /**
943    * Shows signin UI.
944    * @param {string} opt_email An optional email for signin UI.
945    */
946   DisplayManager.showSigninUI = function(opt_email) {
947     var currentScreenId = Oobe.getInstance().currentScreen.id;
948     if (currentScreenId == SCREEN_GAIA_SIGNIN)
949       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
950     else if (currentScreenId == SCREEN_ACCOUNT_PICKER)
951       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.ACCOUNT_PICKER;
952     chrome.send('showAddUser', [opt_email]);
953   };
955   /**
956    * Resets sign-in input fields.
957    * @param {boolean} forceOnline Whether online sign-in should be forced.
958    *     If |forceOnline| is false previously used sign-in type will be used.
959    */
960   DisplayManager.resetSigninUI = function(forceOnline) {
961     var currentScreenId = Oobe.getInstance().currentScreen.id;
963     $(SCREEN_GAIA_SIGNIN).reset(
964         currentScreenId == SCREEN_GAIA_SIGNIN, forceOnline);
965     $('login-header-bar').disabled = false;
966     $('pod-row').reset(currentScreenId == SCREEN_ACCOUNT_PICKER);
967   };
969   /**
970    * Shows sign-in error bubble.
971    * @param {number} loginAttempts Number of login attemps tried.
972    * @param {string} message Error message to show.
973    * @param {string} link Text to use for help link.
974    * @param {number} helpId Help topic Id associated with help link.
975    */
976   DisplayManager.showSignInError = function(loginAttempts, message, link,
977                                             helpId) {
978     var error = document.createElement('div');
980     var messageDiv = document.createElement('div');
981     messageDiv.className = 'error-message-bubble';
982     messageDiv.textContent = message;
983     error.appendChild(messageDiv);
985     if (link) {
986       messageDiv.classList.add('error-message-bubble-padding');
988       var helpLink = document.createElement('a');
989       helpLink.href = '#';
990       helpLink.textContent = link;
991       helpLink.addEventListener('click', function(e) {
992         chrome.send('launchHelpApp', [helpId]);
993         e.preventDefault();
994       });
995       error.appendChild(helpLink);
996     }
998     error.setAttribute('aria-live', 'assertive');
1000     var currentScreen = Oobe.getInstance().currentScreen;
1001     if (currentScreen && typeof currentScreen.showErrorBubble === 'function') {
1002       currentScreen.showErrorBubble(loginAttempts, error);
1003       this.errorMessageWasShownForTesting_ = true;
1004     }
1005   };
1007   /**
1008    * Shows password changed screen that offers migration.
1009    * @param {boolean} showError Whether to show the incorrect password error.
1010    * @param {string} email What user does reauth. Being used for display in the
1011    * new UI.
1012    */
1013   DisplayManager.showPasswordChangedScreen = function(showError, email) {
1014     login.PasswordChangedScreen.show(showError, email);
1015   };
1017   /**
1018    * Shows dialog to create a supervised user.
1019    */
1020   DisplayManager.showSupervisedUserCreationScreen = function() {
1021     login.SupervisedUserCreationScreen.show();
1022   };
1024   /**
1025    * Shows TPM error screen.
1026    */
1027   DisplayManager.showTpmError = function() {
1028     login.TPMErrorMessageScreen.show();
1029   };
1031   /**
1032    * Clears error bubble.
1033    */
1034   DisplayManager.clearErrors = function() {
1035     $('bubble').hide();
1036     this.errorMessageWasShownForTesting_ = false;
1038     var bubbles = document.querySelectorAll('.bubble-shown');
1039     for (var i = 0; i < bubbles.length; ++i)
1040       bubbles[i].classList.remove('bubble-shown');
1041   };
1043   /**
1044    * Sets text content for a div with |labelId|.
1045    * @param {string} labelId Id of the label div.
1046    * @param {string} labelText Text for the label.
1047    */
1048   DisplayManager.setLabelText = function(labelId, labelText) {
1049     $(labelId).textContent = labelText;
1050   };
1052   /**
1053    * Sets the text content of the enterprise info message and asset ID.
1054    * @param {string} messageText The message text.
1055    * @param {string} assetId The device asset ID.
1056    */
1057   DisplayManager.setEnterpriseInfo = function(messageText, assetId) {
1058     $('offline-gaia').enterpriseInfo = messageText;
1059     $('enterprise-info-message').textContent = messageText;
1060     if (messageText) {
1061       $('enterprise-info').hidden = false;
1062     }
1064     $('asset-id').textContent = ((assetId == "") ? "" :
1065         loadTimeData.getStringF('assetIdLabel', assetId));
1066   };
1068   /**
1069    * Disable Add users button if said.
1070    * @param {boolean} disable true to disable
1071    */
1072   DisplayManager.updateAddUserButtonStatus = function(disable) {
1073     $('add-user-button').disabled = disable;
1074     $('add-user-button').classList[
1075         disable ? 'add' : 'remove']('button-restricted');
1076     $('add-user-button').title = disable ?
1077         loadTimeData.getString('disabledAddUserTooltip') : '';
1078   }
1080   /**
1081    * Clears password field in user-pod.
1082    */
1083   DisplayManager.clearUserPodPassword = function() {
1084     $('pod-row').clearFocusedPod();
1085   };
1087   /**
1088    * Restores input focus to currently selected pod.
1089    */
1090   DisplayManager.refocusCurrentPod = function() {
1091     $('pod-row').refocusCurrentPod();
1092   };
1094   // Export
1095   return {
1096     DisplayManager: DisplayManager
1097   };