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