1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
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 * Maximal number of columns currently supported by pod-row.
30 var MAX_NUMBER_OF_COLUMNS = 6;
33 * Maximal number of rows if sign-in banner is displayed alonside.
37 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2;
40 * Variables used for pod placement processing. Width and height should be
41 * synced with computed CSS sizes of pods.
44 var CROS_POD_HEIGHT = 213;
45 var DESKTOP_POD_HEIGHT = 216;
46 var POD_ROW_PADDING = 10;
49 * Whether to preselect the first pod automatically on login screen.
53 var PRESELECT_FIRST_POD = true;
56 * Maximum time for which the pod row remains hidden until all user images
61 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000;
64 * Public session help topic identifier.
68 var HELP_TOPIC_PUBLIC_SESSION = 3041033;
71 * Tab order for user pods. Update these when adding new controls.
75 var UserPodTabOrder = {
76 POD_INPUT: 1, // Password input fields (and whole pods themselves).
77 HEADER_BAR: 2, // Buttons on the header bar (Shutdown, Add User).
78 ACTION_BOX: 3, // Action box buttons.
79 PAD_MENU_ITEM: 4 // User pad menu items (Remove this user).
82 // Focus and tab order are organized as follows:
84 // (1) all user pods have tab index 1 so they are traversed first;
85 // (2) when a user pod is activated, its tab index is set to -1 and its
86 // main input field gets focus and tab index 1;
87 // (3) buttons on the header bar have tab index 2 so they follow user pods;
88 // (4) Action box buttons have tab index 3 and follow header bar buttons;
89 // (5) lastly, focus jumps to the Status Area and back to user pods.
91 // 'Focus' event is handled by a capture handler for the whole document
92 // and in some cases 'mousedown' event handlers are used instead of 'click'
93 // handlers where it's necessary to prevent 'focus' event from being fired.
96 * Helper function to remove a class from given element.
97 * @param {!HTMLElement} el Element whose class list to change.
98 * @param {string} cl Class to remove.
100 function removeClass(el, cl) {
101 el.classList.remove(cl);
105 * Creates a user pod.
107 * @extends {HTMLDivElement}
109 var UserPod = cr.ui.define(function() {
110 var node = $('user-pod-template').cloneNode(true);
111 node.removeAttribute('id');
116 * Stops event propagation from the any user pod child element.
117 * @param {Event} e Event to handle.
119 function stopEventPropagation(e) {
120 // Prevent default so that we don't trigger a 'focus' event.
126 * Unique salt added to user image URLs to prevent caching. Dictionary with
127 * user names as keys.
130 UserPod.userImageSalt_ = {};
132 UserPod.prototype = {
133 __proto__: HTMLDivElement.prototype,
136 decorate: function() {
137 this.tabIndex = UserPodTabOrder.POD_INPUT;
138 this.customButton.tabIndex = UserPodTabOrder.POD_INPUT;
139 this.actionBoxAreaElement.tabIndex = UserPodTabOrder.ACTION_BOX;
141 this.addEventListener('click',
142 this.handleClickOnPod_.bind(this));
144 this.signinButtonElement.addEventListener('click',
145 this.activate.bind(this));
147 this.actionBoxAreaElement.addEventListener('mousedown',
148 stopEventPropagation);
149 this.actionBoxAreaElement.addEventListener('click',
150 this.handleActionAreaButtonClick_.bind(this));
151 this.actionBoxAreaElement.addEventListener('keydown',
152 this.handleActionAreaButtonKeyDown_.bind(this));
154 this.actionBoxMenuRemoveElement.addEventListener('click',
155 this.handleRemoveCommandClick_.bind(this));
156 this.actionBoxMenuRemoveElement.addEventListener('keydown',
157 this.handleRemoveCommandKeyDown_.bind(this));
158 this.actionBoxMenuRemoveElement.addEventListener('blur',
159 this.handleRemoveCommandBlur_.bind(this));
161 if (this.actionBoxRemoveUserWarningButtonElement) {
162 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
164 this.handleRemoveUserConfirmationClick_.bind(this));
167 this.customButton.addEventListener('click',
168 this.handleCustomButtonClick_.bind(this));
172 * Initializes the pod after its properties set and added to a pod row.
174 initialize: function() {
175 this.passwordElement.addEventListener('keydown',
176 this.parentNode.handleKeyDown.bind(this.parentNode));
177 this.passwordElement.addEventListener('keypress',
178 this.handlePasswordKeyPress_.bind(this));
180 this.imageElement.addEventListener('load',
181 this.parentNode.handlePodImageLoad.bind(this.parentNode, this));
185 * Resets tab order for pod elements to its initial state.
187 resetTabOrder: function() {
188 this.tabIndex = UserPodTabOrder.POD_INPUT;
189 this.mainInput.tabIndex = -1;
193 * Handles keypress event (i.e. any textual input) on password input.
194 * @param {Event} e Keypress Event object.
197 handlePasswordKeyPress_: function(e) {
198 // When tabbing from the system tray a tab key press is received. Suppress
199 // this so as not to type a tab character into the password field.
200 if (e.keyCode == 9) {
207 * Top edge margin number of pixels.
211 this.style.top = cr.ui.toCssPx(top);
214 * Left edge margin number of pixels.
218 this.style.left = cr.ui.toCssPx(left);
222 * Gets signed in indicator element.
223 * @type {!HTMLDivElement}
225 get signedInIndicatorElement() {
226 return this.querySelector('.signed-in-indicator');
230 * Gets image element.
231 * @type {!HTMLImageElement}
234 return this.querySelector('.user-image');
239 * @type {!HTMLDivElement}
242 return this.querySelector('.name');
246 * Gets password field.
247 * @type {!HTMLInputElement}
249 get passwordElement() {
250 return this.querySelector('.password');
254 * Gets Caps Lock hint image.
255 * @type {!HTMLImageElement}
257 get capslockHintElement() {
258 return this.querySelector('.capslock-hint');
262 * Gets user sign in button.
263 * @type {!HTMLButtonElement}
265 get signinButtonElement() {
266 return this.querySelector('.signin-button');
270 * Gets launch app button.
271 * @type {!HTMLButtonElement}
273 get launchAppButtonElement() {
274 return this.querySelector('.launch-app-button');
278 * Gets action box area.
279 * @type {!HTMLInputElement}
281 get actionBoxAreaElement() {
282 return this.querySelector('.action-box-area');
286 * Gets user type icon area.
287 * @type {!HTMLDivElement}
289 get userTypeIconAreaElement() {
290 return this.querySelector('.user-type-icon-area');
294 * Gets user type icon.
295 * @type {!HTMLDivElement}
297 get userTypeIconElement() {
298 return this.querySelector('.user-type-icon-image');
302 * Gets action box menu.
303 * @type {!HTMLInputElement}
305 get actionBoxMenuElement() {
306 return this.querySelector('.action-box-menu');
310 * Gets action box menu title.
311 * @type {!HTMLInputElement}
313 get actionBoxMenuTitleElement() {
314 return this.querySelector('.action-box-menu-title');
318 * Gets action box menu title, user name item.
319 * @type {!HTMLInputElement}
321 get actionBoxMenuTitleNameElement() {
322 return this.querySelector('.action-box-menu-title-name');
326 * Gets action box menu title, user email item.
327 * @type {!HTMLInputElement}
329 get actionBoxMenuTitleEmailElement() {
330 return this.querySelector('.action-box-menu-title-email');
334 * Gets action box menu, remove user command item.
335 * @type {!HTMLInputElement}
337 get actionBoxMenuCommandElement() {
338 return this.querySelector('.action-box-menu-remove-command');
342 * Gets action box menu, remove user command item div.
343 * @type {!HTMLInputElement}
345 get actionBoxMenuRemoveElement() {
346 return this.querySelector('.action-box-menu-remove');
350 * Gets action box menu, remove user command item div.
351 * @type {!HTMLInputElement}
353 get actionBoxRemoveUserWarningElement() {
354 return this.querySelector('.action-box-remove-user-warning');
358 * Gets action box menu, remove user command item div.
359 * @type {!HTMLInputElement}
361 get actionBoxRemoveUserWarningButtonElement() {
362 return this.querySelector(
363 '.remove-warning-button');
367 * Gets the locked user indicator box.
368 * @type {!HTMLInputElement}
370 get lockedIndicatorElement() {
371 return this.querySelector('.locked-indicator');
375 * Gets the custom button. This button is normally hidden, but can be shown
376 * using the chrome.screenlockPrivate API.
377 * @type {!HTMLInputElement}
380 return this.querySelector('.custom-button');
384 * Updates the user pod element.
387 this.imageElement.src = 'chrome://userimage/' + this.user.username +
388 '?id=' + UserPod.userImageSalt_[this.user.username];
390 this.nameElement.textContent = this.user_.displayName;
391 this.signedInIndicatorElement.hidden = !this.user_.signedIn;
393 var forceOnlineSignin = this.forceOnlineSignin;
394 this.passwordElement.hidden = forceOnlineSignin;
395 this.signinButtonElement.hidden = !forceOnlineSignin;
397 this.updateActionBoxArea();
399 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
400 'passwordFieldAccessibleName', this.user_.emailAddress));
402 this.customizeUserPodPerUserType();
405 updateActionBoxArea: function() {
406 if (this.user_.publicAccount || this.user_.isApp) {
407 this.actionBoxAreaElement.hidden = true;
411 this.actionBoxAreaElement.hidden = false;
412 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
414 this.actionBoxAreaElement.setAttribute(
415 'aria-label', loadTimeData.getStringF(
416 'podMenuButtonAccessibleName', this.user_.emailAddress));
417 this.actionBoxMenuRemoveElement.setAttribute(
418 'aria-label', loadTimeData.getString(
419 'podMenuRemoveItemAccessibleName'));
420 this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ?
421 loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) :
422 this.user_.displayName;
423 this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress;
424 this.actionBoxMenuTitleEmailElement.hidden =
425 this.user_.locallyManagedUser;
427 this.actionBoxMenuCommandElement.textContent =
428 loadTimeData.getString('removeUser');
431 customizeUserPodPerUserType: function() {
432 var isMultiProfilesUI =
433 (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
435 if (this.user_.locallyManagedUser) {
436 this.setUserPodIconType('supervised');
437 } else if (isMultiProfilesUI && !this.user_.isMultiProfilesAllowed) {
438 // Mark user pod as not focusable which in addition to the grayed out
439 // filter makes it look in disabled state.
440 this.classList.add('not-focusable');
441 this.setUserPodIconType('policy');
443 this.querySelector('.mp-policy-title').hidden = false;
444 if (this.user.multiProfilesPolicy == 'primary-only')
445 this.querySelector('.mp-policy-primary-only-msg').hidden = false;
447 this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
448 } else if (this.user_.isApp) {
449 this.setUserPodIconType('app');
453 setUserPodIconType: function(userTypeClass) {
454 this.userTypeIconAreaElement.classList.add(userTypeClass);
455 this.userTypeIconAreaElement.hidden = false;
459 * The user that this pod represents.
467 this.user_ = userDict;
472 * Whether this user must authenticate against GAIA.
474 get forceOnlineSignin() {
475 return this.user.forceOnlineSignin && !this.user.signedIn;
479 * Gets main input element.
480 * @type {(HTMLButtonElement|HTMLInputElement)}
483 if (!this.signinButtonElement.hidden)
484 return this.signinButtonElement;
486 return this.passwordElement;
490 * Whether action box button is in active state.
493 get isActionBoxMenuActive() {
494 return this.actionBoxAreaElement.classList.contains('active');
496 set isActionBoxMenuActive(active) {
497 if (active == this.isActionBoxMenuActive)
501 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
502 if (this.actionBoxRemoveUserWarningElement)
503 this.actionBoxRemoveUserWarningElement.hidden = true;
505 // Clear focus first if another pod is focused.
506 if (!this.parentNode.isFocused(this)) {
507 this.parentNode.focusPod(undefined, true);
508 this.actionBoxAreaElement.focus();
510 this.actionBoxAreaElement.classList.add('active');
512 this.actionBoxAreaElement.classList.remove('active');
517 * Whether action box button is in hovered state.
520 get isActionBoxMenuHovered() {
521 return this.actionBoxAreaElement.classList.contains('hovered');
523 set isActionBoxMenuHovered(hovered) {
524 if (hovered == this.isActionBoxMenuHovered)
528 this.actionBoxAreaElement.classList.add('hovered');
529 this.classList.add('hovered');
531 this.actionBoxAreaElement.classList.remove('hovered');
532 this.classList.remove('hovered');
537 * Updates the image element of the user.
539 updateUserImage: function() {
540 UserPod.userImageSalt_[this.user.username] = new Date().getTime();
545 * Focuses on input element.
547 focusInput: function() {
548 var forceOnlineSignin = this.forceOnlineSignin;
549 this.signinButtonElement.hidden = !forceOnlineSignin;
550 this.passwordElement.hidden = forceOnlineSignin;
552 // Move tabIndex from the whole pod to the main input.
554 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
555 this.mainInput.focus();
560 * @param {Event} e Event object.
561 * @return {boolean} True if activated successfully.
563 activate: function(e) {
564 if (this.forceOnlineSignin) {
566 } else if (!this.passwordElement.value) {
569 Oobe.disableSigninUI();
570 chrome.send('authenticateUser',
571 [this.user.username, this.passwordElement.value]);
577 showSupervisedUserSigninWarning: function() {
578 // Locally managed user token has been invalidated.
579 // Make sure that pod is focused i.e. "Sign in" button is seen.
580 this.parentNode.focusPod(this);
582 var error = document.createElement('div');
583 var messageDiv = document.createElement('div');
584 messageDiv.className = 'error-message-bubble';
585 messageDiv.textContent =
586 loadTimeData.getString('supervisedUserExpiredTokenWarning');
587 error.appendChild(messageDiv);
589 $('bubble').showContentForElement(
590 this.signinButtonElement,
591 cr.ui.Bubble.Attachment.TOP,
593 this.signinButtonElement.offsetWidth / 2,
598 * Shows signin UI for this user.
600 showSigninUI: function() {
601 if (this.user.locallyManagedUser) {
602 this.showSupervisedUserSigninWarning();
604 this.parentNode.showSigninUI(this.user.emailAddress);
609 * Resets the input field and updates the tab order of pod controls.
610 * @param {boolean} takeFocus If true, input field takes focus.
612 reset: function(takeFocus) {
613 this.passwordElement.value = '';
615 this.focusInput(); // This will set a custom tab order.
617 this.resetTabOrder();
621 * Handles a click event on action area button.
622 * @param {Event} e Click event.
624 handleActionAreaButtonClick_: function(e) {
625 if (this.parentNode.disabled)
627 this.isActionBoxMenuActive = !this.isActionBoxMenuActive;
631 * Handles a keydown event on action area button.
632 * @param {Event} e KeyDown event.
634 handleActionAreaButtonKeyDown_: function(e) {
637 switch (e.keyIdentifier) {
639 case 'U+0020': // Space
640 if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive)
641 this.isActionBoxMenuActive = true;
646 if (this.isActionBoxMenuActive) {
647 this.actionBoxMenuRemoveElement.tabIndex =
648 UserPodTabOrder.PAD_MENU_ITEM;
649 this.actionBoxMenuRemoveElement.focus();
653 case 'U+001B': // Esc
654 this.isActionBoxMenuActive = false;
657 case 'U+0009': // Tab
658 this.parentNode.focusPod();
660 this.isActionBoxMenuActive = false;
666 * Handles a click event on remove user command.
667 * @param {Event} e Click event.
669 handleRemoveCommandClick_: function(e) {
670 if (this.user.locallyManagedUser || this.user.isDesktopUser) {
671 this.showRemoveWarning_();
674 if (this.isActionBoxMenuActive)
675 chrome.send('removeUser', [this.user.username]);
679 * Shows remove warning for managed users.
681 showRemoveWarning_: function() {
682 this.actionBoxMenuRemoveElement.hidden = true;
683 this.actionBoxRemoveUserWarningElement.hidden = false;
687 * Handles a click event on remove user confirmation button.
688 * @param {Event} e Click event.
690 handleRemoveUserConfirmationClick_: function(e) {
691 if (this.isActionBoxMenuActive)
692 chrome.send('removeUser', [this.user.username]);
696 * Handles a keydown event on remove command.
697 * @param {Event} e KeyDown event.
699 handleRemoveCommandKeyDown_: function(e) {
702 switch (e.keyIdentifier) {
704 chrome.send('removeUser', [this.user.username]);
711 case 'U+001B': // Esc
712 this.actionBoxAreaElement.focus();
713 this.isActionBoxMenuActive = false;
717 this.actionBoxAreaElement.focus();
718 this.isActionBoxMenuActive = false;
724 * Handles a blur event on remove command.
725 * @param {Event} e Blur event.
727 handleRemoveCommandBlur_: function(e) {
730 this.actionBoxMenuRemoveElement.tabIndex = -1;
734 * Handles click event on a user pod.
735 * @param {Event} e Click event.
737 handleClickOnPod_: function(e) {
738 if (this.parentNode.disabled)
741 if (this.forceOnlineSignin && !this.isActionBoxMenuActive) {
743 // Prevent default so that we don't trigger 'focus' event.
749 * Called when the custom button is clicked.
751 handleCustomButtonClick_: function() {
752 chrome.send('customButtonClicked', [this.user.username]);
757 * Creates a public account user pod.
761 var PublicAccountUserPod = cr.ui.define(function() {
762 var node = UserPod();
764 var extras = $('public-account-user-pod-extras-template').children;
765 for (var i = 0; i < extras.length; ++i) {
766 var el = extras[i].cloneNode(true);
767 node.appendChild(el);
773 PublicAccountUserPod.prototype = {
774 __proto__: UserPod.prototype,
777 * "Enter" button in expanded side pane.
778 * @type {!HTMLButtonElement}
780 get enterButtonElement() {
781 return this.querySelector('.enter-button');
785 * Boolean flag of whether the pod is showing the side pane. The flag
786 * controls whether 'expanded' class is added to the pod's class list and
787 * resets tab order because main input element changes when the 'expanded'
792 return this.classList.contains('expanded');
794 set expanded(expanded) {
795 if (this.expanded == expanded)
798 this.resetTabOrder();
799 this.classList.toggle('expanded', expanded);
802 this.classList.add('animating');
803 this.addEventListener('webkitTransitionEnd', function f(e) {
804 self.removeEventListener('webkitTransitionEnd', f);
805 self.classList.remove('animating');
807 // Accessibility focus indicator does not move with the focused
808 // element. Sends a 'focus' event on the currently focused element
809 // so that accessibility focus indicator updates its location.
810 if (document.activeElement)
811 document.activeElement.dispatchEvent(new Event('focus'));
816 get forceOnlineSignin() {
823 return this.enterButtonElement;
825 return this.nameElement;
829 decorate: function() {
830 UserPod.prototype.decorate.call(this);
832 this.classList.remove('need-password');
833 this.classList.add('public-account');
835 this.nameElement.addEventListener('keydown', (function(e) {
836 if (e.keyIdentifier == 'Enter') {
837 this.parentNode.setActivatedPod(this, e);
838 // Stop this keydown event from bubbling up to PodRow handler.
840 // Prevent default so that we don't trigger a 'click' event on the
841 // newly focused "Enter" button.
846 var learnMore = this.querySelector('.learn-more');
847 learnMore.addEventListener('mousedown', stopEventPropagation);
848 learnMore.addEventListener('click', this.handleLearnMoreEvent);
849 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
851 learnMore = this.querySelector('.side-pane-learn-more');
852 learnMore.addEventListener('click', this.handleLearnMoreEvent);
853 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
855 this.enterButtonElement.addEventListener('click', (function(e) {
856 this.enterButtonElement.disabled = true;
857 chrome.send('launchPublicAccount', [this.user.username]);
862 * Updates the user pod element.
865 UserPod.prototype.update.call(this);
866 this.querySelector('.side-pane-name').textContent =
867 this.user_.displayName;
868 this.querySelector('.info').textContent =
869 loadTimeData.getStringF('publicAccountInfoFormat',
870 this.user_.enterpriseDomain);
874 focusInput: function() {
875 // Move tabIndex from the whole pod to the main input.
877 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
878 this.mainInput.focus();
882 reset: function(takeFocus) {
884 this.expanded = false;
885 this.enterButtonElement.disabled = false;
886 UserPod.prototype.reset.call(this, takeFocus);
890 activate: function(e) {
891 this.expanded = true;
897 handleClickOnPod_: function(e) {
898 if (this.parentNode.disabled)
901 this.parentNode.focusPod(this);
902 this.parentNode.setActivatedPod(this, e);
903 // Prevent default so that we don't trigger 'focus' event.
908 * Handle mouse and keyboard events for the learn more button. Triggering
909 * the button causes information about public sessions to be shown.
910 * @param {Event} event Mouse or keyboard event.
912 handleLearnMoreEvent: function(event) {
913 switch (event.type) {
914 // Show informaton on left click. Let any other clicks propagate.
916 if (event.button != 0)
919 // Show informaton when <Return> or <Space> is pressed. Let any other
920 // key presses propagate.
922 switch (event.keyCode) {
931 chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]);
932 stopEventPropagation(event);
937 * Creates a user pod to be used only in desktop chrome.
941 var DesktopUserPod = cr.ui.define(function() {
942 // Don't just instantiate a UserPod(), as this will call decorate() on the
943 // parent object, and add duplicate event listeners.
944 var node = $('user-pod-template').cloneNode(true);
945 node.removeAttribute('id');
949 DesktopUserPod.prototype = {
950 __proto__: UserPod.prototype,
954 if (!this.passwordElement.hidden)
955 return this.passwordElement;
957 return this.nameElement;
961 decorate: function() {
962 UserPod.prototype.decorate.call(this);
967 // TODO(noms): Use the actual profile avatar for local profiles once the
968 // new, non-pixellated avatars are available.
969 this.imageElement.src = this.user.emailAddress == '' ?
970 'chrome://theme/IDR_USER_MANAGER_DEFAULT_AVATAR' :
972 this.nameElement.textContent = this.user.displayName;
974 var isLockedUser = this.user.needsSignin;
975 this.signinButtonElement.hidden = true;
976 this.lockedIndicatorElement.hidden = !isLockedUser;
977 this.passwordElement.hidden = !isLockedUser;
978 this.nameElement.hidden = isLockedUser;
980 UserPod.prototype.updateActionBoxArea.call(this);
984 focusInput: function() {
985 // For focused pods, display the name unless the pod is locked.
986 var isLockedUser = this.user.needsSignin;
987 this.signinButtonElement.hidden = true;
988 this.lockedIndicatorElement.hidden = !isLockedUser;
989 this.passwordElement.hidden = !isLockedUser;
990 this.nameElement.hidden = isLockedUser;
992 // Move tabIndex from the whole pod to the main input.
994 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
995 this.mainInput.focus();
999 reset: function(takeFocus) {
1000 // Always display the user's name for unfocused pods.
1002 this.nameElement.hidden = false;
1003 UserPod.prototype.reset.call(this, takeFocus);
1007 activate: function(e) {
1008 if (this.passwordElement.hidden) {
1009 Oobe.launchUser(this.user.emailAddress, this.user.displayName);
1010 } else if (!this.passwordElement.value) {
1013 chrome.send('authenticatedLaunchUser',
1014 [this.user.emailAddress,
1015 this.user.displayName,
1016 this.passwordElement.value]);
1018 this.passwordElement.value = '';
1023 handleClickOnPod_: function(e) {
1024 if (this.parentNode.disabled)
1028 this.parentNode.lastFocusedPod_ = this;
1030 // If this is an unlocked pod, then open a browser window. Otherwise
1031 // just activate the pod and show the password field.
1032 if (!this.user.needsSignin && !this.isActionBoxMenuActive)
1037 handleRemoveUserConfirmationClick_: function(e) {
1038 chrome.send('removeUser', [this.user.profilePath]);
1043 * Creates a user pod that represents kiosk app.
1045 * @extends {UserPod}
1047 var KioskAppPod = cr.ui.define(function() {
1048 var node = UserPod();
1052 KioskAppPod.prototype = {
1053 __proto__: UserPod.prototype,
1056 decorate: function() {
1057 UserPod.prototype.decorate.call(this);
1058 this.launchAppButtonElement.addEventListener('click',
1059 this.activate.bind(this));
1063 update: function() {
1064 this.imageElement.src = this.user.iconUrl;
1065 if (this.user.iconHeight && this.user.iconWidth) {
1066 this.imageElement.style.height = this.user.iconHeight;
1067 this.imageElement.style.width = this.user.iconWidth;
1069 this.imageElement.alt = this.user.label;
1070 this.imageElement.title = this.user.label;
1071 this.passwordElement.hidden = true;
1072 this.signinButtonElement.hidden = true;
1073 this.launchAppButtonElement.hidden = false;
1074 this.signedInIndicatorElement.hidden = true;
1075 this.nameElement.textContent = this.user.label;
1077 UserPod.prototype.updateActionBoxArea.call(this);
1078 UserPod.prototype.customizeUserPodPerUserType.call(this);
1083 return this.launchAppButtonElement;
1087 focusInput: function() {
1088 this.signinButtonElement.hidden = true;
1089 this.launchAppButtonElement.hidden = false;
1090 this.passwordElement.hidden = true;
1092 // Move tabIndex from the whole pod to the main input.
1094 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1095 this.mainInput.focus();
1099 get forceOnlineSignin() {
1104 activate: function(e) {
1105 var diagnosticMode = e && e.ctrlKey;
1106 this.launchApp_(this.user, diagnosticMode);
1111 handleClickOnPod_: function(e) {
1112 if (this.parentNode.disabled)
1116 this.parentNode.lastFocusedPod_ = this;
1121 * Launch the app. If |diagnosticMode| is true, ask user to confirm.
1122 * @param {Object} app App data.
1123 * @param {boolean} diagnosticMode Whether to run the app in diagnostic
1126 launchApp_: function(app, diagnosticMode) {
1127 if (!diagnosticMode) {
1128 chrome.send('launchKioskApp', [app.id, false]);
1132 var oobe = $('oobe');
1133 if (!oobe.confirmDiagnosticMode_) {
1134 oobe.confirmDiagnosticMode_ =
1135 new cr.ui.dialogs.ConfirmDialog(document.body);
1136 oobe.confirmDiagnosticMode_.setOkLabel(
1137 loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
1138 oobe.confirmDiagnosticMode_.setCancelLabel(
1139 loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
1142 oobe.confirmDiagnosticMode_.show(
1143 loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
1146 chrome.send('launchKioskApp', [app.id, true]);
1152 * Creates a new pod row element.
1154 * @extends {HTMLDivElement}
1156 var PodRow = cr.ui.define('podrow');
1158 PodRow.prototype = {
1159 __proto__: HTMLDivElement.prototype,
1161 // Whether this user pod row is shown for the first time.
1164 // True if inside focusPod().
1165 insideFocusPod_: false,
1168 focusedPod_: undefined,
1170 // Activated pod, i.e. the pod of current login attempt.
1171 activatedPod_: undefined,
1173 // Pod that was most recently focused, if any.
1174 lastFocusedPod_: undefined,
1176 // Pods whose initial images haven't been loaded yet.
1177 podsWithPendingImages_: [],
1179 // Whether pod creation is animated.
1180 userAddIsAnimated_: false,
1182 // Whether pod placement has been postponed.
1183 podPlacementPostponed_: false,
1185 // Standard user pod height/width.
1189 // Array of apps that are shown in addition to other user pods.
1192 // Array of users that are shown (public/supervised/regular).
1196 decorate: function() {
1197 // Event listeners that are installed for the time period during which
1198 // the element is visible.
1200 focus: [this.handleFocus_.bind(this), true /* useCapture */],
1201 click: [this.handleClick_.bind(this), true],
1202 mousemove: [this.handleMouseMove_.bind(this), false],
1203 keydown: [this.handleKeyDown.bind(this), false]
1206 var isDesktopUserManager = Oobe.getInstance().displayType ==
1207 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1208 this.userPodHeight_ = isDesktopUserManager ? DESKTOP_POD_HEIGHT :
1210 // Same for Chrome OS and desktop.
1211 this.userPodWidth_ = POD_WIDTH;
1215 * Returns all the pods in this pod row.
1219 return Array.prototype.slice.call(this.children);
1223 * Return true if user pod row has only single user pod in it.
1227 return this.children.length == 1;
1231 * Returns pod with the given app id.
1232 * @param {!string} app_id Application id to be matched.
1233 * @return {Object} Pod with the given app id. null if pod hasn't been
1236 getPodWithAppId_: function(app_id) {
1237 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1238 if (pod.user.isApp && pod.user.id == app_id)
1245 * Returns pod with the given username (null if there is no such pod).
1246 * @param {string} username Username to be matched.
1247 * @return {Object} Pod with the given username. null if pod hasn't been
1250 getPodWithUsername_: function(username) {
1251 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1252 if (pod.user.username == username)
1259 * True if the the pod row is disabled (handles no user interaction).
1264 return this.disabled_;
1266 set disabled(value) {
1267 this.disabled_ = value;
1268 var controls = this.querySelectorAll('button,input');
1269 for (var i = 0, control; control = controls[i]; ++i) {
1270 control.disabled = value;
1275 * Creates a user pod from given email.
1276 * @param {!Object} user User info dictionary.
1278 createUserPod: function(user) {
1280 if (user.isDesktopUser)
1281 userPod = new DesktopUserPod({user: user});
1282 else if (user.publicAccount)
1283 userPod = new PublicAccountUserPod({user: user});
1284 else if (user.isApp)
1285 userPod = new KioskAppPod({user: user});
1287 userPod = new UserPod({user: user});
1289 userPod.hidden = false;
1294 * Add an existing user pod to this pod row.
1295 * @param {!Object} user User info dictionary.
1296 * @param {boolean} animated Whether to use init animation.
1298 addUserPod: function(user, animated) {
1299 var userPod = this.createUserPod(user);
1301 userPod.classList.add('init');
1302 userPod.nameElement.classList.add('init');
1305 this.appendChild(userPod);
1306 userPod.initialize();
1310 * Runs app with a given id from the list of loaded apps.
1311 * @param {!string} app_id of an app to run.
1312 * @param {boolean=} opt_diagnostic_mode Whether to run the app in
1313 * diagnostic mode. Default is false.
1315 findAndRunAppForTesting: function(app_id, opt_diagnostic_mode) {
1316 var app = this.getPodWithAppId_(app_id);
1318 var activationEvent = cr.doc.createEvent('MouseEvents');
1319 var ctrlKey = opt_diagnostic_mode;
1320 activationEvent.initMouseEvent('click', true, true, null,
1321 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
1322 app.dispatchEvent(activationEvent);
1327 * Removes user pod from pod row.
1328 * @param {string} email User's email.
1330 removeUserPod: function(username) {
1331 var podToRemove = this.getPodWithUsername_(username);
1332 if (podToRemove == null) {
1333 console.warn('Attempt to remove not existing pod for ' + username +
1337 this.removeChild(podToRemove);
1342 * Returns index of given pod or -1 if not found.
1343 * @param {UserPod} pod Pod to look up.
1346 indexOf_: function(pod) {
1347 for (var i = 0; i < this.pods.length; ++i) {
1348 if (pod == this.pods[i])
1355 * Start first time show animation.
1357 startInitAnimation: function() {
1358 // Schedule init animation.
1359 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1360 window.setTimeout(removeClass, 500 + i * 70, pod, 'init');
1361 window.setTimeout(removeClass, 700 + i * 70, pod.nameElement, 'init');
1366 * Start login success animation.
1368 startAuthenticatedAnimation: function() {
1369 var activated = this.indexOf_(this.activatedPod_);
1370 if (activated == -1)
1373 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1375 pod.classList.add('left');
1376 else if (i > activated)
1377 pod.classList.add('right');
1379 pod.classList.add('zoom');
1384 * Populates pod row with given existing users and start init animation.
1385 * @param {array} users Array of existing user emails.
1386 * @param {boolean} animated Whether to use init animation.
1388 loadPods: function(users, animated) {
1389 this.users_ = users;
1390 this.userAddIsAnimated_ = animated;
1396 * Rebuilds pod row using users_ and apps_ that were previously set or
1399 rebuildPods: function() {
1400 var emptyPodRow = this.pods.length == 0;
1402 // Clear existing pods.
1403 this.innerHTML = '';
1404 this.focusedPod_ = undefined;
1405 this.activatedPod_ = undefined;
1406 this.lastFocusedPod_ = undefined;
1408 // Switch off animation
1409 Oobe.getInstance().toggleClass('flying-pods', false);
1411 // Populate the pod row.
1412 for (var i = 0; i < this.users_.length; ++i)
1413 this.addUserPod(this.users_[i], this.userAddIsAnimated_);
1415 for (var i = 0, pod; pod = this.pods[i]; ++i)
1416 this.podsWithPendingImages_.push(pod);
1418 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
1419 for (var i = 0; i < this.apps_.length; ++i)
1420 this.addUserPod(this.apps_[i], this.userAddIsAnimated_);
1422 // Make sure we eventually show the pod row, even if some image is stuck.
1423 setTimeout(function() {
1424 $('pod-row').classList.remove('images-loading');
1425 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS);
1427 var isCrosAccountPicker = $('login-header-bar').signinUIState ==
1428 SIGNIN_UI_STATE.ACCOUNT_PICKER;
1429 var isDesktopUserManager = Oobe.getInstance().displayType ==
1430 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1432 // Chrome OS: immediately recalculate pods layout only when current UI
1433 // is account picker. Otherwise postpone it.
1434 // Desktop: recalculate pods layout right away.
1435 if (isDesktopUserManager || isCrosAccountPicker) {
1438 // Without timeout changes in pods positions will be animated even
1439 // though it happened when 'flying-pods' class was disabled.
1440 setTimeout(function() {
1441 Oobe.getInstance().toggleClass('flying-pods', true);
1444 this.focusPod(this.preselectedPod);
1446 this.podPlacementPostponed_ = true;
1448 // Update [Cancel] button state.
1449 if ($('login-header-bar').signinUIState ==
1450 SIGNIN_UI_STATE.GAIA_SIGNIN &&
1452 this.pods.length > 0) {
1453 login.GaiaSigninScreen.updateCancelButtonState();
1459 * Adds given apps to the pod row.
1460 * @param {array} apps Array of apps.
1462 setApps: function(apps) {
1465 chrome.send('kioskAppsLoaded');
1467 // Check whether there's a pending kiosk app error.
1468 window.setTimeout(function() {
1469 chrome.send('checkKioskAppLaunchError');
1474 * Shows a button on a user pod with an icon. Clicking on this button
1475 * triggers an event used by the chrome.screenlockPrivate API.
1476 * @param {string} username Username of pod to add button
1477 * @param {string} iconURL URL of the button icon
1479 showUserPodButton: function(username, iconURL) {
1480 var pod = this.getPodWithUsername_(username);
1482 console.error('Unable to show user pod button for ' + username +
1483 ': user pod not found.');
1487 pod.customButton.hidden = false;
1489 pod.customButton.querySelector('.custom-button-icon');
1494 * Called when window was resized.
1496 onWindowResize: function() {
1497 var layout = this.calculateLayout_();
1498 if (layout.columns != this.columns || layout.rows != this.rows)
1503 * Returns width of podrow having |columns| number of columns.
1506 columnsToWidth_: function(columns) {
1507 var margin = MARGIN_BY_COLUMNS[columns];
1508 return 2 * POD_ROW_PADDING + columns *
1509 this.userPodWidth_ + (columns - 1) * margin;
1513 * Returns height of podrow having |rows| number of rows.
1516 rowsToHeight_: function(rows) {
1517 return 2 * POD_ROW_PADDING + rows * this.userPodHeight_;
1521 * Calculates number of columns and rows that podrow should have in order to
1522 * hold as much its pods as possible for current screen size. Also it tries
1523 * to choose layout that looks good.
1524 * @return {{columns: number, rows: number}}
1526 calculateLayout_: function() {
1527 var preferredColumns = this.pods.length < COLUMNS.length ?
1528 COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1];
1529 var maxWidth = Oobe.getInstance().clientAreaSize.width;
1530 var columns = preferredColumns;
1531 while (maxWidth < this.columnsToWidth_(columns) && columns > 1)
1533 var rows = Math.floor((this.pods.length - 1) / columns) + 1;
1534 if (getComputedStyle(
1535 $('signin-banner'), null).getPropertyValue('display') != 'none') {
1536 rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER);
1538 var maxHeigth = Oobe.getInstance().clientAreaSize.height;
1539 while (maxHeigth < this.rowsToHeight_(rows) && rows > 1)
1541 // One more iteration if it's not enough cells to place all pods.
1542 while (maxWidth >= this.columnsToWidth_(columns + 1) &&
1543 columns * rows < this.pods.length &&
1544 columns < MAX_NUMBER_OF_COLUMNS) {
1547 return {columns: columns, rows: rows};
1551 * Places pods onto their positions onto pod grid.
1554 placePods_: function() {
1555 var layout = this.calculateLayout_();
1556 var columns = this.columns = layout.columns;
1557 var rows = this.rows = layout.rows;
1558 var maxPodsNumber = columns * rows;
1559 var margin = MARGIN_BY_COLUMNS[columns];
1560 this.parentNode.setPreferredSize(
1561 this.columnsToWidth_(columns), this.rowsToHeight_(rows));
1562 var height = this.userPodHeight_;
1563 var width = this.userPodWidth_;
1564 this.pods.forEach(function(pod, index) {
1565 if (pod.offsetHeight != height) {
1566 console.error('Pod offsetHeight (' + pod.offsetHeight +
1567 ') and POD_HEIGHT (' + height + ') are not equal.');
1569 if (pod.offsetWidth != width) {
1570 console.error('Pod offsetWidth (' + pod.offsetWidth +
1571 ') and POD_WIDTH (' + width + ') are not equal.');
1573 if (index >= maxPodsNumber) {
1578 var column = index % columns;
1579 var row = Math.floor(index / columns);
1580 pod.left = POD_ROW_PADDING + column * (width + margin);
1581 pod.top = POD_ROW_PADDING + row * height;
1583 Oobe.getInstance().updateScreenSize(this.parentNode);
1587 * Number of columns.
1590 set columns(columns) {
1591 // Cannot use 'columns' here.
1592 this.setAttribute('ncolumns', columns);
1595 return this.getAttribute('ncolumns');
1603 // Cannot use 'rows' here.
1604 this.setAttribute('nrows', rows);
1607 return this.getAttribute('nrows');
1611 * Whether the pod is currently focused.
1612 * @param {UserPod} pod Pod to check for focus.
1613 * @return {boolean} Pod focus status.
1615 isFocused: function(pod) {
1616 return this.focusedPod_ == pod;
1620 * Focuses a given user pod or clear focus when given null.
1621 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
1622 * @param {boolean=} opt_force If true, forces focus update even when
1623 * podToFocus is already focused.
1625 focusPod: function(podToFocus, opt_force) {
1626 if (this.isFocused(podToFocus) && !opt_force) {
1627 this.keyboardActivated_ = false;
1631 // Make sure that we don't focus pods that are not allowed to be focused.
1632 // TODO(nkostylev): Fix various keyboard focus related issues caused
1633 // by this approach. http://crbug.com/339042
1634 if (podToFocus && podToFocus.classList.contains('not-focusable')) {
1635 this.keyboardActivated_ = false;
1639 // Make sure there's only one focusPod operation happening at a time.
1640 if (this.insideFocusPod_) {
1641 this.keyboardActivated_ = false;
1644 this.insideFocusPod_ = true;
1646 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1647 if (!this.isSinglePod) {
1648 pod.isActionBoxMenuActive = false;
1650 if (pod != podToFocus) {
1651 pod.isActionBoxMenuHovered = false;
1652 pod.classList.remove('focused');
1653 pod.classList.remove('faded');
1658 // Clear any error messages for previous pod.
1659 if (!this.isFocused(podToFocus))
1662 var hadFocus = !!this.focusedPod_;
1663 this.focusedPod_ = podToFocus;
1665 podToFocus.classList.remove('faded');
1666 podToFocus.classList.add('focused');
1667 podToFocus.reset(true); // Reset and give focus.
1668 // focusPod() automatically loads wallpaper
1669 if (!podToFocus.user.isApp)
1670 chrome.send('focusPod', [podToFocus.user.username]);
1671 this.firstShown_ = false;
1672 this.lastFocusedPod_ = podToFocus;
1674 this.insideFocusPod_ = false;
1675 this.keyboardActivated_ = false;
1679 * Focuses a given user pod by index or clear focus when given null.
1680 * @param {int=} podToFocus index of User pod to focus.
1681 * @param {boolean=} opt_force If true, forces focus update even when
1682 * podToFocus is already focused.
1684 focusPodByIndex: function(podToFocus, opt_force) {
1685 if (podToFocus < this.pods.length)
1686 this.focusPod(this.pods[podToFocus], opt_force);
1690 * Resets wallpaper to the last active user's wallpaper, if any.
1692 loadLastWallpaper: function() {
1693 if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp)
1694 chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]);
1698 * Returns the currently activated pod.
1701 get activatedPod() {
1702 return this.activatedPod_;
1706 * Sets currently activated pod.
1707 * @param {UserPod} pod Pod to check for focus.
1708 * @param {Event} e Event object.
1710 setActivatedPod: function(pod, e) {
1711 if (pod && pod.activate(e))
1712 this.activatedPod_ = pod;
1716 * The pod of the signed-in user, if any; null otherwise.
1720 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1721 if (pod.user.signedIn)
1728 * The pod that is preselected on user pod row show.
1731 get preselectedPod() {
1732 var lockedPod = this.lockedPod;
1733 var preselectedPod = PRESELECT_FIRST_POD ?
1734 lockedPod || this.pods[0] : lockedPod;
1735 return preselectedPod;
1740 * @param {boolean} takeFocus True to take focus.
1742 reset: function(takeFocus) {
1743 this.disabled = false;
1744 if (this.activatedPod_)
1745 this.activatedPod_.reset(takeFocus);
1749 * Restores input focus to current selected pod, if there is any.
1751 refocusCurrentPod: function() {
1752 if (this.focusedPod_) {
1753 this.focusedPod_.focusInput();
1758 * Clears focused pod password field.
1760 clearFocusedPod: function() {
1761 if (!this.disabled && this.focusedPod_)
1762 this.focusedPod_.reset(true);
1767 * @param {string} email Email for signin UI.
1769 showSigninUI: function(email) {
1770 // Clear any error messages that might still be around.
1772 this.disabled = true;
1773 this.lastFocusedPod_ = this.getPodWithUsername_(email);
1774 Oobe.showSigninUI(email);
1778 * Updates current image of a user.
1779 * @param {string} username User for which to update the image.
1781 updateUserImage: function(username) {
1782 var pod = this.getPodWithUsername_(username);
1784 pod.updateUserImage();
1788 * Indicates that the given user must authenticate against GAIA during the
1790 * @param {string} username User for whom to enforce GAIA sign-in.
1792 forceOnlineSigninForUser: function(username) {
1793 var pod = this.getPodWithUsername_(username);
1795 pod.user.forceOnlineSignin = true;
1798 console.log('Failed to update GAIA state for: ' + username);
1803 * Handler of click event.
1804 * @param {Event} e Click Event object.
1807 handleClick_: function(e) {
1811 // Clear all menus if the click is outside pod menu and its
1813 if (!findAncestorByClass(e.target, 'action-box-menu') &&
1814 !findAncestorByClass(e.target, 'action-box-area')) {
1815 for (var i = 0, pod; pod = this.pods[i]; ++i)
1816 pod.isActionBoxMenuActive = false;
1819 // Clears focus if not clicked on a pod and if there's more than one pod.
1820 var pod = findAncestorByClass(e.target, 'pod');
1821 if ((!pod || pod.parentNode != this) && !this.isSinglePod) {
1826 pod.isActionBoxMenuHovered = true;
1828 // Return focus back to single pod.
1829 if (this.isSinglePod) {
1830 this.focusPod(this.focusedPod_, true /* force */);
1832 this.focusedPod_.isActionBoxMenuHovered = false;
1837 * Handler of mouse move event.
1838 * @param {Event} e Click Event object.
1841 handleMouseMove_: function(e) {
1844 if (e.webkitMovementX == 0 && e.webkitMovementY == 0)
1847 // Defocus (thus hide) action box, if it is focused on a user pod
1848 // and the pointer is not hovering over it.
1849 var pod = findAncestorByClass(e.target, 'pod');
1850 if (document.activeElement &&
1851 document.activeElement.parentNode != pod &&
1852 document.activeElement.classList.contains('action-box-area')) {
1853 document.activeElement.parentNode.focus();
1857 pod.isActionBoxMenuHovered = true;
1859 // Hide action boxes on other user pods.
1860 for (var i = 0, p; p = this.pods[i]; ++i)
1861 if (p != pod && !p.isActionBoxMenuActive)
1862 p.isActionBoxMenuHovered = false;
1866 * Handles focus event.
1867 * @param {Event} e Focus Event object.
1870 handleFocus_: function(e) {
1873 if (e.target.parentNode == this) {
1875 if (e.target.classList.contains('focused'))
1876 e.target.focusInput();
1878 this.focusPod(e.target);
1882 var pod = findAncestorByClass(e.target, 'pod');
1883 if (pod && pod.parentNode == this) {
1884 // Focus on a control of a pod but not on the action area button.
1885 if (!pod.classList.contains('focused') &&
1886 !e.target.classList.contains('action-box-button')) {
1893 // Clears pod focus when we reach here. It means new focus is neither
1894 // on a pod nor on a button/input for a pod.
1895 // Do not "defocus" user pod when it is a single pod.
1896 // That means that 'focused' class will not be removed and
1897 // input field/button will always be visible.
1898 if (!this.isSinglePod)
1903 * Handler of keydown event.
1904 * @param {Event} e KeyDown Event object.
1906 handleKeyDown: function(e) {
1909 var editing = e.target.tagName == 'INPUT' && e.target.value;
1910 switch (e.keyIdentifier) {
1913 this.keyboardActivated_ = true;
1914 if (this.focusedPod_ && this.focusedPod_.previousElementSibling)
1915 this.focusPod(this.focusedPod_.previousElementSibling);
1917 this.focusPod(this.lastElementChild);
1919 e.stopPropagation();
1924 this.keyboardActivated_ = true;
1925 if (this.focusedPod_ && this.focusedPod_.nextElementSibling)
1926 this.focusPod(this.focusedPod_.nextElementSibling);
1928 this.focusPod(this.firstElementChild);
1930 e.stopPropagation();
1934 if (this.focusedPod_) {
1935 this.setActivatedPod(this.focusedPod_, e);
1936 e.stopPropagation();
1939 case 'U+001B': // Esc
1940 if (!this.isSinglePod)
1947 * Called right after the pod row is shown.
1949 handleAfterShow: function() {
1950 // Without timeout changes in pods positions will be animated even though
1951 // it happened when 'flying-pods' class was disabled.
1952 setTimeout(function() {
1953 Oobe.getInstance().toggleClass('flying-pods', true);
1955 // Force input focus for user pod on show and once transition ends.
1956 if (this.focusedPod_) {
1957 var focusedPod = this.focusedPod_;
1958 var screen = this.parentNode;
1960 focusedPod.addEventListener('webkitTransitionEnd', function f(e) {
1961 focusedPod.removeEventListener('webkitTransitionEnd', f);
1962 focusedPod.reset(true);
1963 // Notify screen that it is ready.
1965 if (!focusedPod.user.isApp)
1966 chrome.send('loadWallpaper', [focusedPod.user.username]);
1968 // Guard timer for 1 second -- it would conver all possible animations.
1969 ensureTransitionEndEvent(focusedPod, 1000);
1974 * Called right before the pod row is shown.
1976 handleBeforeShow: function() {
1977 Oobe.getInstance().toggleClass('flying-pods', false);
1978 for (var event in this.listeners_) {
1979 this.ownerDocument.addEventListener(
1980 event, this.listeners_[event][0], this.listeners_[event][1]);
1982 $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR;
1984 if (this.podPlacementPostponed_) {
1985 this.podPlacementPostponed_ = false;
1987 this.focusPod(this.preselectedPod);
1992 * Called when the element is hidden.
1994 handleHide: function() {
1995 for (var event in this.listeners_) {
1996 this.ownerDocument.removeEventListener(
1997 event, this.listeners_[event][0], this.listeners_[event][1]);
1999 $('login-header-bar').buttonsTabIndex = 0;
2003 * Called when a pod's user image finishes loading.
2005 handlePodImageLoad: function(pod) {
2006 var index = this.podsWithPendingImages_.indexOf(pod);
2011 this.podsWithPendingImages_.splice(index, 1);
2012 if (this.podsWithPendingImages_.length == 0) {
2013 this.classList.remove('images-loading');