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