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