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