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