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