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