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