| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview User pod row implementation. |
| */ |
| |
| cr.define('login', function() { |
| /** |
| * Number of displayed columns depending on user pod count. |
| * @type {Array.<number>} |
| * @const |
| */ |
| var COLUMNS = [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6]; |
| |
| /** |
| * Mapping between number of columns in pod-row and margin between user pods |
| * for such layout. |
| * @type {Array.<number>} |
| * @const |
| */ |
| var MARGIN_BY_COLUMNS = [undefined, 40, 40, 40, 40, 40, 12]; |
| |
| /** |
| * Mapping between number of columns in the desktop pod-row and margin |
| * between user pods for such layout. |
| * @type {Array.<number>} |
| * @const |
| */ |
| var DESKTOP_MARGIN_BY_COLUMNS = [undefined, 15, 15, 15, 15, 15, 15]; |
| |
| /** |
| * Maximal number of columns currently supported by pod-row. |
| * @type {number} |
| * @const |
| */ |
| var MAX_NUMBER_OF_COLUMNS = 6; |
| |
| /** |
| * Maximal number of rows if sign-in banner is displayed alonside. |
| * @type {number} |
| * @const |
| */ |
| var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2; |
| |
| /** |
| * Variables used for pod placement processing. Width and height should be |
| * synced with computed CSS sizes of pods. |
| */ |
| var POD_WIDTH = 180; |
| var PUBLIC_EXPANDED_BASIC_WIDTH = 500; |
| var PUBLIC_EXPANDED_ADVANCED_WIDTH = 610; |
| var CROS_POD_HEIGHT = 213; |
| var DESKTOP_POD_HEIGHT = 226; |
| var POD_ROW_PADDING = 10; |
| var DESKTOP_ROW_PADDING = 15; |
| var CUSTOM_ICON_CONTAINER_SIZE = 40; |
| |
| /** |
| * Minimal padding between user pod and virtual keyboard. |
| * @type {number} |
| * @const |
| */ |
| var USER_POD_KEYBOARD_MIN_PADDING = 20; |
| |
| /** |
| * Maximum time for which the pod row remains hidden until all user images |
| * have been loaded. |
| * @type {number} |
| * @const |
| */ |
| var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000; |
| |
| /** |
| * Public session help topic identifier. |
| * @type {number} |
| * @const |
| */ |
| var HELP_TOPIC_PUBLIC_SESSION = 3041033; |
| |
| /** |
| * Tab order for user pods. Update these when adding new controls. |
| * @enum {number} |
| * @const |
| */ |
| var UserPodTabOrder = { |
| POD_INPUT: 1, // Password input fields (and whole pods themselves). |
| POD_CUSTOM_ICON: 2, // Pod custom icon next to passwrod input field. |
| HEADER_BAR: 3, // Buttons on the header bar (Shutdown, Add User). |
| ACTION_BOX: 4, // Action box buttons. |
| PAD_MENU_ITEM: 5 // User pad menu items (Remove this user). |
| }; |
| |
| /** |
| * Supported authentication types. Keep in sync with the enum in |
| * chrome/browser/signin/screenlock_bridge.h |
| * @enum {number} |
| * @const |
| */ |
| var AUTH_TYPE = { |
| OFFLINE_PASSWORD: 0, |
| ONLINE_SIGN_IN: 1, |
| NUMERIC_PIN: 2, |
| USER_CLICK: 3, |
| EXPAND_THEN_USER_CLICK: 4, |
| FORCE_OFFLINE_PASSWORD: 5 |
| }; |
| |
| /** |
| * Names of authentication types. |
| */ |
| var AUTH_TYPE_NAMES = { |
| 0: 'offlinePassword', |
| 1: 'onlineSignIn', |
| 2: 'numericPin', |
| 3: 'userClick', |
| 4: 'expandThenUserClick', |
| 5: 'forceOfflinePassword' |
| }; |
| |
| // Focus and tab order are organized as follows: |
| // |
| // (1) all user pods have tab index 1 so they are traversed first; |
| // (2) when a user pod is activated, its tab index is set to -1 and its |
| // main input field gets focus and tab index 1; |
| // (3) if user pod custom icon is interactive, it has tab index 2 so it |
| // follows the input. |
| // (4) buttons on the header bar have tab index 3 so they follow the custom |
| // icon, or user pod if custom icon is not interactive; |
| // (5) Action box buttons have tab index 4 and follow header bar buttons; |
| // (6) lastly, focus jumps to the Status Area and back to user pods. |
| // |
| // 'Focus' event is handled by a capture handler for the whole document |
| // and in some cases 'mousedown' event handlers are used instead of 'click' |
| // handlers where it's necessary to prevent 'focus' event from being fired. |
| |
| /** |
| * Helper function to remove a class from given element. |
| * @param {!HTMLElement} el Element whose class list to change. |
| * @param {string} cl Class to remove. |
| */ |
| function removeClass(el, cl) { |
| el.classList.remove(cl); |
| } |
| |
| /** |
| * Creates a user pod. |
| * @constructor |
| * @extends {HTMLDivElement} |
| */ |
| var UserPod = cr.ui.define(function() { |
| var node = $('user-pod-template').cloneNode(true); |
| node.removeAttribute('id'); |
| return node; |
| }); |
| |
| /** |
| * Stops event propagation from the any user pod child element. |
| * @param {Event} e Event to handle. |
| */ |
| function stopEventPropagation(e) { |
| // Prevent default so that we don't trigger a 'focus' event. |
| e.preventDefault(); |
| e.stopPropagation(); |
| } |
| |
| /** |
| * Creates an element for custom icon shown in a user pod next to the input |
| * field. |
| * @constructor |
| * @extends {HTMLDivElement} |
| */ |
| var UserPodCustomIcon = cr.ui.define(function() { |
| var node = document.createElement('div'); |
| node.classList.add('custom-icon-container'); |
| node.hidden = true; |
| |
| // Create the actual icon element and add it as a child to the container. |
| var iconNode = document.createElement('div'); |
| iconNode.classList.add('custom-icon'); |
| node.appendChild(iconNode); |
| return node; |
| }); |
| |
| /** |
| * The supported user pod custom icons. |
| * {@code id} properties should be in sync with values set by C++ side. |
| * {@code class} properties are CSS classes used to set the icons' background. |
| * @const {Array.<{id: !string, class: !string}>} |
| */ |
| UserPodCustomIcon.ICONS = [ |
| {id: 'locked', class: 'custom-icon-locked'}, |
| {id: 'unlocked', class: 'custom-icon-unlocked'}, |
| {id: 'hardlocked', class: 'custom-icon-hardlocked'}, |
| {id: 'spinner', class: 'custom-icon-spinner'} |
| ]; |
| |
| UserPodCustomIcon.prototype = { |
| __proto__: HTMLDivElement.prototype, |
| |
| /** |
| * Tooltip to be shown when the user hovers over the icon. The icon |
| * properties may be set so the tooltip is shown automatically when the icon |
| * is updated. The tooltip is shown in a bubble attached to the icon |
| * element. |
| * @type {string} |
| * @private |
| */ |
| tooltip_: '', |
| |
| /** |
| * Whether the tooltip is shown and the user is hovering over the icon. |
| * @type {boolean} |
| * @private |
| */ |
| tooltipActive_: false, |
| |
| /** |
| * Whether the icon has been shown as a result of |autoshow| parameter begin |
| * set rather than user hovering over the icon. |
| * If this is set, the tooltip will not be removed when the mouse leaves the |
| * icon. |
| * @type {boolean} |
| * @private |
| */ |
| tooltipAutoshown_: false, |
| |
| /** |
| * A reference to the timeout for showing tooltip after mouse enters the |
| * icon. |
| * @type {?number} |
| * @private |
| */ |
| showTooltipTimeout_: null, |
| |
| /** |
| * When {@code fadeOut} is called, the element gets hidden using fadeout |
| * animation. This is reference to the listener for transition end added to |
| * the icon element while it's fading out. |
| * @type {?function(Event)} |
| * @private |
| */ |
| hideTransitionListener_: null, |
| |
| /** |
| * Callback for click and 'Enter' key events that gets set if the icon is |
| * interactive. |
| * @type {?function()} |
| * @private |
| */ |
| actionHandler_: null, |
| |
| /** @override */ |
| decorate: function() { |
| this.iconElement.addEventListener('mouseover', |
| this.showTooltipSoon_.bind(this)); |
| this.iconElement.addEventListener('mouseout', |
| this.hideTooltip_.bind(this, false)); |
| this.iconElement.addEventListener('mousedown', |
| this.handleMouseDown_.bind(this)); |
| this.iconElement.addEventListener('click', |
| this.handleClick_.bind(this)); |
| this.iconElement.addEventListener('keydown', |
| this.handleKeyDown_.bind(this)); |
| |
| // When the icon is focused using mouse, there should be no outline shown. |
| // Preventing default mousedown event accomplishes this. |
| this.iconElement.addEventListener('mousedown', function(e) { |
| e.preventDefault(); |
| }); |
| }, |
| |
| /** |
| * Getter for the icon element's div. |
| * @return {HTMLDivElement} |
| */ |
| get iconElement() { |
| return this.querySelector('.custom-icon'); |
| }, |
| |
| /** |
| * Updates the icon element class list to properly represent the provided |
| * icon. |
| * @param {!string} id The id of the icon that should be shown. Should be |
| * one of the ids listed in {@code UserPodCustomIcon.ICONS}. |
| */ |
| setIcon: function(id) { |
| UserPodCustomIcon.ICONS.forEach(function(icon) { |
| this.iconElement.classList.toggle(icon.class, id == icon.id); |
| }, this); |
| }, |
| |
| /** |
| * Shows the icon. |
| */ |
| show: function() { |
| this.resetHideTransitionState_(); |
| this.hidden = false; |
| }, |
| |
| /** |
| * Hides the icon using a fade-out animation. |
| */ |
| fadeOut: function() { |
| // The icon is already being hidden. |
| if (this.iconElement.classList.contains('faded')) |
| return; |
| |
| this.hideTooltip_(true); |
| this.iconElement.classList.add('faded'); |
| this.hideTransitionListener_ = this.hide.bind(this); |
| this.iconElement.addEventListener('webkitTransitionEnd', |
| this.hideTransitionListener_); |
| ensureTransitionEndEvent(this.iconElement, 200); |
| }, |
| |
| /** |
| * Updates the icon tooltip. If {@code autoshow} parameter is set the |
| * tooltip is immediatelly shown. If tooltip text is not set, the method |
| * ensures the tooltip gets hidden. If tooltip is shown prior to this call, |
| * it remains shown, but the tooltip text is updated. |
| * @param {!{text: string, autoshow: boolean}} tooltip The tooltip |
| * parameters. |
| */ |
| setTooltip: function(tooltip) { |
| this.iconElement.classList.toggle('icon-with-tooltip', !!tooltip.text); |
| |
| if (this.tooltip_ == tooltip.text && !tooltip.autoshow) |
| return; |
| this.tooltip_ = tooltip.text; |
| |
| // If tooltip is already shown, just update the text. |
| if (tooltip.text && this.tooltipActive_ && !$('bubble').hidden) { |
| // If both previous and the new tooltip are supposed to be shown |
| // automatically, keep the autoshown flag. |
| var markAutoshown = this.tooltipAutoshown_ && tooltip.autoshow; |
| this.hideTooltip_(true); |
| this.showTooltip_(); |
| this.tooltipAutoshown_ = markAutoshown; |
| return; |
| } |
| |
| // If autoshow flag is set, make sure the tooltip gets shown. |
| if (tooltip.text && tooltip.autoshow) { |
| this.hideTooltip_(true); |
| this.showTooltip_(); |
| this.tooltipAutoshown_ = true; |
| // Reset the tooltip active flag, which gets set in |showTooltip_|. |
| this.tooltipActive_ = false; |
| return; |
| } |
| |
| this.hideTooltip_(true); |
| |
| if (this.tooltip_) |
| this.iconElement.setAttribute('aria-lablel', this.tooltip_); |
| }, |
| |
| /** |
| * Sets up icon tabIndex attribute and handler for click and 'Enter' key |
| * down events. |
| * @param {?function()} callback If icon should be interactive, the |
| * function to get called on click and 'Enter' key down events. Should |
| * be null to make the icon non interactive. |
| */ |
| setInteractive: function(callback) { |
| this.iconElement.classList.toggle('interactive-custom-icon', !!callback); |
| |
| // Update tabIndex property if needed. |
| if (!!this.actionHandler_ != !!callback) { |
| if (callback) { |
| this.iconElement.setAttribute('tabIndex', |
| UserPodTabOrder.POD_CUSTOM_ICON); |
| } else { |
| this.iconElement.removeAttribute('tabIndex'); |
| } |
| } |
| |
| // Set the new action handler. |
| this.actionHandler_ = callback; |
| }, |
| |
| /** |
| * Hides the icon and cleans its state. |
| */ |
| hide: function() { |
| this.hideTooltip_(true); |
| this.hidden = true; |
| this.setInteractive(null); |
| this.resetHideTransitionState_(); |
| }, |
| |
| /** |
| * Ensures the icon's transition state potentially set by a call to |
| * {@code fadeOut} is cleared. |
| * @private |
| */ |
| resetHideTransitionState_: function() { |
| if (this.hideTransitionListener_) { |
| this.iconElement.removeEventListener('webkitTransitionEnd', |
| this.hideTransitionListener_); |
| this.hideTransitionListener_ = null; |
| } |
| this.iconElement.classList.toggle('faded', false); |
| }, |
| |
| /** |
| * Handles mouse down event in the icon element. |
| * @param {Event} e The mouse down event. |
| * @private |
| */ |
| handleMouseDown_: function(e) { |
| this.hideTooltip_(false); |
| // Stop the event propagation so in the case the click ends up on the |
| // user pod (outside the custom icon) auth is not attempted. |
| stopEventPropagation(e); |
| }, |
| |
| /** |
| * Handles click event on the icon element. No-op if |
| * {@code this.actionHandler_} is not set. |
| * @param {Event} e The click event. |
| * @private |
| */ |
| handleClick_: function(e) { |
| if (!this.actionHandler_) |
| return; |
| this.actionHandler_(); |
| stopEventPropagation(e); |
| }, |
| |
| /** |
| * Handles key down event on the icon element. Only 'Enter' key is handled. |
| * No-op if {@code this.actionHandler_} is not set. |
| * @param {Event} e The key down event. |
| * @private |
| */ |
| handleKeyDown_: function(e) { |
| if (!this.actionHandler_ || e.keyIdentifier != 'Enter') |
| return; |
| this.actionHandler_(e); |
| stopEventPropagation(e); |
| }, |
| |
| /** |
| * Called when mouse enters the icon. It sets timeout for showing the |
| * tooltip. |
| * @private |
| */ |
| showTooltipSoon_: function() { |
| if (this.showTooltipTimeout_ || this.tooltipActive_) |
| return; |
| this.showTooltipTimeout_ = |
| setTimeout(this.showTooltip_.bind(this), 1000); |
| }, |
| |
| /** |
| * Shows the current tooltip if one is set. |
| * @private |
| */ |
| showTooltip_: function() { |
| if (this.hidden || !this.tooltip_ || this.tooltipActive_) |
| return; |
| |
| // If autoshown bubble got hidden, clear the autoshown flag. |
| if ($('bubble').hidden && this.tooltipAutoshown_) |
| this.tooltipAutoshown_ = false; |
| |
| // Show the tooltip bubble. |
| var bubbleContent = document.createElement('div'); |
| bubbleContent.textContent = this.tooltip_; |
| |
| /** @const */ var BUBBLE_OFFSET = CUSTOM_ICON_CONTAINER_SIZE / 2; |
| /** @const */ var BUBBLE_PADDING = 8; |
| $('bubble').showContentForElement(this, |
| cr.ui.Bubble.Attachment.RIGHT, |
| bubbleContent, |
| BUBBLE_OFFSET, |
| BUBBLE_PADDING); |
| this.ensureTooltipTimeoutCleared_(); |
| this.tooltipActive_ = true; |
| }, |
| |
| /** |
| * Hides the tooltip. If the current tooltip was automatically shown, it |
| * will be hidden only if |force| is set. |
| * @param {boolean} Whether the tooltip should be hidden if it got shown |
| * because autoshow flag was set. |
| * @private |
| */ |
| hideTooltip_: function(force) { |
| this.tooltipActive_ = false; |
| this.ensureTooltipTimeoutCleared_(); |
| if (!force && this.tooltipAutoshown_) |
| return; |
| $('bubble').hideForElement(this); |
| this.tooltipAutoshown_ = false; |
| this.iconElement.removeAttribute('aria-label'); |
| }, |
| |
| /** |
| * Clears timaout for showing the tooltip if it's set. |
| * @private |
| */ |
| ensureTooltipTimeoutCleared_: function() { |
| if (this.showTooltipTimeout_) { |
| clearTimeout(this.showTooltipTimeout_); |
| this.showTooltipTimeout_ = null; |
| } |
| }, |
| }; |
| |
| /** |
| * Unique salt added to user image URLs to prevent caching. Dictionary with |
| * user names as keys. |
| * @type {Object} |
| */ |
| UserPod.userImageSalt_ = {}; |
| |
| UserPod.prototype = { |
| __proto__: HTMLDivElement.prototype, |
| |
| /** |
| * Whether click on the pod can issue a user click auth attempt. The |
| * attempt can be issued iff the pod was focused when the click |
| * started (i.e. on mouse down event). |
| * @type {boolean} |
| * @private |
| */ |
| userClickAuthAllowed_: false, |
| |
| /** @override */ |
| decorate: function() { |
| this.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.actionBoxAreaElement.tabIndex = UserPodTabOrder.ACTION_BOX; |
| |
| this.addEventListener('keydown', this.handlePodKeyDown_.bind(this)); |
| this.addEventListener('click', this.handleClickOnPod_.bind(this)); |
| this.addEventListener('mousedown', this.handlePodMouseDown_.bind(this)); |
| |
| this.signinButtonElement.addEventListener('click', |
| this.activate.bind(this)); |
| |
| this.actionBoxAreaElement.addEventListener('mousedown', |
| stopEventPropagation); |
| this.actionBoxAreaElement.addEventListener('click', |
| this.handleActionAreaButtonClick_.bind(this)); |
| this.actionBoxAreaElement.addEventListener('keydown', |
| this.handleActionAreaButtonKeyDown_.bind(this)); |
| |
| this.actionBoxMenuRemoveElement.addEventListener('click', |
| this.handleRemoveCommandClick_.bind(this)); |
| this.actionBoxMenuRemoveElement.addEventListener('keydown', |
| this.handleRemoveCommandKeyDown_.bind(this)); |
| this.actionBoxMenuRemoveElement.addEventListener('blur', |
| this.handleRemoveCommandBlur_.bind(this)); |
| this.actionBoxRemoveUserWarningButtonElement.addEventListener( |
| 'click', |
| this.handleRemoveUserConfirmationClick_.bind(this)); |
| this.actionBoxRemoveUserWarningButtonElement.addEventListener( |
| 'keydown', |
| this.handleRemoveUserConfirmationKeyDown_.bind(this)); |
| |
| var customIcon = this.customIconElement; |
| customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon); |
| }, |
| |
| /** |
| * Initializes the pod after its properties set and added to a pod row. |
| */ |
| initialize: function() { |
| this.passwordElement.addEventListener('keydown', |
| this.parentNode.handleKeyDown.bind(this.parentNode)); |
| this.passwordElement.addEventListener('keypress', |
| this.handlePasswordKeyPress_.bind(this)); |
| |
| this.imageElement.addEventListener('load', |
| this.parentNode.handlePodImageLoad.bind(this.parentNode, this)); |
| |
| var initialAuthType = this.user.initialAuthType || |
| AUTH_TYPE.OFFLINE_PASSWORD; |
| this.setAuthType(initialAuthType, null); |
| |
| this.userClickAuthAllowed_ = false; |
| }, |
| |
| /** |
| * Resets tab order for pod elements to its initial state. |
| */ |
| resetTabOrder: function() { |
| // Note: the |mainInput| can be the pod itself. |
| this.mainInput.tabIndex = -1; |
| this.tabIndex = UserPodTabOrder.POD_INPUT; |
| }, |
| |
| /** |
| * Handles keypress event (i.e. any textual input) on password input. |
| * @param {Event} e Keypress Event object. |
| * @private |
| */ |
| handlePasswordKeyPress_: function(e) { |
| // When tabbing from the system tray a tab key press is received. Suppress |
| // this so as not to type a tab character into the password field. |
| if (e.keyCode == 9) { |
| e.preventDefault(); |
| return; |
| } |
| }, |
| |
| /** |
| * Top edge margin number of pixels. |
| * @type {?number} |
| */ |
| set top(top) { |
| this.style.top = cr.ui.toCssPx(top); |
| }, |
| |
| /** |
| * Top edge margin number of pixels. |
| */ |
| get top() { |
| return parseInt(this.style.top); |
| }, |
| |
| /** |
| * Left edge margin number of pixels. |
| * @type {?number} |
| */ |
| set left(left) { |
| this.style.left = cr.ui.toCssPx(left); |
| }, |
| |
| /** |
| * Left edge margin number of pixels. |
| */ |
| get left() { |
| return parseInt(this.style.left); |
| }, |
| |
| /** |
| * Height number of pixels. |
| */ |
| get height() { |
| return this.offsetHeight; |
| }, |
| |
| /** |
| * Gets image element. |
| * @type {!HTMLImageElement} |
| */ |
| get imageElement() { |
| return this.querySelector('.user-image'); |
| }, |
| |
| /** |
| * Gets name element. |
| * @type {!HTMLDivElement} |
| */ |
| get nameElement() { |
| return this.querySelector('.name'); |
| }, |
| |
| /** |
| * Gets the container holding the password field. |
| * @type {!HTMLInputElement} |
| */ |
| get passwordEntryContainerElement() { |
| return this.querySelector('.password-entry-container'); |
| }, |
| |
| /** |
| * Gets password field. |
| * @type {!HTMLInputElement} |
| */ |
| get passwordElement() { |
| return this.querySelector('.password'); |
| }, |
| |
| /** |
| * Gets the password label, which is used to show a message where the |
| * password field is normally. |
| * @type {!HTMLInputElement} |
| */ |
| get passwordLabelElement() { |
| return this.querySelector('.password-label'); |
| }, |
| |
| /** |
| * Gets user sign in button. |
| * @type {!HTMLButtonElement} |
| */ |
| get signinButtonElement() { |
| return this.querySelector('.signin-button'); |
| }, |
| |
| /** |
| * Gets the container holding the launch app button. |
| * @type {!HTMLButtonElement} |
| */ |
| get launchAppButtonContainerElement() { |
| return this.querySelector('.launch-app-button-container'); |
| }, |
| |
| /** |
| * Gets launch app button. |
| * @type {!HTMLButtonElement} |
| */ |
| get launchAppButtonElement() { |
| return this.querySelector('.launch-app-button'); |
| }, |
| |
| /** |
| * Gets action box area. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxAreaElement() { |
| return this.querySelector('.action-box-area'); |
| }, |
| |
| /** |
| * Gets user type icon area. |
| * @type {!HTMLDivElement} |
| */ |
| get userTypeIconAreaElement() { |
| return this.querySelector('.user-type-icon-area'); |
| }, |
| |
| /** |
| * Gets user type bubble like multi-profiles policy restriction message. |
| * @type {!HTMLDivElement} |
| */ |
| get userTypeBubbleElement() { |
| return this.querySelector('.user-type-bubble'); |
| }, |
| |
| /** |
| * Gets action box menu. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenu() { |
| return this.querySelector('.action-box-menu'); |
| }, |
| |
| /** |
| * Gets action box menu title, user name item. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenuTitleNameElement() { |
| return this.querySelector('.action-box-menu-title-name'); |
| }, |
| |
| /** |
| * Gets action box menu title, user email item. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenuTitleEmailElement() { |
| return this.querySelector('.action-box-menu-title-email'); |
| }, |
| |
| /** |
| * Gets action box menu, remove user command item. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenuCommandElement() { |
| return this.querySelector('.action-box-menu-remove-command'); |
| }, |
| |
| /** |
| * Gets action box menu, remove user command item div. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenuRemoveElement() { |
| return this.querySelector('.action-box-menu-remove'); |
| }, |
| |
| /** |
| * Gets action box menu, remove user warning text div. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxRemoveUserWarningTextElement() { |
| return this.querySelector('.action-box-remove-user-warning-text'); |
| }, |
| |
| /** |
| * Gets action box menu, remove supervised user warning text div. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxRemoveSupervisedUserWarningTextElement() { |
| return this.querySelector( |
| '.action-box-remove-supervised-user-warning-text'); |
| }, |
| |
| /** |
| * Gets action box menu, remove user command item div. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxRemoveUserWarningElement() { |
| return this.querySelector('.action-box-remove-user-warning'); |
| }, |
| |
| /** |
| * Gets action box menu, remove user command item div. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxRemoveUserWarningButtonElement() { |
| return this.querySelector('.remove-warning-button'); |
| }, |
| |
| /** |
| * Gets the custom icon. This icon is normally hidden, but can be shown |
| * using the chrome.screenlockPrivate API. |
| * @type {!HTMLDivElement} |
| */ |
| get customIconElement() { |
| return this.querySelector('.custom-icon-container'); |
| }, |
| |
| /** |
| * Updates the user pod element. |
| */ |
| update: function() { |
| this.imageElement.src = 'chrome://userimage/' + this.user.username + |
| '?id=' + UserPod.userImageSalt_[this.user.username]; |
| |
| this.nameElement.textContent = this.user_.displayName; |
| this.classList.toggle('signed-in', this.user_.signedIn); |
| |
| if (this.isAuthTypeUserClick) |
| this.passwordLabelElement.textContent = this.authValue; |
| |
| this.updateActionBoxArea(); |
| |
| this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF( |
| 'passwordFieldAccessibleName', this.user_.emailAddress)); |
| |
| this.customizeUserPodPerUserType(); |
| }, |
| |
| updateActionBoxArea: function() { |
| if (this.user_.publicAccount || this.user_.isApp) { |
| this.actionBoxAreaElement.hidden = true; |
| return; |
| } |
| |
| this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove; |
| |
| this.actionBoxAreaElement.setAttribute( |
| 'aria-label', loadTimeData.getStringF( |
| 'podMenuButtonAccessibleName', this.user_.emailAddress)); |
| this.actionBoxMenuRemoveElement.setAttribute( |
| 'aria-label', loadTimeData.getString( |
| 'podMenuRemoveItemAccessibleName')); |
| this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ? |
| loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) : |
| this.user_.displayName; |
| this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress; |
| this.actionBoxMenuTitleEmailElement.hidden = this.user_.supervisedUser; |
| |
| this.actionBoxMenuCommandElement.textContent = |
| loadTimeData.getString('removeUser'); |
| }, |
| |
| customizeUserPodPerUserType: function() { |
| if (this.user_.supervisedUser && !this.user_.isDesktopUser) { |
| this.setUserPodIconType('supervised'); |
| } else if (this.multiProfilesPolicyApplied) { |
| // Mark user pod as not focusable which in addition to the grayed out |
| // filter makes it look in disabled state. |
| this.classList.add('multiprofiles-policy-applied'); |
| this.setUserPodIconType('policy'); |
| |
| if (this.user.multiProfilesPolicy == 'primary-only') |
| this.querySelector('.mp-policy-primary-only-msg').hidden = false; |
| else if (this.user.multiProfilesPolicy == 'owner-primary-only') |
| this.querySelector('.mp-owner-primary-only-msg').hidden = false; |
| else |
| this.querySelector('.mp-policy-not-allowed-msg').hidden = false; |
| } else if (this.user_.isApp) { |
| this.setUserPodIconType('app'); |
| } |
| }, |
| |
| setUserPodIconType: function(userTypeClass) { |
| this.userTypeIconAreaElement.classList.add(userTypeClass); |
| this.userTypeIconAreaElement.hidden = false; |
| }, |
| |
| /** |
| * The user that this pod represents. |
| * @type {!Object} |
| */ |
| user_: undefined, |
| get user() { |
| return this.user_; |
| }, |
| set user(userDict) { |
| this.user_ = userDict; |
| this.update(); |
| }, |
| |
| /** |
| * Returns true if multi-profiles sign in is currently active and this |
| * user pod is restricted per policy. |
| * @type {boolean} |
| */ |
| get multiProfilesPolicyApplied() { |
| var isMultiProfilesUI = |
| (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING); |
| return isMultiProfilesUI && !this.user_.isMultiProfilesAllowed; |
| }, |
| |
| /** |
| * Gets main input element. |
| * @type {(HTMLButtonElement|HTMLInputElement)} |
| */ |
| get mainInput() { |
| if (this.isAuthTypePassword) { |
| return this.passwordElement; |
| } else if (this.isAuthTypeOnlineSignIn) { |
| return this.signinButtonElement; |
| } else if (this.isAuthTypeUserClick) { |
| return this.passwordLabelElement; |
| } |
| }, |
| |
| /** |
| * Whether action box button is in active state. |
| * @type {boolean} |
| */ |
| get isActionBoxMenuActive() { |
| return this.actionBoxAreaElement.classList.contains('active'); |
| }, |
| set isActionBoxMenuActive(active) { |
| if (active == this.isActionBoxMenuActive) |
| return; |
| |
| if (active) { |
| this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove; |
| this.actionBoxRemoveUserWarningElement.hidden = true; |
| |
| // Clear focus first if another pod is focused. |
| if (!this.parentNode.isFocused(this)) { |
| this.parentNode.focusPod(undefined, true); |
| this.actionBoxAreaElement.focus(); |
| } |
| |
| // Hide user-type-bubble. |
| this.userTypeBubbleElement.classList.remove('bubble-shown'); |
| |
| this.actionBoxAreaElement.classList.add('active'); |
| |
| // If the user pod is on either edge of the screen, then the menu |
| // could be displayed partially ofscreen. |
| this.actionBoxMenu.classList.remove('left-edge-offset'); |
| this.actionBoxMenu.classList.remove('right-edge-offset'); |
| |
| var offsetLeft = |
| cr.ui.login.DisplayManager.getOffset(this.actionBoxMenu).left; |
| var menuWidth = this.actionBoxMenu.offsetWidth; |
| if (offsetLeft < 0) |
| this.actionBoxMenu.classList.add('left-edge-offset'); |
| else if (offsetLeft + menuWidth > window.innerWidth) |
| this.actionBoxMenu.classList.add('right-edge-offset'); |
| } else { |
| this.actionBoxAreaElement.classList.remove('active'); |
| this.actionBoxAreaElement.classList.remove('menu-moved-up'); |
| this.actionBoxMenu.classList.remove('menu-moved-up'); |
| } |
| }, |
| |
| /** |
| * Whether action box button is in hovered state. |
| * @type {boolean} |
| */ |
| get isActionBoxMenuHovered() { |
| return this.actionBoxAreaElement.classList.contains('hovered'); |
| }, |
| set isActionBoxMenuHovered(hovered) { |
| if (hovered == this.isActionBoxMenuHovered) |
| return; |
| |
| if (hovered) { |
| this.actionBoxAreaElement.classList.add('hovered'); |
| this.classList.add('hovered'); |
| } else { |
| if (this.multiProfilesPolicyApplied) |
| this.userTypeBubbleElement.classList.remove('bubble-shown'); |
| this.actionBoxAreaElement.classList.remove('hovered'); |
| this.classList.remove('hovered'); |
| } |
| }, |
| |
| /** |
| * Set the authentication type for the pod. |
| * @param {number} An auth type value defined in the AUTH_TYPE enum. |
| * @param {string} authValue The initial value used for the auth type. |
| */ |
| setAuthType: function(authType, authValue) { |
| this.authType_ = authType; |
| this.authValue_ = authValue; |
| this.setAttribute('auth-type', AUTH_TYPE_NAMES[this.authType_]); |
| this.update(); |
| this.reset(this.parentNode.isFocused(this)); |
| }, |
| |
| /** |
| * The auth type of the user pod. This value is one of the enum |
| * values in AUTH_TYPE. |
| * @type {number} |
| */ |
| get authType() { |
| return this.authType_; |
| }, |
| |
| /** |
| * The initial value used for the pod's authentication type. |
| * eg. a prepopulated password input when using password authentication. |
| */ |
| get authValue() { |
| return this.authValue_; |
| }, |
| |
| /** |
| * True if the the user pod uses a password to authenticate. |
| * @type {bool} |
| */ |
| get isAuthTypePassword() { |
| return this.authType_ == AUTH_TYPE.OFFLINE_PASSWORD || |
| this.authType_ == AUTH_TYPE.FORCE_OFFLINE_PASSWORD; |
| }, |
| |
| /** |
| * True if the the user pod uses a user click to authenticate. |
| * @type {bool} |
| */ |
| get isAuthTypeUserClick() { |
| return this.authType_ == AUTH_TYPE.USER_CLICK; |
| }, |
| |
| /** |
| * True if the the user pod uses a online sign in to authenticate. |
| * @type {bool} |
| */ |
| get isAuthTypeOnlineSignIn() { |
| return this.authType_ == AUTH_TYPE.ONLINE_SIGN_IN; |
| }, |
| |
| /** |
| * Updates the image element of the user. |
| */ |
| updateUserImage: function() { |
| UserPod.userImageSalt_[this.user.username] = new Date().getTime(); |
| this.update(); |
| }, |
| |
| /** |
| * Focuses on input element. |
| */ |
| focusInput: function() { |
| // Move tabIndex from the whole pod to the main input. |
| // Note: the |mainInput| can be the pod itself. |
| this.tabIndex = -1; |
| this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.mainInput.focus(); |
| }, |
| |
| /** |
| * Activates the pod. |
| * @param {Event} e Event object. |
| * @return {boolean} True if activated successfully. |
| */ |
| activate: function(e) { |
| if (this.isAuthTypeOnlineSignIn) { |
| this.showSigninUI(); |
| } else if (this.isAuthTypeUserClick) { |
| Oobe.disableSigninUI(); |
| chrome.send('attemptUnlock', [this.user.username]); |
| } else if (this.isAuthTypePassword) { |
| if (!this.passwordElement.value) |
| return false; |
| Oobe.disableSigninUI(); |
| chrome.send('authenticateUser', |
| [this.user.username, this.passwordElement.value]); |
| } else { |
| console.error('Activating user pod with invalid authentication type: ' + |
| this.authType); |
| } |
| |
| return true; |
| }, |
| |
| showSupervisedUserSigninWarning: function() { |
| // Supervised user token has been invalidated. |
| // Make sure that pod is focused i.e. "Sign in" button is seen. |
| this.parentNode.focusPod(this); |
| |
| var error = document.createElement('div'); |
| var messageDiv = document.createElement('div'); |
| messageDiv.className = 'error-message-bubble'; |
| messageDiv.textContent = |
| loadTimeData.getString('supervisedUserExpiredTokenWarning'); |
| error.appendChild(messageDiv); |
| |
| $('bubble').showContentForElement( |
| this.signinButtonElement, |
| cr.ui.Bubble.Attachment.TOP, |
| error, |
| this.signinButtonElement.offsetWidth / 2, |
| 4); |
| // Move warning bubble up if it overlaps the shelf. |
| var maxHeight = |
| cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping($('bubble')); |
| if (maxHeight < $('bubble').offsetHeight) { |
| $('bubble').showContentForElement( |
| this.signinButtonElement, |
| cr.ui.Bubble.Attachment.BOTTOM, |
| error, |
| this.signinButtonElement.offsetWidth / 2, |
| 4); |
| } |
| }, |
| |
| /** |
| * Shows signin UI for this user. |
| */ |
| showSigninUI: function() { |
| if (this.user.supervisedUser && !this.user.isDesktopUser) { |
| this.showSupervisedUserSigninWarning(); |
| } else { |
| // Special case for multi-profiles sign in. We show users even if they |
| // are not allowed per policy. Restrict those users from starting GAIA. |
| if (this.multiProfilesPolicyApplied) |
| return; |
| |
| this.parentNode.showSigninUI(this.user.emailAddress); |
| } |
| }, |
| |
| /** |
| * Resets the input field and updates the tab order of pod controls. |
| * @param {boolean} takeFocus If true, input field takes focus. |
| */ |
| reset: function(takeFocus) { |
| this.passwordElement.value = ''; |
| if (takeFocus) { |
| if (!this.multiProfilesPolicyApplied) |
| this.focusInput(); // This will set a custom tab order. |
| } |
| else |
| this.resetTabOrder(); |
| }, |
| |
| /** |
| * Removes a user using the correct identifier based on user type. |
| * @param {Object} user User to be removed. |
| */ |
| removeUser: function(user) { |
| chrome.send('removeUser', |
| [user.isDesktopUser ? user.profilePath : user.username]); |
| }, |
| |
| /** |
| * Handles a click event on action area button. |
| * @param {Event} e Click event. |
| */ |
| handleActionAreaButtonClick_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| this.isActionBoxMenuActive = !this.isActionBoxMenuActive; |
| e.stopPropagation(); |
| }, |
| |
| /** |
| * Handles a keydown event on action area button. |
| * @param {Event} e KeyDown event. |
| */ |
| handleActionAreaButtonKeyDown_: function(e) { |
| if (this.disabled) |
| return; |
| switch (e.keyIdentifier) { |
| case 'Enter': |
| case 'U+0020': // Space |
| if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive) |
| this.isActionBoxMenuActive = true; |
| e.stopPropagation(); |
| break; |
| case 'Up': |
| case 'Down': |
| if (this.isActionBoxMenuActive) { |
| this.actionBoxMenuRemoveElement.tabIndex = |
| UserPodTabOrder.PAD_MENU_ITEM; |
| this.actionBoxMenuRemoveElement.focus(); |
| } |
| e.stopPropagation(); |
| break; |
| case 'U+001B': // Esc |
| this.isActionBoxMenuActive = false; |
| e.stopPropagation(); |
| break; |
| case 'U+0009': // Tab |
| if (!this.parentNode.alwaysFocusSinglePod) |
| this.parentNode.focusPod(); |
| default: |
| this.isActionBoxMenuActive = false; |
| break; |
| } |
| }, |
| |
| /** |
| * Handles a click event on remove user command. |
| * @param {Event} e Click event. |
| */ |
| handleRemoveCommandClick_: function(e) { |
| if (this.user.supervisedUser || this.user.isDesktopUser) { |
| this.showRemoveWarning_(); |
| return; |
| } |
| if (this.isActionBoxMenuActive) |
| chrome.send('removeUser', [this.user.username]); |
| }, |
| |
| /** |
| * Shows remove user warning. Used for supervised users on CrOS, and for all |
| * users on desktop. |
| */ |
| showRemoveWarning_: function() { |
| this.actionBoxMenuRemoveElement.hidden = true; |
| this.actionBoxRemoveUserWarningElement.hidden = false; |
| this.actionBoxRemoveUserWarningButtonElement.focus(); |
| |
| // Move up the menu if it overlaps shelf. |
| var maxHeight = cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping( |
| this.actionBoxMenu); |
| var actualHeight = parseInt( |
| window.getComputedStyle(this.actionBoxMenu).height); |
| if (maxHeight < actualHeight) { |
| this.actionBoxMenu.classList.add('menu-moved-up'); |
| this.actionBoxAreaElement.classList.add('menu-moved-up'); |
| } |
| }, |
| |
| /** |
| * Handles a click event on remove user confirmation button. |
| * @param {Event} e Click event. |
| */ |
| handleRemoveUserConfirmationClick_: function(e) { |
| if (this.isActionBoxMenuActive) { |
| this.isActionBoxMenuActive = false; |
| this.removeUser(this.user); |
| e.stopPropagation(); |
| } |
| }, |
| |
| /** |
| * Handles a keydown event on remove user confirmation button. |
| * @param {Event} e KeyDown event. |
| */ |
| handleRemoveUserConfirmationKeyDown_: function(e) { |
| if (!this.isActionBoxMenuActive) |
| return; |
| |
| // Only handle pressing 'Enter' or 'Space', and let all other events |
| // bubble to the action box menu. |
| if (e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020') { |
| this.isActionBoxMenuActive = false; |
| this.removeUser(this.user); |
| e.stopPropagation(); |
| // Prevent default so that we don't trigger a 'click' event. |
| e.preventDefault(); |
| } |
| }, |
| |
| /** |
| * Handles a keydown event on remove command. |
| * @param {Event} e KeyDown event. |
| */ |
| handleRemoveCommandKeyDown_: function(e) { |
| if (this.disabled) |
| return; |
| switch (e.keyIdentifier) { |
| case 'Enter': |
| if (this.user.supervisedUser || this.user.isDesktopUser) { |
| // Prevent default so that we don't trigger a 'click' event on the |
| // remove button that will be focused. |
| e.preventDefault(); |
| this.showRemoveWarning_(); |
| } else { |
| this.removeUser(this.user); |
| } |
| e.stopPropagation(); |
| break; |
| case 'Up': |
| case 'Down': |
| e.stopPropagation(); |
| break; |
| case 'U+001B': // Esc |
| this.actionBoxAreaElement.focus(); |
| this.isActionBoxMenuActive = false; |
| e.stopPropagation(); |
| break; |
| default: |
| this.actionBoxAreaElement.focus(); |
| this.isActionBoxMenuActive = false; |
| break; |
| } |
| }, |
| |
| /** |
| * Handles a blur event on remove command. |
| * @param {Event} e Blur event. |
| */ |
| handleRemoveCommandBlur_: function(e) { |
| if (this.disabled) |
| return; |
| this.actionBoxMenuRemoveElement.tabIndex = -1; |
| }, |
| |
| /** |
| * Handles mouse down event. It sets whether the user click auth will be |
| * allowed on the next mouse click event. The auth is allowed iff the pod |
| * was focused on the mouse down event starting the click. |
| * @param {Event} e The mouse down event. |
| */ |
| handlePodMouseDown_: function(e) { |
| this.userClickAuthAllowed_ = this.parentNode.isFocused(this); |
| }, |
| |
| /** |
| * Handles click event on a user pod. |
| * @param {Event} e Click event. |
| */ |
| handleClickOnPod_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| |
| if (!this.isActionBoxMenuActive) { |
| if (this.isAuthTypeOnlineSignIn) { |
| this.showSigninUI(); |
| } else if (this.isAuthTypeUserClick && this.userClickAuthAllowed_) { |
| // Note that this.userClickAuthAllowed_ is set in mouse down event |
| // handler. |
| this.parentNode.setActivatedPod(this); |
| } |
| |
| if (this.multiProfilesPolicyApplied) |
| this.userTypeBubbleElement.classList.add('bubble-shown'); |
| |
| // Prevent default so that we don't trigger 'focus' event. |
| e.preventDefault(); |
| } |
| }, |
| |
| /** |
| * Handles keydown event for a user pod. |
| * @param {Event} e Key event. |
| */ |
| handlePodKeyDown_: function(e) { |
| if (!this.isAuthTypeUserClick || this.disabled) |
| return; |
| switch (e.keyIdentifier) { |
| case 'Enter': |
| case 'U+0020': // Space |
| if (this.parentNode.isFocused(this)) |
| this.parentNode.setActivatedPod(this); |
| break; |
| } |
| } |
| }; |
| |
| /** |
| * Creates a public account user pod. |
| * @constructor |
| * @extends {UserPod} |
| */ |
| var PublicAccountUserPod = cr.ui.define(function() { |
| var node = UserPod(); |
| |
| var extras = $('public-account-user-pod-extras-template').children; |
| for (var i = 0; i < extras.length; ++i) { |
| var el = extras[i].cloneNode(true); |
| node.appendChild(el); |
| } |
| |
| return node; |
| }); |
| |
| PublicAccountUserPod.prototype = { |
| __proto__: UserPod.prototype, |
| |
| /** |
| * "Enter" button in expanded side pane. |
| * @type {!HTMLButtonElement} |
| */ |
| get enterButtonElement() { |
| return this.querySelector('.enter-button'); |
| }, |
| |
| /** |
| * Boolean flag of whether the pod is showing the side pane. The flag |
| * controls whether 'expanded' class is added to the pod's class list and |
| * resets tab order because main input element changes when the 'expanded' |
| * state changes. |
| * @type {boolean} |
| */ |
| get expanded() { |
| return this.classList.contains('expanded'); |
| }, |
| |
| set expanded(expanded) { |
| if (this.expanded == expanded) |
| return; |
| |
| this.resetTabOrder(); |
| this.classList.toggle('expanded', expanded); |
| if (expanded) { |
| // Show the advanced expanded pod directly if there are at least two |
| // recommended locales. This will be the case in multilingual |
| // environments where users are likely to want to choose among locales. |
| if (this.querySelector('.language-select').multipleRecommendedLocales) |
| this.classList.add('advanced'); |
| this.usualLeft = this.left; |
| this.makeSpaceForExpandedPod_(); |
| } else if (typeof(this.usualLeft) != 'undefined') { |
| this.left = this.usualLeft; |
| } |
| |
| var self = this; |
| this.classList.add('animating'); |
| this.addEventListener('webkitTransitionEnd', function f(e) { |
| self.removeEventListener('webkitTransitionEnd', f); |
| self.classList.remove('animating'); |
| |
| // Accessibility focus indicator does not move with the focused |
| // element. Sends a 'focus' event on the currently focused element |
| // so that accessibility focus indicator updates its location. |
| if (document.activeElement) |
| document.activeElement.dispatchEvent(new Event('focus')); |
| }); |
| // Guard timer set to animation duration + 20ms. |
| ensureTransitionEndEvent(this, 200); |
| }, |
| |
| get advanced() { |
| return this.classList.contains('advanced'); |
| }, |
| |
| /** @override */ |
| get mainInput() { |
| if (this.expanded) |
| return this.enterButtonElement; |
| else |
| return this.nameElement; |
| }, |
| |
| /** @override */ |
| decorate: function() { |
| UserPod.prototype.decorate.call(this); |
| |
| this.classList.add('public-account'); |
| |
| this.nameElement.addEventListener('keydown', (function(e) { |
| if (e.keyIdentifier == 'Enter') { |
| this.parentNode.setActivatedPod(this, e); |
| // Stop this keydown event from bubbling up to PodRow handler. |
| e.stopPropagation(); |
| // Prevent default so that we don't trigger a 'click' event on the |
| // newly focused "Enter" button. |
| e.preventDefault(); |
| } |
| }).bind(this)); |
| |
| var learnMore = this.querySelector('.learn-more'); |
| learnMore.addEventListener('mousedown', stopEventPropagation); |
| learnMore.addEventListener('click', this.handleLearnMoreEvent); |
| learnMore.addEventListener('keydown', this.handleLearnMoreEvent); |
| |
| learnMore = this.querySelector('.expanded-pane-learn-more'); |
| learnMore.addEventListener('click', this.handleLearnMoreEvent); |
| learnMore.addEventListener('keydown', this.handleLearnMoreEvent); |
| |
| var languageSelect = this.querySelector('.language-select'); |
| languageSelect.tabIndex = UserPodTabOrder.POD_INPUT; |
| languageSelect.manuallyChanged = false; |
| languageSelect.addEventListener( |
| 'change', |
| function() { |
| languageSelect.manuallyChanged = true; |
| this.getPublicSessionKeyboardLayouts_(); |
| }.bind(this)); |
| |
| var keyboardSelect = this.querySelector('.keyboard-select'); |
| keyboardSelect.tabIndex = UserPodTabOrder.POD_INPUT; |
| keyboardSelect.loadedLocale = null; |
| |
| var languageAndInput = this.querySelector('.language-and-input'); |
| languageAndInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| languageAndInput.addEventListener('click', |
| this.transitionToAdvanced_.bind(this)); |
| |
| this.enterButtonElement.addEventListener('click', (function(e) { |
| this.enterButtonElement.disabled = true; |
| var locale = this.querySelector('.language-select').value; |
| var keyboardSelect = this.querySelector('.keyboard-select'); |
| // The contents of |keyboardSelect| is updated asynchronously. If its |
| // locale does not match |locale|, it has not updated yet and the |
| // currently selected keyboard layout may not be applicable to |locale|. |
| // Do not return any keyboard layout in this case and let the backend |
| // choose a suitable layout. |
| var keyboardLayout = |
| keyboardSelect.loadedLocale == locale ? keyboardSelect.value : ''; |
| chrome.send('launchPublicSession', |
| [this.user.username, locale, keyboardLayout]); |
| }).bind(this)); |
| }, |
| |
| /** @override **/ |
| initialize: function() { |
| UserPod.prototype.initialize.call(this); |
| |
| id = this.user.username + '-keyboard'; |
| this.querySelector('.keyboard-select-label').htmlFor = id; |
| this.querySelector('.keyboard-select').setAttribute('id', id); |
| |
| var id = this.user.username + '-language'; |
| this.querySelector('.language-select-label').htmlFor = id; |
| var languageSelect = this.querySelector('.language-select'); |
| languageSelect.setAttribute('id', id); |
| this.populateLanguageSelect(this.user.initialLocales, |
| this.user.initialLocale, |
| this.user.initialMultipleRecommendedLocales); |
| }, |
| |
| /** @override **/ |
| update: function() { |
| UserPod.prototype.update.call(this); |
| this.querySelector('.expanded-pane-name').textContent = |
| this.user_.displayName; |
| this.querySelector('.info').textContent = |
| loadTimeData.getStringF('publicAccountInfoFormat', |
| this.user_.enterpriseDomain); |
| }, |
| |
| /** @override */ |
| focusInput: function() { |
| // Move tabIndex from the whole pod to the main input. |
| this.tabIndex = -1; |
| this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.mainInput.focus(); |
| }, |
| |
| /** @override */ |
| reset: function(takeFocus) { |
| if (!takeFocus) |
| this.expanded = false; |
| this.enterButtonElement.disabled = false; |
| UserPod.prototype.reset.call(this, takeFocus); |
| }, |
| |
| /** @override */ |
| activate: function(e) { |
| if (!this.expanded) { |
| this.expanded = true; |
| this.focusInput(); |
| } |
| return true; |
| }, |
| |
| /** @override */ |
| handleClickOnPod_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| |
| this.parentNode.focusPod(this); |
| this.parentNode.setActivatedPod(this, e); |
| // Prevent default so that we don't trigger 'focus' event. |
| e.preventDefault(); |
| }, |
| |
| /** |
| * Updates the display name shown on the pod. |
| * @param {string} displayName The new display name |
| */ |
| setDisplayName: function(displayName) { |
| this.user_.displayName = displayName; |
| this.update(); |
| }, |
| |
| /** |
| * Handle mouse and keyboard events for the learn more button. Triggering |
| * the button causes information about public sessions to be shown. |
| * @param {Event} event Mouse or keyboard event. |
| */ |
| handleLearnMoreEvent: function(event) { |
| switch (event.type) { |
| // Show informaton on left click. Let any other clicks propagate. |
| case 'click': |
| if (event.button != 0) |
| return; |
| break; |
| // Show informaton when <Return> or <Space> is pressed. Let any other |
| // key presses propagate. |
| case 'keydown': |
| switch (event.keyCode) { |
| case 13: // Return. |
| case 32: // Space. |
| break; |
| default: |
| return; |
| } |
| break; |
| } |
| chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]); |
| stopEventPropagation(event); |
| }, |
| |
| makeSpaceForExpandedPod_: function() { |
| var width = this.classList.contains('advanced') ? |
| PUBLIC_EXPANDED_ADVANCED_WIDTH : PUBLIC_EXPANDED_BASIC_WIDTH; |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| POD_ROW_PADDING; |
| if (this.left + width > $('pod-row').offsetWidth - rowPadding) |
| this.left = $('pod-row').offsetWidth - rowPadding - width; |
| }, |
| |
| /** |
| * Transition the expanded pod from the basic to the advanced view. |
| */ |
| transitionToAdvanced_: function() { |
| var pod = this; |
| var languageAndInputSection = |
| this.querySelector('.language-and-input-section'); |
| this.classList.add('transitioning-to-advanced'); |
| setTimeout(function() { |
| pod.classList.add('advanced'); |
| pod.makeSpaceForExpandedPod_(); |
| languageAndInputSection.addEventListener('webkitTransitionEnd', |
| function observer() { |
| languageAndInputSection.removeEventListener('webkitTransitionEnd', |
| observer); |
| pod.classList.remove('transitioning-to-advanced'); |
| pod.querySelector('.language-select').focus(); |
| }); |
| // Guard timer set to animation duration + 20ms. |
| ensureTransitionEndEvent(languageAndInputSection, 380); |
| }, 0); |
| }, |
| |
| /** |
| * Retrieves the list of keyboard layouts available for the currently |
| * selected locale. |
| */ |
| getPublicSessionKeyboardLayouts_: function() { |
| var selectedLocale = this.querySelector('.language-select').value; |
| if (selectedLocale == |
| this.querySelector('.keyboard-select').loadedLocale) { |
| // If the list of keyboard layouts was loaded for the currently selected |
| // locale, it is already up to date. |
| return; |
| } |
| chrome.send('getPublicSessionKeyboardLayouts', |
| [this.user.username, selectedLocale]); |
| }, |
| |
| /** |
| * Populates the keyboard layout "select" element with a list of layouts. |
| * @param {string} locale The locale to which this list of keyboard layouts |
| * applies |
| * @param {!Object} list List of available keyboard layouts |
| */ |
| populateKeyboardSelect: function(locale, list) { |
| if (locale != this.querySelector('.language-select').value) { |
| // The selected locale has changed and the list of keyboard layouts is |
| // not applicable. This method will be called again when a list of |
| // keyboard layouts applicable to the selected locale is retrieved. |
| return; |
| } |
| |
| var keyboardSelect = this.querySelector('.keyboard-select'); |
| keyboardSelect.loadedLocale = locale; |
| keyboardSelect.innerHTML = ''; |
| for (var i = 0; i < list.length; ++i) { |
| var item = list[i]; |
| keyboardSelect.appendChild( |
| new Option(item.title, item.value, item.selected, item.selected)); |
| } |
| }, |
| |
| /** |
| * Populates the language "select" element with a list of locales. |
| * @param {!Object} locales The list of available locales |
| * @param {string} defaultLocale The locale to select by default |
| * @param {boolean} multipleRecommendedLocales Whether |locales| contains |
| * two or more recommended locales |
| */ |
| populateLanguageSelect: function(locales, |
| defaultLocale, |
| multipleRecommendedLocales) { |
| var languageSelect = this.querySelector('.language-select'); |
| // If the user manually selected a locale, do not change the selection. |
| // Otherwise, select the new |defaultLocale|. |
| var selected = |
| languageSelect.manuallyChanged ? languageSelect.value : defaultLocale; |
| languageSelect.innerHTML = ''; |
| var group = languageSelect; |
| for (var i = 0; i < locales.length; ++i) { |
| var item = locales[i]; |
| if (item.optionGroupName) { |
| group = document.createElement('optgroup'); |
| group.label = item.optionGroupName; |
| languageSelect.appendChild(group); |
| } else { |
| group.appendChild(new Option(item.title, |
| item.value, |
| item.value == selected, |
| item.value == selected)); |
| } |
| } |
| languageSelect.multipleRecommendedLocales = multipleRecommendedLocales; |
| |
| // Retrieve a list of keyboard layouts applicable to the locale that is |
| // now selected. |
| this.getPublicSessionKeyboardLayouts_(); |
| } |
| }; |
| |
| /** |
| * Creates a user pod to be used only in desktop chrome. |
| * @constructor |
| * @extends {UserPod} |
| */ |
| var DesktopUserPod = cr.ui.define(function() { |
| // Don't just instantiate a UserPod(), as this will call decorate() on the |
| // parent object, and add duplicate event listeners. |
| var node = $('user-pod-template').cloneNode(true); |
| node.removeAttribute('id'); |
| return node; |
| }); |
| |
| DesktopUserPod.prototype = { |
| __proto__: UserPod.prototype, |
| |
| /** @override */ |
| get mainInput() { |
| if (this.user.needsSignin) |
| return this.passwordElement; |
| else |
| return this.nameElement; |
| }, |
| |
| /** @override */ |
| update: function() { |
| this.imageElement.src = this.user.userImage; |
| this.nameElement.textContent = this.user.displayName; |
| |
| var isLockedUser = this.user.needsSignin; |
| var isSupervisedUser = this.user.supervisedUser; |
| this.classList.toggle('locked', isLockedUser); |
| this.classList.toggle('supervised-user', isSupervisedUser); |
| |
| if (this.isAuthTypeUserClick) |
| this.passwordLabelElement.textContent = this.authValue; |
| |
| this.actionBoxRemoveUserWarningTextElement.hidden = isSupervisedUser; |
| this.actionBoxRemoveSupervisedUserWarningTextElement.hidden = |
| !isSupervisedUser; |
| |
| UserPod.prototype.updateActionBoxArea.call(this); |
| }, |
| |
| /** @override */ |
| focusInput: function() { |
| // Move tabIndex from the whole pod to the main input. |
| this.tabIndex = -1; |
| this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.mainInput.focus(); |
| }, |
| |
| /** @override */ |
| activate: function(e) { |
| if (!this.user.needsSignin) { |
| Oobe.launchUser(this.user.emailAddress, this.user.displayName); |
| } else if (!this.passwordElement.value) { |
| return false; |
| } else { |
| chrome.send('authenticatedLaunchUser', |
| [this.user.emailAddress, |
| this.user.displayName, |
| this.passwordElement.value]); |
| } |
| this.passwordElement.value = ''; |
| return true; |
| }, |
| |
| /** @override */ |
| handleClickOnPod_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| |
| Oobe.clearErrors(); |
| this.parentNode.lastFocusedPod_ = this; |
| |
| // If this is an unlocked pod, then open a browser window. Otherwise |
| // just activate the pod and show the password field. |
| if (!this.user.needsSignin && !this.isActionBoxMenuActive) |
| this.activate(e); |
| |
| if (this.isAuthTypeUserClick) |
| chrome.send('attemptUnlock', [this.user.emailAddress]); |
| }, |
| }; |
| |
| /** |
| * Creates a user pod that represents kiosk app. |
| * @constructor |
| * @extends {UserPod} |
| */ |
| var KioskAppPod = cr.ui.define(function() { |
| var node = UserPod(); |
| return node; |
| }); |
| |
| KioskAppPod.prototype = { |
| __proto__: UserPod.prototype, |
| |
| /** @override */ |
| decorate: function() { |
| UserPod.prototype.decorate.call(this); |
| this.launchAppButtonElement.addEventListener('click', |
| this.activate.bind(this)); |
| }, |
| |
| /** @override */ |
| update: function() { |
| this.imageElement.src = this.user.iconUrl; |
| this.imageElement.alt = this.user.label; |
| this.imageElement.title = this.user.label; |
| this.passwordEntryContainerElement.hidden = true; |
| this.launchAppButtonContainerElement.hidden = false; |
| this.nameElement.textContent = this.user.label; |
| |
| UserPod.prototype.updateActionBoxArea.call(this); |
| UserPod.prototype.customizeUserPodPerUserType.call(this); |
| }, |
| |
| /** @override */ |
| get mainInput() { |
| return this.launchAppButtonElement; |
| }, |
| |
| /** @override */ |
| focusInput: function() { |
| // Move tabIndex from the whole pod to the main input. |
| this.tabIndex = -1; |
| this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.mainInput.focus(); |
| }, |
| |
| /** @override */ |
| get forceOnlineSignin() { |
| return false; |
| }, |
| |
| /** @override */ |
| activate: function(e) { |
| var diagnosticMode = e && e.ctrlKey; |
| this.launchApp_(this.user, diagnosticMode); |
| return true; |
| }, |
| |
| /** @override */ |
| handleClickOnPod_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| |
| Oobe.clearErrors(); |
| this.parentNode.lastFocusedPod_ = this; |
| this.activate(e); |
| }, |
| |
| /** |
| * Launch the app. If |diagnosticMode| is true, ask user to confirm. |
| * @param {Object} app App data. |
| * @param {boolean} diagnosticMode Whether to run the app in diagnostic |
| * mode. |
| */ |
| launchApp_: function(app, diagnosticMode) { |
| if (!diagnosticMode) { |
| chrome.send('launchKioskApp', [app.id, false]); |
| return; |
| } |
| |
| var oobe = $('oobe'); |
| if (!oobe.confirmDiagnosticMode_) { |
| oobe.confirmDiagnosticMode_ = |
| new cr.ui.dialogs.ConfirmDialog(document.body); |
| oobe.confirmDiagnosticMode_.setOkLabel( |
| loadTimeData.getString('confirmKioskAppDiagnosticModeYes')); |
| oobe.confirmDiagnosticMode_.setCancelLabel( |
| loadTimeData.getString('confirmKioskAppDiagnosticModeNo')); |
| } |
| |
| oobe.confirmDiagnosticMode_.show( |
| loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat', |
| app.label), |
| function() { |
| chrome.send('launchKioskApp', [app.id, true]); |
| }); |
| }, |
| }; |
| |
| /** |
| * Creates a new pod row element. |
| * @constructor |
| * @extends {HTMLDivElement} |
| */ |
| var PodRow = cr.ui.define('podrow'); |
| |
| PodRow.prototype = { |
| __proto__: HTMLDivElement.prototype, |
| |
| // Whether this user pod row is shown for the first time. |
| firstShown_: true, |
| |
| // True if inside focusPod(). |
| insideFocusPod_: false, |
| |
| // Focused pod. |
| focusedPod_: undefined, |
| |
| // Activated pod, i.e. the pod of current login attempt. |
| activatedPod_: undefined, |
| |
| // Pod that was most recently focused, if any. |
| lastFocusedPod_: undefined, |
| |
| // Pods whose initial images haven't been loaded yet. |
| podsWithPendingImages_: [], |
| |
| // Whether pod placement has been postponed. |
| podPlacementPostponed_: false, |
| |
| // Standard user pod height/width. |
| userPodHeight_: 0, |
| userPodWidth_: 0, |
| |
| // Array of apps that are shown in addition to other user pods. |
| apps_: [], |
| |
| // True to show app pods along with user pods. |
| shouldShowApps_: true, |
| |
| // Array of users that are shown (public/supervised/regular). |
| users_: [], |
| |
| // If we're disabling single pod autofocus for Touch View. |
| touchViewSinglePodExperimentOn_: true, |
| |
| |
| /** @override */ |
| decorate: function() { |
| // Event listeners that are installed for the time period during which |
| // the element is visible. |
| this.listeners_ = { |
| focus: [this.handleFocus_.bind(this), true /* useCapture */], |
| click: [this.handleClick_.bind(this), true], |
| mousemove: [this.handleMouseMove_.bind(this), false], |
| keydown: [this.handleKeyDown.bind(this), false] |
| }; |
| |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| this.userPodHeight_ = isDesktopUserManager ? DESKTOP_POD_HEIGHT : |
| CROS_POD_HEIGHT; |
| // Same for Chrome OS and desktop. |
| this.userPodWidth_ = POD_WIDTH; |
| }, |
| |
| /** |
| * Returns all the pods in this pod row. |
| * @type {NodeList} |
| */ |
| get pods() { |
| return Array.prototype.slice.call(this.children); |
| }, |
| |
| /** |
| * Return true if user pod row has only single user pod in it, which should |
| * always be focused except desktop and touch view modes. |
| * @type {boolean} |
| */ |
| get alwaysFocusSinglePod() { |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| |
| return (isDesktopUserManager || |
| (this.touchViewSinglePodExperimentOn_ && |
| this.touchViewEnabled_)) ? |
| false : this.children.length == 1; |
| }, |
| |
| /** |
| * Returns pod with the given app id. |
| * @param {!string} app_id Application id to be matched. |
| * @return {Object} Pod with the given app id. null if pod hasn't been |
| * found. |
| */ |
| getPodWithAppId_: function(app_id) { |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (pod.user.isApp && pod.user.id == app_id) |
| return pod; |
| } |
| return null; |
| }, |
| |
| /** |
| * Returns pod with the given username (null if there is no such pod). |
| * @param {string} username Username to be matched. |
| * @return {Object} Pod with the given username. null if pod hasn't been |
| * found. |
| */ |
| getPodWithUsername_: function(username) { |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (pod.user.username == username) |
| return pod; |
| } |
| return null; |
| }, |
| |
| /** |
| * True if the the pod row is disabled (handles no user interaction). |
| * @type {boolean} |
| */ |
| disabled_: false, |
| get disabled() { |
| return this.disabled_; |
| }, |
| set disabled(value) { |
| this.disabled_ = value; |
| var controls = this.querySelectorAll('button,input'); |
| for (var i = 0, control; control = controls[i]; ++i) { |
| control.disabled = value; |
| } |
| }, |
| |
| /** |
| * Creates a user pod from given email. |
| * @param {!Object} user User info dictionary. |
| */ |
| createUserPod: function(user) { |
| var userPod; |
| if (user.isDesktopUser) |
| userPod = new DesktopUserPod({user: user}); |
| else if (user.publicAccount) |
| userPod = new PublicAccountUserPod({user: user}); |
| else if (user.isApp) |
| userPod = new KioskAppPod({user: user}); |
| else |
| userPod = new UserPod({user: user}); |
| |
| userPod.hidden = false; |
| return userPod; |
| }, |
| |
| /** |
| * Add an existing user pod to this pod row. |
| * @param {!Object} user User info dictionary. |
| */ |
| addUserPod: function(user) { |
| var userPod = this.createUserPod(user); |
| this.appendChild(userPod); |
| userPod.initialize(); |
| }, |
| |
| /** |
| * Runs app with a given id from the list of loaded apps. |
| * @param {!string} app_id of an app to run. |
| * @param {boolean=} opt_diagnostic_mode Whether to run the app in |
| * diagnostic mode. Default is false. |
| */ |
| findAndRunAppForTesting: function(app_id, opt_diagnostic_mode) { |
| var app = this.getPodWithAppId_(app_id); |
| if (app) { |
| var activationEvent = cr.doc.createEvent('MouseEvents'); |
| var ctrlKey = opt_diagnostic_mode; |
| activationEvent.initMouseEvent('click', true, true, null, |
| 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null); |
| app.dispatchEvent(activationEvent); |
| } |
| }, |
| |
| /** |
| * Removes user pod from pod row. |
| * @param {string} email User's email. |
| */ |
| removeUserPod: function(username) { |
| var podToRemove = this.getPodWithUsername_(username); |
| if (podToRemove == null) { |
| console.warn('Attempt to remove not existing pod for ' + username + |
| '.'); |
| return; |
| } |
| this.removeChild(podToRemove); |
| if (this.pods.length > 0) |
| this.placePods_(); |
| }, |
| |
| /** |
| * Returns index of given pod or -1 if not found. |
| * @param {UserPod} pod Pod to look up. |
| * @private |
| */ |
| indexOf_: function(pod) { |
| for (var i = 0; i < this.pods.length; ++i) { |
| if (pod == this.pods[i]) |
| return i; |
| } |
| return -1; |
| }, |
| |
| /** |
| * Populates pod row with given existing users and start init animation. |
| * @param {array} users Array of existing user emails. |
| */ |
| loadPods: function(users) { |
| this.users_ = users; |
| |
| this.rebuildPods(); |
| }, |
| |
| /** |
| * Scrolls focused user pod into view. |
| */ |
| scrollFocusedPodIntoView: function() { |
| var pod = this.focusedPod_; |
| if (!pod) |
| return; |
| |
| // First check whether focused pod is already fully visible. |
| var visibleArea = $('scroll-container'); |
| var scrollTop = visibleArea.scrollTop; |
| var clientHeight = visibleArea.clientHeight; |
| var podTop = $('oobe').offsetTop + pod.offsetTop; |
| var padding = USER_POD_KEYBOARD_MIN_PADDING; |
| if (podTop + pod.height + padding <= scrollTop + clientHeight && |
| podTop - padding >= scrollTop) { |
| return; |
| } |
| |
| // Scroll so that user pod is as centered as possible. |
| visibleArea.scrollTop = podTop - (clientHeight - pod.offsetHeight) / 2; |
| }, |
| |
| /** |
| * Rebuilds pod row using users_ and apps_ that were previously set or |
| * updated. |
| */ |
| rebuildPods: function() { |
| var emptyPodRow = this.pods.length == 0; |
| |
| // Clear existing pods. |
| this.innerHTML = ''; |
| this.focusedPod_ = undefined; |
| this.activatedPod_ = undefined; |
| this.lastFocusedPod_ = undefined; |
| |
| // Switch off animation |
| Oobe.getInstance().toggleClass('flying-pods', false); |
| |
| // Populate the pod row. |
| for (var i = 0; i < this.users_.length; ++i) |
| this.addUserPod(this.users_[i]); |
| |
| for (var i = 0, pod; pod = this.pods[i]; ++i) |
| this.podsWithPendingImages_.push(pod); |
| |
| // TODO(nkostylev): Edge case handling when kiosk apps are not fitting. |
| if (this.shouldShowApps_) { |
| for (var i = 0; i < this.apps_.length; ++i) |
| this.addUserPod(this.apps_[i]); |
| } |
| |
| // Make sure we eventually show the pod row, even if some image is stuck. |
| setTimeout(function() { |
| $('pod-row').classList.remove('images-loading'); |
| }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS); |
| |
| var isAccountPicker = $('login-header-bar').signinUIState == |
| SIGNIN_UI_STATE.ACCOUNT_PICKER; |
| |
| // Immediately recalculate pods layout only when current UI is account |
| // picker. Otherwise postpone it. |
| if (isAccountPicker) { |
| this.placePods_(); |
| this.maybePreselectPod(); |
| |
| // Without timeout changes in pods positions will be animated even |
| // though it happened when 'flying-pods' class was disabled. |
| setTimeout(function() { |
| Oobe.getInstance().toggleClass('flying-pods', true); |
| }, 0); |
| } else { |
| this.podPlacementPostponed_ = true; |
| |
| // Update [Cancel] button state. |
| if ($('login-header-bar').signinUIState == |
| SIGNIN_UI_STATE.GAIA_SIGNIN && |
| emptyPodRow && |
| this.pods.length > 0) { |
| login.GaiaSigninScreen.updateCancelButtonState(); |
| } |
| } |
| }, |
| |
| /** |
| * Adds given apps to the pod row. |
| * @param {array} apps Array of apps. |
| */ |
| setApps: function(apps) { |
| this.apps_ = apps; |
| this.rebuildPods(); |
| chrome.send('kioskAppsLoaded'); |
| |
| // Check whether there's a pending kiosk app error. |
| window.setTimeout(function() { |
| chrome.send('checkKioskAppLaunchError'); |
| }, 500); |
| }, |
| |
| /** |
| * Sets whether should show app pods. |
| * @param {boolean} shouldShowApps Whether app pods should be shown. |
| */ |
| setShouldShowApps: function(shouldShowApps) { |
| if (this.shouldShowApps_ == shouldShowApps) |
| return; |
| |
| this.shouldShowApps_ = shouldShowApps; |
| this.rebuildPods(); |
| }, |
| |
| /** |
| * Shows a custom icon on a user pod besides the input field. |
| * @param {string} username Username of pod to add button |
| * @param {!{id: !string, |
| * hardlockOnClick: boolean, |
| * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon |
| * The icon parameters. |
| */ |
| showUserPodCustomIcon: function(username, icon) { |
| var pod = this.getPodWithUsername_(username); |
| if (pod == null) { |
| console.error('Unable to show user pod button for ' + username + |
| ': user pod not found.'); |
| return; |
| } |
| |
| if (!icon.id) |
| return; |
| |
| pod.customIconElement.setIcon(icon.id); |
| |
| if (icon.hardlockOnClick) { |
| pod.customIconElement.setInteractive( |
| this.hardlockUserPod_.bind(this, username)); |
| } else { |
| pod.customIconElement.setInteractive(null); |
| } |
| |
| pod.customIconElement.show(); |
| |
| // This has to be called after |show| in case the tooltip should be shown |
| // immediatelly. |
| pod.customIconElement.setTooltip( |
| icon.tooltip || {text: '', autoshow: false}); |
| }, |
| |
| /** |
| * Hard-locks user pod for the user. If user pod is hard-locked, it can be |
| * only unlocked using password, and the authentication type cannot be |
| * changed. |
| * @param {!string} username The user's username. |
| * @private |
| */ |
| hardlockUserPod_: function(username) { |
| chrome.send('hardlockPod', [username]); |
| }, |
| |
| /** |
| * Hides the custom icon in the user pod added by showUserPodCustomIcon(). |
| * @param {string} username Username of pod to remove button |
| */ |
| hideUserPodCustomIcon: function(username) { |
| var pod = this.getPodWithUsername_(username); |
| if (pod == null) { |
| console.error('Unable to hide user pod button for ' + username + |
| ': user pod not found.'); |
| return; |
| } |
| |
| // TODO(tengs): Allow option for a fading transition. |
| pod.customIconElement.hide(); |
| }, |
| |
| /** |
| * Sets the authentication type used to authenticate the user. |
| * @param {string} username Username of selected user |
| * @param {number} authType Authentication type, must be one of the |
| * values listed in AUTH_TYPE enum. |
| * @param {string} value The initial value to use for authentication. |
| */ |
| setAuthType: function(username, authType, value) { |
| var pod = this.getPodWithUsername_(username); |
| if (pod == null) { |
| console.error('Unable to set auth type for ' + username + |
| ': user pod not found.'); |
| return; |
| } |
| pod.setAuthType(authType, value); |
| }, |
| |
| /** |
| * Sets the state of touch view mode. |
| * @param {boolean} isTouchViewEnabled true if the mode is on. |
| */ |
| setTouchViewState: function(isTouchViewEnabled) { |
| this.touchViewEnabled_ = isTouchViewEnabled; |
| }, |
| |
| /** |
| * Updates the display name shown on a public session pod. |
| * @param {string} userID The user ID of the public session |
| * @param {string} displayName The new display name |
| */ |
| setPublicSessionDisplayName: function(userID, displayName) { |
| var pod = this.getPodWithUsername_(userID); |
| if (pod != null) |
| pod.setDisplayName(displayName); |
| }, |
| |
| /** |
| * Updates the list of locales available for a public session. |
| * @param {string} userID The user ID of the public session |
| * @param {!Object} locales The list of available locales |
| * @param {string} defaultLocale The locale to select by default |
| * @param {boolean} multipleRecommendedLocales Whether |locales| contains |
| * two or more recommended locales |
| */ |
| setPublicSessionLocales: function(userID, |
| locales, |
| defaultLocale, |
| multipleRecommendedLocales) { |
| var pod = this.getPodWithUsername_(userID); |
| if (pod != null) { |
| pod.populateLanguageSelect(locales, |
| defaultLocale, |
| multipleRecommendedLocales); |
| } |
| }, |
| |
| /** |
| * Updates the list of available keyboard layouts for a public session pod. |
| * @param {string} userID The user ID of the public session |
| * @param {string} locale The locale to which this list of keyboard layouts |
| * applies |
| * @param {!Object} list List of available keyboard layouts |
| */ |
| setPublicSessionKeyboardLayouts: function(userID, locale, list) { |
| var pod = this.getPodWithUsername_(userID); |
| if (pod != null) |
| pod.populateKeyboardSelect(locale, list); |
| }, |
| |
| /** |
| * Called when window was resized. |
| */ |
| onWindowResize: function() { |
| var layout = this.calculateLayout_(); |
| if (layout.columns != this.columns || layout.rows != this.rows) |
| this.placePods_(); |
| |
| if (Oobe.getInstance().virtualKeyboardShown) |
| this.scrollFocusedPodIntoView(); |
| }, |
| |
| /** |
| * Returns width of podrow having |columns| number of columns. |
| * @private |
| */ |
| columnsToWidth_: function(columns) { |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] : |
| MARGIN_BY_COLUMNS[columns]; |
| var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| POD_ROW_PADDING; |
| return 2 * rowPadding + columns * this.userPodWidth_ + |
| (columns - 1) * margin; |
| }, |
| |
| /** |
| * Returns height of podrow having |rows| number of rows. |
| * @private |
| */ |
| rowsToHeight_: function(rows) { |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| POD_ROW_PADDING; |
| return 2 * rowPadding + rows * this.userPodHeight_; |
| }, |
| |
| /** |
| * Calculates number of columns and rows that podrow should have in order to |
| * hold as much its pods as possible for current screen size. Also it tries |
| * to choose layout that looks good. |
| * @return {{columns: number, rows: number}} |
| */ |
| calculateLayout_: function() { |
| var preferredColumns = this.pods.length < COLUMNS.length ? |
| COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1]; |
| var maxWidth = Oobe.getInstance().clientAreaSize.width; |
| var columns = preferredColumns; |
| while (maxWidth < this.columnsToWidth_(columns) && columns > 1) |
| --columns; |
| var rows = Math.floor((this.pods.length - 1) / columns) + 1; |
| if (getComputedStyle( |
| $('signin-banner'), null).getPropertyValue('display') != 'none') { |
| rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER); |
| } |
| var maxHeigth = Oobe.getInstance().clientAreaSize.height; |
| while (maxHeigth < this.rowsToHeight_(rows) && rows > 1) |
| --rows; |
| // One more iteration if it's not enough cells to place all pods. |
| while (maxWidth >= this.columnsToWidth_(columns + 1) && |
| columns * rows < this.pods.length && |
| columns < MAX_NUMBER_OF_COLUMNS) { |
| ++columns; |
| } |
| return {columns: columns, rows: rows}; |
| }, |
| |
| /** |
| * Places pods onto their positions onto pod grid. |
| * @private |
| */ |
| placePods_: function() { |
| var layout = this.calculateLayout_(); |
| var columns = this.columns = layout.columns; |
| var rows = this.rows = layout.rows; |
| var maxPodsNumber = columns * rows; |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] : |
| MARGIN_BY_COLUMNS[columns]; |
| this.parentNode.setPreferredSize( |
| this.columnsToWidth_(columns), this.rowsToHeight_(rows)); |
| var height = this.userPodHeight_; |
| var width = this.userPodWidth_; |
| this.pods.forEach(function(pod, index) { |
| if (index >= maxPodsNumber) { |
| pod.hidden = true; |
| return; |
| } |
| pod.hidden = false; |
| if (pod.offsetHeight != height) { |
| console.error('Pod offsetHeight (' + pod.offsetHeight + |
| ') and POD_HEIGHT (' + height + ') are not equal.'); |
| } |
| if (pod.offsetWidth != width) { |
| console.error('Pod offsetWidth (' + pod.offsetWidth + |
| ') and POD_WIDTH (' + width + ') are not equal.'); |
| } |
| var column = index % columns; |
| var row = Math.floor(index / columns); |
| var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| POD_ROW_PADDING; |
| pod.left = rowPadding + column * (width + margin); |
| |
| // On desktop, we want the rows to always be equally spaced. |
| pod.top = isDesktopUserManager ? row * (height + rowPadding) : |
| row * height + rowPadding; |
| }); |
| Oobe.getInstance().updateScreenSize(this.parentNode); |
| }, |
| |
| /** |
| * Number of columns. |
| * @type {?number} |
| */ |
| set columns(columns) { |
| // Cannot use 'columns' here. |
| this.setAttribute('ncolumns', columns); |
| }, |
| get columns() { |
| return parseInt(this.getAttribute('ncolumns')); |
| }, |
| |
| /** |
| * Number of rows. |
| * @type {?number} |
| */ |
| set rows(rows) { |
| // Cannot use 'rows' here. |
| this.setAttribute('nrows', rows); |
| }, |
| get rows() { |
| return parseInt(this.getAttribute('nrows')); |
| }, |
| |
| /** |
| * Whether the pod is currently focused. |
| * @param {UserPod} pod Pod to check for focus. |
| * @return {boolean} Pod focus status. |
| */ |
| isFocused: function(pod) { |
| return this.focusedPod_ == pod; |
| }, |
| |
| /** |
| * Focuses a given user pod or clear focus when given null. |
| * @param {UserPod=} podToFocus User pod to focus (undefined clears focus). |
| * @param {boolean=} opt_force If true, forces focus update even when |
| * podToFocus is already focused. |
| */ |
| focusPod: function(podToFocus, opt_force) { |
| if (this.isFocused(podToFocus) && !opt_force) { |
| // Calling focusPod w/o podToFocus means reset. |
| if (!podToFocus) |
| Oobe.clearErrors(); |
| this.keyboardActivated_ = false; |
| return; |
| } |
| |
| // Make sure there's only one focusPod operation happening at a time. |
| if (this.insideFocusPod_) { |
| this.keyboardActivated_ = false; |
| return; |
| } |
| this.insideFocusPod_ = true; |
| |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (!this.alwaysFocusSinglePod) { |
| pod.isActionBoxMenuActive = false; |
| } |
| if (pod != podToFocus) { |
| pod.isActionBoxMenuHovered = false; |
| pod.classList.remove('focused'); |
| // On Desktop, the faded style is not set correctly, so we should |
| // manually fade out non-focused pods if there is a focused pod. |
| if (pod.user.isDesktopUser && podToFocus) |
| pod.classList.add('faded'); |
| else |
| pod.classList.remove('faded'); |
| pod.reset(false); |
| } |
| } |
| |
| // Clear any error messages for previous pod. |
| if (!this.isFocused(podToFocus)) |
| Oobe.clearErrors(); |
| |
| var hadFocus = !!this.focusedPod_; |
| this.focusedPod_ = podToFocus; |
| if (podToFocus) { |
| podToFocus.classList.remove('faded'); |
| podToFocus.classList.add('focused'); |
| if (!podToFocus.multiProfilesPolicyApplied) |
| podToFocus.reset(true); // Reset and give focus. |
| else { |
| podToFocus.userTypeBubbleElement.classList.add('bubble-shown'); |
| podToFocus.focus(); |
| } |
| |
| // focusPod() automatically loads wallpaper |
| if (!podToFocus.user.isApp) |
| chrome.send('focusPod', [podToFocus.user.username]); |
| this.firstShown_ = false; |
| this.lastFocusedPod_ = podToFocus; |
| |
| if (Oobe.getInstance().virtualKeyboardShown) |
| this.scrollFocusedPodIntoView(); |
| } |
| this.insideFocusPod_ = false; |
| this.keyboardActivated_ = false; |
| }, |
| |
| /** |
| * Resets wallpaper to the last active user's wallpaper, if any. |
| */ |
| loadLastWallpaper: function() { |
| if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp) |
| chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]); |
| }, |
| |
| /** |
| * Returns the currently activated pod. |
| * @type {UserPod} |
| */ |
| get activatedPod() { |
| return this.activatedPod_; |
| }, |
| |
| /** |
| * Sets currently activated pod. |
| * @param {UserPod} pod Pod to check for focus. |
| * @param {Event} e Event object. |
| */ |
| setActivatedPod: function(pod, e) { |
| if (pod && pod.activate(e)) |
| this.activatedPod_ = pod; |
| }, |
| |
| /** |
| * The pod of the signed-in user, if any; null otherwise. |
| * @type {?UserPod} |
| */ |
| get lockedPod() { |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (pod.user.signedIn) |
| return pod; |
| } |
| return null; |
| }, |
| |
| /** |
| * The pod that is preselected on user pod row show. |
| * @type {?UserPod} |
| */ |
| get preselectedPod() { |
| var isDesktopUserManager = Oobe.getInstance().displayType == |
| DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| if (isDesktopUserManager) { |
| // On desktop, don't pre-select a pod if it's the only one. |
| if (this.pods.length == 1) |
| return null; |
| |
| // The desktop User Manager can send the index of a pod that should be |
| // initially focused in url hash. |
| var podIndex = parseInt(window.location.hash.substr(1)); |
| if (isNaN(podIndex) || podIndex >= this.pods.length) |
| return null; |
| return this.pods[podIndex]; |
| } |
| |
| var lockedPod = this.lockedPod; |
| if (lockedPod) |
| return lockedPod; |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (!pod.multiProfilesPolicyApplied) { |
| return pod; |
| } |
| } |
| return this.pods[0]; |
| }, |
| |
| /** |
| * Resets input UI. |
| * @param {boolean} takeFocus True to take focus. |
| */ |
| reset: function(takeFocus) { |
| this.disabled = false; |
| if (this.activatedPod_) |
| this.activatedPod_.reset(takeFocus); |
| }, |
| |
| /** |
| * Restores input focus to current selected pod, if there is any. |
| */ |
| refocusCurrentPod: function() { |
| if (this.focusedPod_ && !this.focusedPod_.multiProfilesPolicyApplied) { |
| this.focusedPod_.focusInput(); |
| } |
| }, |
| |
| /** |
| * Clears focused pod password field. |
| */ |
| clearFocusedPod: function() { |
| if (!this.disabled && this.focusedPod_) |
| this.focusedPod_.reset(true); |
| }, |
| |
| /** |
| * Shows signin UI. |
| * @param {string} email Email for signin UI. |
| */ |
| showSigninUI: function(email) { |
| // Clear any error messages that might still be around. |
| Oobe.clearErrors(); |
| this.disabled = true; |
| this.lastFocusedPod_ = this.getPodWithUsername_(email); |
| Oobe.showSigninUI(email); |
| }, |
| |
| /** |
| * Updates current image of a user. |
| * @param {string} username User for which to update the image. |
| */ |
| updateUserImage: function(username) { |
| var pod = this.getPodWithUsername_(username); |
| if (pod) |
| pod.updateUserImage(); |
| }, |
| |
| /** |
| * Handler of click event. |
| * @param {Event} e Click Event object. |
| * @private |
| */ |
| handleClick_: function(e) { |
| if (this.disabled) |
| return; |
| |
| // Clear all menus if the click is outside pod menu and its |
| // button area. |
| if (!findAncestorByClass(e.target, 'action-box-menu') && |
| !findAncestorByClass(e.target, 'action-box-area')) { |
| for (var i = 0, pod; pod = this.pods[i]; ++i) |
| pod.isActionBoxMenuActive = false; |
| } |
| |
| // Clears focus if not clicked on a pod and if there's more than one pod. |
| var pod = findAncestorByClass(e.target, 'pod'); |
| if ((!pod || pod.parentNode != this) && !this.alwaysFocusSinglePod) { |
| this.focusPod(); |
| } |
| |
| if (pod) |
| pod.isActionBoxMenuHovered = true; |
| |
| // Return focus back to single pod. |
| if (this.alwaysFocusSinglePod && !pod) { |
| this.focusPod(this.focusedPod_, true /* force */); |
| this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown'); |
| this.focusedPod_.isActionBoxMenuHovered = false; |
| } |
| }, |
| |
| /** |
| * Handler of mouse move event. |
| * @param {Event} e Click Event object. |
| * @private |
| */ |
| handleMouseMove_: function(e) { |
| if (this.disabled) |
| return; |
| if (e.webkitMovementX == 0 && e.webkitMovementY == 0) |
| return; |
| |
| // Defocus (thus hide) action box, if it is focused on a user pod |
| // and the pointer is not hovering over it. |
| var pod = findAncestorByClass(e.target, 'pod'); |
| if (document.activeElement && |
| document.activeElement.parentNode != pod && |
| document.activeElement.classList.contains('action-box-area')) { |
| document.activeElement.parentNode.focus(); |
| } |
| |
| if (pod) |
| pod.isActionBoxMenuHovered = true; |
| |
| // Hide action boxes on other user pods. |
| for (var i = 0, p; p = this.pods[i]; ++i) |
| if (p != pod && !p.isActionBoxMenuActive) |
| p.isActionBoxMenuHovered = false; |
| }, |
| |
| /** |
| * Handles focus event. |
| * @param {Event} e Focus Event object. |
| * @private |
| */ |
| handleFocus_: function(e) { |
| if (this.disabled) |
| return; |
| if (e.target.parentNode == this) { |
| // Focus on a pod |
| if (e.target.classList.contains('focused')) { |
| if (!e.target.multiProfilesPolicyApplied) |
| e.target.focusInput(); |
| else |
| e.target.userTypeBubbleElement.classList.add('bubble-shown'); |
| } else |
| this.focusPod(e.target); |
| return; |
| } |
| |
| var pod = findAncestorByClass(e.target, 'pod'); |
| if (pod && pod.parentNode == this) { |
| // Focus on a control of a pod but not on the action area button. |
| if (!pod.classList.contains('focused') && |
| !e.target.classList.contains('action-box-button')) { |
| this.focusPod(pod); |
| pod.userTypeBubbleElement.classList.remove('bubble-shown'); |
| e.target.focus(); |
| } |
| return; |
| } |
| |
| // Clears pod focus when we reach here. It means new focus is neither |
| // on a pod nor on a button/input for a pod. |
| // Do not "defocus" user pod when it is a single pod. |
| // That means that 'focused' class will not be removed and |
| // input field/button will always be visible. |
| if (!this.alwaysFocusSinglePod) |
| this.focusPod(); |
| else { |
| // Hide user-type-bubble in case this is one pod and we lost focus of |
| // it. |
| this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown'); |
| } |
| }, |
| |
| /** |
| * Handler of keydown event. |
| * @param {Event} e KeyDown Event object. |
| */ |
| handleKeyDown: function(e) { |
| if (this.disabled) |
| return; |
| var editing = e.target.tagName == 'INPUT' && e.target.value; |
| switch (e.keyIdentifier) { |
| case 'Left': |
| if (!editing) { |
| this.keyboardActivated_ = true; |
| if (this.focusedPod_ && this.focusedPod_.previousElementSibling) |
| this.focusPod(this.focusedPod_.previousElementSibling); |
| else |
| this.focusPod(this.lastElementChild); |
| |
| e.stopPropagation(); |
| } |
| break; |
| case 'Right': |
| if (!editing) { |
| this.keyboardActivated_ = true; |
| if (this.focusedPod_ && this.focusedPod_.nextElementSibling) |
| this.focusPod(this.focusedPod_.nextElementSibling); |
| else |
| this.focusPod(this.firstElementChild); |
| |
| e.stopPropagation(); |
| } |
| break; |
| case 'Enter': |
| if (this.focusedPod_) { |
| var targetTag = e.target.tagName; |
| if (e.target == this.focusedPod_.passwordElement || |
| (targetTag != 'INPUT' && |
| targetTag != 'BUTTON' && |
| targetTag != 'A')) { |
| this.setActivatedPod(this.focusedPod_, e); |
| e.stopPropagation(); |
| } |
| } |
| break; |
| case 'U+001B': // Esc |
| if (!this.alwaysFocusSinglePod) |
| this.focusPod(); |
| break; |
| } |
| }, |
| |
| /** |
| * Called right after the pod row is shown. |
| */ |
| handleAfterShow: function() { |
| // Without timeout changes in pods positions will be animated even though |
| // it happened when 'flying-pods' class was disabled. |
| setTimeout(function() { |
| Oobe.getInstance().toggleClass('flying-pods', true); |
| }, 0); |
| // Force input focus for user pod on show and once transition ends. |
| if (this.focusedPod_) { |
| var focusedPod = this.focusedPod_; |
| var screen = this.parentNode; |
| var self = this; |
| focusedPod.addEventListener('webkitTransitionEnd', function f(e) { |
| focusedPod.removeEventListener('webkitTransitionEnd', f); |
| focusedPod.reset(true); |
| // Notify screen that it is ready. |
| screen.onShow(); |
| }); |
| // Guard timer for 1 second -- it would conver all possible animations. |
| ensureTransitionEndEvent(focusedPod, 1000); |
| } |
| }, |
| |
| /** |
| * Called right before the pod row is shown. |
| */ |
| handleBeforeShow: function() { |
| Oobe.getInstance().toggleClass('flying-pods', false); |
| for (var event in this.listeners_) { |
| this.ownerDocument.addEventListener( |
| event, this.listeners_[event][0], this.listeners_[event][1]); |
| } |
| $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR; |
| |
| if (this.podPlacementPostponed_) { |
| this.podPlacementPostponed_ = false; |
| this.placePods_(); |
| this.maybePreselectPod(); |
| } |
| }, |
| |
| /** |
| * Called when the element is hidden. |
| */ |
| handleHide: function() { |
| for (var event in this.listeners_) { |
| this.ownerDocument.removeEventListener( |
| event, this.listeners_[event][0], this.listeners_[event][1]); |
| } |
| $('login-header-bar').buttonsTabIndex = 0; |
| }, |
| |
| /** |
| * Called when a pod's user image finishes loading. |
| */ |
| handlePodImageLoad: function(pod) { |
| var index = this.podsWithPendingImages_.indexOf(pod); |
| if (index == -1) { |
| return; |
| } |
| |
| this.podsWithPendingImages_.splice(index, 1); |
| if (this.podsWithPendingImages_.length == 0) { |
| this.classList.remove('images-loading'); |
| } |
| }, |
| |
| /** |
| * Preselects pod, if needed. |
| */ |
| maybePreselectPod: function() { |
| var pod = this.preselectedPod; |
| this.focusPod(pod); |
| |
| // Hide user-type-bubble in case all user pods are disabled and we focus |
| // first pod. |
| if (pod && pod.multiProfilesPolicyApplied) { |
| pod.userTypeBubbleElement.classList.remove('bubble-shown'); |
| } |
| } |
| }; |
| |
| return { |
| PodRow: PodRow |
| }; |
| }); |