Home | History | Annotate | Download | only in login
      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 
     70 cr.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