1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview User pod row implementation.
9 cr.define('login', function() {
11 * Number of displayed columns depending on user pod count.
12 * @type {Array.<number>}
15 var COLUMNS = [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6];
18 * Mapping between number of columns in pod-row and margin between user pods
20 * @type {Array.<number>}
23 var MARGIN_BY_COLUMNS = [undefined, 40, 40, 40, 40, 40, 12];
26 * Mapping between number of columns in the desktop pod-row and margin
27 * between user pods for such layout.
28 * @type {Array.<number>}
31 var DESKTOP_MARGIN_BY_COLUMNS = [undefined, 15, 15, 15, 15, 15, 15];
34 * Maximal number of columns currently supported by pod-row.
38 var MAX_NUMBER_OF_COLUMNS = 6;
41 * Maximal number of rows if sign-in banner is displayed alonside.
45 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2;
48 * Variables used for pod placement processing. Width and height should be
49 * synced with computed CSS sizes of pods.
52 var PUBLIC_EXPANDED_BASIC_WIDTH = 500;
53 var PUBLIC_EXPANDED_ADVANCED_WIDTH = 610;
54 var CROS_POD_HEIGHT = 213;
55 var DESKTOP_POD_HEIGHT = 226;
56 var POD_ROW_PADDING = 10;
57 var DESKTOP_ROW_PADDING = 15;
58 var CUSTOM_ICON_CONTAINER_SIZE = 40;
61 * Minimal padding between user pod and virtual keyboard.
65 var USER_POD_KEYBOARD_MIN_PADDING = 20;
68 * Maximum time for which the pod row remains hidden until all user images
73 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000;
76 * Public session help topic identifier.
80 var HELP_TOPIC_PUBLIC_SESSION = 3041033;
83 * Tab order for user pods. Update these when adding new controls.
87 var UserPodTabOrder = {
88 POD_INPUT: 1, // Password input fields (and whole pods themselves).
89 POD_CUSTOM_ICON: 2, // Pod custom icon next to passwrod input field.
90 HEADER_BAR: 3, // Buttons on the header bar (Shutdown, Add User).
91 ACTION_BOX: 4, // Action box buttons.
92 PAD_MENU_ITEM: 5 // User pad menu items (Remove this user).
96 * Supported authentication types. Keep in sync with the enum in
97 * chrome/browser/signin/screenlock_bridge.h
106 EXPAND_THEN_USER_CLICK: 4,
107 FORCE_OFFLINE_PASSWORD: 5
111 * Names of authentication types.
113 var AUTH_TYPE_NAMES = {
114 0: 'offlinePassword',
118 4: 'expandThenUserClick',
119 5: 'forceOfflinePassword'
122 // Focus and tab order are organized as follows:
124 // (1) all user pods have tab index 1 so they are traversed first;
125 // (2) when a user pod is activated, its tab index is set to -1 and its
126 // main input field gets focus and tab index 1;
127 // (3) if user pod custom icon is interactive, it has tab index 2 so it
128 // follows the input.
129 // (4) buttons on the header bar have tab index 3 so they follow the custom
130 // icon, or user pod if custom icon is not interactive;
131 // (5) Action box buttons have tab index 4 and follow header bar buttons;
132 // (6) lastly, focus jumps to the Status Area and back to user pods.
134 // 'Focus' event is handled by a capture handler for the whole document
135 // and in some cases 'mousedown' event handlers are used instead of 'click'
136 // handlers where it's necessary to prevent 'focus' event from being fired.
139 * Helper function to remove a class from given element.
140 * @param {!HTMLElement} el Element whose class list to change.
141 * @param {string} cl Class to remove.
143 function removeClass(el, cl) {
144 el.classList.remove(cl);
148 * Creates a user pod.
150 * @extends {HTMLDivElement}
152 var UserPod = cr.ui.define(function() {
153 var node = $('user-pod-template').cloneNode(true);
154 node.removeAttribute('id');
159 * Stops event propagation from the any user pod child element.
160 * @param {Event} e Event to handle.
162 function stopEventPropagation(e) {
163 // Prevent default so that we don't trigger a 'focus' event.
169 * Creates an element for custom icon shown in a user pod next to the input
172 * @extends {HTMLDivElement}
174 var UserPodCustomIcon = cr.ui.define(function() {
175 var node = document.createElement('div');
176 node.classList.add('custom-icon-container');
179 // Create the actual icon element and add it as a child to the container.
180 var iconNode = document.createElement('div');
181 iconNode.classList.add('custom-icon');
182 node.appendChild(iconNode);
186 UserPodCustomIcon.prototype = {
187 __proto__: HTMLDivElement.prototype,
204 * Tooltip to be shown when the user hovers over the icon. The icon
205 * properties may be set so the tooltip is shown automatically when the icon
206 * is updated. The tooltip is shown in a bubble attached to the icon
214 * Whether the tooltip is shown and the user is hovering over the icon.
218 tooltipActive_: false,
221 * Whether the icon has been shown as a result of |autoshow| parameter begin
222 * set rather than user hovering over the icon.
223 * If this is set, the tooltip will not be removed when the mouse leaves the
228 tooltipAutoshown_: false,
231 * A reference to the timeout for showing tooltip after mouse enters the
236 showTooltipTimeout_: null,
239 * If animation is set, the current horizontal background offset for the
244 lastAnimationOffset_: 0,
247 * The reference to interval for progressing the animation.
251 animationInterval_: null,
254 * The width of the resource that contains representations for different
259 animationResourceSize_: 0,
262 * When {@code fadeOut} is called, the element gets hidden using fadeout
263 * animation. This is reference to the listener for transition end added to
264 * the icon element while it's fading out.
265 * @type {?function(Event)}
268 hideTransitionListener_: null,
271 * Callback for click and 'Enter' key events that gets set if the icon is
273 * @type {?function()}
276 actionHandler_: null,
279 decorate: function() {
280 this.iconElement.addEventListener('mouseover',
281 this.showTooltipSoon_.bind(this));
282 this.iconElement.addEventListener('mouseout',
283 this.hideTooltip_.bind(this, false));
284 this.iconElement.addEventListener('mousedown',
285 this.hideTooltip_.bind(this, false));
286 this.iconElement.addEventListener('click',
287 this.handleClick_.bind(this));
288 this.iconElement.addEventListener('keydown',
289 this.handleKeyDown_.bind(this));
291 // When the icon is focused using mouse, there should be no outline shown.
292 // Preventing default mousedown event accomplishes this.
293 this.iconElement.addEventListener('mousedown', function(e) {
299 * Getter for the icon element's div.
300 * @return {HTMLDivElement}
303 return this.querySelector('.custom-icon');
307 * Set the icon's background image as image set with different
308 * representations for available screen scale factors.
309 * @param {!{scale1x: string, scale2x: string}} icon The icon
312 setIconAsImageSet: function(icon) {
313 this.iconElement.style.backgroundImage =
314 '-webkit-image-set(' +
315 'url(' + icon.scale1x + ') 1x,' +
316 'url(' + icon.scale2x + ') 2x)';
320 * Sets the icon background image to a chrome://theme URL.
321 * @param {!string} iconUrl The icon's background image URL.
323 setIconAsResourceUrl: function(iconUrl) {
324 this.iconElement.style.backgroundImage =
325 '-webkit-image-set(' +
326 'url(' + iconUrl + '@1x) 1x,' +
327 'url(' + iconUrl + '@2x) 2x)';
334 this.resetHideTransitionState_();
339 * Hides the icon using a fade-out animation.
341 fadeOut: function() {
342 // The icon is already being hidden.
343 if (this.iconElement.classList.contains('faded'))
346 this.hideTooltip_(true);
347 this.iconElement.classList.add('faded');
348 this.hideTransitionListener_ = this.hide.bind(this);
349 this.iconElement.addEventListener('webkitTransitionEnd',
350 this.hideTransitionListener_);
351 ensureTransitionEndEvent(this.iconElement, 200);
355 * Sets the icon size element size.
356 * @param {!{width: number, height: number}} size The icon size.
358 setSize: function(size) {
359 this.height_ = size.height < CUSTOM_ICON_CONTAINER_SIZE ?
360 size.height : CUSTOM_ICON_COTAINER_SIZE;
361 this.iconElement.style.height = this.height_ + 'px';
363 this.width_ = size.width < CUSTOM_ICON_CONTAINER_SIZE ?
364 size.width : CUSTOM_ICON_COTAINER_SIZE;
365 this.iconElement.style.width = this.width_ + 'px';
366 this.style.width = this.width_ + 'px';
370 * Sets the icon opacity.
371 * @param {number} opacity The icon opacity in [0-100] scale.
373 setOpacity: function(opacity) {
375 this.style.opacity = 1;
376 } else if (opacity < 0) {
377 this.style.opacity = 0;
379 this.style.opacity = opacity / 100;
384 * Updates the icon tooltip. If {@code autoshow} parameter is set the
385 * tooltip is immediatelly shown. If tooltip text is not set, the method
386 * ensures the tooltip gets hidden. If tooltip is shown prior to this call,
387 * it remains shown, but the tooltip text is updated.
388 * @param {!{text: string, autoshow: boolean}} tooltip The tooltip
391 setTooltip: function(tooltip) {
392 if (this.tooltip_ == tooltip.text && !tooltip.autoshow)
394 this.tooltip_ = tooltip.text;
396 // If tooltip is already shown, just update the text.
397 if (tooltip.text && this.tooltipActive_ && !$('bubble').hidden) {
398 // If both previous and the new tooltip are supposed to be shown
399 // automatically, keep the autoshown flag.
400 var markAutoshown = this.tooltipAutoshown_ && tooltip.autoshow;
401 this.hideTooltip_(true);
403 this.tooltipAutoshown_ = markAutoshown;
407 // If autoshow flag is set, make sure the tooltip gets shown.
408 if (tooltip.text && tooltip.autoshow) {
409 this.hideTooltip_(true);
411 this.tooltipAutoshown_ = true;
412 // Reset the tooltip active flag, which gets set in |showTooltip_|.
413 this.tooltipActive_ = false;
417 this.hideTooltip_(true);
420 this.iconElement.setAttribute('aria-lablel', this.tooltip_);
424 * Sets the icon animation parameter and starts the animation.
425 * The animation is set using the resource containing all animation frames
426 * concatenated horizontally. The animator offsets the background image in
428 * @param {?{resourceWidth: number, frameLengthMs: number}} animation
429 * |resourceWidth|: Total width for the resource containing the
431 * |frameLengthMs|: Time interval for which a single animation frame is
434 setAnimation: function(animation) {
435 if (this.animationInterval_)
436 clearInterval(this.animationInterval_);
437 this.iconElement.style.backgroundPosition = 'center';
440 this.lastAnimationOffset_ = 0;
441 this.animationResourceSize_ = animation.resourceWidth;
442 this.animationInterval_ = setInterval(this.progressAnimation_.bind(this),
443 animation.frameLengthMs);
447 * Sets up icon tabIndex attribute and handler for click and 'Enter' key
449 * @param {?function()} callback If icon should be interactive, the
450 * function to get called on click and 'Enter' key down events. Should
451 * be null to make the icon non interactive.
453 setInteractive: function(callback) {
454 // Update tabIndex property if needed.
455 if (!!this.actionHandler_ != !!callback) {
457 this.iconElement.setAttribute('tabIndex',
458 UserPodTabOrder.POD_CUSTOM_ICON);
460 this.iconElement.removeAttribute('tabIndex');
464 // Set the new action handler.
465 this.actionHandler_ = callback;
469 * Hides the icon. Makes sure the tooltip is hidden and animation reset.
472 this.hideTooltip_(true);
474 this.setAnimation(null);
475 this.setInteractive(null);
476 this.resetHideTransitionState_();
480 * Ensures the icon's transition state potentially set by a call to
481 * {@code fadeOut} is cleared.
484 resetHideTransitionState_: function() {
485 if (this.hideTransitionListener_) {
486 this.iconElement.removeEventListener('webkitTransitionEnd',
487 this.hideTransitionListener_);
488 this.hideTransitionListener_ = null;
490 this.iconElement.classList.toggle('faded', false);
494 * Handles click event on the icon element. No-op if
495 * {@code this.actionHandler_} is not set.
496 * @param {Event} e The click event.
499 handleClick_: function(e) {
500 if (!this.actionHandler_)
502 this.actionHandler_();
503 stopEventPropagation(e);
507 * Handles key down event on the icon element. Only 'Enter' key is handled.
508 * No-op if {@code this.actionHandler_} is not set.
509 * @param {Event} e The key down event.
512 handleKeyDown_: function(e) {
513 if (!this.actionHandler_ || e.keyIdentifier != 'Enter')
515 this.actionHandler_(e);
516 stopEventPropagation(e);
520 * Called when mouse enters the icon. It sets timeout for showing the
524 showTooltipSoon_: function() {
525 if (this.showTooltipTimeout_ || this.tooltipActive_)
527 this.showTooltipTimeout_ =
528 setTimeout(this.showTooltip_.bind(this), 1000);
532 * Shows the current tooltip if one is set.
535 showTooltip_: function() {
536 if (this.hidden || !this.tooltip_ || this.tooltipActive_)
539 // If autoshown bubble got hidden, clear the autoshown flag.
540 if ($('bubble').hidden && this.tooltipAutoshown_)
541 this.tooltipAutoshown_ = false;
543 // Show the tooltip bubble.
544 var bubbleContent = document.createElement('div');
545 bubbleContent.textContent = this.tooltip_;
547 /** @const */ var BUBBLE_OFFSET = CUSTOM_ICON_CONTAINER_SIZE / 2;
548 /** @const */ var BUBBLE_PADDING = 8;
549 $('bubble').showContentForElement(this,
550 cr.ui.Bubble.Attachment.RIGHT,
554 this.ensureTooltipTimeoutCleared_();
555 this.tooltipActive_ = true;
559 * Hides the tooltip. If the current tooltip was automatically shown, it
560 * will be hidden only if |force| is set.
561 * @param {boolean} Whether the tooltip should be hidden if it got shown
562 * because autoshow flag was set.
565 hideTooltip_: function(force) {
566 this.tooltipActive_ = false;
567 this.ensureTooltipTimeoutCleared_();
568 if (!force && this.tooltipAutoshown_)
570 $('bubble').hideForElement(this);
571 this.tooltipAutoshown_ = false;
572 this.iconElement.removeAttribute('aria-label');
576 * Clears timaout for showing the tooltip if it's set.
579 ensureTooltipTimeoutCleared_: function() {
580 if (this.showTooltipTimeout_) {
581 clearTimeout(this.showTooltipTimeout_);
582 this.showTooltipTimeout_ = null;
587 * Horizontally offsets the animated icon's background for a single icon
591 progressAnimation_: function() {
592 this.lastAnimationOffset_ += this.width_;
593 if (this.lastAnimationOffset_ >= this.animationResourceSize_)
594 this.lastAnimationOffset_ = 0;
595 this.iconElement.style.backgroundPosition =
596 '-' + this.lastAnimationOffset_ + 'px center';
601 * Unique salt added to user image URLs to prevent caching. Dictionary with
602 * user names as keys.
605 UserPod.userImageSalt_ = {};
607 UserPod.prototype = {
608 __proto__: HTMLDivElement.prototype,
611 decorate: function() {
612 this.tabIndex = UserPodTabOrder.POD_INPUT;
613 this.actionBoxAreaElement.tabIndex = UserPodTabOrder.ACTION_BOX;
615 this.addEventListener('keydown', this.handlePodKeyDown_.bind(this));
616 this.addEventListener('click', this.handleClickOnPod_.bind(this));
618 this.signinButtonElement.addEventListener('click',
619 this.activate.bind(this));
621 this.actionBoxAreaElement.addEventListener('mousedown',
622 stopEventPropagation);
623 this.actionBoxAreaElement.addEventListener('click',
624 this.handleActionAreaButtonClick_.bind(this));
625 this.actionBoxAreaElement.addEventListener('keydown',
626 this.handleActionAreaButtonKeyDown_.bind(this));
628 this.actionBoxMenuRemoveElement.addEventListener('click',
629 this.handleRemoveCommandClick_.bind(this));
630 this.actionBoxMenuRemoveElement.addEventListener('keydown',
631 this.handleRemoveCommandKeyDown_.bind(this));
632 this.actionBoxMenuRemoveElement.addEventListener('blur',
633 this.handleRemoveCommandBlur_.bind(this));
634 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
636 this.handleRemoveUserConfirmationClick_.bind(this));
637 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
639 this.handleRemoveUserConfirmationKeyDown_.bind(this));
641 var customIcon = this.customIconElement;
642 customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon);
646 * Initializes the pod after its properties set and added to a pod row.
648 initialize: function() {
649 this.passwordElement.addEventListener('keydown',
650 this.parentNode.handleKeyDown.bind(this.parentNode));
651 this.passwordElement.addEventListener('keypress',
652 this.handlePasswordKeyPress_.bind(this));
654 this.imageElement.addEventListener('load',
655 this.parentNode.handlePodImageLoad.bind(this.parentNode, this));
657 var initialAuthType = this.user.initialAuthType ||
658 AUTH_TYPE.OFFLINE_PASSWORD;
659 this.setAuthType(initialAuthType, null);
663 * Resets tab order for pod elements to its initial state.
665 resetTabOrder: function() {
666 // Note: the |mainInput| can be the pod itself.
667 this.mainInput.tabIndex = -1;
668 this.tabIndex = UserPodTabOrder.POD_INPUT;
672 * Handles keypress event (i.e. any textual input) on password input.
673 * @param {Event} e Keypress Event object.
676 handlePasswordKeyPress_: function(e) {
677 // When tabbing from the system tray a tab key press is received. Suppress
678 // this so as not to type a tab character into the password field.
679 if (e.keyCode == 9) {
686 * Top edge margin number of pixels.
690 this.style.top = cr.ui.toCssPx(top);
694 * Top edge margin number of pixels.
697 return parseInt(this.style.top);
701 * Left edge margin number of pixels.
705 this.style.left = cr.ui.toCssPx(left);
709 * Left edge margin number of pixels.
712 return parseInt(this.style.left);
716 * Height number of pixels.
719 return this.offsetHeight;
723 * Gets image element.
724 * @type {!HTMLImageElement}
727 return this.querySelector('.user-image');
732 * @type {!HTMLDivElement}
735 return this.querySelector('.name');
739 * Gets the container holding the password field.
740 * @type {!HTMLInputElement}
742 get passwordEntryContainerElement() {
743 return this.querySelector('.password-entry-container');
747 * Gets password field.
748 * @type {!HTMLInputElement}
750 get passwordElement() {
751 return this.querySelector('.password');
755 * Gets the password label, which is used to show a message where the
756 * password field is normally.
757 * @type {!HTMLInputElement}
759 get passwordLabelElement() {
760 return this.querySelector('.password-label');
764 * Gets user sign in button.
765 * @type {!HTMLButtonElement}
767 get signinButtonElement() {
768 return this.querySelector('.signin-button');
772 * Gets the container holding the launch app button.
773 * @type {!HTMLButtonElement}
775 get launchAppButtonContainerElement() {
776 return this.querySelector('.launch-app-button-container');
780 * Gets launch app button.
781 * @type {!HTMLButtonElement}
783 get launchAppButtonElement() {
784 return this.querySelector('.launch-app-button');
788 * Gets action box area.
789 * @type {!HTMLInputElement}
791 get actionBoxAreaElement() {
792 return this.querySelector('.action-box-area');
796 * Gets user type icon area.
797 * @type {!HTMLDivElement}
799 get userTypeIconAreaElement() {
800 return this.querySelector('.user-type-icon-area');
804 * Gets user type bubble like multi-profiles policy restriction message.
805 * @type {!HTMLDivElement}
807 get userTypeBubbleElement() {
808 return this.querySelector('.user-type-bubble');
812 * Gets action box menu.
813 * @type {!HTMLInputElement}
815 get actionBoxMenu() {
816 return this.querySelector('.action-box-menu');
820 * Gets action box menu title, user name item.
821 * @type {!HTMLInputElement}
823 get actionBoxMenuTitleNameElement() {
824 return this.querySelector('.action-box-menu-title-name');
828 * Gets action box menu title, user email item.
829 * @type {!HTMLInputElement}
831 get actionBoxMenuTitleEmailElement() {
832 return this.querySelector('.action-box-menu-title-email');
836 * Gets action box menu, remove user command item.
837 * @type {!HTMLInputElement}
839 get actionBoxMenuCommandElement() {
840 return this.querySelector('.action-box-menu-remove-command');
844 * Gets action box menu, remove user command item div.
845 * @type {!HTMLInputElement}
847 get actionBoxMenuRemoveElement() {
848 return this.querySelector('.action-box-menu-remove');
852 * Gets action box menu, remove user warning text div.
853 * @type {!HTMLInputElement}
855 get actionBoxRemoveUserWarningTextElement() {
856 return this.querySelector('.action-box-remove-user-warning-text');
860 * Gets action box menu, remove supervised user warning text div.
861 * @type {!HTMLInputElement}
863 get actionBoxRemoveSupervisedUserWarningTextElement() {
864 return this.querySelector(
865 '.action-box-remove-supervised-user-warning-text');
869 * Gets action box menu, remove user command item div.
870 * @type {!HTMLInputElement}
872 get actionBoxRemoveUserWarningElement() {
873 return this.querySelector('.action-box-remove-user-warning');
877 * Gets action box menu, remove user command item div.
878 * @type {!HTMLInputElement}
880 get actionBoxRemoveUserWarningButtonElement() {
881 return this.querySelector('.remove-warning-button');
885 * Gets the custom icon. This icon is normally hidden, but can be shown
886 * using the chrome.screenlockPrivate API.
887 * @type {!HTMLDivElement}
889 get customIconElement() {
890 return this.querySelector('.custom-icon-container');
894 * Updates the user pod element.
897 this.imageElement.src = 'chrome://userimage/' + this.user.username +
898 '?id=' + UserPod.userImageSalt_[this.user.username];
900 this.nameElement.textContent = this.user_.displayName;
901 this.classList.toggle('signed-in', this.user_.signedIn);
903 if (this.isAuthTypeUserClick)
904 this.passwordLabelElement.textContent = this.authValue;
906 this.updateActionBoxArea();
908 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
909 'passwordFieldAccessibleName', this.user_.emailAddress));
911 this.customizeUserPodPerUserType();
914 updateActionBoxArea: function() {
915 if (this.user_.publicAccount || this.user_.isApp) {
916 this.actionBoxAreaElement.hidden = true;
920 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
922 this.actionBoxAreaElement.setAttribute(
923 'aria-label', loadTimeData.getStringF(
924 'podMenuButtonAccessibleName', this.user_.emailAddress));
925 this.actionBoxMenuRemoveElement.setAttribute(
926 'aria-label', loadTimeData.getString(
927 'podMenuRemoveItemAccessibleName'));
928 this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ?
929 loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) :
930 this.user_.displayName;
931 this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress;
932 this.actionBoxMenuTitleEmailElement.hidden = this.user_.supervisedUser;
934 this.actionBoxMenuCommandElement.textContent =
935 loadTimeData.getString('removeUser');
938 customizeUserPodPerUserType: function() {
939 if (this.user_.supervisedUser && !this.user_.isDesktopUser) {
940 this.setUserPodIconType('supervised');
941 } else if (this.multiProfilesPolicyApplied) {
942 // Mark user pod as not focusable which in addition to the grayed out
943 // filter makes it look in disabled state.
944 this.classList.add('multiprofiles-policy-applied');
945 this.setUserPodIconType('policy');
947 if (this.user.multiProfilesPolicy == 'primary-only')
948 this.querySelector('.mp-policy-primary-only-msg').hidden = false;
949 else if (this.user.multiProfilesPolicy == 'owner-primary-only')
950 this.querySelector('.mp-owner-primary-only-msg').hidden = false;
952 this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
953 } else if (this.user_.isApp) {
954 this.setUserPodIconType('app');
958 setUserPodIconType: function(userTypeClass) {
959 this.userTypeIconAreaElement.classList.add(userTypeClass);
960 this.userTypeIconAreaElement.hidden = false;
964 * The user that this pod represents.
972 this.user_ = userDict;
977 * Returns true if multi-profiles sign in is currently active and this
978 * user pod is restricted per policy.
981 get multiProfilesPolicyApplied() {
982 var isMultiProfilesUI =
983 (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
984 return isMultiProfilesUI && !this.user_.isMultiProfilesAllowed;
988 * Gets main input element.
989 * @type {(HTMLButtonElement|HTMLInputElement)}
992 if (this.isAuthTypePassword) {
993 return this.passwordElement;
994 } else if (this.isAuthTypeOnlineSignIn) {
995 return this.signinButtonElement;
996 } else if (this.isAuthTypeUserClick) {
1002 * Whether action box button is in active state.
1005 get isActionBoxMenuActive() {
1006 return this.actionBoxAreaElement.classList.contains('active');
1008 set isActionBoxMenuActive(active) {
1009 if (active == this.isActionBoxMenuActive)
1013 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
1014 this.actionBoxRemoveUserWarningElement.hidden = true;
1016 // Clear focus first if another pod is focused.
1017 if (!this.parentNode.isFocused(this)) {
1018 this.parentNode.focusPod(undefined, true);
1019 this.actionBoxAreaElement.focus();
1022 // Hide user-type-bubble.
1023 this.userTypeBubbleElement.classList.remove('bubble-shown');
1025 this.actionBoxAreaElement.classList.add('active');
1027 this.actionBoxAreaElement.classList.remove('active');
1028 this.actionBoxAreaElement.classList.remove('menu-moved-up');
1029 this.actionBoxMenu.classList.remove('menu-moved-up');
1034 * Whether action box button is in hovered state.
1037 get isActionBoxMenuHovered() {
1038 return this.actionBoxAreaElement.classList.contains('hovered');
1040 set isActionBoxMenuHovered(hovered) {
1041 if (hovered == this.isActionBoxMenuHovered)
1045 this.actionBoxAreaElement.classList.add('hovered');
1046 this.classList.add('hovered');
1048 if (this.multiProfilesPolicyApplied)
1049 this.userTypeBubbleElement.classList.remove('bubble-shown');
1050 this.actionBoxAreaElement.classList.remove('hovered');
1051 this.classList.remove('hovered');
1056 * Set the authentication type for the pod.
1057 * @param {number} An auth type value defined in the AUTH_TYPE enum.
1058 * @param {string} authValue The initial value used for the auth type.
1060 setAuthType: function(authType, authValue) {
1061 this.authType_ = authType;
1062 this.authValue_ = authValue;
1063 this.setAttribute('auth-type', AUTH_TYPE_NAMES[this.authType_]);
1065 this.reset(this.parentNode.isFocused(this));
1069 * The auth type of the user pod. This value is one of the enum
1070 * values in AUTH_TYPE.
1074 return this.authType_;
1078 * The initial value used for the pod's authentication type.
1079 * eg. a prepopulated password input when using password authentication.
1082 return this.authValue_;
1086 * True if the the user pod uses a password to authenticate.
1089 get isAuthTypePassword() {
1090 return this.authType_ == AUTH_TYPE.OFFLINE_PASSWORD ||
1091 this.authType_ == AUTH_TYPE.FORCE_OFFLINE_PASSWORD;
1095 * True if the the user pod uses a user click to authenticate.
1098 get isAuthTypeUserClick() {
1099 return this.authType_ == AUTH_TYPE.USER_CLICK;
1103 * True if the the user pod uses a online sign in to authenticate.
1106 get isAuthTypeOnlineSignIn() {
1107 return this.authType_ == AUTH_TYPE.ONLINE_SIGN_IN;
1111 * Updates the image element of the user.
1113 updateUserImage: function() {
1114 UserPod.userImageSalt_[this.user.username] = new Date().getTime();
1119 * Focuses on input element.
1121 focusInput: function() {
1122 // Move tabIndex from the whole pod to the main input.
1123 // Note: the |mainInput| can be the pod itself.
1125 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1126 this.mainInput.focus();
1130 * Activates the pod.
1131 * @param {Event} e Event object.
1132 * @return {boolean} True if activated successfully.
1134 activate: function(e) {
1135 if (this.isAuthTypeOnlineSignIn) {
1136 this.showSigninUI();
1137 } else if (this.isAuthTypeUserClick) {
1138 Oobe.disableSigninUI();
1139 chrome.send('attemptUnlock', [this.user.username]);
1140 } else if (this.isAuthTypePassword) {
1141 if (!this.passwordElement.value)
1143 Oobe.disableSigninUI();
1144 chrome.send('authenticateUser',
1145 [this.user.username, this.passwordElement.value]);
1147 console.error('Activating user pod with invalid authentication type: ' +
1154 showSupervisedUserSigninWarning: function() {
1155 // Supervised user token has been invalidated.
1156 // Make sure that pod is focused i.e. "Sign in" button is seen.
1157 this.parentNode.focusPod(this);
1159 var error = document.createElement('div');
1160 var messageDiv = document.createElement('div');
1161 messageDiv.className = 'error-message-bubble';
1162 messageDiv.textContent =
1163 loadTimeData.getString('supervisedUserExpiredTokenWarning');
1164 error.appendChild(messageDiv);
1166 $('bubble').showContentForElement(
1167 this.signinButtonElement,
1168 cr.ui.Bubble.Attachment.TOP,
1170 this.signinButtonElement.offsetWidth / 2,
1172 // Move warning bubble up if it overlaps the shelf.
1174 cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping($('bubble'));
1175 if (maxHeight < $('bubble').offsetHeight) {
1176 $('bubble').showContentForElement(
1177 this.signinButtonElement,
1178 cr.ui.Bubble.Attachment.BOTTOM,
1180 this.signinButtonElement.offsetWidth / 2,
1186 * Shows signin UI for this user.
1188 showSigninUI: function() {
1189 if (this.user.supervisedUser && !this.user.isDesktopUser) {
1190 this.showSupervisedUserSigninWarning();
1192 // Special case for multi-profiles sign in. We show users even if they
1193 // are not allowed per policy. Restrict those users from starting GAIA.
1194 if (this.multiProfilesPolicyApplied)
1197 this.parentNode.showSigninUI(this.user.emailAddress);
1202 * Resets the input field and updates the tab order of pod controls.
1203 * @param {boolean} takeFocus If true, input field takes focus.
1205 reset: function(takeFocus) {
1206 this.passwordElement.value = '';
1208 if (!this.multiProfilesPolicyApplied)
1209 this.focusInput(); // This will set a custom tab order.
1212 this.resetTabOrder();
1216 * Removes a user using the correct identifier based on user type.
1217 * @param {Object} user User to be removed.
1219 removeUser: function(user) {
1220 chrome.send('removeUser',
1221 [user.isDesktopUser ? user.profilePath : user.username]);
1225 * Handles a click event on action area button.
1226 * @param {Event} e Click event.
1228 handleActionAreaButtonClick_: function(e) {
1229 if (this.parentNode.disabled)
1231 this.isActionBoxMenuActive = !this.isActionBoxMenuActive;
1232 e.stopPropagation();
1236 * Handles a keydown event on action area button.
1237 * @param {Event} e KeyDown event.
1239 handleActionAreaButtonKeyDown_: function(e) {
1242 switch (e.keyIdentifier) {
1244 case 'U+0020': // Space
1245 if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive)
1246 this.isActionBoxMenuActive = true;
1247 e.stopPropagation();
1251 if (this.isActionBoxMenuActive) {
1252 this.actionBoxMenuRemoveElement.tabIndex =
1253 UserPodTabOrder.PAD_MENU_ITEM;
1254 this.actionBoxMenuRemoveElement.focus();
1256 e.stopPropagation();
1258 case 'U+001B': // Esc
1259 this.isActionBoxMenuActive = false;
1260 e.stopPropagation();
1262 case 'U+0009': // Tab
1263 if (!this.parentNode.alwaysFocusSinglePod)
1264 this.parentNode.focusPod();
1266 this.isActionBoxMenuActive = false;
1272 * Handles a click event on remove user command.
1273 * @param {Event} e Click event.
1275 handleRemoveCommandClick_: function(e) {
1276 if (this.user.supervisedUser || this.user.isDesktopUser) {
1277 this.showRemoveWarning_();
1280 if (this.isActionBoxMenuActive)
1281 chrome.send('removeUser', [this.user.username]);
1285 * Shows remove user warning. Used for supervised users on CrOS, and for all
1288 showRemoveWarning_: function() {
1289 this.actionBoxMenuRemoveElement.hidden = true;
1290 this.actionBoxRemoveUserWarningElement.hidden = false;
1291 this.actionBoxRemoveUserWarningButtonElement.focus();
1293 // Move up the menu if it overlaps shelf.
1294 var maxHeight = cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping(
1295 this.actionBoxMenu);
1296 var actualHeight = parseInt(
1297 window.getComputedStyle(this.actionBoxMenu).height);
1298 if (maxHeight < actualHeight) {
1299 this.actionBoxMenu.classList.add('menu-moved-up');
1300 this.actionBoxAreaElement.classList.add('menu-moved-up');
1305 * Handles a click event on remove user confirmation button.
1306 * @param {Event} e Click event.
1308 handleRemoveUserConfirmationClick_: function(e) {
1309 if (this.isActionBoxMenuActive) {
1310 this.isActionBoxMenuActive = false;
1311 this.removeUser(this.user);
1312 e.stopPropagation();
1317 * Handles a keydown event on remove user confirmation button.
1318 * @param {Event} e KeyDown event.
1320 handleRemoveUserConfirmationKeyDown_: function(e) {
1321 if (!this.isActionBoxMenuActive)
1324 // Only handle pressing 'Enter' or 'Space', and let all other events
1325 // bubble to the action box menu.
1326 if (e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020') {
1327 this.isActionBoxMenuActive = false;
1328 this.removeUser(this.user);
1329 e.stopPropagation();
1330 // Prevent default so that we don't trigger a 'click' event.
1336 * Handles a keydown event on remove command.
1337 * @param {Event} e KeyDown event.
1339 handleRemoveCommandKeyDown_: function(e) {
1342 switch (e.keyIdentifier) {
1344 if (this.user.supervisedUser || this.user.isDesktopUser) {
1345 // Prevent default so that we don't trigger a 'click' event on the
1346 // remove button that will be focused.
1348 this.showRemoveWarning_();
1350 this.removeUser(this.user);
1352 e.stopPropagation();
1356 e.stopPropagation();
1358 case 'U+001B': // Esc
1359 this.actionBoxAreaElement.focus();
1360 this.isActionBoxMenuActive = false;
1361 e.stopPropagation();
1364 this.actionBoxAreaElement.focus();
1365 this.isActionBoxMenuActive = false;
1371 * Handles a blur event on remove command.
1372 * @param {Event} e Blur event.
1374 handleRemoveCommandBlur_: function(e) {
1377 this.actionBoxMenuRemoveElement.tabIndex = -1;
1381 * Handles click event on a user pod.
1382 * @param {Event} e Click event.
1384 handleClickOnPod_: function(e) {
1385 if (this.parentNode.disabled)
1388 if (!this.isActionBoxMenuActive) {
1389 if (this.isAuthTypeOnlineSignIn) {
1390 this.showSigninUI();
1391 } else if (this.isAuthTypeUserClick) {
1392 this.parentNode.setActivatedPod(this);
1395 if (this.multiProfilesPolicyApplied)
1396 this.userTypeBubbleElement.classList.add('bubble-shown');
1398 // Prevent default so that we don't trigger 'focus' event.
1404 * Handles keydown event for a user pod.
1405 * @param {Event} e Key event.
1407 handlePodKeyDown_: function(e) {
1408 if (!this.isAuthTypeUserClick || this.disabled)
1410 switch (e.keyIdentifier) {
1412 case 'U+0020': // Space
1413 if (this.parentNode.isFocused(this))
1414 this.parentNode.setActivatedPod(this);
1421 * Creates a public account user pod.
1423 * @extends {UserPod}
1425 var PublicAccountUserPod = cr.ui.define(function() {
1426 var node = UserPod();
1428 var extras = $('public-account-user-pod-extras-template').children;
1429 for (var i = 0; i < extras.length; ++i) {
1430 var el = extras[i].cloneNode(true);
1431 node.appendChild(el);
1437 PublicAccountUserPod.prototype = {
1438 __proto__: UserPod.prototype,
1441 * "Enter" button in expanded side pane.
1442 * @type {!HTMLButtonElement}
1444 get enterButtonElement() {
1445 return this.querySelector('.enter-button');
1449 * Boolean flag of whether the pod is showing the side pane. The flag
1450 * controls whether 'expanded' class is added to the pod's class list and
1451 * resets tab order because main input element changes when the 'expanded'
1456 return this.classList.contains('expanded');
1459 set expanded(expanded) {
1460 if (this.expanded == expanded)
1463 this.resetTabOrder();
1464 this.classList.toggle('expanded', expanded);
1466 // Show the advanced expanded pod directly if there are at least two
1467 // recommended locales. This will be the case in multilingual
1468 // environments where users are likely to want to choose among locales.
1469 if (this.querySelector('.language-select').multipleRecommendedLocales)
1470 this.classList.add('advanced');
1471 this.usualLeft = this.left;
1472 this.makeSpaceForExpandedPod_();
1473 } else if (typeof(this.usualLeft) != 'undefined') {
1474 this.left = this.usualLeft;
1478 this.classList.add('animating');
1479 this.addEventListener('webkitTransitionEnd', function f(e) {
1480 self.removeEventListener('webkitTransitionEnd', f);
1481 self.classList.remove('animating');
1483 // Accessibility focus indicator does not move with the focused
1484 // element. Sends a 'focus' event on the currently focused element
1485 // so that accessibility focus indicator updates its location.
1486 if (document.activeElement)
1487 document.activeElement.dispatchEvent(new Event('focus'));
1489 // Guard timer set to animation duration + 20ms.
1490 ensureTransitionEndEvent(this, 200);
1494 return this.classList.contains('advanced');
1500 return this.enterButtonElement;
1502 return this.nameElement;
1506 decorate: function() {
1507 UserPod.prototype.decorate.call(this);
1509 this.classList.add('public-account');
1511 this.nameElement.addEventListener('keydown', (function(e) {
1512 if (e.keyIdentifier == 'Enter') {
1513 this.parentNode.setActivatedPod(this, e);
1514 // Stop this keydown event from bubbling up to PodRow handler.
1515 e.stopPropagation();
1516 // Prevent default so that we don't trigger a 'click' event on the
1517 // newly focused "Enter" button.
1522 var learnMore = this.querySelector('.learn-more');
1523 learnMore.addEventListener('mousedown', stopEventPropagation);
1524 learnMore.addEventListener('click', this.handleLearnMoreEvent);
1525 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
1527 learnMore = this.querySelector('.expanded-pane-learn-more');
1528 learnMore.addEventListener('click', this.handleLearnMoreEvent);
1529 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
1531 var languageSelect = this.querySelector('.language-select');
1532 languageSelect.tabIndex = UserPodTabOrder.POD_INPUT;
1533 languageSelect.manuallyChanged = false;
1534 languageSelect.addEventListener(
1537 languageSelect.manuallyChanged = true;
1538 this.getPublicSessionKeyboardLayouts_();
1541 var keyboardSelect = this.querySelector('.keyboard-select');
1542 keyboardSelect.tabIndex = UserPodTabOrder.POD_INPUT;
1543 keyboardSelect.loadedLocale = null;
1545 var languageAndInput = this.querySelector('.language-and-input');
1546 languageAndInput.tabIndex = UserPodTabOrder.POD_INPUT;
1547 languageAndInput.addEventListener('click',
1548 this.transitionToAdvanced_.bind(this));
1550 this.enterButtonElement.addEventListener('click', (function(e) {
1551 this.enterButtonElement.disabled = true;
1552 var locale = this.querySelector('.language-select').value;
1553 var keyboardSelect = this.querySelector('.keyboard-select');
1554 // The contents of |keyboardSelect| is updated asynchronously. If its
1555 // locale does not match |locale|, it has not updated yet and the
1556 // currently selected keyboard layout may not be applicable to |locale|.
1557 // Do not return any keyboard layout in this case and let the backend
1558 // choose a suitable layout.
1559 var keyboardLayout =
1560 keyboardSelect.loadedLocale == locale ? keyboardSelect.value : '';
1561 chrome.send('launchPublicSession',
1562 [this.user.username, locale, keyboardLayout]);
1567 initialize: function() {
1568 UserPod.prototype.initialize.call(this);
1570 id = this.user.username + '-keyboard';
1571 this.querySelector('.keyboard-select-label').htmlFor = id;
1572 this.querySelector('.keyboard-select').setAttribute('id', id);
1574 var id = this.user.username + '-language';
1575 this.querySelector('.language-select-label').htmlFor = id;
1576 var languageSelect = this.querySelector('.language-select');
1577 languageSelect.setAttribute('id', id);
1578 this.populateLanguageSelect(this.user.initialLocales,
1579 this.user.initialLocale,
1580 this.user.initialMultipleRecommendedLocales);
1584 update: function() {
1585 UserPod.prototype.update.call(this);
1586 this.querySelector('.expanded-pane-name').textContent =
1587 this.user_.displayName;
1588 this.querySelector('.info').textContent =
1589 loadTimeData.getStringF('publicAccountInfoFormat',
1590 this.user_.enterpriseDomain);
1594 focusInput: function() {
1595 // Move tabIndex from the whole pod to the main input.
1597 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1598 this.mainInput.focus();
1602 reset: function(takeFocus) {
1604 this.expanded = false;
1605 this.enterButtonElement.disabled = false;
1606 UserPod.prototype.reset.call(this, takeFocus);
1610 activate: function(e) {
1611 if (!this.expanded) {
1612 this.expanded = true;
1619 handleClickOnPod_: function(e) {
1620 if (this.parentNode.disabled)
1623 this.parentNode.focusPod(this);
1624 this.parentNode.setActivatedPod(this, e);
1625 // Prevent default so that we don't trigger 'focus' event.
1630 * Updates the display name shown on the pod.
1631 * @param {string} displayName The new display name
1633 setDisplayName: function(displayName) {
1634 this.user_.displayName = displayName;
1639 * Handle mouse and keyboard events for the learn more button. Triggering
1640 * the button causes information about public sessions to be shown.
1641 * @param {Event} event Mouse or keyboard event.
1643 handleLearnMoreEvent: function(event) {
1644 switch (event.type) {
1645 // Show informaton on left click. Let any other clicks propagate.
1647 if (event.button != 0)
1650 // Show informaton when <Return> or <Space> is pressed. Let any other
1651 // key presses propagate.
1653 switch (event.keyCode) {
1662 chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]);
1663 stopEventPropagation(event);
1666 makeSpaceForExpandedPod_: function() {
1667 var width = this.classList.contains('advanced') ?
1668 PUBLIC_EXPANDED_ADVANCED_WIDTH : PUBLIC_EXPANDED_BASIC_WIDTH;
1669 var isDesktopUserManager = Oobe.getInstance().displayType ==
1670 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1671 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
1673 if (this.left + width > $('pod-row').offsetWidth - rowPadding)
1674 this.left = $('pod-row').offsetWidth - rowPadding - width;
1678 * Transition the expanded pod from the basic to the advanced view.
1680 transitionToAdvanced_: function() {
1682 var languageAndInputSection =
1683 this.querySelector('.language-and-input-section');
1684 this.classList.add('transitioning-to-advanced');
1685 setTimeout(function() {
1686 pod.classList.add('advanced');
1687 pod.makeSpaceForExpandedPod_();
1688 languageAndInputSection.addEventListener('webkitTransitionEnd',
1689 function observer() {
1690 languageAndInputSection.removeEventListener('webkitTransitionEnd',
1692 pod.classList.remove('transitioning-to-advanced');
1693 pod.querySelector('.language-select').focus();
1695 // Guard timer set to animation duration + 20ms.
1696 ensureTransitionEndEvent(languageAndInputSection, 380);
1701 * Retrieves the list of keyboard layouts available for the currently
1704 getPublicSessionKeyboardLayouts_: function() {
1705 var selectedLocale = this.querySelector('.language-select').value;
1706 if (selectedLocale ==
1707 this.querySelector('.keyboard-select').loadedLocale) {
1708 // If the list of keyboard layouts was loaded for the currently selected
1709 // locale, it is already up to date.
1712 chrome.send('getPublicSessionKeyboardLayouts',
1713 [this.user.username, selectedLocale]);
1717 * Populates the keyboard layout "select" element with a list of layouts.
1718 * @param {string} locale The locale to which this list of keyboard layouts
1720 * @param {!Object} list List of available keyboard layouts
1722 populateKeyboardSelect: function(locale, list) {
1723 if (locale != this.querySelector('.language-select').value) {
1724 // The selected locale has changed and the list of keyboard layouts is
1725 // not applicable. This method will be called again when a list of
1726 // keyboard layouts applicable to the selected locale is retrieved.
1730 var keyboardSelect = this.querySelector('.keyboard-select');
1731 keyboardSelect.loadedLocale = locale;
1732 keyboardSelect.innerHTML = '';
1733 for (var i = 0; i < list.length; ++i) {
1735 keyboardSelect.appendChild(
1736 new Option(item.title, item.value, item.selected, item.selected));
1741 * Populates the language "select" element with a list of locales.
1742 * @param {!Object} locales The list of available locales
1743 * @param {string} defaultLocale The locale to select by default
1744 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
1745 * two or more recommended locales
1747 populateLanguageSelect: function(locales,
1749 multipleRecommendedLocales) {
1750 var languageSelect = this.querySelector('.language-select');
1751 // If the user manually selected a locale, do not change the selection.
1752 // Otherwise, select the new |defaultLocale|.
1754 languageSelect.manuallyChanged ? languageSelect.value : defaultLocale;
1755 languageSelect.innerHTML = '';
1756 var group = languageSelect;
1757 for (var i = 0; i < locales.length; ++i) {
1758 var item = locales[i];
1759 if (item.optionGroupName) {
1760 group = document.createElement('optgroup');
1761 group.label = item.optionGroupName;
1762 languageSelect.appendChild(group);
1764 group.appendChild(new Option(item.title,
1766 item.value == selected,
1767 item.value == selected));
1770 languageSelect.multipleRecommendedLocales = multipleRecommendedLocales;
1772 // Retrieve a list of keyboard layouts applicable to the locale that is
1774 this.getPublicSessionKeyboardLayouts_();
1779 * Creates a user pod to be used only in desktop chrome.
1781 * @extends {UserPod}
1783 var DesktopUserPod = cr.ui.define(function() {
1784 // Don't just instantiate a UserPod(), as this will call decorate() on the
1785 // parent object, and add duplicate event listeners.
1786 var node = $('user-pod-template').cloneNode(true);
1787 node.removeAttribute('id');
1791 DesktopUserPod.prototype = {
1792 __proto__: UserPod.prototype,
1796 if (this.user.needsSignin)
1797 return this.passwordElement;
1799 return this.nameElement;
1803 update: function() {
1804 this.imageElement.src = this.user.userImage;
1805 this.nameElement.textContent = this.user.displayName;
1807 var isLockedUser = this.user.needsSignin;
1808 var isSupervisedUser = this.user.supervisedUser;
1809 this.classList.toggle('locked', isLockedUser);
1810 this.classList.toggle('supervised-user', isSupervisedUser);
1812 if (this.isAuthTypeUserClick)
1813 this.passwordLabelElement.textContent = this.authValue;
1815 this.actionBoxRemoveUserWarningTextElement.hidden = isSupervisedUser;
1816 this.actionBoxRemoveSupervisedUserWarningTextElement.hidden =
1819 UserPod.prototype.updateActionBoxArea.call(this);
1823 focusInput: function() {
1824 // Move tabIndex from the whole pod to the main input.
1826 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1827 this.mainInput.focus();
1831 activate: function(e) {
1832 if (!this.user.needsSignin) {
1833 Oobe.launchUser(this.user.emailAddress, this.user.displayName);
1834 } else if (!this.passwordElement.value) {
1837 chrome.send('authenticatedLaunchUser',
1838 [this.user.emailAddress,
1839 this.user.displayName,
1840 this.passwordElement.value]);
1842 this.passwordElement.value = '';
1847 handleClickOnPod_: function(e) {
1848 if (this.parentNode.disabled)
1852 this.parentNode.lastFocusedPod_ = this;
1854 // If this is an unlocked pod, then open a browser window. Otherwise
1855 // just activate the pod and show the password field.
1856 if (!this.user.needsSignin && !this.isActionBoxMenuActive)
1859 if (this.isAuthTypeUserClick)
1860 chrome.send('attemptUnlock', [this.user.emailAddress]);
1865 * Creates a user pod that represents kiosk app.
1867 * @extends {UserPod}
1869 var KioskAppPod = cr.ui.define(function() {
1870 var node = UserPod();
1874 KioskAppPod.prototype = {
1875 __proto__: UserPod.prototype,
1878 decorate: function() {
1879 UserPod.prototype.decorate.call(this);
1880 this.launchAppButtonElement.addEventListener('click',
1881 this.activate.bind(this));
1885 update: function() {
1886 this.imageElement.src = this.user.iconUrl;
1887 this.imageElement.alt = this.user.label;
1888 this.imageElement.title = this.user.label;
1889 this.passwordEntryContainerElement.hidden = true;
1890 this.launchAppButtonContainerElement.hidden = false;
1891 this.nameElement.textContent = this.user.label;
1893 UserPod.prototype.updateActionBoxArea.call(this);
1894 UserPod.prototype.customizeUserPodPerUserType.call(this);
1899 return this.launchAppButtonElement;
1903 focusInput: function() {
1904 // Move tabIndex from the whole pod to the main input.
1906 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1907 this.mainInput.focus();
1911 get forceOnlineSignin() {
1916 activate: function(e) {
1917 var diagnosticMode = e && e.ctrlKey;
1918 this.launchApp_(this.user, diagnosticMode);
1923 handleClickOnPod_: function(e) {
1924 if (this.parentNode.disabled)
1928 this.parentNode.lastFocusedPod_ = this;
1933 * Launch the app. If |diagnosticMode| is true, ask user to confirm.
1934 * @param {Object} app App data.
1935 * @param {boolean} diagnosticMode Whether to run the app in diagnostic
1938 launchApp_: function(app, diagnosticMode) {
1939 if (!diagnosticMode) {
1940 chrome.send('launchKioskApp', [app.id, false]);
1944 var oobe = $('oobe');
1945 if (!oobe.confirmDiagnosticMode_) {
1946 oobe.confirmDiagnosticMode_ =
1947 new cr.ui.dialogs.ConfirmDialog(document.body);
1948 oobe.confirmDiagnosticMode_.setOkLabel(
1949 loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
1950 oobe.confirmDiagnosticMode_.setCancelLabel(
1951 loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
1954 oobe.confirmDiagnosticMode_.show(
1955 loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
1958 chrome.send('launchKioskApp', [app.id, true]);
1964 * Creates a new pod row element.
1966 * @extends {HTMLDivElement}
1968 var PodRow = cr.ui.define('podrow');
1970 PodRow.prototype = {
1971 __proto__: HTMLDivElement.prototype,
1973 // Whether this user pod row is shown for the first time.
1976 // True if inside focusPod().
1977 insideFocusPod_: false,
1980 focusedPod_: undefined,
1982 // Activated pod, i.e. the pod of current login attempt.
1983 activatedPod_: undefined,
1985 // Pod that was most recently focused, if any.
1986 lastFocusedPod_: undefined,
1988 // Pods whose initial images haven't been loaded yet.
1989 podsWithPendingImages_: [],
1991 // Whether pod placement has been postponed.
1992 podPlacementPostponed_: false,
1994 // Standard user pod height/width.
1998 // Array of apps that are shown in addition to other user pods.
2001 // True to show app pods along with user pods.
2002 shouldShowApps_: true,
2004 // Array of users that are shown (public/supervised/regular).
2008 decorate: function() {
2009 // Event listeners that are installed for the time period during which
2010 // the element is visible.
2012 focus: [this.handleFocus_.bind(this), true /* useCapture */],
2013 click: [this.handleClick_.bind(this), true],
2014 mousemove: [this.handleMouseMove_.bind(this), false],
2015 keydown: [this.handleKeyDown.bind(this), false]
2018 var isDesktopUserManager = Oobe.getInstance().displayType ==
2019 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2020 this.userPodHeight_ = isDesktopUserManager ? DESKTOP_POD_HEIGHT :
2022 // Same for Chrome OS and desktop.
2023 this.userPodWidth_ = POD_WIDTH;
2027 * Returns all the pods in this pod row.
2031 return Array.prototype.slice.call(this.children);
2035 * Return true if user pod row has only single user pod in it, which should
2036 * always be focused.
2039 get alwaysFocusSinglePod() {
2040 var isDesktopUserManager = Oobe.getInstance().displayType ==
2041 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2043 return isDesktopUserManager ? false : this.children.length == 1;
2047 * Returns pod with the given app id.
2048 * @param {!string} app_id Application id to be matched.
2049 * @return {Object} Pod with the given app id. null if pod hasn't been
2052 getPodWithAppId_: function(app_id) {
2053 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2054 if (pod.user.isApp && pod.user.id == app_id)
2061 * Returns pod with the given username (null if there is no such pod).
2062 * @param {string} username Username to be matched.
2063 * @return {Object} Pod with the given username. null if pod hasn't been
2066 getPodWithUsername_: function(username) {
2067 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2068 if (pod.user.username == username)
2075 * True if the the pod row is disabled (handles no user interaction).
2080 return this.disabled_;
2082 set disabled(value) {
2083 this.disabled_ = value;
2084 var controls = this.querySelectorAll('button,input');
2085 for (var i = 0, control; control = controls[i]; ++i) {
2086 control.disabled = value;
2091 * Creates a user pod from given email.
2092 * @param {!Object} user User info dictionary.
2094 createUserPod: function(user) {
2096 if (user.isDesktopUser)
2097 userPod = new DesktopUserPod({user: user});
2098 else if (user.publicAccount)
2099 userPod = new PublicAccountUserPod({user: user});
2100 else if (user.isApp)
2101 userPod = new KioskAppPod({user: user});
2103 userPod = new UserPod({user: user});
2105 userPod.hidden = false;
2110 * Add an existing user pod to this pod row.
2111 * @param {!Object} user User info dictionary.
2113 addUserPod: function(user) {
2114 var userPod = this.createUserPod(user);
2115 this.appendChild(userPod);
2116 userPod.initialize();
2120 * Runs app with a given id from the list of loaded apps.
2121 * @param {!string} app_id of an app to run.
2122 * @param {boolean=} opt_diagnostic_mode Whether to run the app in
2123 * diagnostic mode. Default is false.
2125 findAndRunAppForTesting: function(app_id, opt_diagnostic_mode) {
2126 var app = this.getPodWithAppId_(app_id);
2128 var activationEvent = cr.doc.createEvent('MouseEvents');
2129 var ctrlKey = opt_diagnostic_mode;
2130 activationEvent.initMouseEvent('click', true, true, null,
2131 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
2132 app.dispatchEvent(activationEvent);
2137 * Removes user pod from pod row.
2138 * @param {string} email User's email.
2140 removeUserPod: function(username) {
2141 var podToRemove = this.getPodWithUsername_(username);
2142 if (podToRemove == null) {
2143 console.warn('Attempt to remove not existing pod for ' + username +
2147 this.removeChild(podToRemove);
2148 if (this.pods.length > 0)
2153 * Returns index of given pod or -1 if not found.
2154 * @param {UserPod} pod Pod to look up.
2157 indexOf_: function(pod) {
2158 for (var i = 0; i < this.pods.length; ++i) {
2159 if (pod == this.pods[i])
2166 * Populates pod row with given existing users and start init animation.
2167 * @param {array} users Array of existing user emails.
2169 loadPods: function(users) {
2170 this.users_ = users;
2176 * Scrolls focused user pod into view.
2178 scrollFocusedPodIntoView: function() {
2179 var pod = this.focusedPod_;
2183 // First check whether focused pod is already fully visible.
2184 var visibleArea = $('scroll-container');
2185 var scrollTop = visibleArea.scrollTop;
2186 var clientHeight = visibleArea.clientHeight;
2187 var podTop = $('oobe').offsetTop + pod.offsetTop;
2188 var padding = USER_POD_KEYBOARD_MIN_PADDING;
2189 if (podTop + pod.height + padding <= scrollTop + clientHeight &&
2190 podTop - padding >= scrollTop) {
2194 // Scroll so that user pod is as centered as possible.
2195 visibleArea.scrollTop = podTop - (clientHeight - pod.offsetHeight) / 2;
2199 * Rebuilds pod row using users_ and apps_ that were previously set or
2202 rebuildPods: function() {
2203 var emptyPodRow = this.pods.length == 0;
2205 // Clear existing pods.
2206 this.innerHTML = '';
2207 this.focusedPod_ = undefined;
2208 this.activatedPod_ = undefined;
2209 this.lastFocusedPod_ = undefined;
2211 // Switch off animation
2212 Oobe.getInstance().toggleClass('flying-pods', false);
2214 // Populate the pod row.
2215 for (var i = 0; i < this.users_.length; ++i)
2216 this.addUserPod(this.users_[i]);
2218 for (var i = 0, pod; pod = this.pods[i]; ++i)
2219 this.podsWithPendingImages_.push(pod);
2221 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
2222 if (this.shouldShowApps_) {
2223 for (var i = 0; i < this.apps_.length; ++i)
2224 this.addUserPod(this.apps_[i]);
2227 // Make sure we eventually show the pod row, even if some image is stuck.
2228 setTimeout(function() {
2229 $('pod-row').classList.remove('images-loading');
2230 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS);
2232 var isAccountPicker = $('login-header-bar').signinUIState ==
2233 SIGNIN_UI_STATE.ACCOUNT_PICKER;
2235 // Immediately recalculate pods layout only when current UI is account
2236 // picker. Otherwise postpone it.
2237 if (isAccountPicker) {
2239 this.maybePreselectPod();
2241 // Without timeout changes in pods positions will be animated even
2242 // though it happened when 'flying-pods' class was disabled.
2243 setTimeout(function() {
2244 Oobe.getInstance().toggleClass('flying-pods', true);
2247 this.podPlacementPostponed_ = true;
2249 // Update [Cancel] button state.
2250 if ($('login-header-bar').signinUIState ==
2251 SIGNIN_UI_STATE.GAIA_SIGNIN &&
2253 this.pods.length > 0) {
2254 login.GaiaSigninScreen.updateCancelButtonState();
2260 * Adds given apps to the pod row.
2261 * @param {array} apps Array of apps.
2263 setApps: function(apps) {
2266 chrome.send('kioskAppsLoaded');
2268 // Check whether there's a pending kiosk app error.
2269 window.setTimeout(function() {
2270 chrome.send('checkKioskAppLaunchError');
2275 * Sets whether should show app pods.
2276 * @param {boolean} shouldShowApps Whether app pods should be shown.
2278 setShouldShowApps: function(shouldShowApps) {
2279 if (this.shouldShowApps_ == shouldShowApps)
2282 this.shouldShowApps_ = shouldShowApps;
2287 * Shows a custom icon on a user pod besides the input field.
2288 * @param {string} username Username of pod to add button
2289 * @param {!{resourceUrl: (string | undefined),
2290 * data: ({scale1x: string, scale2x: string} | undefined),
2291 * size: ({width: number, height: number} | undefined),
2292 * animation: ({resourceWidth: number, frameLength: number} |
2294 * opacity: (number | undefined),
2295 * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon
2296 * The icon parameters.
2298 showUserPodCustomIcon: function(username, icon) {
2299 var pod = this.getPodWithUsername_(username);
2301 console.error('Unable to show user pod button for ' + username +
2302 ': user pod not found.');
2306 if (icon.resourceUrl) {
2307 pod.customIconElement.setIconAsResourceUrl(icon.resourceUrl);
2308 } else if (icon.data) {
2309 pod.customIconElement.setIconAsImageSet(icon.data);
2314 pod.customIconElement.setSize(icon.size || {width: 0, height: 0});
2315 pod.customIconElement.setAnimation(icon.animation || null);
2316 pod.customIconElement.setOpacity(icon.opacity || 100);
2317 if (icon.hardlockOnClick) {
2318 pod.customIconElement.setInteractive(
2319 this.hardlockUserPod_.bind(this, username));
2321 pod.customIconElement.setInteractive(null);
2323 pod.customIconElement.show();
2324 // This has to be called after |show| in case the tooltip should be shown
2326 pod.customIconElement.setTooltip(
2327 icon.tooltip || {text: '', autoshow: false});
2331 * Hard-locks user pod for the user. If user pod is hard-locked, it can be
2332 * only unlocked using password, and the authentication type cannot be
2334 * @param {!string} username The user's username.
2337 hardlockUserPod_: function(username) {
2338 chrome.send('hardlockPod', [username]);
2342 * Hides the custom icon in the user pod added by showUserPodCustomIcon().
2343 * @param {string} username Username of pod to remove button
2345 hideUserPodCustomIcon: function(username) {
2346 var pod = this.getPodWithUsername_(username);
2348 console.error('Unable to hide user pod button for ' + username +
2349 ': user pod not found.');
2353 // TODO(tengs): Allow option for a fading transition.
2354 pod.customIconElement.hide();
2358 * Sets the authentication type used to authenticate the user.
2359 * @param {string} username Username of selected user
2360 * @param {number} authType Authentication type, must be one of the
2361 * values listed in AUTH_TYPE enum.
2362 * @param {string} value The initial value to use for authentication.
2364 setAuthType: function(username, authType, value) {
2365 var pod = this.getPodWithUsername_(username);
2367 console.error('Unable to set auth type for ' + username +
2368 ': user pod not found.');
2371 pod.setAuthType(authType, value);
2375 * Updates the display name shown on a public session pod.
2376 * @param {string} userID The user ID of the public session
2377 * @param {string} displayName The new display name
2379 setPublicSessionDisplayName: function(userID, displayName) {
2380 var pod = this.getPodWithUsername_(userID);
2382 pod.setDisplayName(displayName);
2386 * Updates the list of locales available for a public session.
2387 * @param {string} userID The user ID of the public session
2388 * @param {!Object} locales The list of available locales
2389 * @param {string} defaultLocale The locale to select by default
2390 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
2391 * two or more recommended locales
2393 setPublicSessionLocales: function(userID,
2396 multipleRecommendedLocales) {
2397 var pod = this.getPodWithUsername_(userID);
2399 pod.populateLanguageSelect(locales,
2401 multipleRecommendedLocales);
2406 * Updates the list of available keyboard layouts for a public session pod.
2407 * @param {string} userID The user ID of the public session
2408 * @param {string} locale The locale to which this list of keyboard layouts
2410 * @param {!Object} list List of available keyboard layouts
2412 setPublicSessionKeyboardLayouts: function(userID, locale, list) {
2413 var pod = this.getPodWithUsername_(userID);
2415 pod.populateKeyboardSelect(locale, list);
2419 * Called when window was resized.
2421 onWindowResize: function() {
2422 var layout = this.calculateLayout_();
2423 if (layout.columns != this.columns || layout.rows != this.rows)
2426 if (Oobe.getInstance().virtualKeyboardShown)
2427 this.scrollFocusedPodIntoView();
2431 * Returns width of podrow having |columns| number of columns.
2434 columnsToWidth_: function(columns) {
2435 var isDesktopUserManager = Oobe.getInstance().displayType ==
2436 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2437 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
2438 MARGIN_BY_COLUMNS[columns];
2439 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
2441 return 2 * rowPadding + columns * this.userPodWidth_ +
2442 (columns - 1) * margin;
2446 * Returns height of podrow having |rows| number of rows.
2449 rowsToHeight_: function(rows) {
2450 var isDesktopUserManager = Oobe.getInstance().displayType ==
2451 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2452 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
2454 return 2 * rowPadding + rows * this.userPodHeight_;
2458 * Calculates number of columns and rows that podrow should have in order to
2459 * hold as much its pods as possible for current screen size. Also it tries
2460 * to choose layout that looks good.
2461 * @return {{columns: number, rows: number}}
2463 calculateLayout_: function() {
2464 var preferredColumns = this.pods.length < COLUMNS.length ?
2465 COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1];
2466 var maxWidth = Oobe.getInstance().clientAreaSize.width;
2467 var columns = preferredColumns;
2468 while (maxWidth < this.columnsToWidth_(columns) && columns > 1)
2470 var rows = Math.floor((this.pods.length - 1) / columns) + 1;
2471 if (getComputedStyle(
2472 $('signin-banner'), null).getPropertyValue('display') != 'none') {
2473 rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER);
2475 var maxHeigth = Oobe.getInstance().clientAreaSize.height;
2476 while (maxHeigth < this.rowsToHeight_(rows) && rows > 1)
2478 // One more iteration if it's not enough cells to place all pods.
2479 while (maxWidth >= this.columnsToWidth_(columns + 1) &&
2480 columns * rows < this.pods.length &&
2481 columns < MAX_NUMBER_OF_COLUMNS) {
2484 return {columns: columns, rows: rows};
2488 * Places pods onto their positions onto pod grid.
2491 placePods_: function() {
2492 var layout = this.calculateLayout_();
2493 var columns = this.columns = layout.columns;
2494 var rows = this.rows = layout.rows;
2495 var maxPodsNumber = columns * rows;
2496 var isDesktopUserManager = Oobe.getInstance().displayType ==
2497 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2498 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
2499 MARGIN_BY_COLUMNS[columns];
2500 this.parentNode.setPreferredSize(
2501 this.columnsToWidth_(columns), this.rowsToHeight_(rows));
2502 var height = this.userPodHeight_;
2503 var width = this.userPodWidth_;
2504 this.pods.forEach(function(pod, index) {
2505 if (index >= maxPodsNumber) {
2510 if (pod.offsetHeight != height) {
2511 console.error('Pod offsetHeight (' + pod.offsetHeight +
2512 ') and POD_HEIGHT (' + height + ') are not equal.');
2514 if (pod.offsetWidth != width) {
2515 console.error('Pod offsetWidth (' + pod.offsetWidth +
2516 ') and POD_WIDTH (' + width + ') are not equal.');
2518 var column = index % columns;
2519 var row = Math.floor(index / columns);
2520 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
2522 pod.left = rowPadding + column * (width + margin);
2524 // On desktop, we want the rows to always be equally spaced.
2525 pod.top = isDesktopUserManager ? row * (height + rowPadding) :
2526 row * height + rowPadding;
2528 Oobe.getInstance().updateScreenSize(this.parentNode);
2532 * Number of columns.
2535 set columns(columns) {
2536 // Cannot use 'columns' here.
2537 this.setAttribute('ncolumns', columns);
2540 return parseInt(this.getAttribute('ncolumns'));
2548 // Cannot use 'rows' here.
2549 this.setAttribute('nrows', rows);
2552 return parseInt(this.getAttribute('nrows'));
2556 * Whether the pod is currently focused.
2557 * @param {UserPod} pod Pod to check for focus.
2558 * @return {boolean} Pod focus status.
2560 isFocused: function(pod) {
2561 return this.focusedPod_ == pod;
2565 * Focuses a given user pod or clear focus when given null.
2566 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
2567 * @param {boolean=} opt_force If true, forces focus update even when
2568 * podToFocus is already focused.
2570 focusPod: function(podToFocus, opt_force) {
2571 if (this.isFocused(podToFocus) && !opt_force) {
2572 // Calling focusPod w/o podToFocus means reset.
2575 this.keyboardActivated_ = false;
2579 // Make sure there's only one focusPod operation happening at a time.
2580 if (this.insideFocusPod_) {
2581 this.keyboardActivated_ = false;
2584 this.insideFocusPod_ = true;
2586 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2587 if (!this.alwaysFocusSinglePod) {
2588 pod.isActionBoxMenuActive = false;
2590 if (pod != podToFocus) {
2591 pod.isActionBoxMenuHovered = false;
2592 pod.classList.remove('focused');
2593 // On Desktop, the faded style is not set correctly, so we should
2594 // manually fade out non-focused pods if there is a focused pod.
2595 if (pod.user.isDesktopUser && podToFocus)
2596 pod.classList.add('faded');
2598 pod.classList.remove('faded');
2603 // Clear any error messages for previous pod.
2604 if (!this.isFocused(podToFocus))
2607 var hadFocus = !!this.focusedPod_;
2608 this.focusedPod_ = podToFocus;
2610 podToFocus.classList.remove('faded');
2611 podToFocus.classList.add('focused');
2612 if (!podToFocus.multiProfilesPolicyApplied)
2613 podToFocus.reset(true); // Reset and give focus.
2615 podToFocus.userTypeBubbleElement.classList.add('bubble-shown');
2619 // focusPod() automatically loads wallpaper
2620 if (!podToFocus.user.isApp)
2621 chrome.send('focusPod', [podToFocus.user.username]);
2622 this.firstShown_ = false;
2623 this.lastFocusedPod_ = podToFocus;
2625 if (Oobe.getInstance().virtualKeyboardShown)
2626 this.scrollFocusedPodIntoView();
2628 this.insideFocusPod_ = false;
2629 this.keyboardActivated_ = false;
2633 * Resets wallpaper to the last active user's wallpaper, if any.
2635 loadLastWallpaper: function() {
2636 if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp)
2637 chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]);
2641 * Returns the currently activated pod.
2644 get activatedPod() {
2645 return this.activatedPod_;
2649 * Sets currently activated pod.
2650 * @param {UserPod} pod Pod to check for focus.
2651 * @param {Event} e Event object.
2653 setActivatedPod: function(pod, e) {
2654 if (pod && pod.activate(e))
2655 this.activatedPod_ = pod;
2659 * The pod of the signed-in user, if any; null otherwise.
2663 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2664 if (pod.user.signedIn)
2671 * The pod that is preselected on user pod row show.
2674 get preselectedPod() {
2675 var isDesktopUserManager = Oobe.getInstance().displayType ==
2676 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2677 if (isDesktopUserManager) {
2678 // On desktop, don't pre-select a pod if it's the only one.
2679 if (this.pods.length == 1)
2682 // The desktop User Manager can send the index of a pod that should be
2683 // initially focused in url hash.
2684 var podIndex = parseInt(window.location.hash.substr(1));
2685 if (isNaN(podIndex) || podIndex >= this.pods.length)
2687 return this.pods[podIndex];
2690 var lockedPod = this.lockedPod;
2693 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2694 if (!pod.multiProfilesPolicyApplied) {
2698 return this.pods[0];
2703 * @param {boolean} takeFocus True to take focus.
2705 reset: function(takeFocus) {
2706 this.disabled = false;
2707 if (this.activatedPod_)
2708 this.activatedPod_.reset(takeFocus);
2712 * Restores input focus to current selected pod, if there is any.
2714 refocusCurrentPod: function() {
2715 if (this.focusedPod_ && !this.focusedPod_.multiProfilesPolicyApplied) {
2716 this.focusedPod_.focusInput();
2721 * Clears focused pod password field.
2723 clearFocusedPod: function() {
2724 if (!this.disabled && this.focusedPod_)
2725 this.focusedPod_.reset(true);
2730 * @param {string} email Email for signin UI.
2732 showSigninUI: function(email) {
2733 // Clear any error messages that might still be around.
2735 this.disabled = true;
2736 this.lastFocusedPod_ = this.getPodWithUsername_(email);
2737 Oobe.showSigninUI(email);
2741 * Updates current image of a user.
2742 * @param {string} username User for which to update the image.
2744 updateUserImage: function(username) {
2745 var pod = this.getPodWithUsername_(username);
2747 pod.updateUserImage();
2751 * Handler of click event.
2752 * @param {Event} e Click Event object.
2755 handleClick_: function(e) {
2759 // Clear all menus if the click is outside pod menu and its
2761 if (!findAncestorByClass(e.target, 'action-box-menu') &&
2762 !findAncestorByClass(e.target, 'action-box-area')) {
2763 for (var i = 0, pod; pod = this.pods[i]; ++i)
2764 pod.isActionBoxMenuActive = false;
2767 // Clears focus if not clicked on a pod and if there's more than one pod.
2768 var pod = findAncestorByClass(e.target, 'pod');
2769 if ((!pod || pod.parentNode != this) && !this.alwaysFocusSinglePod) {
2774 pod.isActionBoxMenuHovered = true;
2776 // Return focus back to single pod.
2777 if (this.alwaysFocusSinglePod && !pod) {
2778 this.focusPod(this.focusedPod_, true /* force */);
2779 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
2780 this.focusedPod_.isActionBoxMenuHovered = false;
2785 * Handler of mouse move event.
2786 * @param {Event} e Click Event object.
2789 handleMouseMove_: function(e) {
2792 if (e.webkitMovementX == 0 && e.webkitMovementY == 0)
2795 // Defocus (thus hide) action box, if it is focused on a user pod
2796 // and the pointer is not hovering over it.
2797 var pod = findAncestorByClass(e.target, 'pod');
2798 if (document.activeElement &&
2799 document.activeElement.parentNode != pod &&
2800 document.activeElement.classList.contains('action-box-area')) {
2801 document.activeElement.parentNode.focus();
2805 pod.isActionBoxMenuHovered = true;
2807 // Hide action boxes on other user pods.
2808 for (var i = 0, p; p = this.pods[i]; ++i)
2809 if (p != pod && !p.isActionBoxMenuActive)
2810 p.isActionBoxMenuHovered = false;
2814 * Handles focus event.
2815 * @param {Event} e Focus Event object.
2818 handleFocus_: function(e) {
2821 if (e.target.parentNode == this) {
2823 if (e.target.classList.contains('focused')) {
2824 if (!e.target.multiProfilesPolicyApplied)
2825 e.target.focusInput();
2827 e.target.userTypeBubbleElement.classList.add('bubble-shown');
2829 this.focusPod(e.target);
2833 var pod = findAncestorByClass(e.target, 'pod');
2834 if (pod && pod.parentNode == this) {
2835 // Focus on a control of a pod but not on the action area button.
2836 if (!pod.classList.contains('focused') &&
2837 !e.target.classList.contains('action-box-button')) {
2839 pod.userTypeBubbleElement.classList.remove('bubble-shown');
2845 // Clears pod focus when we reach here. It means new focus is neither
2846 // on a pod nor on a button/input for a pod.
2847 // Do not "defocus" user pod when it is a single pod.
2848 // That means that 'focused' class will not be removed and
2849 // input field/button will always be visible.
2850 if (!this.alwaysFocusSinglePod)
2853 // Hide user-type-bubble in case this is one pod and we lost focus of
2855 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
2860 * Handler of keydown event.
2861 * @param {Event} e KeyDown Event object.
2863 handleKeyDown: function(e) {
2866 var editing = e.target.tagName == 'INPUT' && e.target.value;
2867 switch (e.keyIdentifier) {
2870 this.keyboardActivated_ = true;
2871 if (this.focusedPod_ && this.focusedPod_.previousElementSibling)
2872 this.focusPod(this.focusedPod_.previousElementSibling);
2874 this.focusPod(this.lastElementChild);
2876 e.stopPropagation();
2881 this.keyboardActivated_ = true;
2882 if (this.focusedPod_ && this.focusedPod_.nextElementSibling)
2883 this.focusPod(this.focusedPod_.nextElementSibling);
2885 this.focusPod(this.firstElementChild);
2887 e.stopPropagation();
2891 if (this.focusedPod_) {
2892 var targetTag = e.target.tagName;
2893 if (e.target == this.focusedPod_.passwordElement ||
2894 (targetTag != 'INPUT' &&
2895 targetTag != 'BUTTON' &&
2896 targetTag != 'A')) {
2897 this.setActivatedPod(this.focusedPod_, e);
2898 e.stopPropagation();
2902 case 'U+001B': // Esc
2903 if (!this.alwaysFocusSinglePod)
2910 * Called right after the pod row is shown.
2912 handleAfterShow: function() {
2913 // Without timeout changes in pods positions will be animated even though
2914 // it happened when 'flying-pods' class was disabled.
2915 setTimeout(function() {
2916 Oobe.getInstance().toggleClass('flying-pods', true);
2918 // Force input focus for user pod on show and once transition ends.
2919 if (this.focusedPod_) {
2920 var focusedPod = this.focusedPod_;
2921 var screen = this.parentNode;
2923 focusedPod.addEventListener('webkitTransitionEnd', function f(e) {
2924 focusedPod.removeEventListener('webkitTransitionEnd', f);
2925 focusedPod.reset(true);
2926 // Notify screen that it is ready.
2929 // Guard timer for 1 second -- it would conver all possible animations.
2930 ensureTransitionEndEvent(focusedPod, 1000);
2935 * Called right before the pod row is shown.
2937 handleBeforeShow: function() {
2938 Oobe.getInstance().toggleClass('flying-pods', false);
2939 for (var event in this.listeners_) {
2940 this.ownerDocument.addEventListener(
2941 event, this.listeners_[event][0], this.listeners_[event][1]);
2943 $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR;
2945 if (this.podPlacementPostponed_) {
2946 this.podPlacementPostponed_ = false;
2948 this.maybePreselectPod();
2953 * Called when the element is hidden.
2955 handleHide: function() {
2956 for (var event in this.listeners_) {
2957 this.ownerDocument.removeEventListener(
2958 event, this.listeners_[event][0], this.listeners_[event][1]);
2960 $('login-header-bar').buttonsTabIndex = 0;
2964 * Called when a pod's user image finishes loading.
2966 handlePodImageLoad: function(pod) {
2967 var index = this.podsWithPendingImages_.indexOf(pod);
2972 this.podsWithPendingImages_.splice(index, 1);
2973 if (this.podsWithPendingImages_.length == 0) {
2974 this.classList.remove('images-loading');
2979 * Preselects pod, if needed.
2981 maybePreselectPod: function() {
2982 var pod = this.preselectedPod;
2985 // Hide user-type-bubble in case all user pods are disabled and we focus
2987 if (pod && pod.multiProfilesPolicyApplied) {
2988 pod.userTypeBubbleElement.classList.remove('bubble-shown');