Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / ui / login / account_picker / user_pod_row.js
blob6115625876b80feb04eddd91c5c6add47165e425
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.
5 /**
6 * @fileoverview User pod row implementation.
7 */
9 cr.define('login', function() {
10 /**
11 * Number of displayed columns depending on user pod count.
12 * @type {Array<number>}
13 * @const
15 var COLUMNS = [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6];
17 /**
18 * Mapping between number of columns in pod-row and margin between user pods
19 * for such layout.
20 * @type {Array<number>}
21 * @const
23 var MARGIN_BY_COLUMNS = [undefined, 40, 40, 40, 40, 40, 12];
25 /**
26 * Mapping between number of columns in the desktop pod-row and margin
27 * between user pods for such layout.
28 * @type {Array<number>}
29 * @const
31 var DESKTOP_MARGIN_BY_COLUMNS = [undefined, 15, 15, 15, 15, 15, 15];
33 /**
34 * Maximal number of columns currently supported by pod-row.
35 * @type {number}
36 * @const
38 var MAX_NUMBER_OF_COLUMNS = 6;
40 /**
41 * Maximal number of rows if sign-in banner is displayed alonside.
42 * @type {number}
43 * @const
45 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2;
47 /**
48 * Variables used for pod placement processing. Width and height should be
49 * synced with computed CSS sizes of pods.
51 var POD_WIDTH = 180;
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;
60 /**
61 * Minimal padding between user pod and virtual keyboard.
62 * @type {number}
63 * @const
65 var USER_POD_KEYBOARD_MIN_PADDING = 20;
67 /**
68 * Maximum time for which the pod row remains hidden until all user images
69 * have been loaded.
70 * @type {number}
71 * @const
73 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000;
75 /**
76 * Public session help topic identifier.
77 * @type {number}
78 * @const
80 var HELP_TOPIC_PUBLIC_SESSION = 3041033;
82 /**
83 * Tab order for user pods. Update these when adding new controls.
84 * @enum {number}
85 * @const
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).
95 /**
96 * Supported authentication types. Keep in sync with the enum in
97 * chrome/browser/signin/screenlock_bridge.h
98 * @enum {number}
99 * @const
101 var AUTH_TYPE = {
102 OFFLINE_PASSWORD: 0,
103 ONLINE_SIGN_IN: 1,
104 NUMERIC_PIN: 2,
105 USER_CLICK: 3,
106 EXPAND_THEN_USER_CLICK: 4,
107 FORCE_OFFLINE_PASSWORD: 5
111 * Names of authentication types.
113 var AUTH_TYPE_NAMES = {
114 0: 'offlinePassword',
115 1: 'onlineSignIn',
116 2: 'numericPin',
117 3: 'userClick',
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.
149 * @constructor
150 * @extends {HTMLDivElement}
152 var UserPod = cr.ui.define(function() {
153 var node = $('user-pod-template').cloneNode(true);
154 node.removeAttribute('id');
155 return node;
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.
164 e.preventDefault();
165 e.stopPropagation();
169 * Creates an element for custom icon shown in a user pod next to the input
170 * field.
171 * @constructor
172 * @extends {HTMLDivElement}
174 var UserPodCustomIcon = cr.ui.define(function() {
175 var node = document.createElement('div');
176 node.classList.add('custom-icon-container');
177 node.hidden = true;
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);
183 return node;
187 * The supported user pod custom icons.
188 * {@code id} properties should be in sync with values set by C++ side.
189 * {@code class} properties are CSS classes used to set the icons' background.
190 * @const {Array<{id: !string, class: !string}>}
192 UserPodCustomIcon.ICONS = [
193 {id: 'locked', class: 'custom-icon-locked'},
194 {id: 'locked-to-be-activated',
195 class: 'custom-icon-locked-to-be-activated'},
196 {id: 'locked-with-proximity-hint',
197 class: 'custom-icon-locked-with-proximity-hint'},
198 {id: 'unlocked', class: 'custom-icon-unlocked'},
199 {id: 'hardlocked', class: 'custom-icon-hardlocked'},
200 {id: 'spinner', class: 'custom-icon-spinner'}
204 * The hover state for the icon. When user hovers over the icon, a tooltip
205 * should be shown after a short delay. This enum is used to keep track of
206 * the tooltip status related to hover state.
207 * @enum {string}
209 UserPodCustomIcon.HoverState = {
210 /** The user is not hovering over the icon. */
211 NO_HOVER: 'no_hover',
213 /** The user is hovering over the icon but the tooltip is not activated. */
214 HOVER: 'hover',
217 * User is hovering over the icon and the tooltip is activated due to the
218 * hover state (which happens with delay after user starts hovering).
220 HOVER_TOOLTIP: 'hover_tooltip'
224 * If the icon has a tooltip that should be automatically shown, the tooltip
225 * is shown even when there is no user action (i.e. user is not hovering over
226 * the icon), after a short delay. The tooltip should be hidden after some
227 * time. Note that the icon will not be considered autoshown if it was
228 * previously shown as a result of the user action.
229 * This enum is used to keep track of this state.
230 * @enum {string}
232 UserPodCustomIcon.TooltipAutoshowState = {
233 /** The tooltip should not be or was not automatically shown. */
234 DISABLED: 'disabled',
237 * The tooltip should be automatically shown, but the timeout for showing
238 * the tooltip has not yet passed.
240 ENABLED: 'enabled',
242 /** The tooltip was automatically shown. */
243 ACTIVE : 'active'
246 UserPodCustomIcon.prototype = {
247 __proto__: HTMLDivElement.prototype,
250 * The id of the icon being shown.
251 * @type {string}
252 * @private
254 iconId_: '',
257 * A reference to the timeout for updating icon hover state. Non-null
258 * only if there is an active timeout.
259 * @type {?number}
260 * @private
262 updateHoverStateTimeout_: null,
265 * A reference to the timeout for updating icon tooltip autoshow state.
266 * Non-null only if there is an active timeout.
267 * @type {?number}
268 * @private
270 updateTooltipAutoshowStateTimeout_: null,
273 * Callback for click and 'Enter' key events that gets set if the icon is
274 * interactive.
275 * @type {?function()}
276 * @private
278 actionHandler_: null,
281 * The current tooltip state.
282 * @type {{active: function(): boolean,
283 * autoshow: !UserPodCustomIcon.TooltipAutoshowState,
284 * hover: !UserPodCustomIcon.HoverState,
285 * text: string}}
286 * @private
288 tooltipState_: {
290 * Utility method for determining whether the tooltip is active, either as
291 * a result of hover state or being autoshown.
292 * @return {boolean}
294 active: function() {
295 return this.autoshow == UserPodCustomIcon.TooltipAutoshowState.ACTIVE ||
296 this.hover == UserPodCustomIcon.HoverState.HOVER_TOOLTIP;
300 * @type {!UserPodCustomIcon.TooltipAutoshowState}
302 autoshow: UserPodCustomIcon.TooltipAutoshowState.DISABLED,
305 * @type {!UserPodCustomIcon.HoverState}
307 hover: UserPodCustomIcon.HoverState.NO_HOVER,
310 * The tooltip text.
311 * @type {string}
313 text: ''
316 /** @override */
317 decorate: function() {
318 this.iconElement.addEventListener(
319 'mouseover',
320 this.updateHoverState_.bind(this,
321 UserPodCustomIcon.HoverState.HOVER));
322 this.iconElement.addEventListener(
323 'mouseout',
324 this.updateHoverState_.bind(this,
325 UserPodCustomIcon.HoverState.NO_HOVER));
326 this.iconElement.addEventListener('mousedown',
327 this.handleMouseDown_.bind(this));
328 this.iconElement.addEventListener('click',
329 this.handleClick_.bind(this));
330 this.iconElement.addEventListener('keydown',
331 this.handleKeyDown_.bind(this));
333 // When the icon is focused using mouse, there should be no outline shown.
334 // Preventing default mousedown event accomplishes this.
335 this.iconElement.addEventListener('mousedown', function(e) {
336 e.preventDefault();
341 * Getter for the icon element's div.
342 * @return {HTMLDivElement}
344 get iconElement() {
345 return this.querySelector('.custom-icon');
349 * Updates the icon element class list to properly represent the provided
350 * icon.
351 * @param {!string} id The id of the icon that should be shown. Should be
352 * one of the ids listed in {@code UserPodCustomIcon.ICONS}.
354 setIcon: function(id) {
355 this.iconId_ = id;
356 UserPodCustomIcon.ICONS.forEach(function(icon) {
357 this.iconElement.classList.toggle(icon.class, id == icon.id);
358 }, this);
362 * Sets the ARIA label for the icon.
363 * @param {!string} ariaLabel
365 setAriaLabel: function(ariaLabel) {
366 this.iconElement.setAttribute('aria-label', ariaLabel);
370 * Shows the icon.
372 show: function() {
373 this.hidden = false;
377 * Updates the icon tooltip. If {@code autoshow} parameter is set the
378 * tooltip is immediatelly shown. If tooltip text is not set, the method
379 * ensures the tooltip gets hidden. If tooltip is shown prior to this call,
380 * it remains shown, but the tooltip text is updated.
381 * @param {!{text: string, autoshow: boolean}} tooltip The tooltip
382 * parameters.
384 setTooltip: function(tooltip) {
385 this.iconElement.classList.toggle('icon-with-tooltip', !!tooltip.text);
387 this.updateTooltipAutoshowState_(
388 tooltip.autoshow ?
389 UserPodCustomIcon.TooltipAutoshowState.ENABLED :
390 UserPodCustomIcon.TooltipAutoshowState.DISABLED);
391 this.tooltipState_.text = tooltip.text;
392 this.updateTooltip_();
396 * Sets up icon tabIndex attribute and handler for click and 'Enter' key
397 * down events.
398 * @param {?function()} callback If icon should be interactive, the
399 * function to get called on click and 'Enter' key down events. Should
400 * be null to make the icon non interactive.
402 setInteractive: function(callback) {
403 this.iconElement.classList.toggle('interactive-custom-icon', !!callback);
405 // Update tabIndex property if needed.
406 if (!!this.actionHandler_ != !!callback) {
407 if (callback) {
408 this.iconElement.setAttribute('tabIndex',
409 UserPodTabOrder.POD_CUSTOM_ICON);
410 } else {
411 this.iconElement.removeAttribute('tabIndex');
415 // Set the new action handler.
416 this.actionHandler_ = callback;
420 * Hides the icon and cleans its state.
422 hide: function() {
423 this.hideTooltip_();
424 this.clearUpdateHoverStateTimeout_();
425 this.clearUpdateTooltipAutoshowStateTimeout_();
426 this.setInteractive(null);
427 this.hidden = true;
431 * Clears timeout for showing a tooltip if one is set. Used to cancel
432 * showing the tooltip when the user starts typing the password.
434 cancelDelayedTooltipShow: function() {
435 this.updateTooltipAutoshowState_(
436 UserPodCustomIcon.TooltipAutoshowState.DISABLED);
437 this.clearUpdateHoverStateTimeout_();
441 * Handles mouse down event in the icon element.
442 * @param {Event} e The mouse down event.
443 * @private
445 handleMouseDown_: function(e) {
446 this.updateHoverState_(UserPodCustomIcon.HoverState.NO_HOVER);
447 this.updateTooltipAutoshowState_(
448 UserPodCustomIcon.TooltipAutoshowState.DISABLED);
450 // Stop the event propagation so in the case the click ends up on the
451 // user pod (outside the custom icon) auth is not attempted.
452 stopEventPropagation(e);
456 * Handles click event on the icon element. No-op if
457 * {@code this.actionHandler_} is not set.
458 * @param {Event} e The click event.
459 * @private
461 handleClick_: function(e) {
462 if (!this.actionHandler_)
463 return;
464 this.actionHandler_();
465 stopEventPropagation(e);
469 * Handles key down event on the icon element. Only 'Enter' key is handled.
470 * No-op if {@code this.actionHandler_} is not set.
471 * @param {Event} e The key down event.
472 * @private
474 handleKeyDown_: function(e) {
475 if (!this.actionHandler_ || e.keyIdentifier != 'Enter')
476 return;
477 this.actionHandler_(e);
478 stopEventPropagation(e);
482 * Changes the tooltip hover state and updates tooltip visibility if needed.
483 * @param {!UserPodCustomIcon.HoverState} state
484 * @private
486 updateHoverState_: function(state) {
487 this.clearUpdateHoverStateTimeout_();
488 this.sanitizeTooltipStateIfBubbleHidden_();
490 if (state == UserPodCustomIcon.HoverState.HOVER) {
491 if (this.tooltipState_.active()) {
492 this.tooltipState_.hover = UserPodCustomIcon.HoverState.HOVER_TOOLTIP;
493 } else {
494 this.updateHoverStateSoon_(
495 UserPodCustomIcon.HoverState.HOVER_TOOLTIP);
497 return;
500 if (state != UserPodCustomIcon.HoverState.NO_HOVER &&
501 state != UserPodCustomIcon.HoverState.HOVER_TOOLTIP) {
502 console.error('Invalid hover state ' + state);
503 return;
506 this.tooltipState_.hover = state;
507 this.updateTooltip_();
511 * Sets up a timeout for updating icon hover state.
512 * @param {!UserPodCustomIcon.HoverState} state
513 * @private
515 updateHoverStateSoon_: function(state) {
516 if (this.updateHoverStateTimeout_)
517 clearTimeout(this.updateHoverStateTimeout_);
518 this.updateHoverStateTimeout_ =
519 setTimeout(this.updateHoverState_.bind(this, state), 1000);
523 * Clears a timeout for updating icon hover state if there is one set.
524 * @private
526 clearUpdateHoverStateTimeout_: function() {
527 if (this.updateHoverStateTimeout_) {
528 clearTimeout(this.updateHoverStateTimeout_);
529 this.updateHoverStateTimeout_ = null;
534 * Changes the tooltip autoshow state and changes tooltip visibility if
535 * needed.
536 * @param {!UserPodCustomIcon.TooltipAutoshowState} state
537 * @private
539 updateTooltipAutoshowState_: function(state) {
540 this.clearUpdateTooltipAutoshowStateTimeout_();
541 this.sanitizeTooltipStateIfBubbleHidden_();
543 if (state == UserPodCustomIcon.TooltipAutoshowState.DISABLED) {
544 if (this.tooltipState_.autoshow != state) {
545 this.tooltipState_.autoshow = state;
546 this.updateTooltip_();
548 return;
551 if (this.tooltipState_.active()) {
552 if (this.tooltipState_.autoshow !=
553 UserPodCustomIcon.TooltipAutoshowState.ACTIVE) {
554 this.tooltipState_.autoshow =
555 UserPodCustomIcon.TooltipAutoshowState.DISABLED;
556 } else {
557 // If the tooltip is already automatically shown, the timeout for
558 // removing it should be reset.
559 this.updateTooltipAutoshowStateSoon_(
560 UserPodCustomIcon.TooltipAutoshowState.DISABLED);
562 return;
565 if (state == UserPodCustomIcon.TooltipAutoshowState.ENABLED) {
566 this.updateTooltipAutoshowStateSoon_(
567 UserPodCustomIcon.TooltipAutoshowState.ACTIVE);
568 } else if (state == UserPodCustomIcon.TooltipAutoshowState.ACTIVE) {
569 this.updateTooltipAutoshowStateSoon_(
570 UserPodCustomIcon.TooltipAutoshowState.DISABLED);
573 this.tooltipState_.autoshow = state;
574 this.updateTooltip_();
578 * Sets up a timeout for updating tooltip autoshow state.
579 * @param {!UserPodCustomIcon.TooltipAutoshowState} state
580 * @private
582 updateTooltipAutoshowStateSoon_: function(state) {
583 if (this.updateTooltipAutoshowStateTimeout_)
584 clearTimeout(this.updateTooltupAutoshowStateTimeout_);
585 var timeout =
586 state == UserPodCustomIcon.TooltipAutoshowState.DISABLED ?
587 5000 : 1000;
588 this.updateTooltipAutoshowStateTimeout_ =
589 setTimeout(this.updateTooltipAutoshowState_.bind(this, state),
590 timeout);
594 * Clears the timeout for updating tooltip autoshow state if one is set.
595 * @private
597 clearUpdateTooltipAutoshowStateTimeout_: function() {
598 if (this.updateTooltipAutoshowStateTimeout_) {
599 clearTimeout(this.updateTooltipAutoshowStateTimeout_);
600 this.updateTooltipAutoshowStateTimeout_ = null;
605 * If tooltip bubble is hidden, this makes sure that hover and tooltip
606 * autoshow states are not the ones that imply an active tooltip.
607 * Used to handle a case where the tooltip bubble is hidden by an event that
608 * does not update one of the states (e.g. click outside the pod will not
609 * update tooltip autoshow state). Should be called before making
610 * tooltip state updates.
611 * @private
613 sanitizeTooltipStateIfBubbleHidden_: function() {
614 if (!$('bubble').hidden)
615 return;
617 if (this.tooltipState_.hover ==
618 UserPodCustomIcon.HoverState.HOVER_TOOLTIP &&
619 this.tooltipState_.text) {
620 this.tooltipState_.hover = UserPodCustomIcon.HoverState.NO_HOVER;
621 this.clearUpdateHoverStateTimeout_();
624 if (this.tooltipState_.autoshow ==
625 UserPodCustomIcon.TooltipAutoshowState.ACTIVE) {
626 this.tooltipState_.autoshow =
627 UserPodCustomIcon.TooltipAutoshowState.DISABLED;
628 this.clearUpdateTooltipAutoshowStateTimeout_();
633 * Returns whether the user pod to which the custom icon belongs is focused.
634 * @return {boolean}
635 * @private
637 isParentPodFocused_: function() {
638 if ($('account-picker').hidden)
639 return false;
640 var parentPod = this.parentNode;
641 while (parentPod && !parentPod.classList.contains('pod'))
642 parentPod = parentPod.parentNode;
643 return parentPod && parentPod.parentNode.isFocused(parentPod);
647 * Depending on {@code this.tooltipState_}, it updates tooltip visibility
648 * and text.
649 * @private
651 updateTooltip_: function() {
652 if (this.hidden || !this.isParentPodFocused_())
653 return;
655 if (!this.tooltipState_.active() || !this.tooltipState_.text) {
656 this.hideTooltip_();
657 return;
660 // Show the tooltip bubble.
661 var bubbleContent = document.createElement('div');
662 bubbleContent.textContent = this.tooltipState_.text;
664 /** @const */ var BUBBLE_OFFSET = CUSTOM_ICON_CONTAINER_SIZE / 2;
665 // TODO(tengs): Introduce a special reauth state for the account picker,
666 // instead of showing the tooltip bubble here (crbug.com/409427).
667 /** @const */ var BUBBLE_PADDING = 8 + (this.iconId_ ? 0 : 23);
668 $('bubble').showContentForElement(this,
669 cr.ui.Bubble.Attachment.RIGHT,
670 bubbleContent,
671 BUBBLE_OFFSET,
672 BUBBLE_PADDING);
676 * Hides the tooltip.
677 * @private
679 hideTooltip_: function() {
680 $('bubble').hideForElement(this);
685 * Unique salt added to user image URLs to prevent caching. Dictionary with
686 * user names as keys.
687 * @type {Object}
689 UserPod.userImageSalt_ = {};
691 UserPod.prototype = {
692 __proto__: HTMLDivElement.prototype,
695 * Whether click on the pod can issue a user click auth attempt. The
696 * attempt can be issued iff the pod was focused when the click
697 * started (i.e. on mouse down event).
698 * @type {boolean}
699 * @private
701 userClickAuthAllowed_: false,
703 /** @override */
704 decorate: function() {
705 this.tabIndex = UserPodTabOrder.POD_INPUT;
706 this.actionBoxAreaElement.tabIndex = UserPodTabOrder.ACTION_BOX;
708 this.addEventListener('keydown', this.handlePodKeyDown_.bind(this));
709 this.addEventListener('click', this.handleClickOnPod_.bind(this));
710 this.addEventListener('mousedown', this.handlePodMouseDown_.bind(this));
712 this.reauthWarningElement.addEventListener('click',
713 this.activate.bind(this));
715 this.actionBoxAreaElement.addEventListener('mousedown',
716 stopEventPropagation);
717 this.actionBoxAreaElement.addEventListener('click',
718 this.handleActionAreaButtonClick_.bind(this));
719 this.actionBoxAreaElement.addEventListener('keydown',
720 this.handleActionAreaButtonKeyDown_.bind(this));
722 this.actionBoxMenuRemoveElement.addEventListener('click',
723 this.handleRemoveCommandClick_.bind(this));
724 this.actionBoxMenuRemoveElement.addEventListener('keydown',
725 this.handleRemoveCommandKeyDown_.bind(this));
726 this.actionBoxMenuRemoveElement.addEventListener('blur',
727 this.handleRemoveCommandBlur_.bind(this));
728 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
729 'click',
730 this.handleRemoveUserConfirmationClick_.bind(this));
731 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
732 'keydown',
733 this.handleRemoveUserConfirmationKeyDown_.bind(this));
735 var customIcon = this.customIconElement;
736 customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon);
740 * Initializes the pod after its properties set and added to a pod row.
742 initialize: function() {
743 this.passwordElement.addEventListener('keydown',
744 this.parentNode.handleKeyDown.bind(this.parentNode));
745 this.passwordElement.addEventListener('keypress',
746 this.handlePasswordKeyPress_.bind(this));
748 this.imageElement.addEventListener('load',
749 this.parentNode.handlePodImageLoad.bind(this.parentNode, this));
751 var initialAuthType = this.user.initialAuthType ||
752 AUTH_TYPE.OFFLINE_PASSWORD;
753 this.setAuthType(initialAuthType, null);
755 this.userClickAuthAllowed_ = false;
759 * Resets tab order for pod elements to its initial state.
761 resetTabOrder: function() {
762 // Note: the |mainInput| can be the pod itself.
763 this.mainInput.tabIndex = -1;
764 this.tabIndex = UserPodTabOrder.POD_INPUT;
768 * Handles keypress event (i.e. any textual input) on password input.
769 * @param {Event} e Keypress Event object.
770 * @private
772 handlePasswordKeyPress_: function(e) {
773 // When tabbing from the system tray a tab key press is received. Suppress
774 // this so as not to type a tab character into the password field.
775 if (e.keyCode == 9) {
776 e.preventDefault();
777 return;
779 this.customIconElement.cancelDelayedTooltipShow();
783 * Top edge margin number of pixels.
784 * @type {?number}
786 set top(top) {
787 this.style.top = cr.ui.toCssPx(top);
791 * Top edge margin number of pixels.
793 get top() {
794 return parseInt(this.style.top);
798 * Left edge margin number of pixels.
799 * @type {?number}
801 set left(left) {
802 this.style.left = cr.ui.toCssPx(left);
806 * Left edge margin number of pixels.
808 get left() {
809 return parseInt(this.style.left);
813 * Height number of pixels.
815 get height() {
816 return this.offsetHeight;
820 * Gets image element.
821 * @type {!HTMLImageElement}
823 get imageElement() {
824 return this.querySelector('.user-image');
828 * Gets name element.
829 * @type {!HTMLDivElement}
831 get nameElement() {
832 return this.querySelector('.name');
836 * Gets reauth name hint element.
837 * @type {!HTMLDivElement}
839 get reauthNameHintElement() {
840 return this.querySelector('.reauth-name-hint');
844 * Gets the container holding the password field.
845 * @type {!HTMLInputElement}
847 get passwordEntryContainerElement() {
848 return this.querySelector('.password-entry-container');
852 * Gets password field.
853 * @type {!HTMLInputElement}
855 get passwordElement() {
856 return this.querySelector('.password');
860 * Gets the password label, which is used to show a message where the
861 * password field is normally.
862 * @type {!HTMLInputElement}
864 get passwordLabelElement() {
865 return this.querySelector('.password-label');
869 * Gets user online sign in hint element.
870 * @type {!HTMLDivElement}
872 get reauthWarningElement() {
873 return this.querySelector('.reauth-hint-container');
877 * Gets the container holding the launch app button.
878 * @type {!HTMLButtonElement}
880 get launchAppButtonContainerElement() {
881 return this.querySelector('.launch-app-button-container');
885 * Gets launch app button.
886 * @type {!HTMLButtonElement}
888 get launchAppButtonElement() {
889 return this.querySelector('.launch-app-button');
893 * Gets action box area.
894 * @type {!HTMLInputElement}
896 get actionBoxAreaElement() {
897 return this.querySelector('.action-box-area');
901 * Gets user type icon area.
902 * @type {!HTMLDivElement}
904 get userTypeIconAreaElement() {
905 return this.querySelector('.user-type-icon-area');
909 * Gets user type bubble like multi-profiles policy restriction message.
910 * @type {!HTMLDivElement}
912 get userTypeBubbleElement() {
913 return this.querySelector('.user-type-bubble');
917 * Gets action box menu.
918 * @type {!HTMLInputElement}
920 get actionBoxMenu() {
921 return this.querySelector('.action-box-menu');
925 * Gets action box menu title, user name item.
926 * @type {!HTMLInputElement}
928 get actionBoxMenuTitleNameElement() {
929 return this.querySelector('.action-box-menu-title-name');
933 * Gets action box menu title, user email item.
934 * @type {!HTMLInputElement}
936 get actionBoxMenuTitleEmailElement() {
937 return this.querySelector('.action-box-menu-title-email');
941 * Gets action box menu, remove user command item.
942 * @type {!HTMLInputElement}
944 get actionBoxMenuCommandElement() {
945 return this.querySelector('.action-box-menu-remove-command');
949 * Gets action box menu, remove user command item div.
950 * @type {!HTMLInputElement}
952 get actionBoxMenuRemoveElement() {
953 return this.querySelector('.action-box-menu-remove');
957 * Gets action box menu, remove user warning text div.
958 * @type {!HTMLInputElement}
960 get actionBoxRemoveUserWarningTextElement() {
961 return this.querySelector('.action-box-remove-user-warning-text');
965 * Gets action box menu, remove legacy supervised user warning text div.
966 * @type {!HTMLInputElement}
968 get actionBoxRemoveLegacySupervisedUserWarningTextElement() {
969 return this.querySelector(
970 '.action-box-remove-legacy-supervised-user-warning-text');
974 * Gets action box menu, remove user command item div.
975 * @type {!HTMLInputElement}
977 get actionBoxRemoveUserWarningElement() {
978 return this.querySelector('.action-box-remove-user-warning');
982 * Gets action box menu, remove user command item div.
983 * @type {!HTMLInputElement}
985 get actionBoxRemoveUserWarningButtonElement() {
986 return this.querySelector('.remove-warning-button');
990 * Gets the custom icon. This icon is normally hidden, but can be shown
991 * using the chrome.screenlockPrivate API.
992 * @type {!HTMLDivElement}
994 get customIconElement() {
995 return this.querySelector('.custom-icon-container');
999 * Updates the user pod element.
1001 update: function() {
1002 this.imageElement.src = 'chrome://userimage/' + this.user.username +
1003 '?id=' + UserPod.userImageSalt_[this.user.username];
1005 this.nameElement.textContent = this.user_.displayName;
1006 this.reauthNameHintElement.textContent = this.user_.displayName;
1007 this.classList.toggle('signed-in', this.user_.signedIn);
1009 if (this.isAuthTypeUserClick)
1010 this.passwordLabelElement.textContent = this.authValue;
1012 this.updateActionBoxArea();
1014 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
1015 'passwordFieldAccessibleName', this.user_.emailAddress));
1017 this.customizeUserPodPerUserType();
1020 updateActionBoxArea: function() {
1021 if (this.user_.publicAccount || this.user_.isApp) {
1022 this.actionBoxAreaElement.hidden = true;
1023 return;
1026 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
1028 this.actionBoxAreaElement.setAttribute(
1029 'aria-label', loadTimeData.getStringF(
1030 'podMenuButtonAccessibleName', this.user_.emailAddress));
1031 this.actionBoxMenuRemoveElement.setAttribute(
1032 'aria-label', loadTimeData.getString(
1033 'podMenuRemoveItemAccessibleName'));
1034 this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ?
1035 loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) :
1036 this.user_.displayName;
1037 this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress;
1039 this.actionBoxMenuTitleEmailElement.hidden =
1040 this.user_.legacySupervisedUser;
1042 this.actionBoxMenuCommandElement.textContent =
1043 loadTimeData.getString('removeUser');
1046 customizeUserPodPerUserType: function() {
1047 if (this.user_.childUser && !this.user_.isDesktopUser) {
1048 this.setUserPodIconType('child');
1049 } else if (this.user_.legacySupervisedUser && !this.user_.isDesktopUser) {
1050 this.setUserPodIconType('legacySupervised');
1051 } else if (this.multiProfilesPolicyApplied) {
1052 // Mark user pod as not focusable which in addition to the grayed out
1053 // filter makes it look in disabled state.
1054 this.classList.add('multiprofiles-policy-applied');
1055 this.setUserPodIconType('policy');
1057 if (this.user.multiProfilesPolicy == 'primary-only')
1058 this.querySelector('.mp-policy-primary-only-msg').hidden = false;
1059 else if (this.user.multiProfilesPolicy == 'owner-primary-only')
1060 this.querySelector('.mp-owner-primary-only-msg').hidden = false;
1061 else
1062 this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
1063 } else if (this.user_.isApp) {
1064 this.setUserPodIconType('app');
1068 setUserPodIconType: function(userTypeClass) {
1069 this.userTypeIconAreaElement.classList.add(userTypeClass);
1070 this.userTypeIconAreaElement.hidden = false;
1074 * The user that this pod represents.
1075 * @type {!Object}
1077 user_: undefined,
1078 get user() {
1079 return this.user_;
1081 set user(userDict) {
1082 this.user_ = userDict;
1083 this.update();
1087 * Returns true if multi-profiles sign in is currently active and this
1088 * user pod is restricted per policy.
1089 * @type {boolean}
1091 get multiProfilesPolicyApplied() {
1092 var isMultiProfilesUI =
1093 (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
1094 return isMultiProfilesUI && !this.user_.isMultiProfilesAllowed;
1098 * Gets main input element.
1099 * @type {(HTMLButtonElement|HTMLInputElement)}
1101 get mainInput() {
1102 if (this.isAuthTypePassword) {
1103 return this.passwordElement;
1104 } else if (this.isAuthTypeOnlineSignIn) {
1105 return this;
1106 } else if (this.isAuthTypeUserClick) {
1107 return this.passwordLabelElement;
1112 * Whether action box button is in active state.
1113 * @type {boolean}
1115 get isActionBoxMenuActive() {
1116 return this.actionBoxAreaElement.classList.contains('active');
1118 set isActionBoxMenuActive(active) {
1119 if (active == this.isActionBoxMenuActive)
1120 return;
1122 if (active) {
1123 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
1124 this.actionBoxRemoveUserWarningElement.hidden = true;
1126 // Clear focus first if another pod is focused.
1127 if (!this.parentNode.isFocused(this)) {
1128 this.parentNode.focusPod(undefined, true);
1129 this.actionBoxAreaElement.focus();
1132 // Hide user-type-bubble.
1133 this.userTypeBubbleElement.classList.remove('bubble-shown');
1135 this.actionBoxAreaElement.classList.add('active');
1137 // If the user pod is on either edge of the screen, then the menu
1138 // could be displayed partially ofscreen.
1139 this.actionBoxMenu.classList.remove('left-edge-offset');
1140 this.actionBoxMenu.classList.remove('right-edge-offset');
1142 var offsetLeft =
1143 cr.ui.login.DisplayManager.getOffset(this.actionBoxMenu).left;
1144 var menuWidth = this.actionBoxMenu.offsetWidth;
1145 if (offsetLeft < 0)
1146 this.actionBoxMenu.classList.add('left-edge-offset');
1147 else if (offsetLeft + menuWidth > window.innerWidth)
1148 this.actionBoxMenu.classList.add('right-edge-offset');
1149 } else {
1150 this.actionBoxAreaElement.classList.remove('active');
1151 this.actionBoxAreaElement.classList.remove('menu-moved-up');
1152 this.actionBoxMenu.classList.remove('menu-moved-up');
1157 * Whether action box button is in hovered state.
1158 * @type {boolean}
1160 get isActionBoxMenuHovered() {
1161 return this.actionBoxAreaElement.classList.contains('hovered');
1163 set isActionBoxMenuHovered(hovered) {
1164 if (hovered == this.isActionBoxMenuHovered)
1165 return;
1167 if (hovered) {
1168 this.actionBoxAreaElement.classList.add('hovered');
1169 this.classList.add('hovered');
1170 } else {
1171 if (this.multiProfilesPolicyApplied)
1172 this.userTypeBubbleElement.classList.remove('bubble-shown');
1173 this.actionBoxAreaElement.classList.remove('hovered');
1174 this.classList.remove('hovered');
1179 * Set the authentication type for the pod.
1180 * @param {number} An auth type value defined in the AUTH_TYPE enum.
1181 * @param {string} authValue The initial value used for the auth type.
1183 setAuthType: function(authType, authValue) {
1184 this.authType_ = authType;
1185 this.authValue_ = authValue;
1186 this.setAttribute('auth-type', AUTH_TYPE_NAMES[this.authType_]);
1187 this.update();
1188 this.reset(this.parentNode.isFocused(this));
1192 * The auth type of the user pod. This value is one of the enum
1193 * values in AUTH_TYPE.
1194 * @type {number}
1196 get authType() {
1197 return this.authType_;
1201 * The initial value used for the pod's authentication type.
1202 * eg. a prepopulated password input when using password authentication.
1204 get authValue() {
1205 return this.authValue_;
1209 * True if the the user pod uses a password to authenticate.
1210 * @type {bool}
1212 get isAuthTypePassword() {
1213 return this.authType_ == AUTH_TYPE.OFFLINE_PASSWORD ||
1214 this.authType_ == AUTH_TYPE.FORCE_OFFLINE_PASSWORD;
1218 * True if the the user pod uses a user click to authenticate.
1219 * @type {bool}
1221 get isAuthTypeUserClick() {
1222 return this.authType_ == AUTH_TYPE.USER_CLICK;
1226 * True if the the user pod uses a online sign in to authenticate.
1227 * @type {bool}
1229 get isAuthTypeOnlineSignIn() {
1230 return this.authType_ == AUTH_TYPE.ONLINE_SIGN_IN;
1234 * Updates the image element of the user.
1236 updateUserImage: function() {
1237 UserPod.userImageSalt_[this.user.username] = new Date().getTime();
1238 this.update();
1242 * Focuses on input element.
1244 focusInput: function() {
1245 // Move tabIndex from the whole pod to the main input.
1246 // Note: the |mainInput| can be the pod itself.
1247 this.tabIndex = -1;
1248 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1249 this.mainInput.focus();
1253 * Activates the pod.
1254 * @param {Event} e Event object.
1255 * @return {boolean} True if activated successfully.
1257 activate: function(e) {
1258 if (this.isAuthTypeOnlineSignIn) {
1259 this.showSigninUI();
1260 } else if (this.isAuthTypeUserClick) {
1261 Oobe.disableSigninUI();
1262 this.classList.toggle('signing-in', true);
1263 chrome.send('attemptUnlock', [this.user.username]);
1264 } else if (this.isAuthTypePassword) {
1265 if (!this.passwordElement.value)
1266 return false;
1267 Oobe.disableSigninUI();
1268 chrome.send('authenticateUser',
1269 [this.user.username, this.passwordElement.value]);
1270 } else {
1271 console.error('Activating user pod with invalid authentication type: ' +
1272 this.authType);
1275 return true;
1278 showSupervisedUserSigninWarning: function() {
1279 // Legacy supervised user token has been invalidated.
1280 // Make sure that pod is focused i.e. "Sign in" button is seen.
1281 this.parentNode.focusPod(this);
1283 var error = document.createElement('div');
1284 var messageDiv = document.createElement('div');
1285 messageDiv.className = 'error-message-bubble';
1286 messageDiv.textContent =
1287 loadTimeData.getString('supervisedUserExpiredTokenWarning');
1288 error.appendChild(messageDiv);
1290 $('bubble').showContentForElement(
1291 this.reauthWarningElement,
1292 cr.ui.Bubble.Attachment.TOP,
1293 error,
1294 this.reauthWarningElement.offsetWidth / 2,
1296 // Move warning bubble up if it overlaps the shelf.
1297 var maxHeight =
1298 cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping($('bubble'));
1299 if (maxHeight < $('bubble').offsetHeight) {
1300 $('bubble').showContentForElement(
1301 this.reauthWarningElement,
1302 cr.ui.Bubble.Attachment.BOTTOM,
1303 error,
1304 this.reauthWarningElement.offsetWidth / 2,
1310 * Shows signin UI for this user.
1312 showSigninUI: function() {
1313 if (this.user.legacySupervisedUser && !this.user.isDesktopUser) {
1314 this.showSupervisedUserSigninWarning();
1315 } else {
1316 // Special case for multi-profiles sign in. We show users even if they
1317 // are not allowed per policy. Restrict those users from starting GAIA.
1318 if (this.multiProfilesPolicyApplied)
1319 return;
1321 this.parentNode.showSigninUI(this.user.emailAddress);
1326 * Resets the input field and updates the tab order of pod controls.
1327 * @param {boolean} takeFocus If true, input field takes focus.
1329 reset: function(takeFocus) {
1330 this.passwordElement.value = '';
1331 this.classList.toggle('signing-in', false);
1332 if (takeFocus) {
1333 if (!this.multiProfilesPolicyApplied)
1334 this.focusInput(); // This will set a custom tab order.
1336 else
1337 this.resetTabOrder();
1341 * Removes a user using the correct identifier based on user type.
1342 * @param {Object} user User to be removed.
1344 removeUser: function(user) {
1345 chrome.send('removeUser',
1346 [user.isDesktopUser ? user.profilePath : user.username]);
1350 * Handles a click event on action area button.
1351 * @param {Event} e Click event.
1353 handleActionAreaButtonClick_: function(e) {
1354 if (this.parentNode.disabled)
1355 return;
1356 this.isActionBoxMenuActive = !this.isActionBoxMenuActive;
1357 e.stopPropagation();
1361 * Handles a keydown event on action area button.
1362 * @param {Event} e KeyDown event.
1364 handleActionAreaButtonKeyDown_: function(e) {
1365 if (this.disabled)
1366 return;
1367 switch (e.keyIdentifier) {
1368 case 'Enter':
1369 case 'U+0020': // Space
1370 if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive)
1371 this.isActionBoxMenuActive = true;
1372 e.stopPropagation();
1373 break;
1374 case 'Up':
1375 case 'Down':
1376 if (this.isActionBoxMenuActive) {
1377 this.actionBoxMenuRemoveElement.tabIndex =
1378 UserPodTabOrder.PAD_MENU_ITEM;
1379 this.actionBoxMenuRemoveElement.focus();
1381 e.stopPropagation();
1382 break;
1383 case 'U+001B': // Esc
1384 this.isActionBoxMenuActive = false;
1385 e.stopPropagation();
1386 break;
1387 case 'U+0009': // Tab
1388 if (!this.parentNode.alwaysFocusSinglePod)
1389 this.parentNode.focusPod();
1390 default:
1391 this.isActionBoxMenuActive = false;
1392 break;
1397 * Handles a click event on remove user command.
1398 * @param {Event} e Click event.
1400 handleRemoveCommandClick_: function(e) {
1401 if (this.user.legacySupervisedUser || this.user.isDesktopUser) {
1402 this.showRemoveWarning_();
1403 return;
1405 if (this.isActionBoxMenuActive)
1406 chrome.send('removeUser', [this.user.username]);
1410 * Shows remove user warning. Used for legacy supervised users on CrOS, and
1411 * for all users on desktop.
1413 showRemoveWarning_: function() {
1414 this.actionBoxMenuRemoveElement.hidden = true;
1415 this.actionBoxRemoveUserWarningElement.hidden = false;
1416 this.actionBoxRemoveUserWarningButtonElement.focus();
1418 // Move up the menu if it overlaps shelf.
1419 var maxHeight = cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping(
1420 this.actionBoxMenu);
1421 var actualHeight = parseInt(
1422 window.getComputedStyle(this.actionBoxMenu).height);
1423 if (maxHeight < actualHeight) {
1424 this.actionBoxMenu.classList.add('menu-moved-up');
1425 this.actionBoxAreaElement.classList.add('menu-moved-up');
1427 chrome.send('logRemoveUserWarningShown');
1431 * Handles a click event on remove user confirmation button.
1432 * @param {Event} e Click event.
1434 handleRemoveUserConfirmationClick_: function(e) {
1435 if (this.isActionBoxMenuActive) {
1436 this.isActionBoxMenuActive = false;
1437 this.removeUser(this.user);
1438 e.stopPropagation();
1443 * Handles a keydown event on remove user confirmation button.
1444 * @param {Event} e KeyDown event.
1446 handleRemoveUserConfirmationKeyDown_: function(e) {
1447 if (!this.isActionBoxMenuActive)
1448 return;
1450 // Only handle pressing 'Enter' or 'Space', and let all other events
1451 // bubble to the action box menu.
1452 if (e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020') {
1453 this.isActionBoxMenuActive = false;
1454 this.removeUser(this.user);
1455 e.stopPropagation();
1456 // Prevent default so that we don't trigger a 'click' event.
1457 e.preventDefault();
1462 * Handles a keydown event on remove command.
1463 * @param {Event} e KeyDown event.
1465 handleRemoveCommandKeyDown_: function(e) {
1466 if (this.disabled)
1467 return;
1468 switch (e.keyIdentifier) {
1469 case 'Enter':
1470 if (this.user.legacySupervisedUser || this.user.isDesktopUser) {
1471 // Prevent default so that we don't trigger a 'click' event on the
1472 // remove button that will be focused.
1473 e.preventDefault();
1474 this.showRemoveWarning_();
1475 } else {
1476 this.removeUser(this.user);
1478 e.stopPropagation();
1479 break;
1480 case 'Up':
1481 case 'Down':
1482 e.stopPropagation();
1483 break;
1484 case 'U+001B': // Esc
1485 this.actionBoxAreaElement.focus();
1486 this.isActionBoxMenuActive = false;
1487 e.stopPropagation();
1488 break;
1489 default:
1490 this.actionBoxAreaElement.focus();
1491 this.isActionBoxMenuActive = false;
1492 break;
1497 * Handles a blur event on remove command.
1498 * @param {Event} e Blur event.
1500 handleRemoveCommandBlur_: function(e) {
1501 if (this.disabled)
1502 return;
1503 this.actionBoxMenuRemoveElement.tabIndex = -1;
1507 * Handles mouse down event. It sets whether the user click auth will be
1508 * allowed on the next mouse click event. The auth is allowed iff the pod
1509 * was focused on the mouse down event starting the click.
1510 * @param {Event} e The mouse down event.
1512 handlePodMouseDown_: function(e) {
1513 this.userClickAuthAllowed_ = this.parentNode.isFocused(this);
1517 * Handles click event on a user pod.
1518 * @param {Event} e Click event.
1520 handleClickOnPod_: function(e) {
1521 if (this.parentNode.disabled)
1522 return;
1524 if (!this.isActionBoxMenuActive) {
1525 if (this.isAuthTypeOnlineSignIn) {
1526 this.showSigninUI();
1527 } else if (this.isAuthTypeUserClick && this.userClickAuthAllowed_) {
1528 // Note that this.userClickAuthAllowed_ is set in mouse down event
1529 // handler.
1530 this.parentNode.setActivatedPod(this);
1533 if (this.multiProfilesPolicyApplied)
1534 this.userTypeBubbleElement.classList.add('bubble-shown');
1536 // Prevent default so that we don't trigger 'focus' event.
1537 e.preventDefault();
1542 * Handles keydown event for a user pod.
1543 * @param {Event} e Key event.
1545 handlePodKeyDown_: function(e) {
1546 if (!this.isAuthTypeUserClick || this.disabled)
1547 return;
1548 switch (e.keyIdentifier) {
1549 case 'Enter':
1550 case 'U+0020': // Space
1551 if (this.parentNode.isFocused(this))
1552 this.parentNode.setActivatedPod(this);
1553 break;
1559 * Creates a public account user pod.
1560 * @constructor
1561 * @extends {UserPod}
1563 var PublicAccountUserPod = cr.ui.define(function() {
1564 var node = UserPod();
1566 var extras = $('public-account-user-pod-extras-template').children;
1567 for (var i = 0; i < extras.length; ++i) {
1568 var el = extras[i].cloneNode(true);
1569 node.appendChild(el);
1572 return node;
1575 PublicAccountUserPod.prototype = {
1576 __proto__: UserPod.prototype,
1579 * "Enter" button in expanded side pane.
1580 * @type {!HTMLButtonElement}
1582 get enterButtonElement() {
1583 return this.querySelector('.enter-button');
1587 * Boolean flag of whether the pod is showing the side pane. The flag
1588 * controls whether 'expanded' class is added to the pod's class list and
1589 * resets tab order because main input element changes when the 'expanded'
1590 * state changes.
1591 * @type {boolean}
1593 get expanded() {
1594 return this.classList.contains('expanded');
1597 set expanded(expanded) {
1598 if (this.expanded == expanded)
1599 return;
1601 this.resetTabOrder();
1602 this.classList.toggle('expanded', expanded);
1603 if (expanded) {
1604 // Show the advanced expanded pod directly if there are at least two
1605 // recommended locales. This will be the case in multilingual
1606 // environments where users are likely to want to choose among locales.
1607 if (this.querySelector('.language-select').multipleRecommendedLocales)
1608 this.classList.add('advanced');
1609 this.usualLeft = this.left;
1610 this.makeSpaceForExpandedPod_();
1611 } else if (typeof(this.usualLeft) != 'undefined') {
1612 this.left = this.usualLeft;
1615 var self = this;
1616 this.classList.add('animating');
1617 this.addEventListener('webkitTransitionEnd', function f(e) {
1618 self.removeEventListener('webkitTransitionEnd', f);
1619 self.classList.remove('animating');
1621 // Accessibility focus indicator does not move with the focused
1622 // element. Sends a 'focus' event on the currently focused element
1623 // so that accessibility focus indicator updates its location.
1624 if (document.activeElement)
1625 document.activeElement.dispatchEvent(new Event('focus'));
1627 // Guard timer set to animation duration + 20ms.
1628 ensureTransitionEndEvent(this, 200);
1631 get advanced() {
1632 return this.classList.contains('advanced');
1635 /** @override */
1636 get mainInput() {
1637 if (this.expanded)
1638 return this.enterButtonElement;
1639 else
1640 return this.nameElement;
1643 /** @override */
1644 decorate: function() {
1645 UserPod.prototype.decorate.call(this);
1647 this.classList.add('public-account');
1649 this.nameElement.addEventListener('keydown', (function(e) {
1650 if (e.keyIdentifier == 'Enter') {
1651 this.parentNode.setActivatedPod(this, e);
1652 // Stop this keydown event from bubbling up to PodRow handler.
1653 e.stopPropagation();
1654 // Prevent default so that we don't trigger a 'click' event on the
1655 // newly focused "Enter" button.
1656 e.preventDefault();
1658 }).bind(this));
1660 var learnMore = this.querySelector('.learn-more');
1661 learnMore.addEventListener('mousedown', stopEventPropagation);
1662 learnMore.addEventListener('click', this.handleLearnMoreEvent);
1663 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
1665 learnMore = this.querySelector('.expanded-pane-learn-more');
1666 learnMore.addEventListener('click', this.handleLearnMoreEvent);
1667 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
1669 var languageSelect = this.querySelector('.language-select');
1670 languageSelect.tabIndex = UserPodTabOrder.POD_INPUT;
1671 languageSelect.manuallyChanged = false;
1672 languageSelect.addEventListener(
1673 'change',
1674 function() {
1675 languageSelect.manuallyChanged = true;
1676 this.getPublicSessionKeyboardLayouts_();
1677 }.bind(this));
1679 var keyboardSelect = this.querySelector('.keyboard-select');
1680 keyboardSelect.tabIndex = UserPodTabOrder.POD_INPUT;
1681 keyboardSelect.loadedLocale = null;
1683 var languageAndInput = this.querySelector('.language-and-input');
1684 languageAndInput.tabIndex = UserPodTabOrder.POD_INPUT;
1685 languageAndInput.addEventListener('click',
1686 this.transitionToAdvanced_.bind(this));
1688 this.enterButtonElement.addEventListener('click', (function(e) {
1689 this.enterButtonElement.disabled = true;
1690 var locale = this.querySelector('.language-select').value;
1691 var keyboardSelect = this.querySelector('.keyboard-select');
1692 // The contents of |keyboardSelect| is updated asynchronously. If its
1693 // locale does not match |locale|, it has not updated yet and the
1694 // currently selected keyboard layout may not be applicable to |locale|.
1695 // Do not return any keyboard layout in this case and let the backend
1696 // choose a suitable layout.
1697 var keyboardLayout =
1698 keyboardSelect.loadedLocale == locale ? keyboardSelect.value : '';
1699 chrome.send('launchPublicSession',
1700 [this.user.username, locale, keyboardLayout]);
1701 }).bind(this));
1704 /** @override **/
1705 initialize: function() {
1706 UserPod.prototype.initialize.call(this);
1708 id = this.user.username + '-keyboard';
1709 this.querySelector('.keyboard-select-label').htmlFor = id;
1710 this.querySelector('.keyboard-select').setAttribute('id', id);
1712 var id = this.user.username + '-language';
1713 this.querySelector('.language-select-label').htmlFor = id;
1714 var languageSelect = this.querySelector('.language-select');
1715 languageSelect.setAttribute('id', id);
1716 this.populateLanguageSelect(this.user.initialLocales,
1717 this.user.initialLocale,
1718 this.user.initialMultipleRecommendedLocales);
1721 /** @override **/
1722 update: function() {
1723 UserPod.prototype.update.call(this);
1724 this.querySelector('.expanded-pane-name').textContent =
1725 this.user_.displayName;
1726 this.querySelector('.info').textContent =
1727 loadTimeData.getStringF('publicAccountInfoFormat',
1728 this.user_.enterpriseDomain);
1731 /** @override */
1732 focusInput: function() {
1733 // Move tabIndex from the whole pod to the main input.
1734 this.tabIndex = -1;
1735 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1736 this.mainInput.focus();
1739 /** @override */
1740 reset: function(takeFocus) {
1741 if (!takeFocus)
1742 this.expanded = false;
1743 this.enterButtonElement.disabled = false;
1744 UserPod.prototype.reset.call(this, takeFocus);
1747 /** @override */
1748 activate: function(e) {
1749 if (!this.expanded) {
1750 this.expanded = true;
1751 this.focusInput();
1753 return true;
1756 /** @override */
1757 handleClickOnPod_: function(e) {
1758 if (this.parentNode.disabled)
1759 return;
1761 this.parentNode.focusPod(this);
1762 this.parentNode.setActivatedPod(this, e);
1763 // Prevent default so that we don't trigger 'focus' event.
1764 e.preventDefault();
1768 * Updates the display name shown on the pod.
1769 * @param {string} displayName The new display name
1771 setDisplayName: function(displayName) {
1772 this.user_.displayName = displayName;
1773 this.update();
1777 * Handle mouse and keyboard events for the learn more button. Triggering
1778 * the button causes information about public sessions to be shown.
1779 * @param {Event} event Mouse or keyboard event.
1781 handleLearnMoreEvent: function(event) {
1782 switch (event.type) {
1783 // Show informaton on left click. Let any other clicks propagate.
1784 case 'click':
1785 if (event.button != 0)
1786 return;
1787 break;
1788 // Show informaton when <Return> or <Space> is pressed. Let any other
1789 // key presses propagate.
1790 case 'keydown':
1791 switch (event.keyCode) {
1792 case 13: // Return.
1793 case 32: // Space.
1794 break;
1795 default:
1796 return;
1798 break;
1800 chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]);
1801 stopEventPropagation(event);
1804 makeSpaceForExpandedPod_: function() {
1805 var width = this.classList.contains('advanced') ?
1806 PUBLIC_EXPANDED_ADVANCED_WIDTH : PUBLIC_EXPANDED_BASIC_WIDTH;
1807 var isDesktopUserManager = Oobe.getInstance().displayType ==
1808 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1809 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
1810 POD_ROW_PADDING;
1811 if (this.left + width > $('pod-row').offsetWidth - rowPadding)
1812 this.left = $('pod-row').offsetWidth - rowPadding - width;
1816 * Transition the expanded pod from the basic to the advanced view.
1818 transitionToAdvanced_: function() {
1819 var pod = this;
1820 var languageAndInputSection =
1821 this.querySelector('.language-and-input-section');
1822 this.classList.add('transitioning-to-advanced');
1823 setTimeout(function() {
1824 pod.classList.add('advanced');
1825 pod.makeSpaceForExpandedPod_();
1826 languageAndInputSection.addEventListener('webkitTransitionEnd',
1827 function observer() {
1828 languageAndInputSection.removeEventListener('webkitTransitionEnd',
1829 observer);
1830 pod.classList.remove('transitioning-to-advanced');
1831 pod.querySelector('.language-select').focus();
1833 // Guard timer set to animation duration + 20ms.
1834 ensureTransitionEndEvent(languageAndInputSection, 380);
1835 }, 0);
1839 * Retrieves the list of keyboard layouts available for the currently
1840 * selected locale.
1842 getPublicSessionKeyboardLayouts_: function() {
1843 var selectedLocale = this.querySelector('.language-select').value;
1844 if (selectedLocale ==
1845 this.querySelector('.keyboard-select').loadedLocale) {
1846 // If the list of keyboard layouts was loaded for the currently selected
1847 // locale, it is already up to date.
1848 return;
1850 chrome.send('getPublicSessionKeyboardLayouts',
1851 [this.user.username, selectedLocale]);
1855 * Populates the keyboard layout "select" element with a list of layouts.
1856 * @param {string} locale The locale to which this list of keyboard layouts
1857 * applies
1858 * @param {!Object} list List of available keyboard layouts
1860 populateKeyboardSelect: function(locale, list) {
1861 if (locale != this.querySelector('.language-select').value) {
1862 // The selected locale has changed and the list of keyboard layouts is
1863 // not applicable. This method will be called again when a list of
1864 // keyboard layouts applicable to the selected locale is retrieved.
1865 return;
1868 var keyboardSelect = this.querySelector('.keyboard-select');
1869 keyboardSelect.loadedLocale = locale;
1870 keyboardSelect.innerHTML = '';
1871 for (var i = 0; i < list.length; ++i) {
1872 var item = list[i];
1873 keyboardSelect.appendChild(
1874 new Option(item.title, item.value, item.selected, item.selected));
1879 * Populates the language "select" element with a list of locales.
1880 * @param {!Object} locales The list of available locales
1881 * @param {string} defaultLocale The locale to select by default
1882 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
1883 * two or more recommended locales
1885 populateLanguageSelect: function(locales,
1886 defaultLocale,
1887 multipleRecommendedLocales) {
1888 var languageSelect = this.querySelector('.language-select');
1889 // If the user manually selected a locale, do not change the selection.
1890 // Otherwise, select the new |defaultLocale|.
1891 var selected =
1892 languageSelect.manuallyChanged ? languageSelect.value : defaultLocale;
1893 languageSelect.innerHTML = '';
1894 var group = languageSelect;
1895 for (var i = 0; i < locales.length; ++i) {
1896 var item = locales[i];
1897 if (item.optionGroupName) {
1898 group = document.createElement('optgroup');
1899 group.label = item.optionGroupName;
1900 languageSelect.appendChild(group);
1901 } else {
1902 group.appendChild(new Option(item.title,
1903 item.value,
1904 item.value == selected,
1905 item.value == selected));
1908 languageSelect.multipleRecommendedLocales = multipleRecommendedLocales;
1910 // Retrieve a list of keyboard layouts applicable to the locale that is
1911 // now selected.
1912 this.getPublicSessionKeyboardLayouts_();
1917 * Creates a user pod to be used only in desktop chrome.
1918 * @constructor
1919 * @extends {UserPod}
1921 var DesktopUserPod = cr.ui.define(function() {
1922 // Don't just instantiate a UserPod(), as this will call decorate() on the
1923 // parent object, and add duplicate event listeners.
1924 var node = $('user-pod-template').cloneNode(true);
1925 node.removeAttribute('id');
1926 return node;
1929 DesktopUserPod.prototype = {
1930 __proto__: UserPod.prototype,
1932 /** @override */
1933 initialize: function() {
1934 if (this.user.needsSignin) {
1935 if (this.user.hasLocalCreds) {
1936 this.user.initialAuthType = AUTH_TYPE.OFFLINE_PASSWORD;
1937 } else {
1938 this.user.initialAuthType = AUTH_TYPE.ONLINE_SIGN_IN;
1941 UserPod.prototype.initialize.call(this);
1944 /** @override */
1945 update: function() {
1946 this.imageElement.src = this.user.userImage;
1947 this.nameElement.textContent = this.user.displayName;
1948 this.reauthNameHintElement.textContent = this.user.displayName;
1950 var isLockedUser = this.user.needsSignin;
1951 var isLegacySupervisedUser = this.user.legacySupervisedUser;
1952 var isChildUser = this.user.childUser;
1953 this.classList.toggle('locked', isLockedUser);
1954 this.classList.toggle('legacy-supervised', isLegacySupervisedUser);
1955 this.classList.toggle('child', isChildUser);
1957 if (this.isAuthTypeUserClick)
1958 this.passwordLabelElement.textContent = this.authValue;
1960 this.actionBoxRemoveUserWarningTextElement.hidden =
1961 isLegacySupervisedUser;
1962 this.actionBoxRemoveLegacySupervisedUserWarningTextElement.hidden =
1963 !isLegacySupervisedUser;
1965 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
1966 'passwordFieldAccessibleName', this.user_.emailAddress));
1968 UserPod.prototype.updateActionBoxArea.call(this);
1971 /** @override */
1972 activate: function(e) {
1973 if (!this.user.needsSignin) {
1974 Oobe.launchUser(this.user.profilePath);
1975 } else if (this.user.hasLocalCreds && !this.passwordElement.value) {
1976 return false;
1977 } else {
1978 chrome.send('authenticatedLaunchUser',
1979 [this.user.profilePath,
1980 this.user.emailAddress,
1981 this.passwordElement.value]);
1983 this.passwordElement.value = '';
1984 return true;
1987 /** @override */
1988 handleClickOnPod_: function(e) {
1989 if (this.parentNode.disabled)
1990 return;
1992 Oobe.clearErrors();
1993 this.parentNode.lastFocusedPod_ = this;
1995 // If this is a locked pod and there are local credentials, show the
1996 // password field. Otherwise call activate() which will open up a browser
1997 // window or show the reauth dialog, as needed.
1998 if (!(this.user.needsSignin && this.user.hasLocalCreds) &&
1999 !this.isActionBoxMenuActive) {
2000 this.activate(e);
2003 if (this.isAuthTypeUserClick)
2004 chrome.send('attemptUnlock', [this.user.emailAddress]);
2009 * Creates a user pod that represents kiosk app.
2010 * @constructor
2011 * @extends {UserPod}
2013 var KioskAppPod = cr.ui.define(function() {
2014 var node = UserPod();
2015 return node;
2018 KioskAppPod.prototype = {
2019 __proto__: UserPod.prototype,
2021 /** @override */
2022 decorate: function() {
2023 UserPod.prototype.decorate.call(this);
2024 this.launchAppButtonElement.addEventListener('click',
2025 this.activate.bind(this));
2028 /** @override */
2029 update: function() {
2030 this.imageElement.src = this.user.iconUrl;
2031 this.imageElement.alt = this.user.label;
2032 this.imageElement.title = this.user.label;
2033 this.passwordEntryContainerElement.hidden = true;
2034 this.launchAppButtonContainerElement.hidden = false;
2035 this.nameElement.textContent = this.user.label;
2036 this.reauthNameHintElement.textContent = this.user.label;
2038 UserPod.prototype.updateActionBoxArea.call(this);
2039 UserPod.prototype.customizeUserPodPerUserType.call(this);
2042 /** @override */
2043 get mainInput() {
2044 return this.launchAppButtonElement;
2047 /** @override */
2048 focusInput: function() {
2049 // Move tabIndex from the whole pod to the main input.
2050 this.tabIndex = -1;
2051 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
2052 this.mainInput.focus();
2055 /** @override */
2056 get forceOnlineSignin() {
2057 return false;
2060 /** @override */
2061 activate: function(e) {
2062 var diagnosticMode = e && e.ctrlKey;
2063 this.launchApp_(this.user, diagnosticMode);
2064 return true;
2067 /** @override */
2068 handleClickOnPod_: function(e) {
2069 if (this.parentNode.disabled)
2070 return;
2072 Oobe.clearErrors();
2073 this.parentNode.lastFocusedPod_ = this;
2074 this.activate(e);
2078 * Launch the app. If |diagnosticMode| is true, ask user to confirm.
2079 * @param {Object} app App data.
2080 * @param {boolean} diagnosticMode Whether to run the app in diagnostic
2081 * mode.
2083 launchApp_: function(app, diagnosticMode) {
2084 if (!diagnosticMode) {
2085 chrome.send('launchKioskApp', [app.id, false]);
2086 return;
2089 var oobe = $('oobe');
2090 if (!oobe.confirmDiagnosticMode_) {
2091 oobe.confirmDiagnosticMode_ =
2092 new cr.ui.dialogs.ConfirmDialog(document.body);
2093 oobe.confirmDiagnosticMode_.setOkLabel(
2094 loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
2095 oobe.confirmDiagnosticMode_.setCancelLabel(
2096 loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
2099 oobe.confirmDiagnosticMode_.show(
2100 loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
2101 app.label),
2102 function() {
2103 chrome.send('launchKioskApp', [app.id, true]);
2109 * Creates a new pod row element.
2110 * @constructor
2111 * @extends {HTMLDivElement}
2113 var PodRow = cr.ui.define('podrow');
2115 PodRow.prototype = {
2116 __proto__: HTMLDivElement.prototype,
2118 // Whether this user pod row is shown for the first time.
2119 firstShown_: true,
2121 // True if inside focusPod().
2122 insideFocusPod_: false,
2124 // Focused pod.
2125 focusedPod_: undefined,
2127 // Activated pod, i.e. the pod of current login attempt.
2128 activatedPod_: undefined,
2130 // Pod that was most recently focused, if any.
2131 lastFocusedPod_: undefined,
2133 // Pods whose initial images haven't been loaded yet.
2134 podsWithPendingImages_: [],
2136 // Whether pod placement has been postponed.
2137 podPlacementPostponed_: false,
2139 // Standard user pod height/width.
2140 userPodHeight_: 0,
2141 userPodWidth_: 0,
2143 // Array of apps that are shown in addition to other user pods.
2144 apps_: [],
2146 // True to show app pods along with user pods.
2147 shouldShowApps_: true,
2149 // Array of users that are shown (public/supervised/regular).
2150 users_: [],
2152 // If we're in Touch View mode.
2153 touchViewEnabled_: false,
2155 /** @override */
2156 decorate: function() {
2157 // Event listeners that are installed for the time period during which
2158 // the element is visible.
2159 this.listeners_ = {
2160 focus: [this.handleFocus_.bind(this), true /* useCapture */],
2161 click: [this.handleClick_.bind(this), true],
2162 mousemove: [this.handleMouseMove_.bind(this), false],
2163 keydown: [this.handleKeyDown.bind(this), false]
2166 var isDesktopUserManager = Oobe.getInstance().displayType ==
2167 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2168 this.userPodHeight_ = isDesktopUserManager ? DESKTOP_POD_HEIGHT :
2169 CROS_POD_HEIGHT;
2170 // Same for Chrome OS and desktop.
2171 this.userPodWidth_ = POD_WIDTH;
2175 * Returns all the pods in this pod row.
2176 * @type {NodeList}
2178 get pods() {
2179 return Array.prototype.slice.call(this.children);
2183 * Return true if user pod row has only single user pod in it, which should
2184 * always be focused except desktop and touch view modes.
2185 * @type {boolean}
2187 get alwaysFocusSinglePod() {
2188 var isDesktopUserManager = Oobe.getInstance().displayType ==
2189 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2191 return (isDesktopUserManager || this.touchViewEnabled_) ?
2192 false : this.children.length == 1;
2196 * Returns pod with the given app id.
2197 * @param {!string} app_id Application id to be matched.
2198 * @return {Object} Pod with the given app id. null if pod hasn't been
2199 * found.
2201 getPodWithAppId_: function(app_id) {
2202 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2203 if (pod.user.isApp && pod.user.id == app_id)
2204 return pod;
2206 return null;
2210 * Returns pod with the given username (null if there is no such pod).
2211 * @param {string} username Username to be matched.
2212 * @return {Object} Pod with the given username. null if pod hasn't been
2213 * found.
2215 getPodWithUsername_: function(username) {
2216 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2217 if (pod.user.username == username)
2218 return pod;
2220 return null;
2224 * True if the the pod row is disabled (handles no user interaction).
2225 * @type {boolean}
2227 disabled_: false,
2228 get disabled() {
2229 return this.disabled_;
2231 set disabled(value) {
2232 this.disabled_ = value;
2233 var controls = this.querySelectorAll('button,input');
2234 for (var i = 0, control; control = controls[i]; ++i) {
2235 control.disabled = value;
2240 * Creates a user pod from given email.
2241 * @param {!Object} user User info dictionary.
2243 createUserPod: function(user) {
2244 var userPod;
2245 if (user.isDesktopUser)
2246 userPod = new DesktopUserPod({user: user});
2247 else if (user.publicAccount)
2248 userPod = new PublicAccountUserPod({user: user});
2249 else if (user.isApp)
2250 userPod = new KioskAppPod({user: user});
2251 else
2252 userPod = new UserPod({user: user});
2254 userPod.hidden = false;
2255 return userPod;
2259 * Add an existing user pod to this pod row.
2260 * @param {!Object} user User info dictionary.
2262 addUserPod: function(user) {
2263 var userPod = this.createUserPod(user);
2264 this.appendChild(userPod);
2265 userPod.initialize();
2269 * Runs app with a given id from the list of loaded apps.
2270 * @param {!string} app_id of an app to run.
2271 * @param {boolean=} opt_diagnosticMode Whether to run the app in
2272 * diagnostic mode. Default is false.
2274 findAndRunAppForTesting: function(app_id, opt_diagnosticMode) {
2275 var app = this.getPodWithAppId_(app_id);
2276 if (app) {
2277 var activationEvent = cr.doc.createEvent('MouseEvents');
2278 var ctrlKey = opt_diagnosticMode;
2279 activationEvent.initMouseEvent('click', true, true, null,
2280 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
2281 app.dispatchEvent(activationEvent);
2286 * Removes user pod from pod row.
2287 * @param {string} email User's email.
2289 removeUserPod: function(username) {
2290 var podToRemove = this.getPodWithUsername_(username);
2291 if (podToRemove == null) {
2292 console.warn('Attempt to remove not existing pod for ' + username +
2293 '.');
2294 return;
2296 this.removeChild(podToRemove);
2297 if (this.pods.length > 0)
2298 this.placePods_();
2302 * Returns index of given pod or -1 if not found.
2303 * @param {UserPod} pod Pod to look up.
2304 * @private
2306 indexOf_: function(pod) {
2307 for (var i = 0; i < this.pods.length; ++i) {
2308 if (pod == this.pods[i])
2309 return i;
2311 return -1;
2315 * Populates pod row with given existing users and start init animation.
2316 * @param {array} users Array of existing user emails.
2318 loadPods: function(users) {
2319 this.users_ = users;
2321 this.rebuildPods();
2325 * Scrolls focused user pod into view.
2327 scrollFocusedPodIntoView: function() {
2328 var pod = this.focusedPod_;
2329 if (!pod)
2330 return;
2332 // First check whether focused pod is already fully visible.
2333 var visibleArea = $('scroll-container');
2334 // Visible area may not defined at user manager screen on all platforms.
2335 // Windows, Mac and Linux do not have visible area.
2336 if (!visibleArea)
2337 return;
2338 var scrollTop = visibleArea.scrollTop;
2339 var clientHeight = visibleArea.clientHeight;
2340 var podTop = $('oobe').offsetTop + pod.offsetTop;
2341 var padding = USER_POD_KEYBOARD_MIN_PADDING;
2342 if (podTop + pod.height + padding <= scrollTop + clientHeight &&
2343 podTop - padding >= scrollTop) {
2344 return;
2347 // Scroll so that user pod is as centered as possible.
2348 visibleArea.scrollTop = podTop - (clientHeight - pod.offsetHeight) / 2;
2352 * Rebuilds pod row using users_ and apps_ that were previously set or
2353 * updated.
2355 rebuildPods: function() {
2356 var emptyPodRow = this.pods.length == 0;
2358 // Clear existing pods.
2359 this.innerHTML = '';
2360 this.focusedPod_ = undefined;
2361 this.activatedPod_ = undefined;
2362 this.lastFocusedPod_ = undefined;
2364 // Switch off animation
2365 Oobe.getInstance().toggleClass('flying-pods', false);
2367 // Populate the pod row.
2368 for (var i = 0; i < this.users_.length; ++i)
2369 this.addUserPod(this.users_[i]);
2371 for (var i = 0, pod; pod = this.pods[i]; ++i)
2372 this.podsWithPendingImages_.push(pod);
2374 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
2375 if (this.shouldShowApps_) {
2376 for (var i = 0; i < this.apps_.length; ++i)
2377 this.addUserPod(this.apps_[i]);
2380 // Make sure we eventually show the pod row, even if some image is stuck.
2381 setTimeout(function() {
2382 $('pod-row').classList.remove('images-loading');
2383 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS);
2385 var isAccountPicker = $('login-header-bar').signinUIState ==
2386 SIGNIN_UI_STATE.ACCOUNT_PICKER;
2388 // Immediately recalculate pods layout only when current UI is account
2389 // picker. Otherwise postpone it.
2390 if (isAccountPicker) {
2391 this.placePods_();
2392 this.maybePreselectPod();
2394 // Without timeout changes in pods positions will be animated even
2395 // though it happened when 'flying-pods' class was disabled.
2396 setTimeout(function() {
2397 Oobe.getInstance().toggleClass('flying-pods', true);
2398 }, 0);
2399 } else {
2400 this.podPlacementPostponed_ = true;
2402 // Update [Cancel] button state.
2403 if ($('login-header-bar').signinUIState ==
2404 SIGNIN_UI_STATE.GAIA_SIGNIN &&
2405 emptyPodRow &&
2406 this.pods.length > 0) {
2407 login.GaiaSigninScreen.updateCancelButtonState();
2413 * Adds given apps to the pod row.
2414 * @param {array} apps Array of apps.
2416 setApps: function(apps) {
2417 this.apps_ = apps;
2418 this.rebuildPods();
2419 chrome.send('kioskAppsLoaded');
2421 // Check whether there's a pending kiosk app error.
2422 window.setTimeout(function() {
2423 chrome.send('checkKioskAppLaunchError');
2424 }, 500);
2428 * Sets whether should show app pods.
2429 * @param {boolean} shouldShowApps Whether app pods should be shown.
2431 setShouldShowApps: function(shouldShowApps) {
2432 if (this.shouldShowApps_ == shouldShowApps)
2433 return;
2435 this.shouldShowApps_ = shouldShowApps;
2436 this.rebuildPods();
2440 * Shows a custom icon on a user pod besides the input field.
2441 * @param {string} username Username of pod to add button
2442 * @param {!{id: !string,
2443 * hardlockOnClick: boolean,
2444 * isTrialRun: boolean,
2445 * ariaLabel: string | undefined,
2446 * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon
2447 * The icon parameters.
2449 showUserPodCustomIcon: function(username, icon) {
2450 var pod = this.getPodWithUsername_(username);
2451 if (pod == null) {
2452 console.error('Unable to show user pod button: user pod not found.');
2453 return;
2456 if (!icon.id && !icon.tooltip)
2457 return;
2459 if (icon.id)
2460 pod.customIconElement.setIcon(icon.id);
2462 if (icon.isTrialRun) {
2463 pod.customIconElement.setInteractive(
2464 this.onDidClickLockIconDuringTrialRun_.bind(this, username));
2465 } else if (icon.hardlockOnClick) {
2466 pod.customIconElement.setInteractive(
2467 this.hardlockUserPod_.bind(this, username));
2468 } else {
2469 pod.customIconElement.setInteractive(null);
2472 var ariaLabel = icon.ariaLabel || (icon.tooltip && icon.tooltip.text);
2473 if (ariaLabel)
2474 pod.customIconElement.setAriaLabel(ariaLabel);
2475 else
2476 console.warn('No ARIA label for user pod custom icon.');
2478 pod.customIconElement.show();
2480 // This has to be called after |show| in case the tooltip should be shown
2481 // immediatelly.
2482 pod.customIconElement.setTooltip(
2483 icon.tooltip || {text: '', autoshow: false});
2487 * Hard-locks user pod for the user. If user pod is hard-locked, it can be
2488 * only unlocked using password, and the authentication type cannot be
2489 * changed.
2490 * @param {!string} username The user's username.
2491 * @private
2493 hardlockUserPod_: function(username) {
2494 chrome.send('hardlockPod', [username]);
2498 * Records a metric indicating that the user clicked on the lock icon during
2499 * the trial run for Easy Unlock.
2500 * @param {!string} username The user's username.
2501 * @private
2503 onDidClickLockIconDuringTrialRun_: function(username) {
2504 chrome.send('recordClickOnLockIcon', [username]);
2508 * Hides the custom icon in the user pod added by showUserPodCustomIcon().
2509 * @param {string} username Username of pod to remove button
2511 hideUserPodCustomIcon: function(username) {
2512 var pod = this.getPodWithUsername_(username);
2513 if (pod == null) {
2514 console.error('Unable to hide user pod button: user pod not found.');
2515 return;
2518 // TODO(tengs): Allow option for a fading transition.
2519 pod.customIconElement.hide();
2523 * Sets the authentication type used to authenticate the user.
2524 * @param {string} username Username of selected user
2525 * @param {number} authType Authentication type, must be one of the
2526 * values listed in AUTH_TYPE enum.
2527 * @param {string} value The initial value to use for authentication.
2529 setAuthType: function(username, authType, value) {
2530 var pod = this.getPodWithUsername_(username);
2531 if (pod == null) {
2532 console.error('Unable to set auth type: user pod not found.');
2533 return;
2535 pod.setAuthType(authType, value);
2539 * Sets the state of touch view mode.
2540 * @param {boolean} isTouchViewEnabled true if the mode is on.
2542 setTouchViewState: function(isTouchViewEnabled) {
2543 this.touchViewEnabled_ = isTouchViewEnabled;
2544 this.pods.forEach(function(pod, index) {
2545 pod.actionBoxAreaElement.classList.toggle('forced', isTouchViewEnabled);
2550 * Updates the display name shown on a public session pod.
2551 * @param {string} userID The user ID of the public session
2552 * @param {string} displayName The new display name
2554 setPublicSessionDisplayName: function(userID, displayName) {
2555 var pod = this.getPodWithUsername_(userID);
2556 if (pod != null)
2557 pod.setDisplayName(displayName);
2561 * Updates the list of locales available for a public session.
2562 * @param {string} userID The user ID of the public session
2563 * @param {!Object} locales The list of available locales
2564 * @param {string} defaultLocale The locale to select by default
2565 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
2566 * two or more recommended locales
2568 setPublicSessionLocales: function(userID,
2569 locales,
2570 defaultLocale,
2571 multipleRecommendedLocales) {
2572 var pod = this.getPodWithUsername_(userID);
2573 if (pod != null) {
2574 pod.populateLanguageSelect(locales,
2575 defaultLocale,
2576 multipleRecommendedLocales);
2581 * Updates the list of available keyboard layouts for a public session pod.
2582 * @param {string} userID The user ID of the public session
2583 * @param {string} locale The locale to which this list of keyboard layouts
2584 * applies
2585 * @param {!Object} list List of available keyboard layouts
2587 setPublicSessionKeyboardLayouts: function(userID, locale, list) {
2588 var pod = this.getPodWithUsername_(userID);
2589 if (pod != null)
2590 pod.populateKeyboardSelect(locale, list);
2594 * Called when window was resized.
2596 onWindowResize: function() {
2597 var layout = this.calculateLayout_();
2598 if (layout.columns != this.columns || layout.rows != this.rows)
2599 this.placePods_();
2601 this.scrollFocusedPodIntoView();
2605 * Returns width of podrow having |columns| number of columns.
2606 * @private
2608 columnsToWidth_: function(columns) {
2609 var isDesktopUserManager = Oobe.getInstance().displayType ==
2610 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2611 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
2612 MARGIN_BY_COLUMNS[columns];
2613 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
2614 POD_ROW_PADDING;
2615 return 2 * rowPadding + columns * this.userPodWidth_ +
2616 (columns - 1) * margin;
2620 * Returns height of podrow having |rows| number of rows.
2621 * @private
2623 rowsToHeight_: function(rows) {
2624 var isDesktopUserManager = Oobe.getInstance().displayType ==
2625 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2626 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
2627 POD_ROW_PADDING;
2628 return 2 * rowPadding + rows * this.userPodHeight_;
2632 * Calculates number of columns and rows that podrow should have in order to
2633 * hold as much its pods as possible for current screen size. Also it tries
2634 * to choose layout that looks good.
2635 * @return {{columns: number, rows: number}}
2637 calculateLayout_: function() {
2638 var preferredColumns = this.pods.length < COLUMNS.length ?
2639 COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1];
2640 var maxWidth = Oobe.getInstance().clientAreaSize.width;
2641 var columns = preferredColumns;
2642 while (maxWidth < this.columnsToWidth_(columns) && columns > 1)
2643 --columns;
2644 var rows = Math.floor((this.pods.length - 1) / columns) + 1;
2645 if (getComputedStyle(
2646 $('signin-banner'), null).getPropertyValue('display') != 'none') {
2647 rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER);
2649 var maxHeigth = Oobe.getInstance().clientAreaSize.height;
2650 while (maxHeigth < this.rowsToHeight_(rows) && rows > 1)
2651 --rows;
2652 // One more iteration if it's not enough cells to place all pods.
2653 while (maxWidth >= this.columnsToWidth_(columns + 1) &&
2654 columns * rows < this.pods.length &&
2655 columns < MAX_NUMBER_OF_COLUMNS) {
2656 ++columns;
2658 return {columns: columns, rows: rows};
2662 * Places pods onto their positions onto pod grid.
2663 * @private
2665 placePods_: function() {
2666 var layout = this.calculateLayout_();
2667 var columns = this.columns = layout.columns;
2668 var rows = this.rows = layout.rows;
2669 var maxPodsNumber = columns * rows;
2670 var isDesktopUserManager = Oobe.getInstance().displayType ==
2671 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2672 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
2673 MARGIN_BY_COLUMNS[columns];
2674 this.parentNode.setPreferredSize(
2675 this.columnsToWidth_(columns), this.rowsToHeight_(rows));
2676 var height = this.userPodHeight_;
2677 var width = this.userPodWidth_;
2678 this.pods.forEach(function(pod, index) {
2679 if (index >= maxPodsNumber) {
2680 pod.hidden = true;
2681 return;
2683 pod.hidden = false;
2684 if (pod.offsetHeight != height) {
2685 console.error('Pod offsetHeight (' + pod.offsetHeight +
2686 ') and POD_HEIGHT (' + height + ') are not equal.');
2688 if (pod.offsetWidth != width) {
2689 console.error('Pod offsetWidth (' + pod.offsetWidth +
2690 ') and POD_WIDTH (' + width + ') are not equal.');
2692 var column = index % columns;
2693 var row = Math.floor(index / columns);
2694 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
2695 POD_ROW_PADDING;
2696 pod.left = rowPadding + column * (width + margin);
2698 // On desktop, we want the rows to always be equally spaced.
2699 pod.top = isDesktopUserManager ? row * (height + rowPadding) :
2700 row * height + rowPadding;
2702 Oobe.getInstance().updateScreenSize(this.parentNode);
2706 * Number of columns.
2707 * @type {?number}
2709 set columns(columns) {
2710 // Cannot use 'columns' here.
2711 this.setAttribute('ncolumns', columns);
2713 get columns() {
2714 return parseInt(this.getAttribute('ncolumns'));
2718 * Number of rows.
2719 * @type {?number}
2721 set rows(rows) {
2722 // Cannot use 'rows' here.
2723 this.setAttribute('nrows', rows);
2725 get rows() {
2726 return parseInt(this.getAttribute('nrows'));
2730 * Whether the pod is currently focused.
2731 * @param {UserPod} pod Pod to check for focus.
2732 * @return {boolean} Pod focus status.
2734 isFocused: function(pod) {
2735 return this.focusedPod_ == pod;
2739 * Focuses a given user pod or clear focus when given null.
2740 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
2741 * @param {boolean=} opt_force If true, forces focus update even when
2742 * podToFocus is already focused.
2743 * @param {boolean=} opt_skipInputFocus If true, don't focus on the input
2744 * box of user pod.
2746 focusPod: function(podToFocus, opt_force, opt_skipInputFocus) {
2747 if (this.isFocused(podToFocus) && !opt_force) {
2748 // Calling focusPod w/o podToFocus means reset.
2749 if (!podToFocus)
2750 Oobe.clearErrors();
2751 return;
2754 // Make sure there's only one focusPod operation happening at a time.
2755 if (this.insideFocusPod_) {
2756 return;
2758 this.insideFocusPod_ = true;
2760 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2761 if (!this.alwaysFocusSinglePod) {
2762 pod.isActionBoxMenuActive = false;
2764 if (pod != podToFocus) {
2765 pod.isActionBoxMenuHovered = false;
2766 pod.classList.remove('focused');
2767 // On Desktop, the faded style is not set correctly, so we should
2768 // manually fade out non-focused pods if there is a focused pod.
2769 if (pod.user.isDesktopUser && podToFocus)
2770 pod.classList.add('faded');
2771 else
2772 pod.classList.remove('faded');
2773 pod.reset(false);
2777 // Clear any error messages for previous pod.
2778 if (!this.isFocused(podToFocus))
2779 Oobe.clearErrors();
2781 var hadFocus = !!this.focusedPod_;
2782 this.focusedPod_ = podToFocus;
2783 if (podToFocus) {
2784 podToFocus.classList.remove('faded');
2785 podToFocus.classList.add('focused');
2786 if (!podToFocus.multiProfilesPolicyApplied) {
2787 podToFocus.classList.toggle('signing-in', false);
2788 if (!opt_skipInputFocus)
2789 podToFocus.focusInput();
2790 } else {
2791 podToFocus.userTypeBubbleElement.classList.add('bubble-shown');
2792 // Note it is not necessary to skip this focus request when
2793 // |opt_skipInputFocus| is true. When |multiProfilesPolicyApplied|
2794 // is false, it doesn't focus on the password input box by default.
2795 podToFocus.focus();
2798 // focusPod() automatically loads wallpaper
2799 if (!podToFocus.user.isApp)
2800 chrome.send('focusPod', [podToFocus.user.username]);
2801 this.firstShown_ = false;
2802 this.lastFocusedPod_ = podToFocus;
2803 this.scrollFocusedPodIntoView();
2805 this.insideFocusPod_ = false;
2809 * Resets wallpaper to the last active user's wallpaper, if any.
2811 loadLastWallpaper: function() {
2812 if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp)
2813 chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]);
2817 * Returns the currently activated pod.
2818 * @type {UserPod}
2820 get activatedPod() {
2821 return this.activatedPod_;
2825 * Sets currently activated pod.
2826 * @param {UserPod} pod Pod to check for focus.
2827 * @param {Event} e Event object.
2829 setActivatedPod: function(pod, e) {
2830 if (pod && pod.activate(e))
2831 this.activatedPod_ = pod;
2835 * The pod of the signed-in user, if any; null otherwise.
2836 * @type {?UserPod}
2838 get lockedPod() {
2839 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2840 if (pod.user.signedIn)
2841 return pod;
2843 return null;
2847 * The pod that is preselected on user pod row show.
2848 * @type {?UserPod}
2850 get preselectedPod() {
2851 var isDesktopUserManager = Oobe.getInstance().displayType ==
2852 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
2853 if (isDesktopUserManager) {
2854 // On desktop, don't pre-select a pod if it's the only one.
2855 if (this.pods.length == 1)
2856 return null;
2858 // The desktop User Manager can send the index of a pod that should be
2859 // initially focused in url hash.
2860 var podIndex = parseInt(window.location.hash.substr(1));
2861 if (isNaN(podIndex) || podIndex >= this.pods.length)
2862 return null;
2863 return this.pods[podIndex];
2866 var lockedPod = this.lockedPod;
2867 if (lockedPod)
2868 return lockedPod;
2869 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2870 if (!pod.multiProfilesPolicyApplied) {
2871 return pod;
2874 return this.pods[0];
2878 * Resets input UI.
2879 * @param {boolean} takeFocus True to take focus.
2881 reset: function(takeFocus) {
2882 this.disabled = false;
2883 if (this.activatedPod_)
2884 this.activatedPod_.reset(takeFocus);
2888 * Restores input focus to current selected pod, if there is any.
2890 refocusCurrentPod: function() {
2891 if (this.focusedPod_ && !this.focusedPod_.multiProfilesPolicyApplied) {
2892 this.focusedPod_.focusInput();
2897 * Clears focused pod password field.
2899 clearFocusedPod: function() {
2900 if (!this.disabled && this.focusedPod_)
2901 this.focusedPod_.reset(true);
2905 * Shows signin UI.
2906 * @param {string} email Email for signin UI.
2908 showSigninUI: function(email) {
2909 // Clear any error messages that might still be around.
2910 Oobe.clearErrors();
2911 this.disabled = true;
2912 this.lastFocusedPod_ = this.getPodWithUsername_(email);
2913 Oobe.showSigninUI(email);
2917 * Updates current image of a user.
2918 * @param {string} username User for which to update the image.
2920 updateUserImage: function(username) {
2921 var pod = this.getPodWithUsername_(username);
2922 if (pod)
2923 pod.updateUserImage();
2927 * Handler of click event.
2928 * @param {Event} e Click Event object.
2929 * @private
2931 handleClick_: function(e) {
2932 if (this.disabled)
2933 return;
2935 // Clear all menus if the click is outside pod menu and its
2936 // button area.
2937 if (!findAncestorByClass(e.target, 'action-box-menu') &&
2938 !findAncestorByClass(e.target, 'action-box-area')) {
2939 for (var i = 0, pod; pod = this.pods[i]; ++i)
2940 pod.isActionBoxMenuActive = false;
2943 // Clears focus if not clicked on a pod and if there's more than one pod.
2944 var pod = findAncestorByClass(e.target, 'pod');
2945 if ((!pod || pod.parentNode != this) && !this.alwaysFocusSinglePod) {
2946 this.focusPod();
2949 if (pod)
2950 pod.isActionBoxMenuHovered = true;
2952 // Return focus back to single pod.
2953 if (this.alwaysFocusSinglePod && !pod) {
2954 if ($('login-header-bar').contains(e.target))
2955 return;
2956 this.focusPod(this.focusedPod_, true /* force */);
2957 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
2958 this.focusedPod_.isActionBoxMenuHovered = false;
2963 * Handler of mouse move event.
2964 * @param {Event} e Click Event object.
2965 * @private
2967 handleMouseMove_: function(e) {
2968 if (this.disabled)
2969 return;
2970 if (e.webkitMovementX == 0 && e.webkitMovementY == 0)
2971 return;
2973 // Defocus (thus hide) action box, if it is focused on a user pod
2974 // and the pointer is not hovering over it.
2975 var pod = findAncestorByClass(e.target, 'pod');
2976 if (document.activeElement &&
2977 document.activeElement.parentNode != pod &&
2978 document.activeElement.classList.contains('action-box-area')) {
2979 document.activeElement.parentNode.focus();
2982 if (pod)
2983 pod.isActionBoxMenuHovered = true;
2985 // Hide action boxes on other user pods.
2986 for (var i = 0, p; p = this.pods[i]; ++i)
2987 if (p != pod && !p.isActionBoxMenuActive)
2988 p.isActionBoxMenuHovered = false;
2992 * Handles focus event.
2993 * @param {Event} e Focus Event object.
2994 * @private
2996 handleFocus_: function(e) {
2997 if (this.disabled)
2998 return;
2999 if (e.target.parentNode == this) {
3000 // Focus on a pod
3001 if (e.target.classList.contains('focused')) {
3002 if (!e.target.multiProfilesPolicyApplied)
3003 e.target.focusInput();
3004 else
3005 e.target.userTypeBubbleElement.classList.add('bubble-shown');
3006 } else
3007 this.focusPod(e.target);
3008 return;
3011 var pod = findAncestorByClass(e.target, 'pod');
3012 if (pod && pod.parentNode == this) {
3013 // Focus on a control of a pod but not on the action area button.
3014 if (!pod.classList.contains('focused')) {
3015 if (e.target.classList.contains('action-box-area') ||
3016 e.target.classList.contains('remove-warning-button')) {
3017 // focusPod usually moves focus on the password input box which
3018 // triggers virtual keyboard to show up. But the focus may move to a
3019 // non text input element shortly by e.target.focus. Hence, a
3020 // virtual keyboard flicking might be observed. We need to manually
3021 // prevent focus on password input box to avoid virtual keyboard
3022 // flicking in this case. See crbug.com/396016 for details.
3023 this.focusPod(pod, false, true /* opt_skipInputFocus */);
3024 } else {
3025 this.focusPod(pod);
3027 pod.userTypeBubbleElement.classList.remove('bubble-shown');
3028 e.target.focus();
3030 return;
3033 // Clears pod focus when we reach here. It means new focus is neither
3034 // on a pod nor on a button/input for a pod.
3035 // Do not "defocus" user pod when it is a single pod.
3036 // That means that 'focused' class will not be removed and
3037 // input field/button will always be visible.
3038 if (!this.alwaysFocusSinglePod)
3039 this.focusPod();
3040 else {
3041 // Hide user-type-bubble in case this is one pod and we lost focus of
3042 // it.
3043 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
3048 * Handler of keydown event.
3049 * @param {Event} e KeyDown Event object.
3051 handleKeyDown: function(e) {
3052 if (this.disabled)
3053 return;
3054 var editing = e.target.tagName == 'INPUT' && e.target.value;
3055 switch (e.keyIdentifier) {
3056 case 'Left':
3057 if (!editing) {
3058 if (this.focusedPod_ && this.focusedPod_.previousElementSibling)
3059 this.focusPod(this.focusedPod_.previousElementSibling);
3060 else
3061 this.focusPod(this.lastElementChild);
3063 e.stopPropagation();
3065 break;
3066 case 'Right':
3067 if (!editing) {
3068 if (this.focusedPod_ && this.focusedPod_.nextElementSibling)
3069 this.focusPod(this.focusedPod_.nextElementSibling);
3070 else
3071 this.focusPod(this.firstElementChild);
3073 e.stopPropagation();
3075 break;
3076 case 'Enter':
3077 if (this.focusedPod_) {
3078 var targetTag = e.target.tagName;
3079 if (e.target == this.focusedPod_.passwordElement ||
3080 (targetTag != 'INPUT' &&
3081 targetTag != 'BUTTON' &&
3082 targetTag != 'A')) {
3083 this.setActivatedPod(this.focusedPod_, e);
3084 e.stopPropagation();
3087 break;
3088 case 'U+001B': // Esc
3089 if (!this.alwaysFocusSinglePod)
3090 this.focusPod();
3091 break;
3096 * Called right after the pod row is shown.
3098 handleAfterShow: function() {
3099 // Without timeout changes in pods positions will be animated even though
3100 // it happened when 'flying-pods' class was disabled.
3101 setTimeout(function() {
3102 Oobe.getInstance().toggleClass('flying-pods', true);
3103 }, 0);
3104 // Force input focus for user pod on show and once transition ends.
3105 if (this.focusedPod_) {
3106 var focusedPod = this.focusedPod_;
3107 var screen = this.parentNode;
3108 var self = this;
3109 focusedPod.addEventListener('webkitTransitionEnd', function f(e) {
3110 focusedPod.removeEventListener('webkitTransitionEnd', f);
3111 focusedPod.reset(true);
3112 // Notify screen that it is ready.
3113 screen.onShow();
3115 // Guard timer for 1 second -- it would conver all possible animations.
3116 ensureTransitionEndEvent(focusedPod, 1000);
3121 * Called right before the pod row is shown.
3123 handleBeforeShow: function() {
3124 Oobe.getInstance().toggleClass('flying-pods', false);
3125 for (var event in this.listeners_) {
3126 this.ownerDocument.addEventListener(
3127 event, this.listeners_[event][0], this.listeners_[event][1]);
3129 $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR;
3131 if (this.podPlacementPostponed_) {
3132 this.podPlacementPostponed_ = false;
3133 this.placePods_();
3134 this.maybePreselectPod();
3139 * Called when the element is hidden.
3141 handleHide: function() {
3142 for (var event in this.listeners_) {
3143 this.ownerDocument.removeEventListener(
3144 event, this.listeners_[event][0], this.listeners_[event][1]);
3146 $('login-header-bar').buttonsTabIndex = 0;
3150 * Called when a pod's user image finishes loading.
3152 handlePodImageLoad: function(pod) {
3153 var index = this.podsWithPendingImages_.indexOf(pod);
3154 if (index == -1) {
3155 return;
3158 this.podsWithPendingImages_.splice(index, 1);
3159 if (this.podsWithPendingImages_.length == 0) {
3160 this.classList.remove('images-loading');
3165 * Preselects pod, if needed.
3167 maybePreselectPod: function() {
3168 var pod = this.preselectedPod;
3169 this.focusPod(pod);
3171 // Hide user-type-bubble in case all user pods are disabled and we focus
3172 // first pod.
3173 if (pod && pod.multiProfilesPolicyApplied) {
3174 pod.userTypeBubbleElement.classList.remove('bubble-shown');
3179 return {
3180 PodRow: PodRow