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