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