| // Copyright (c) 2012 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. |
| |
| <include src="wallpaper_loader.js"></include> |
| |
| /** |
| * @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]; |
| |
| /** |
| * Whether to preselect the first pod automatically on login screen. |
| * @type {boolean} |
| * @const |
| */ |
| var PRESELECT_FIRST_POD = true; |
| |
| /** |
| * 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; |
| |
| /** |
| * Oauth token status. These must match UserManager::OAuthTokenStatus. |
| * @enum {number} |
| * @const |
| */ |
| var OAuthTokenStatus = { |
| UNKNOWN: 0, |
| INVALID_OLD: 1, |
| VALID_OLD: 2, |
| INVALID_NEW: 3, |
| VALID_NEW: 4 |
| }; |
| |
| /** |
| * 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). |
| HEADER_BAR: 2, // Buttons on the header bar (Shutdown, Add User). |
| ACTION_BOX: 3, // Action box buttons. |
| PAD_MENU_ITEM: 4 // User pad menu items (Remove this user). |
| }; |
| |
| // 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) buttons on the header bar have tab index 2 so they follow user pods; |
| // (4) Action box buttons have tab index 3 and follow header bar buttons; |
| // (5) 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(); |
| } |
| |
| /** |
| * 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, |
| |
| /** @override */ |
| decorate: function() { |
| this.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.actionBoxAreaElement.tabIndex = UserPodTabOrder.ACTION_BOX; |
| |
| // Mousedown has to be used instead of click to be able to prevent 'focus' |
| // event later. |
| this.addEventListener('mousedown', |
| this.handleMouseDown_.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)); |
| |
| if (this.actionBoxRemoveUserWarningButtonElement) { |
| this.actionBoxRemoveUserWarningButtonElement.addEventListener( |
| 'click', |
| this.handleRemoveUserConfirmationClick_.bind(this)); |
| } |
| }, |
| |
| /** |
| * 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)); |
| }, |
| |
| /** |
| * Resets tab order for pod elements to its initial state. |
| */ |
| resetTabOrder: function() { |
| this.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.mainInput.tabIndex = -1; |
| }, |
| |
| /** |
| * 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; |
| } |
| }, |
| |
| /** |
| * Gets signed in indicator element. |
| * @type {!HTMLDivElement} |
| */ |
| get signedInIndicatorElement() { |
| return this.querySelector('.signed-in-indicator'); |
| }, |
| |
| /** |
| * Gets image element. |
| * @type {!HTMLImageElement} |
| */ |
| get imageElement() { |
| return this.querySelector('.user-image'); |
| }, |
| |
| /** |
| * Gets name element. |
| * @type {!HTMLDivElement} |
| */ |
| get nameElement() { |
| return this.querySelector('.name'); |
| }, |
| |
| /** |
| * Gets password field. |
| * @type {!HTMLInputElement} |
| */ |
| get passwordElement() { |
| return this.querySelector('.password'); |
| }, |
| |
| /** |
| * Gets Caps Lock hint image. |
| * @type {!HTMLImageElement} |
| */ |
| get capslockHintElement() { |
| return this.querySelector('.capslock-hint'); |
| }, |
| |
| /** |
| * Gets user signin button. |
| * @type {!HTMLInputElement} |
| */ |
| get signinButtonElement() { |
| return this.querySelector('.signin-button'); |
| }, |
| |
| /** |
| * Gets action box area. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxAreaElement() { |
| return this.querySelector('.action-box-area'); |
| }, |
| |
| /** |
| * Gets user type icon area. |
| * @type {!HTMLInputElement} |
| */ |
| get userTypeIconAreaElement() { |
| return this.querySelector('.user-type-icon-area'); |
| }, |
| |
| /** |
| * Gets action box menu. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenuElement() { |
| return this.querySelector('.action-box-menu'); |
| }, |
| |
| /** |
| * Gets action box menu title. |
| * @type {!HTMLInputElement} |
| */ |
| get actionBoxMenuTitleElement() { |
| return this.querySelector('.action-box-menu-title'); |
| }, |
| |
| /** |
| * 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 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 locked user indicator box. |
| * @type {!HTMLInputElement} |
| */ |
| get lockedIndicatorElement() { |
| return this.querySelector('.locked-indicator'); |
| }, |
| |
| /** |
| * 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.signedInIndicatorElement.hidden = !this.user_.signedIn; |
| |
| var needSignin = this.needSignin; |
| this.passwordElement.hidden = needSignin; |
| this.signinButtonElement.hidden = !needSignin; |
| |
| this.updateActionBoxArea(); |
| }, |
| |
| updateActionBoxArea: function() { |
| this.actionBoxAreaElement.hidden = this.user_.publicAccount; |
| 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_.locallyManagedUser; |
| |
| this.actionBoxMenuCommandElement.textContent = |
| loadTimeData.getString('removeUser'); |
| this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF( |
| 'passwordFieldAccessibleName', this.user_.emailAddress)); |
| this.userTypeIconAreaElement.hidden = !this.user_.locallyManagedUser; |
| }, |
| |
| /** |
| * The user that this pod represents. |
| * @type {!Object} |
| */ |
| user_: undefined, |
| get user() { |
| return this.user_; |
| }, |
| set user(userDict) { |
| this.user_ = userDict; |
| this.update(); |
| }, |
| |
| /** |
| * Whether signin is required for this user. |
| */ |
| get needSignin() { |
| // Signin is performed if the user has an invalid oauth token and is |
| // not currently signed in (i.e. not the lock screen). |
| return this.user.oauthTokenStatus != OAuthTokenStatus.VALID_OLD && |
| this.user.oauthTokenStatus != OAuthTokenStatus.VALID_NEW && |
| !this.user.signedIn; |
| }, |
| |
| /** |
| * Gets main input element. |
| * @type {(HTMLButtonElement|HTMLInputElement)} |
| */ |
| get mainInput() { |
| if (!this.signinButtonElement.hidden) |
| return this.signinButtonElement; |
| else |
| return this.passwordElement; |
| }, |
| |
| /** |
| * 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; |
| if (this.actionBoxRemoveUserWarningElement) |
| 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(); |
| } |
| this.actionBoxAreaElement.classList.add('active'); |
| } else { |
| this.actionBoxAreaElement.classList.remove('active'); |
| } |
| }, |
| |
| /** |
| * 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 { |
| this.actionBoxAreaElement.classList.remove('hovered'); |
| this.classList.remove('hovered'); |
| } |
| }, |
| |
| /** |
| * 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() { |
| var needSignin = this.needSignin; |
| this.signinButtonElement.hidden = !needSignin; |
| this.passwordElement.hidden = needSignin; |
| |
| // Move tabIndex from the whole pod to the main input. |
| this.tabIndex = -1; |
| this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| this.mainInput.focus(); |
| }, |
| |
| /** |
| * Activates the pod. |
| * @return {boolean} True if activated successfully. |
| */ |
| activate: function() { |
| if (!this.signinButtonElement.hidden) { |
| this.showSigninUI(); |
| } else if (!this.passwordElement.value) { |
| return false; |
| } else { |
| Oobe.disableSigninUI(); |
| chrome.send('authenticateUser', |
| [this.user.username, this.passwordElement.value]); |
| } |
| |
| return true; |
| }, |
| |
| showSupervisedUserSigninWarning: function() { |
| // Locally managed 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); |
| }, |
| |
| /** |
| * Shows signin UI for this user. |
| */ |
| showSigninUI: function() { |
| if (this.user.locallyManagedUser) { |
| this.showSupervisedUserSigninWarning(); |
| } else { |
| 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) |
| this.focusInput(); // This will set a custom tab order. |
| else |
| this.resetTabOrder(); |
| }, |
| |
| /** |
| * 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; |
| }, |
| |
| /** |
| * 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 |
| 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.locallyManagedUser || this.user.isDesktopUser) { |
| this.showRemoveWarning_(); |
| return; |
| } |
| if (this.isActionBoxMenuActive) |
| chrome.send('removeUser', [this.user.username]); |
| }, |
| |
| /** |
| * Shows remove warning for managed users. |
| */ |
| showRemoveWarning_: function() { |
| this.actionBoxMenuRemoveElement.hidden = true; |
| this.actionBoxRemoveUserWarningElement.hidden = false; |
| }, |
| |
| /** |
| * Handles a click event on remove user confirmation button. |
| * @param {Event} e Click event. |
| */ |
| handleRemoveUserConfirmationClick_: function(e) { |
| if (this.isActionBoxMenuActive) |
| chrome.send('removeUser', [this.user.username]); |
| }, |
| |
| /** |
| * Handles a keydown event on remove command. |
| * @param {Event} e KeyDown event. |
| */ |
| handleRemoveCommandKeyDown_: function(e) { |
| if (this.disabled) |
| return; |
| switch (e.keyIdentifier) { |
| case 'Enter': |
| chrome.send('removeUser', [this.user.username]); |
| 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 mousedown event on a user pod. |
| * @param {Event} e Mousedown event. |
| */ |
| handleMouseDown_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| |
| if (!this.signinButtonElement.hidden && !this.isActionBoxMenuActive) { |
| this.showSigninUI(); |
| // Prevent default so that we don't trigger 'focus' event. |
| e.preventDefault(); |
| } |
| } |
| }; |
| |
| /** |
| * 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); |
| |
| 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')); |
| }); |
| }, |
| |
| /** @override */ |
| get needSignin() { |
| return false; |
| }, |
| |
| /** @override */ |
| get mainInput() { |
| if (this.expanded) |
| return this.enterButtonElement; |
| else |
| return this.nameElement; |
| }, |
| |
| /** @override */ |
| decorate: function() { |
| UserPod.prototype.decorate.call(this); |
| |
| this.classList.remove('need-password'); |
| this.classList.add('public-account'); |
| |
| this.nameElement.addEventListener('keydown', (function(e) { |
| if (e.keyIdentifier == 'Enter') { |
| this.parentNode.activatedPod = this; |
| // 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('.side-pane-learn-more'); |
| learnMore.addEventListener('click', this.handleLearnMoreEvent); |
| learnMore.addEventListener('keydown', this.handleLearnMoreEvent); |
| |
| this.enterButtonElement.addEventListener('click', (function(e) { |
| this.enterButtonElement.disabled = true; |
| chrome.send('launchPublicAccount', [this.user.username]); |
| }).bind(this)); |
| }, |
| |
| /** |
| * Updates the user pod element. |
| */ |
| update: function() { |
| UserPod.prototype.update.call(this); |
| this.querySelector('.side-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() { |
| this.expanded = true; |
| this.focusInput(); |
| return true; |
| }, |
| |
| /** @override */ |
| handleMouseDown_: function(e) { |
| if (this.parentNode.disabled) |
| return; |
| |
| this.parentNode.focusPod(this); |
| this.parentNode.activatedPod = this; |
| // Prevent default so that we don't trigger 'focus' event. |
| e.preventDefault(); |
| }, |
| |
| /** |
| * 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); |
| }, |
| }; |
| |
| /** |
| * 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.passwordElement.hidden) |
| return this.passwordElement; |
| else |
| return this.nameElement; |
| }, |
| |
| /** @override */ |
| decorate: function() { |
| UserPod.prototype.decorate.call(this); |
| }, |
| |
| /** @override */ |
| update: function() { |
| // TODO(noms): Use the actual profile avatar for local profiles once the |
| // new, non-pixellated avatars are available. |
| this.imageElement.src = this.user.emailAddress == '' ? |
| 'chrome://theme/IDR_USER_MANAGER_DEFAULT_AVATAR' : |
| this.user.userImage; |
| this.nameElement.textContent = this.user.displayName; |
| |
| var isLockedUser = this.user.needsSignin; |
| this.signinButtonElement.hidden = true; |
| this.lockedIndicatorElement.hidden = !isLockedUser; |
| this.passwordElement.hidden = !isLockedUser; |
| this.nameElement.hidden = isLockedUser; |
| |
| UserPod.prototype.updateActionBoxArea.call(this); |
| }, |
| |
| /** @override */ |
| focusInput: function() { |
| // For focused pods, display the name unless the pod is locked. |
| var isLockedUser = this.user.needsSignin; |
| this.signinButtonElement.hidden = true; |
| this.lockedIndicatorElement.hidden = !isLockedUser; |
| this.passwordElement.hidden = !isLockedUser; |
| this.nameElement.hidden = isLockedUser; |
| |
| // 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) { |
| // Always display the user's name for unfocused pods. |
| if (!takeFocus) |
| this.nameElement.hidden = false; |
| UserPod.prototype.reset.call(this, takeFocus); |
| }, |
| |
| /** @override */ |
| activate: function() { |
| Oobe.launchUser(this.user.emailAddress, this.user.displayName); |
| return true; |
| }, |
| |
| /** @override */ |
| handleMouseDown_: 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(); |
| }, |
| |
| /** @override */ |
| handleRemoveUserConfirmationClick_: function(e) { |
| chrome.send('removeUser', [this.user.profilePath]); |
| }, |
| }; |
| |
| /** |
| * 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, |
| |
| // Note: created only in decorate() ! |
| wallpaperLoader_: undefined, |
| |
| // Pods whose initial images haven't been loaded yet. |
| podsWithPendingImages_: [], |
| |
| /** @override */ |
| decorate: function() { |
| this.style.left = 0; |
| |
| // 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] |
| }; |
| this.wallpaperLoader_ = new login.WallpaperLoader(); |
| }, |
| |
| /** |
| * Returns all the pods in this pod row. |
| * @type {NodeList} |
| */ |
| get pods() { |
| return this.children; |
| }, |
| |
| /** |
| * Return true if user pod row has only single user pod in it. |
| * @type {boolean} |
| */ |
| get isSinglePod() { |
| return this.children.length == 1; |
| }, |
| |
| /** |
| * 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 {string} email User's email. |
| */ |
| createUserPod: function(user) { |
| var userPod; |
| if (user.isDesktopUser) |
| userPod = new DesktopUserPod({user: user}); |
| else if (user.publicAccount) |
| userPod = new PublicAccountUserPod({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. |
| * @param {boolean} animated Whether to use init animation. |
| */ |
| addUserPod: function(user, animated) { |
| var userPod = this.createUserPod(user); |
| if (animated) { |
| userPod.classList.add('init'); |
| userPod.nameElement.classList.add('init'); |
| } |
| |
| this.appendChild(userPod); |
| userPod.initialize(); |
| }, |
| |
| /** |
| * 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; |
| }, |
| |
| /** |
| * Start first time show animation. |
| */ |
| startInitAnimation: function() { |
| // Schedule init animation. |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| window.setTimeout(removeClass, 500 + i * 70, pod, 'init'); |
| window.setTimeout(removeClass, 700 + i * 70, pod.nameElement, 'init'); |
| } |
| }, |
| |
| /** |
| * Start login success animation. |
| */ |
| startAuthenticatedAnimation: function() { |
| var activated = this.indexOf_(this.activatedPod_); |
| if (activated == -1) |
| return; |
| |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (i < activated) |
| pod.classList.add('left'); |
| else if (i > activated) |
| pod.classList.add('right'); |
| else |
| pod.classList.add('zoom'); |
| } |
| }, |
| |
| /** |
| * Populates pod row with given existing users and start init animation. |
| * @param {array} users Array of existing user emails. |
| * @param {boolean} animated Whether to use init animation. |
| */ |
| loadPods: function(users, animated) { |
| // Clear existing pods. |
| this.innerHTML = ''; |
| this.focusedPod_ = undefined; |
| this.activatedPod_ = undefined; |
| this.lastFocusedPod_ = undefined; |
| |
| // Populate the pod row. |
| for (var i = 0; i < users.length; ++i) { |
| this.addUserPod(users[i], animated); |
| } |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| this.podsWithPendingImages_.push(pod); |
| } |
| // 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 columns = users.length < COLUMNS.length ? |
| COLUMNS[users.length] : COLUMNS[COLUMNS.length - 1]; |
| var rows = Math.floor((users.length - 1) / columns) + 1; |
| |
| // Cancel any pending resize operation. |
| this.removeEventListener('mouseout', this.deferredResizeListener_); |
| |
| // If this pod row is used in the desktop user manager, we need to |
| // force a resize, as it may be a background window which won't get a |
| // mouseout event for a while; the pods would be displayed incorrectly |
| // until then. |
| if (this.preselectedPod && this.preselectedPod.user.isDesktopUser) |
| this.resize_(columns, rows); |
| |
| if (!this.columns || !this.rows) { |
| // Set initial dimensions. |
| this.resize_(columns, rows); |
| } else if (columns != this.columns || rows != this.rows) { |
| // Defer the resize until mouse cursor leaves the pod row. |
| this.deferredResizeListener_ = function(e) { |
| if (!findAncestorByClass(e.toElement, 'podrow')) { |
| this.resize_(columns, rows); |
| } |
| }.bind(this); |
| this.addEventListener('mouseout', this.deferredResizeListener_); |
| } |
| |
| this.focusPod(this.preselectedPod); |
| }, |
| |
| /** |
| * Resizes the pod row and cancel any pending resize operations. |
| * @param {number} columns Number of columns. |
| * @param {number} rows Number of rows. |
| * @private |
| */ |
| resize_: function(columns, rows) { |
| this.removeEventListener('mouseout', this.deferredResizeListener_); |
| this.columns = columns; |
| this.rows = rows; |
| if (this.parentNode == Oobe.getInstance().currentScreen) { |
| Oobe.getInstance().updateScreenSize(this.parentNode); |
| } |
| }, |
| |
| /** |
| * Number of columns. |
| * @type {?number} |
| */ |
| set columns(columns) { |
| // Cannot use 'columns' here. |
| this.setAttribute('ncolumns', columns); |
| }, |
| get columns() { |
| return this.getAttribute('ncolumns'); |
| }, |
| |
| /** |
| * Number of rows. |
| * @type {?number} |
| */ |
| set rows(rows) { |
| // Cannot use 'rows' here. |
| this.setAttribute('nrows', rows); |
| }, |
| get rows() { |
| return 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) { |
| 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; |
| |
| this.wallpaperLoader_.reset(); |
| for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| if (!this.isSinglePod) { |
| pod.isActionBoxMenuActive = false; |
| } |
| if (pod != podToFocus) { |
| pod.isActionBoxMenuHovered = false; |
| pod.classList.remove('focused'); |
| 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'); |
| podToFocus.reset(true); // Reset and give focus. |
| chrome.send('focusPod', [podToFocus.user.emailAddress]); |
| |
| this.wallpaperLoader_.scheduleLoad(podToFocus.user.emailAddress); |
| this.firstShown_ = false; |
| this.lastFocusedPod_ = podToFocus; |
| } |
| this.insideFocusPod_ = false; |
| this.keyboardActivated_ = false; |
| }, |
| |
| /** |
| * Resets wallpaper to the last active user's wallpaper, if any. |
| */ |
| loadLastWallpaper: function() { |
| if (this.lastFocusedPod_) |
| this.wallpaperLoader_.scheduleLoad(this.lastFocusedPod_.user.username); |
| }, |
| |
| /** |
| * Handles 'onWallpaperLoaded' event. Recalculates statistics and |
| * [re]schedules next wallpaper load. |
| */ |
| onWallpaperLoaded: function(email) { |
| this.wallpaperLoader_.onWallpaperLoaded(email); |
| }, |
| |
| /** |
| * Returns the currently activated pod. |
| * @type {UserPod} |
| */ |
| get activatedPod() { |
| return this.activatedPod_; |
| }, |
| set activatedPod(pod) { |
| if (pod && pod.activate()) |
| 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 lockedPod = this.lockedPod; |
| var preselectedPod = PRESELECT_FIRST_POD ? |
| lockedPod || this.pods[0] : lockedPod; |
| return preselectedPod; |
| }, |
| |
| /** |
| * 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_.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(); |
| }, |
| |
| /** |
| * Resets OAuth token status (invalidates it). |
| * @param {string} username User for which to reset the status. |
| */ |
| resetUserOAuthTokenStatus: function(username) { |
| var pod = this.getPodWithUsername_(username); |
| if (pod) { |
| pod.user.oauthTokenStatus = OAuthTokenStatus.INVALID_OLD; |
| pod.update(); |
| } else { |
| console.log('Failed to update Gaia state for: ' + username); |
| } |
| }, |
| |
| /** |
| * 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.isSinglePod) { |
| this.focusPod(); |
| } |
| |
| if (pod) |
| pod.isActionBoxMenuHovered = true; |
| |
| // Return focus back to single pod. |
| if (this.isSinglePod) { |
| this.focusPod(this.focusedPod_, true /* force */); |
| if (!pod) |
| this.focusedPod_.isActionBoxMenuHovered = false; |
| } |
| |
| // Also stop event propagation. |
| if (pod && e.target == pod.imageElement) |
| e.stopPropagation(); |
| }, |
| |
| /** |
| * 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')) |
| e.target.focusInput(); |
| 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); |
| 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.isSinglePod) |
| this.focusPod(); |
| }, |
| |
| /** |
| * 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_) { |
| this.activatedPod = this.focusedPod_; |
| e.stopPropagation(); |
| } |
| break; |
| case 'U+001B': // Esc |
| if (!this.isSinglePod) |
| this.focusPod(); |
| break; |
| } |
| }, |
| |
| /** |
| * Called right after the pod row is shown. |
| */ |
| handleAfterShow: function() { |
| // 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) { |
| if (e.target == focusedPod) { |
| focusedPod.removeEventListener('webkitTransitionEnd', f); |
| focusedPod.reset(true); |
| // Notify screen that it is ready. |
| screen.onShow(); |
| self.wallpaperLoader_.scheduleLoad(focusedPod.user.username); |
| } |
| }); |
| // Guard timer for 1 second -- it would conver all possible animations. |
| ensureTransitionEndEvent(focusedPod, 1000); |
| } |
| }, |
| |
| /** |
| * Called right before the pod row is shown. |
| */ |
| handleBeforeShow: function() { |
| 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; |
| }, |
| |
| /** |
| * 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'); |
| } |
| } |
| }; |
| |
| return { |
| PodRow: PodRow |
| }; |
| }); |