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 88cr.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