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