1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview User pod row implementation.
9 cr
.define('login', function() {
11 * Number of displayed columns depending on user pod count.
12 * @type {Array.<number>}
15 var COLUMNS
= [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6];
18 * Mapping between number of columns in pod-row and margin between user pods
20 * @type {Array.<number>}
23 var MARGIN_BY_COLUMNS
= [undefined, 40, 40, 40, 40, 40, 12];
26 * Mapping between number of columns in the desktop pod-row and margin
27 * between user pods for such layout.
28 * @type {Array.<number>}
31 var DESKTOP_MARGIN_BY_COLUMNS
= [undefined, 15, 15, 15, 15, 15, 15];
34 * Maximal number of columns currently supported by pod-row.
38 var MAX_NUMBER_OF_COLUMNS
= 6;
41 * Maximal number of rows if sign-in banner is displayed alonside.
45 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER
= 2;
48 * Variables used for pod placement processing. Width and height should be
49 * synced with computed CSS sizes of pods.
52 var PUBLIC_EXPANDED_BASIC_WIDTH
= 500;
53 var PUBLIC_EXPANDED_ADVANCED_WIDTH
= 610;
54 var CROS_POD_HEIGHT
= 213;
55 var DESKTOP_POD_HEIGHT
= 226;
56 var POD_ROW_PADDING
= 10;
57 var DESKTOP_ROW_PADDING
= 15;
58 var CUSTOM_ICON_CONTAINER_SIZE
= 40;
61 * Minimal padding between user pod and virtual keyboard.
65 var USER_POD_KEYBOARD_MIN_PADDING
= 20;
68 * Maximum time for which the pod row remains hidden until all user images
73 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS
= 3000;
76 * Public session help topic identifier.
80 var HELP_TOPIC_PUBLIC_SESSION
= 3041033;
83 * Tab order for user pods. Update these when adding new controls.
87 var UserPodTabOrder
= {
88 POD_INPUT
: 1, // Password input fields (and whole pods themselves).
89 POD_CUSTOM_ICON
: 2, // Pod custom icon next to passwrod input field.
90 HEADER_BAR
: 3, // Buttons on the header bar (Shutdown, Add User).
91 ACTION_BOX
: 4, // Action box buttons.
92 PAD_MENU_ITEM
: 5 // User pad menu items (Remove this user).
96 * Supported authentication types. Keep in sync with the enum in
97 * chrome/browser/signin/screenlock_bridge.h
106 EXPAND_THEN_USER_CLICK
: 4,
107 FORCE_OFFLINE_PASSWORD
: 5
111 * Names of authentication types.
113 var AUTH_TYPE_NAMES
= {
114 0: 'offlinePassword',
118 4: 'expandThenUserClick',
119 5: 'forceOfflinePassword'
122 // Focus and tab order are organized as follows:
124 // (1) all user pods have tab index 1 so they are traversed first;
125 // (2) when a user pod is activated, its tab index is set to -1 and its
126 // main input field gets focus and tab index 1;
127 // (3) if user pod custom icon is interactive, it has tab index 2 so it
128 // follows the input.
129 // (4) buttons on the header bar have tab index 3 so they follow the custom
130 // icon, or user pod if custom icon is not interactive;
131 // (5) Action box buttons have tab index 4 and follow header bar buttons;
132 // (6) lastly, focus jumps to the Status Area and back to user pods.
134 // 'Focus' event is handled by a capture handler for the whole document
135 // and in some cases 'mousedown' event handlers are used instead of 'click'
136 // handlers where it's necessary to prevent 'focus' event from being fired.
139 * Helper function to remove a class from given element.
140 * @param {!HTMLElement} el Element whose class list to change.
141 * @param {string} cl Class to remove.
143 function removeClass(el
, cl
) {
144 el
.classList
.remove(cl
);
148 * Creates a user pod.
150 * @extends {HTMLDivElement}
152 var UserPod
= cr
.ui
.define(function() {
153 var node
= $('user-pod-template').cloneNode(true);
154 node
.removeAttribute('id');
159 * Stops event propagation from the any user pod child element.
160 * @param {Event} e Event to handle.
162 function stopEventPropagation(e
) {
163 // Prevent default so that we don't trigger a 'focus' event.
169 * Creates an element for custom icon shown in a user pod next to the input
172 * @extends {HTMLDivElement}
174 var UserPodCustomIcon
= cr
.ui
.define(function() {
175 var node
= document
.createElement('div');
176 node
.classList
.add('custom-icon-container');
179 // Create the actual icon element and add it as a child to the container.
180 var iconNode
= document
.createElement('div');
181 iconNode
.classList
.add('custom-icon');
182 node
.appendChild(iconNode
);
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.
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. */
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.
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.
242 /** The tooltip was automatically shown. */
246 UserPodCustomIcon
.prototype = {
247 __proto__
: HTMLDivElement
.prototype,
250 * The id of the icon being shown.
257 * A reference to the timeout for updating icon hover state. Non-null
258 * only if there is an active timeout.
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.
270 updateTooltipAutoshowStateTimeout_
: null,
273 * Callback for click and 'Enter' key events that gets set if the icon is
275 * @type {?function()}
278 actionHandler_
: null,
281 * The current tooltip state.
282 * @type {{active: function(): boolean,
283 * autoshow: !UserPodCustomIcon.TooltipAutoshowState,
284 * hover: !UserPodCustomIcon.HoverState,
290 * Utility method for determining whether the tooltip is active, either as
291 * a result of hover state or being autoshown.
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
,
317 decorate: function() {
318 this.iconElement
.addEventListener(
320 this.updateHoverState_
.bind(this,
321 UserPodCustomIcon
.HoverState
.HOVER
));
322 this.iconElement
.addEventListener(
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
) {
341 * Getter for the icon element's div.
342 * @return {HTMLDivElement}
345 return this.querySelector('.custom-icon');
349 * Updates the icon element class list to properly represent the provided
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
) {
356 UserPodCustomIcon
.ICONS
.forEach(function(icon
) {
357 this.iconElement
.classList
.toggle(icon
.class, id
== icon
.id
);
362 * Sets the ARIA label for the icon.
363 * @param {!string} ariaLabel
365 setAriaLabel: function(ariaLabel
) {
366 this.iconElement
.setAttribute('aria-label', ariaLabel
);
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
384 setTooltip: function(tooltip
) {
385 this.iconElement
.classList
.toggle('icon-with-tooltip', !!tooltip
.text
);
387 this.updateTooltipAutoshowState_(
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
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
) {
408 this.iconElement
.setAttribute('tabIndex',
409 UserPodTabOrder
.POD_CUSTOM_ICON
);
411 this.iconElement
.removeAttribute('tabIndex');
415 // Set the new action handler.
416 this.actionHandler_
= callback
;
420 * Hides the icon and cleans its state.
424 this.clearUpdateHoverStateTimeout_();
425 this.clearUpdateTooltipAutoshowStateTimeout_();
426 this.setInteractive(null);
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.
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.
461 handleClick_: function(e
) {
462 if (!this.actionHandler_
)
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.
474 handleKeyDown_: function(e
) {
475 if (!this.actionHandler_
|| e
.keyIdentifier
!= 'Enter')
477 this.actionHandler_(e
);
478 stopEventPropagation(e
);
482 * Changes the tooltip hover state and updates tooltip visibility if needed.
483 * @param {!UserPodCustomIcon.HoverState} state
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
;
494 this.updateHoverStateSoon_(
495 UserPodCustomIcon
.HoverState
.HOVER_TOOLTIP
);
500 if (state
!= UserPodCustomIcon
.HoverState
.NO_HOVER
&&
501 state
!= UserPodCustomIcon
.HoverState
.HOVER_TOOLTIP
) {
502 console
.error('Invalid hover state ' + state
);
506 this.tooltipState_
.hover
= state
;
507 this.updateTooltip_();
511 * Sets up a timeout for updating icon hover state.
512 * @param {!UserPodCustomIcon.HoverState} state
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.
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
536 * @param {!UserPodCustomIcon.TooltipAutoshowState} state
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_();
551 if (this.tooltipState_
.active()) {
552 if (this.tooltipState_
.autoshow
!=
553 UserPodCustomIcon
.TooltipAutoshowState
.ACTIVE
) {
554 this.tooltipState_
.autoshow
=
555 UserPodCustomIcon
.TooltipAutoshowState
.DISABLED
;
557 // If the tooltip is already automatically shown, the timeout for
558 // removing it should be reset.
559 this.updateTooltipAutoshowStateSoon_(
560 UserPodCustomIcon
.TooltipAutoshowState
.DISABLED
);
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
582 updateTooltipAutoshowStateSoon_: function(state
) {
583 if (this.updateTooltipAutoshowStateTimeout_
)
584 clearTimeout(this.updateTooltupAutoshowStateTimeout_
);
586 state
== UserPodCustomIcon
.TooltipAutoshowState
.DISABLED
?
588 this.updateTooltipAutoshowStateTimeout_
=
589 setTimeout(this.updateTooltipAutoshowState_
.bind(this, state
),
594 * Clears the timeout for updating tooltip autoshow state if one is set.
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.
613 sanitizeTooltipStateIfBubbleHidden_: function() {
614 if (!$('bubble').hidden
)
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.
637 isParentPodFocused_: function() {
638 if ($('account-picker').hidden
)
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
651 updateTooltip_: function() {
652 if (this.hidden
|| !this.isParentPodFocused_())
655 if (!this.tooltipState_
.active() || !this.tooltipState_
.text
) {
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
,
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.
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).
701 userClickAuthAllowed_
: false,
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.signinButtonElement
.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(
730 this.handleRemoveUserConfirmationClick_
.bind(this));
731 this.actionBoxRemoveUserWarningButtonElement
.addEventListener(
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.
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) {
779 this.customIconElement
.cancelDelayedTooltipShow();
783 * Top edge margin number of pixels.
787 this.style
.top
= cr
.ui
.toCssPx(top
);
791 * Top edge margin number of pixels.
794 return parseInt(this.style
.top
);
798 * Left edge margin number of pixels.
802 this.style
.left
= cr
.ui
.toCssPx(left
);
806 * Left edge margin number of pixels.
809 return parseInt(this.style
.left
);
813 * Height number of pixels.
816 return this.offsetHeight
;
820 * Gets image element.
821 * @type {!HTMLImageElement}
824 return this.querySelector('.user-image');
829 * @type {!HTMLDivElement}
832 return this.querySelector('.name');
836 * Gets the container holding the password field.
837 * @type {!HTMLInputElement}
839 get passwordEntryContainerElement() {
840 return this.querySelector('.password-entry-container');
844 * Gets password field.
845 * @type {!HTMLInputElement}
847 get passwordElement() {
848 return this.querySelector('.password');
852 * Gets the password label, which is used to show a message where the
853 * password field is normally.
854 * @type {!HTMLInputElement}
856 get passwordLabelElement() {
857 return this.querySelector('.password-label');
861 * Gets user sign in button.
862 * @type {!HTMLButtonElement}
864 get signinButtonElement() {
865 return this.querySelector('.signin-button');
869 * Gets the container holding the launch app button.
870 * @type {!HTMLButtonElement}
872 get launchAppButtonContainerElement() {
873 return this.querySelector('.launch-app-button-container');
877 * Gets launch app button.
878 * @type {!HTMLButtonElement}
880 get launchAppButtonElement() {
881 return this.querySelector('.launch-app-button');
885 * Gets action box area.
886 * @type {!HTMLInputElement}
888 get actionBoxAreaElement() {
889 return this.querySelector('.action-box-area');
893 * Gets user type icon area.
894 * @type {!HTMLDivElement}
896 get userTypeIconAreaElement() {
897 return this.querySelector('.user-type-icon-area');
901 * Gets user type bubble like multi-profiles policy restriction message.
902 * @type {!HTMLDivElement}
904 get userTypeBubbleElement() {
905 return this.querySelector('.user-type-bubble');
909 * Gets action box menu.
910 * @type {!HTMLInputElement}
912 get actionBoxMenu() {
913 return this.querySelector('.action-box-menu');
917 * Gets action box menu title, user name item.
918 * @type {!HTMLInputElement}
920 get actionBoxMenuTitleNameElement() {
921 return this.querySelector('.action-box-menu-title-name');
925 * Gets action box menu title, user email item.
926 * @type {!HTMLInputElement}
928 get actionBoxMenuTitleEmailElement() {
929 return this.querySelector('.action-box-menu-title-email');
933 * Gets action box menu, remove user command item.
934 * @type {!HTMLInputElement}
936 get actionBoxMenuCommandElement() {
937 return this.querySelector('.action-box-menu-remove-command');
941 * Gets action box menu, remove user command item div.
942 * @type {!HTMLInputElement}
944 get actionBoxMenuRemoveElement() {
945 return this.querySelector('.action-box-menu-remove');
949 * Gets action box menu, remove user warning text div.
950 * @type {!HTMLInputElement}
952 get actionBoxRemoveUserWarningTextElement() {
953 return this.querySelector('.action-box-remove-user-warning-text');
957 * Gets action box menu, remove legacy supervised user warning text div.
958 * @type {!HTMLInputElement}
960 get actionBoxRemoveLegacySupervisedUserWarningTextElement() {
961 return this.querySelector(
962 '.action-box-remove-legacy-supervised-user-warning-text');
966 * Gets action box menu, remove user command item div.
967 * @type {!HTMLInputElement}
969 get actionBoxRemoveUserWarningElement() {
970 return this.querySelector('.action-box-remove-user-warning');
974 * Gets action box menu, remove user command item div.
975 * @type {!HTMLInputElement}
977 get actionBoxRemoveUserWarningButtonElement() {
978 return this.querySelector('.remove-warning-button');
982 * Gets the custom icon. This icon is normally hidden, but can be shown
983 * using the chrome.screenlockPrivate API.
984 * @type {!HTMLDivElement}
986 get customIconElement() {
987 return this.querySelector('.custom-icon-container');
991 * Updates the user pod element.
994 this.imageElement
.src
= 'chrome://userimage/' + this.user
.username
+
995 '?id=' + UserPod
.userImageSalt_
[this.user
.username
];
997 this.nameElement
.textContent
= this.user_
.displayName
;
998 this.classList
.toggle('signed-in', this.user_
.signedIn
);
1000 if (this.isAuthTypeUserClick
)
1001 this.passwordLabelElement
.textContent
= this.authValue
;
1003 this.updateActionBoxArea();
1005 this.passwordElement
.setAttribute('aria-label', loadTimeData
.getStringF(
1006 'passwordFieldAccessibleName', this.user_
.emailAddress
));
1008 this.customizeUserPodPerUserType();
1011 updateActionBoxArea: function() {
1012 if (this.user_
.publicAccount
|| this.user_
.isApp
) {
1013 this.actionBoxAreaElement
.hidden
= true;
1017 this.actionBoxMenuRemoveElement
.hidden
= !this.user_
.canRemove
;
1019 this.actionBoxAreaElement
.setAttribute(
1020 'aria-label', loadTimeData
.getStringF(
1021 'podMenuButtonAccessibleName', this.user_
.emailAddress
));
1022 this.actionBoxMenuRemoveElement
.setAttribute(
1023 'aria-label', loadTimeData
.getString(
1024 'podMenuRemoveItemAccessibleName'));
1025 this.actionBoxMenuTitleNameElement
.textContent
= this.user_
.isOwner
?
1026 loadTimeData
.getStringF('ownerUserPattern', this.user_
.displayName
) :
1027 this.user_
.displayName
;
1028 this.actionBoxMenuTitleEmailElement
.textContent
= this.user_
.emailAddress
;
1030 this.actionBoxMenuTitleEmailElement
.hidden
=
1031 this.user_
.legacySupervisedUser
;
1033 this.actionBoxMenuCommandElement
.textContent
=
1034 loadTimeData
.getString('removeUser');
1037 customizeUserPodPerUserType: function() {
1038 if (this.user_
.childUser
&& !this.user_
.isDesktopUser
) {
1039 this.setUserPodIconType('child');
1040 } else if (this.user_
.legacySupervisedUser
&& !this.user_
.isDesktopUser
) {
1041 this.setUserPodIconType('legacySupervised');
1042 } else if (this.multiProfilesPolicyApplied
) {
1043 // Mark user pod as not focusable which in addition to the grayed out
1044 // filter makes it look in disabled state.
1045 this.classList
.add('multiprofiles-policy-applied');
1046 this.setUserPodIconType('policy');
1048 if (this.user
.multiProfilesPolicy
== 'primary-only')
1049 this.querySelector('.mp-policy-primary-only-msg').hidden
= false;
1050 else if (this.user
.multiProfilesPolicy
== 'owner-primary-only')
1051 this.querySelector('.mp-owner-primary-only-msg').hidden
= false;
1053 this.querySelector('.mp-policy-not-allowed-msg').hidden
= false;
1054 } else if (this.user_
.isApp
) {
1055 this.setUserPodIconType('app');
1059 setUserPodIconType: function(userTypeClass
) {
1060 this.userTypeIconAreaElement
.classList
.add(userTypeClass
);
1061 this.userTypeIconAreaElement
.hidden
= false;
1065 * The user that this pod represents.
1072 set user(userDict
) {
1073 this.user_
= userDict
;
1078 * Returns true if multi-profiles sign in is currently active and this
1079 * user pod is restricted per policy.
1082 get multiProfilesPolicyApplied() {
1083 var isMultiProfilesUI
=
1084 (Oobe
.getInstance().displayType
== DISPLAY_TYPE
.USER_ADDING
);
1085 return isMultiProfilesUI
&& !this.user_
.isMultiProfilesAllowed
;
1089 * Gets main input element.
1090 * @type {(HTMLButtonElement|HTMLInputElement)}
1093 if (this.isAuthTypePassword
) {
1094 return this.passwordElement
;
1095 } else if (this.isAuthTypeOnlineSignIn
) {
1096 return this.signinButtonElement
;
1097 } else if (this.isAuthTypeUserClick
) {
1098 return this.passwordLabelElement
;
1103 * Whether action box button is in active state.
1106 get isActionBoxMenuActive() {
1107 return this.actionBoxAreaElement
.classList
.contains('active');
1109 set isActionBoxMenuActive(active
) {
1110 if (active
== this.isActionBoxMenuActive
)
1114 this.actionBoxMenuRemoveElement
.hidden
= !this.user_
.canRemove
;
1115 this.actionBoxRemoveUserWarningElement
.hidden
= true;
1117 // Clear focus first if another pod is focused.
1118 if (!this.parentNode
.isFocused(this)) {
1119 this.parentNode
.focusPod(undefined, true);
1120 this.actionBoxAreaElement
.focus();
1123 // Hide user-type-bubble.
1124 this.userTypeBubbleElement
.classList
.remove('bubble-shown');
1126 this.actionBoxAreaElement
.classList
.add('active');
1128 // If the user pod is on either edge of the screen, then the menu
1129 // could be displayed partially ofscreen.
1130 this.actionBoxMenu
.classList
.remove('left-edge-offset');
1131 this.actionBoxMenu
.classList
.remove('right-edge-offset');
1134 cr
.ui
.login
.DisplayManager
.getOffset(this.actionBoxMenu
).left
;
1135 var menuWidth
= this.actionBoxMenu
.offsetWidth
;
1137 this.actionBoxMenu
.classList
.add('left-edge-offset');
1138 else if (offsetLeft
+ menuWidth
> window
.innerWidth
)
1139 this.actionBoxMenu
.classList
.add('right-edge-offset');
1141 this.actionBoxAreaElement
.classList
.remove('active');
1142 this.actionBoxAreaElement
.classList
.remove('menu-moved-up');
1143 this.actionBoxMenu
.classList
.remove('menu-moved-up');
1148 * Whether action box button is in hovered state.
1151 get isActionBoxMenuHovered() {
1152 return this.actionBoxAreaElement
.classList
.contains('hovered');
1154 set isActionBoxMenuHovered(hovered
) {
1155 if (hovered
== this.isActionBoxMenuHovered
)
1159 this.actionBoxAreaElement
.classList
.add('hovered');
1160 this.classList
.add('hovered');
1162 if (this.multiProfilesPolicyApplied
)
1163 this.userTypeBubbleElement
.classList
.remove('bubble-shown');
1164 this.actionBoxAreaElement
.classList
.remove('hovered');
1165 this.classList
.remove('hovered');
1170 * Set the authentication type for the pod.
1171 * @param {number} An auth type value defined in the AUTH_TYPE enum.
1172 * @param {string} authValue The initial value used for the auth type.
1174 setAuthType: function(authType
, authValue
) {
1175 this.authType_
= authType
;
1176 this.authValue_
= authValue
;
1177 this.setAttribute('auth-type', AUTH_TYPE_NAMES
[this.authType_
]);
1179 this.reset(this.parentNode
.isFocused(this));
1183 * The auth type of the user pod. This value is one of the enum
1184 * values in AUTH_TYPE.
1188 return this.authType_
;
1192 * The initial value used for the pod's authentication type.
1193 * eg. a prepopulated password input when using password authentication.
1196 return this.authValue_
;
1200 * True if the the user pod uses a password to authenticate.
1203 get isAuthTypePassword() {
1204 return this.authType_
== AUTH_TYPE
.OFFLINE_PASSWORD
||
1205 this.authType_
== AUTH_TYPE
.FORCE_OFFLINE_PASSWORD
;
1209 * True if the the user pod uses a user click to authenticate.
1212 get isAuthTypeUserClick() {
1213 return this.authType_
== AUTH_TYPE
.USER_CLICK
;
1217 * True if the the user pod uses a online sign in to authenticate.
1220 get isAuthTypeOnlineSignIn() {
1221 return this.authType_
== AUTH_TYPE
.ONLINE_SIGN_IN
;
1225 * Updates the image element of the user.
1227 updateUserImage: function() {
1228 UserPod
.userImageSalt_
[this.user
.username
] = new Date().getTime();
1233 * Focuses on input element.
1235 focusInput: function() {
1236 // Move tabIndex from the whole pod to the main input.
1237 // Note: the |mainInput| can be the pod itself.
1239 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1240 this.mainInput
.focus();
1244 * Activates the pod.
1245 * @param {Event} e Event object.
1246 * @return {boolean} True if activated successfully.
1248 activate: function(e
) {
1249 if (this.isAuthTypeOnlineSignIn
) {
1250 this.showSigninUI();
1251 } else if (this.isAuthTypeUserClick
) {
1252 Oobe
.disableSigninUI();
1253 this.classList
.toggle('signing-in', true);
1254 chrome
.send('attemptUnlock', [this.user
.username
]);
1255 } else if (this.isAuthTypePassword
) {
1256 if (!this.passwordElement
.value
)
1258 Oobe
.disableSigninUI();
1259 chrome
.send('authenticateUser',
1260 [this.user
.username
, this.passwordElement
.value
]);
1262 console
.error('Activating user pod with invalid authentication type: ' +
1269 showSupervisedUserSigninWarning: function() {
1270 // Legacy supervised user token has been invalidated.
1271 // Make sure that pod is focused i.e. "Sign in" button is seen.
1272 this.parentNode
.focusPod(this);
1274 var error
= document
.createElement('div');
1275 var messageDiv
= document
.createElement('div');
1276 messageDiv
.className
= 'error-message-bubble';
1277 messageDiv
.textContent
=
1278 loadTimeData
.getString('supervisedUserExpiredTokenWarning');
1279 error
.appendChild(messageDiv
);
1281 $('bubble').showContentForElement(
1282 this.signinButtonElement
,
1283 cr
.ui
.Bubble
.Attachment
.TOP
,
1285 this.signinButtonElement
.offsetWidth
/ 2,
1287 // Move warning bubble up if it overlaps the shelf.
1289 cr
.ui
.LoginUITools
.getMaxHeightBeforeShelfOverlapping($('bubble'));
1290 if (maxHeight
< $('bubble').offsetHeight
) {
1291 $('bubble').showContentForElement(
1292 this.signinButtonElement
,
1293 cr
.ui
.Bubble
.Attachment
.BOTTOM
,
1295 this.signinButtonElement
.offsetWidth
/ 2,
1301 * Shows signin UI for this user.
1303 showSigninUI: function() {
1304 if (this.user
.legacySupervisedUser
&& !this.user
.isDesktopUser
) {
1305 this.showSupervisedUserSigninWarning();
1307 // Special case for multi-profiles sign in. We show users even if they
1308 // are not allowed per policy. Restrict those users from starting GAIA.
1309 if (this.multiProfilesPolicyApplied
)
1312 this.parentNode
.showSigninUI(this.user
.emailAddress
);
1317 * Resets the input field and updates the tab order of pod controls.
1318 * @param {boolean} takeFocus If true, input field takes focus.
1320 reset: function(takeFocus
) {
1321 this.passwordElement
.value
= '';
1322 this.classList
.toggle('signing-in', false);
1324 if (!this.multiProfilesPolicyApplied
)
1325 this.focusInput(); // This will set a custom tab order.
1328 this.resetTabOrder();
1332 * Removes a user using the correct identifier based on user type.
1333 * @param {Object} user User to be removed.
1335 removeUser: function(user
) {
1336 chrome
.send('removeUser',
1337 [user
.isDesktopUser
? user
.profilePath
: user
.username
]);
1341 * Handles a click event on action area button.
1342 * @param {Event} e Click event.
1344 handleActionAreaButtonClick_: function(e
) {
1345 if (this.parentNode
.disabled
)
1347 this.isActionBoxMenuActive
= !this.isActionBoxMenuActive
;
1348 e
.stopPropagation();
1352 * Handles a keydown event on action area button.
1353 * @param {Event} e KeyDown event.
1355 handleActionAreaButtonKeyDown_: function(e
) {
1358 switch (e
.keyIdentifier
) {
1360 case 'U+0020': // Space
1361 if (this.parentNode
.focusedPod_
&& !this.isActionBoxMenuActive
)
1362 this.isActionBoxMenuActive
= true;
1363 e
.stopPropagation();
1367 if (this.isActionBoxMenuActive
) {
1368 this.actionBoxMenuRemoveElement
.tabIndex
=
1369 UserPodTabOrder
.PAD_MENU_ITEM
;
1370 this.actionBoxMenuRemoveElement
.focus();
1372 e
.stopPropagation();
1374 case 'U+001B': // Esc
1375 this.isActionBoxMenuActive
= false;
1376 e
.stopPropagation();
1378 case 'U+0009': // Tab
1379 if (!this.parentNode
.alwaysFocusSinglePod
)
1380 this.parentNode
.focusPod();
1382 this.isActionBoxMenuActive
= false;
1388 * Handles a click event on remove user command.
1389 * @param {Event} e Click event.
1391 handleRemoveCommandClick_: function(e
) {
1392 if (this.user
.legacySupervisedUser
|| this.user
.isDesktopUser
) {
1393 this.showRemoveWarning_();
1396 if (this.isActionBoxMenuActive
)
1397 chrome
.send('removeUser', [this.user
.username
]);
1401 * Shows remove user warning. Used for legacy supervised users on CrOS, and
1402 * for all users on desktop.
1404 showRemoveWarning_: function() {
1405 this.actionBoxMenuRemoveElement
.hidden
= true;
1406 this.actionBoxRemoveUserWarningElement
.hidden
= false;
1407 this.actionBoxRemoveUserWarningButtonElement
.focus();
1409 // Move up the menu if it overlaps shelf.
1410 var maxHeight
= cr
.ui
.LoginUITools
.getMaxHeightBeforeShelfOverlapping(
1411 this.actionBoxMenu
);
1412 var actualHeight
= parseInt(
1413 window
.getComputedStyle(this.actionBoxMenu
).height
);
1414 if (maxHeight
< actualHeight
) {
1415 this.actionBoxMenu
.classList
.add('menu-moved-up');
1416 this.actionBoxAreaElement
.classList
.add('menu-moved-up');
1421 * Handles a click event on remove user confirmation button.
1422 * @param {Event} e Click event.
1424 handleRemoveUserConfirmationClick_: function(e
) {
1425 if (this.isActionBoxMenuActive
) {
1426 this.isActionBoxMenuActive
= false;
1427 this.removeUser(this.user
);
1428 e
.stopPropagation();
1433 * Handles a keydown event on remove user confirmation button.
1434 * @param {Event} e KeyDown event.
1436 handleRemoveUserConfirmationKeyDown_: function(e
) {
1437 if (!this.isActionBoxMenuActive
)
1440 // Only handle pressing 'Enter' or 'Space', and let all other events
1441 // bubble to the action box menu.
1442 if (e
.keyIdentifier
== 'Enter' || e
.keyIdentifier
== 'U+0020') {
1443 this.isActionBoxMenuActive
= false;
1444 this.removeUser(this.user
);
1445 e
.stopPropagation();
1446 // Prevent default so that we don't trigger a 'click' event.
1452 * Handles a keydown event on remove command.
1453 * @param {Event} e KeyDown event.
1455 handleRemoveCommandKeyDown_: function(e
) {
1458 switch (e
.keyIdentifier
) {
1460 if (this.user
.legacySupervisedUser
|| this.user
.isDesktopUser
) {
1461 // Prevent default so that we don't trigger a 'click' event on the
1462 // remove button that will be focused.
1464 this.showRemoveWarning_();
1466 this.removeUser(this.user
);
1468 e
.stopPropagation();
1472 e
.stopPropagation();
1474 case 'U+001B': // Esc
1475 this.actionBoxAreaElement
.focus();
1476 this.isActionBoxMenuActive
= false;
1477 e
.stopPropagation();
1480 this.actionBoxAreaElement
.focus();
1481 this.isActionBoxMenuActive
= false;
1487 * Handles a blur event on remove command.
1488 * @param {Event} e Blur event.
1490 handleRemoveCommandBlur_: function(e
) {
1493 this.actionBoxMenuRemoveElement
.tabIndex
= -1;
1497 * Handles mouse down event. It sets whether the user click auth will be
1498 * allowed on the next mouse click event. The auth is allowed iff the pod
1499 * was focused on the mouse down event starting the click.
1500 * @param {Event} e The mouse down event.
1502 handlePodMouseDown_: function(e
) {
1503 this.userClickAuthAllowed_
= this.parentNode
.isFocused(this);
1507 * Handles click event on a user pod.
1508 * @param {Event} e Click event.
1510 handleClickOnPod_: function(e
) {
1511 if (this.parentNode
.disabled
)
1514 if (!this.isActionBoxMenuActive
) {
1515 if (this.isAuthTypeOnlineSignIn
) {
1516 this.showSigninUI();
1517 } else if (this.isAuthTypeUserClick
&& this.userClickAuthAllowed_
) {
1518 // Note that this.userClickAuthAllowed_ is set in mouse down event
1520 this.parentNode
.setActivatedPod(this);
1523 if (this.multiProfilesPolicyApplied
)
1524 this.userTypeBubbleElement
.classList
.add('bubble-shown');
1526 // Prevent default so that we don't trigger 'focus' event.
1532 * Handles keydown event for a user pod.
1533 * @param {Event} e Key event.
1535 handlePodKeyDown_: function(e
) {
1536 if (!this.isAuthTypeUserClick
|| this.disabled
)
1538 switch (e
.keyIdentifier
) {
1540 case 'U+0020': // Space
1541 if (this.parentNode
.isFocused(this))
1542 this.parentNode
.setActivatedPod(this);
1549 * Creates a public account user pod.
1551 * @extends {UserPod}
1553 var PublicAccountUserPod
= cr
.ui
.define(function() {
1554 var node
= UserPod();
1556 var extras
= $('public-account-user-pod-extras-template').children
;
1557 for (var i
= 0; i
< extras
.length
; ++i
) {
1558 var el
= extras
[i
].cloneNode(true);
1559 node
.appendChild(el
);
1565 PublicAccountUserPod
.prototype = {
1566 __proto__
: UserPod
.prototype,
1569 * "Enter" button in expanded side pane.
1570 * @type {!HTMLButtonElement}
1572 get enterButtonElement() {
1573 return this.querySelector('.enter-button');
1577 * Boolean flag of whether the pod is showing the side pane. The flag
1578 * controls whether 'expanded' class is added to the pod's class list and
1579 * resets tab order because main input element changes when the 'expanded'
1584 return this.classList
.contains('expanded');
1587 set expanded(expanded
) {
1588 if (this.expanded
== expanded
)
1591 this.resetTabOrder();
1592 this.classList
.toggle('expanded', expanded
);
1594 // Show the advanced expanded pod directly if there are at least two
1595 // recommended locales. This will be the case in multilingual
1596 // environments where users are likely to want to choose among locales.
1597 if (this.querySelector('.language-select').multipleRecommendedLocales
)
1598 this.classList
.add('advanced');
1599 this.usualLeft
= this.left
;
1600 this.makeSpaceForExpandedPod_();
1601 } else if (typeof(this.usualLeft
) != 'undefined') {
1602 this.left
= this.usualLeft
;
1606 this.classList
.add('animating');
1607 this.addEventListener('webkitTransitionEnd', function f(e
) {
1608 self
.removeEventListener('webkitTransitionEnd', f
);
1609 self
.classList
.remove('animating');
1611 // Accessibility focus indicator does not move with the focused
1612 // element. Sends a 'focus' event on the currently focused element
1613 // so that accessibility focus indicator updates its location.
1614 if (document
.activeElement
)
1615 document
.activeElement
.dispatchEvent(new Event('focus'));
1617 // Guard timer set to animation duration + 20ms.
1618 ensureTransitionEndEvent(this, 200);
1622 return this.classList
.contains('advanced');
1628 return this.enterButtonElement
;
1630 return this.nameElement
;
1634 decorate: function() {
1635 UserPod
.prototype.decorate
.call(this);
1637 this.classList
.add('public-account');
1639 this.nameElement
.addEventListener('keydown', (function(e
) {
1640 if (e
.keyIdentifier
== 'Enter') {
1641 this.parentNode
.setActivatedPod(this, e
);
1642 // Stop this keydown event from bubbling up to PodRow handler.
1643 e
.stopPropagation();
1644 // Prevent default so that we don't trigger a 'click' event on the
1645 // newly focused "Enter" button.
1650 var learnMore
= this.querySelector('.learn-more');
1651 learnMore
.addEventListener('mousedown', stopEventPropagation
);
1652 learnMore
.addEventListener('click', this.handleLearnMoreEvent
);
1653 learnMore
.addEventListener('keydown', this.handleLearnMoreEvent
);
1655 learnMore
= this.querySelector('.expanded-pane-learn-more');
1656 learnMore
.addEventListener('click', this.handleLearnMoreEvent
);
1657 learnMore
.addEventListener('keydown', this.handleLearnMoreEvent
);
1659 var languageSelect
= this.querySelector('.language-select');
1660 languageSelect
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1661 languageSelect
.manuallyChanged
= false;
1662 languageSelect
.addEventListener(
1665 languageSelect
.manuallyChanged
= true;
1666 this.getPublicSessionKeyboardLayouts_();
1669 var keyboardSelect
= this.querySelector('.keyboard-select');
1670 keyboardSelect
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1671 keyboardSelect
.loadedLocale
= null;
1673 var languageAndInput
= this.querySelector('.language-and-input');
1674 languageAndInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1675 languageAndInput
.addEventListener('click',
1676 this.transitionToAdvanced_
.bind(this));
1678 this.enterButtonElement
.addEventListener('click', (function(e
) {
1679 this.enterButtonElement
.disabled
= true;
1680 var locale
= this.querySelector('.language-select').value
;
1681 var keyboardSelect
= this.querySelector('.keyboard-select');
1682 // The contents of |keyboardSelect| is updated asynchronously. If its
1683 // locale does not match |locale|, it has not updated yet and the
1684 // currently selected keyboard layout may not be applicable to |locale|.
1685 // Do not return any keyboard layout in this case and let the backend
1686 // choose a suitable layout.
1687 var keyboardLayout
=
1688 keyboardSelect
.loadedLocale
== locale
? keyboardSelect
.value
: '';
1689 chrome
.send('launchPublicSession',
1690 [this.user
.username
, locale
, keyboardLayout
]);
1695 initialize: function() {
1696 UserPod
.prototype.initialize
.call(this);
1698 id
= this.user
.username
+ '-keyboard';
1699 this.querySelector('.keyboard-select-label').htmlFor
= id
;
1700 this.querySelector('.keyboard-select').setAttribute('id', id
);
1702 var id
= this.user
.username
+ '-language';
1703 this.querySelector('.language-select-label').htmlFor
= id
;
1704 var languageSelect
= this.querySelector('.language-select');
1705 languageSelect
.setAttribute('id', id
);
1706 this.populateLanguageSelect(this.user
.initialLocales
,
1707 this.user
.initialLocale
,
1708 this.user
.initialMultipleRecommendedLocales
);
1712 update: function() {
1713 UserPod
.prototype.update
.call(this);
1714 this.querySelector('.expanded-pane-name').textContent
=
1715 this.user_
.displayName
;
1716 this.querySelector('.info').textContent
=
1717 loadTimeData
.getStringF('publicAccountInfoFormat',
1718 this.user_
.enterpriseDomain
);
1722 focusInput: function() {
1723 // Move tabIndex from the whole pod to the main input.
1725 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1726 this.mainInput
.focus();
1730 reset: function(takeFocus
) {
1732 this.expanded
= false;
1733 this.enterButtonElement
.disabled
= false;
1734 UserPod
.prototype.reset
.call(this, takeFocus
);
1738 activate: function(e
) {
1739 if (!this.expanded
) {
1740 this.expanded
= true;
1747 handleClickOnPod_: function(e
) {
1748 if (this.parentNode
.disabled
)
1751 this.parentNode
.focusPod(this);
1752 this.parentNode
.setActivatedPod(this, e
);
1753 // Prevent default so that we don't trigger 'focus' event.
1758 * Updates the display name shown on the pod.
1759 * @param {string} displayName The new display name
1761 setDisplayName: function(displayName
) {
1762 this.user_
.displayName
= displayName
;
1767 * Handle mouse and keyboard events for the learn more button. Triggering
1768 * the button causes information about public sessions to be shown.
1769 * @param {Event} event Mouse or keyboard event.
1771 handleLearnMoreEvent: function(event
) {
1772 switch (event
.type
) {
1773 // Show informaton on left click. Let any other clicks propagate.
1775 if (event
.button
!= 0)
1778 // Show informaton when <Return> or <Space> is pressed. Let any other
1779 // key presses propagate.
1781 switch (event
.keyCode
) {
1790 chrome
.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION
]);
1791 stopEventPropagation(event
);
1794 makeSpaceForExpandedPod_: function() {
1795 var width
= this.classList
.contains('advanced') ?
1796 PUBLIC_EXPANDED_ADVANCED_WIDTH
: PUBLIC_EXPANDED_BASIC_WIDTH
;
1797 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
1798 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
1799 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
1801 if (this.left
+ width
> $('pod-row').offsetWidth
- rowPadding
)
1802 this.left
= $('pod-row').offsetWidth
- rowPadding
- width
;
1806 * Transition the expanded pod from the basic to the advanced view.
1808 transitionToAdvanced_: function() {
1810 var languageAndInputSection
=
1811 this.querySelector('.language-and-input-section');
1812 this.classList
.add('transitioning-to-advanced');
1813 setTimeout(function() {
1814 pod
.classList
.add('advanced');
1815 pod
.makeSpaceForExpandedPod_();
1816 languageAndInputSection
.addEventListener('webkitTransitionEnd',
1817 function observer() {
1818 languageAndInputSection
.removeEventListener('webkitTransitionEnd',
1820 pod
.classList
.remove('transitioning-to-advanced');
1821 pod
.querySelector('.language-select').focus();
1823 // Guard timer set to animation duration + 20ms.
1824 ensureTransitionEndEvent(languageAndInputSection
, 380);
1829 * Retrieves the list of keyboard layouts available for the currently
1832 getPublicSessionKeyboardLayouts_: function() {
1833 var selectedLocale
= this.querySelector('.language-select').value
;
1834 if (selectedLocale
==
1835 this.querySelector('.keyboard-select').loadedLocale
) {
1836 // If the list of keyboard layouts was loaded for the currently selected
1837 // locale, it is already up to date.
1840 chrome
.send('getPublicSessionKeyboardLayouts',
1841 [this.user
.username
, selectedLocale
]);
1845 * Populates the keyboard layout "select" element with a list of layouts.
1846 * @param {string} locale The locale to which this list of keyboard layouts
1848 * @param {!Object} list List of available keyboard layouts
1850 populateKeyboardSelect: function(locale
, list
) {
1851 if (locale
!= this.querySelector('.language-select').value
) {
1852 // The selected locale has changed and the list of keyboard layouts is
1853 // not applicable. This method will be called again when a list of
1854 // keyboard layouts applicable to the selected locale is retrieved.
1858 var keyboardSelect
= this.querySelector('.keyboard-select');
1859 keyboardSelect
.loadedLocale
= locale
;
1860 keyboardSelect
.innerHTML
= '';
1861 for (var i
= 0; i
< list
.length
; ++i
) {
1863 keyboardSelect
.appendChild(
1864 new Option(item
.title
, item
.value
, item
.selected
, item
.selected
));
1869 * Populates the language "select" element with a list of locales.
1870 * @param {!Object} locales The list of available locales
1871 * @param {string} defaultLocale The locale to select by default
1872 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
1873 * two or more recommended locales
1875 populateLanguageSelect: function(locales
,
1877 multipleRecommendedLocales
) {
1878 var languageSelect
= this.querySelector('.language-select');
1879 // If the user manually selected a locale, do not change the selection.
1880 // Otherwise, select the new |defaultLocale|.
1882 languageSelect
.manuallyChanged
? languageSelect
.value
: defaultLocale
;
1883 languageSelect
.innerHTML
= '';
1884 var group
= languageSelect
;
1885 for (var i
= 0; i
< locales
.length
; ++i
) {
1886 var item
= locales
[i
];
1887 if (item
.optionGroupName
) {
1888 group
= document
.createElement('optgroup');
1889 group
.label
= item
.optionGroupName
;
1890 languageSelect
.appendChild(group
);
1892 group
.appendChild(new Option(item
.title
,
1894 item
.value
== selected
,
1895 item
.value
== selected
));
1898 languageSelect
.multipleRecommendedLocales
= multipleRecommendedLocales
;
1900 // Retrieve a list of keyboard layouts applicable to the locale that is
1902 this.getPublicSessionKeyboardLayouts_();
1907 * Creates a user pod to be used only in desktop chrome.
1909 * @extends {UserPod}
1911 var DesktopUserPod
= cr
.ui
.define(function() {
1912 // Don't just instantiate a UserPod(), as this will call decorate() on the
1913 // parent object, and add duplicate event listeners.
1914 var node
= $('user-pod-template').cloneNode(true);
1915 node
.removeAttribute('id');
1919 DesktopUserPod
.prototype = {
1920 __proto__
: UserPod
.prototype,
1924 if (this.user
.needsSignin
)
1925 return this.passwordElement
;
1927 return this.nameElement
;
1931 update: function() {
1932 this.imageElement
.src
= this.user
.userImage
;
1933 this.nameElement
.textContent
= this.user
.displayName
;
1935 var isLockedUser
= this.user
.needsSignin
;
1936 var isLegacySupervisedUser
= this.user
.legacySupervisedUser
;
1937 var isChildUser
= this.user
.childUser
;
1938 this.classList
.toggle('locked', isLockedUser
);
1939 this.classList
.toggle('legacy-supervised', isLegacySupervisedUser
);
1940 this.classList
.toggle('child', isChildUser
);
1942 if (this.isAuthTypeUserClick
)
1943 this.passwordLabelElement
.textContent
= this.authValue
;
1945 this.actionBoxRemoveUserWarningTextElement
.hidden
=
1946 isLegacySupervisedUser
;
1947 this.actionBoxRemoveLegacySupervisedUserWarningTextElement
.hidden
=
1948 !isLegacySupervisedUser
;
1950 this.passwordElement
.setAttribute('aria-label', loadTimeData
.getStringF(
1951 'passwordFieldAccessibleName', this.user_
.emailAddress
));
1953 UserPod
.prototype.updateActionBoxArea
.call(this);
1957 focusInput: function() {
1958 // Move tabIndex from the whole pod to the main input.
1960 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1961 this.mainInput
.focus();
1965 activate: function(e
) {
1966 if (!this.user
.needsSignin
) {
1967 Oobe
.launchUser(this.user
.profilePath
);
1968 } else if (!this.passwordElement
.value
) {
1971 chrome
.send('authenticatedLaunchUser',
1972 [this.user
.profilePath
,
1973 this.user
.emailAddress
,
1974 this.passwordElement
.value
]);
1976 this.passwordElement
.value
= '';
1981 handleClickOnPod_: function(e
) {
1982 if (this.parentNode
.disabled
)
1986 this.parentNode
.lastFocusedPod_
= this;
1988 // If this is an unlocked pod, then open a browser window. Otherwise
1989 // just activate the pod and show the password field.
1990 if (!this.user
.needsSignin
&& !this.isActionBoxMenuActive
)
1993 if (this.isAuthTypeUserClick
)
1994 chrome
.send('attemptUnlock', [this.user
.emailAddress
]);
1999 * Creates a user pod that represents kiosk app.
2001 * @extends {UserPod}
2003 var KioskAppPod
= cr
.ui
.define(function() {
2004 var node
= UserPod();
2008 KioskAppPod
.prototype = {
2009 __proto__
: UserPod
.prototype,
2012 decorate: function() {
2013 UserPod
.prototype.decorate
.call(this);
2014 this.launchAppButtonElement
.addEventListener('click',
2015 this.activate
.bind(this));
2019 update: function() {
2020 this.imageElement
.src
= this.user
.iconUrl
;
2021 this.imageElement
.alt
= this.user
.label
;
2022 this.imageElement
.title
= this.user
.label
;
2023 this.passwordEntryContainerElement
.hidden
= true;
2024 this.launchAppButtonContainerElement
.hidden
= false;
2025 this.nameElement
.textContent
= this.user
.label
;
2027 UserPod
.prototype.updateActionBoxArea
.call(this);
2028 UserPod
.prototype.customizeUserPodPerUserType
.call(this);
2033 return this.launchAppButtonElement
;
2037 focusInput: function() {
2038 // Move tabIndex from the whole pod to the main input.
2040 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
2041 this.mainInput
.focus();
2045 get forceOnlineSignin() {
2050 activate: function(e
) {
2051 var diagnosticMode
= e
&& e
.ctrlKey
;
2052 this.launchApp_(this.user
, diagnosticMode
);
2057 handleClickOnPod_: function(e
) {
2058 if (this.parentNode
.disabled
)
2062 this.parentNode
.lastFocusedPod_
= this;
2067 * Launch the app. If |diagnosticMode| is true, ask user to confirm.
2068 * @param {Object} app App data.
2069 * @param {boolean} diagnosticMode Whether to run the app in diagnostic
2072 launchApp_: function(app
, diagnosticMode
) {
2073 if (!diagnosticMode
) {
2074 chrome
.send('launchKioskApp', [app
.id
, false]);
2078 var oobe
= $('oobe');
2079 if (!oobe
.confirmDiagnosticMode_
) {
2080 oobe
.confirmDiagnosticMode_
=
2081 new cr
.ui
.dialogs
.ConfirmDialog(document
.body
);
2082 oobe
.confirmDiagnosticMode_
.setOkLabel(
2083 loadTimeData
.getString('confirmKioskAppDiagnosticModeYes'));
2084 oobe
.confirmDiagnosticMode_
.setCancelLabel(
2085 loadTimeData
.getString('confirmKioskAppDiagnosticModeNo'));
2088 oobe
.confirmDiagnosticMode_
.show(
2089 loadTimeData
.getStringF('confirmKioskAppDiagnosticModeFormat',
2092 chrome
.send('launchKioskApp', [app
.id
, true]);
2098 * Creates a new pod row element.
2100 * @extends {HTMLDivElement}
2102 var PodRow
= cr
.ui
.define('podrow');
2104 PodRow
.prototype = {
2105 __proto__
: HTMLDivElement
.prototype,
2107 // Whether this user pod row is shown for the first time.
2110 // True if inside focusPod().
2111 insideFocusPod_
: false,
2114 focusedPod_
: undefined,
2116 // Activated pod, i.e. the pod of current login attempt.
2117 activatedPod_
: undefined,
2119 // Pod that was most recently focused, if any.
2120 lastFocusedPod_
: undefined,
2122 // Pods whose initial images haven't been loaded yet.
2123 podsWithPendingImages_
: [],
2125 // Whether pod placement has been postponed.
2126 podPlacementPostponed_
: false,
2128 // Standard user pod height/width.
2132 // Array of apps that are shown in addition to other user pods.
2135 // True to show app pods along with user pods.
2136 shouldShowApps_
: true,
2138 // Array of users that are shown (public/supervised/regular).
2141 // If we're disabling single pod autofocus for Touch View.
2142 touchViewSinglePodExperimentOn_
: true,
2146 decorate: function() {
2147 // Event listeners that are installed for the time period during which
2148 // the element is visible.
2150 focus
: [this.handleFocus_
.bind(this), true /* useCapture */],
2151 click
: [this.handleClick_
.bind(this), true],
2152 mousemove
: [this.handleMouseMove_
.bind(this), false],
2153 keydown
: [this.handleKeyDown
.bind(this), false]
2156 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2157 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2158 this.userPodHeight_
= isDesktopUserManager
? DESKTOP_POD_HEIGHT
:
2160 // Same for Chrome OS and desktop.
2161 this.userPodWidth_
= POD_WIDTH
;
2165 * Returns all the pods in this pod row.
2169 return Array
.prototype.slice
.call(this.children
);
2173 * Return true if user pod row has only single user pod in it, which should
2174 * always be focused except desktop and touch view modes.
2177 get alwaysFocusSinglePod() {
2178 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2179 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2181 return (isDesktopUserManager
||
2182 (this.touchViewSinglePodExperimentOn_
&&
2183 this.touchViewEnabled_
)) ?
2184 false : this.children
.length
== 1;
2188 * Returns pod with the given app id.
2189 * @param {!string} app_id Application id to be matched.
2190 * @return {Object} Pod with the given app id. null if pod hasn't been
2193 getPodWithAppId_: function(app_id
) {
2194 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2195 if (pod
.user
.isApp
&& pod
.user
.id
== app_id
)
2202 * Returns pod with the given username (null if there is no such pod).
2203 * @param {string} username Username to be matched.
2204 * @return {Object} Pod with the given username. null if pod hasn't been
2207 getPodWithUsername_: function(username
) {
2208 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2209 if (pod
.user
.username
== username
)
2216 * True if the the pod row is disabled (handles no user interaction).
2221 return this.disabled_
;
2223 set disabled(value
) {
2224 this.disabled_
= value
;
2225 var controls
= this.querySelectorAll('button,input');
2226 for (var i
= 0, control
; control
= controls
[i
]; ++i
) {
2227 control
.disabled
= value
;
2232 * Creates a user pod from given email.
2233 * @param {!Object} user User info dictionary.
2235 createUserPod: function(user
) {
2237 if (user
.isDesktopUser
)
2238 userPod
= new DesktopUserPod({user
: user
});
2239 else if (user
.publicAccount
)
2240 userPod
= new PublicAccountUserPod({user
: user
});
2241 else if (user
.isApp
)
2242 userPod
= new KioskAppPod({user
: user
});
2244 userPod
= new UserPod({user
: user
});
2246 userPod
.hidden
= false;
2251 * Add an existing user pod to this pod row.
2252 * @param {!Object} user User info dictionary.
2254 addUserPod: function(user
) {
2255 var userPod
= this.createUserPod(user
);
2256 this.appendChild(userPod
);
2257 userPod
.initialize();
2261 * Runs app with a given id from the list of loaded apps.
2262 * @param {!string} app_id of an app to run.
2263 * @param {boolean=} opt_diagnostic_mode Whether to run the app in
2264 * diagnostic mode. Default is false.
2266 findAndRunAppForTesting: function(app_id
, opt_diagnostic_mode
) {
2267 var app
= this.getPodWithAppId_(app_id
);
2269 var activationEvent
= cr
.doc
.createEvent('MouseEvents');
2270 var ctrlKey
= opt_diagnostic_mode
;
2271 activationEvent
.initMouseEvent('click', true, true, null,
2272 0, 0, 0, 0, 0, ctrlKey
, false, false, false, 0, null);
2273 app
.dispatchEvent(activationEvent
);
2278 * Removes user pod from pod row.
2279 * @param {string} email User's email.
2281 removeUserPod: function(username
) {
2282 var podToRemove
= this.getPodWithUsername_(username
);
2283 if (podToRemove
== null) {
2284 console
.warn('Attempt to remove not existing pod for ' + username
+
2288 this.removeChild(podToRemove
);
2289 if (this.pods
.length
> 0)
2294 * Returns index of given pod or -1 if not found.
2295 * @param {UserPod} pod Pod to look up.
2298 indexOf_: function(pod
) {
2299 for (var i
= 0; i
< this.pods
.length
; ++i
) {
2300 if (pod
== this.pods
[i
])
2307 * Populates pod row with given existing users and start init animation.
2308 * @param {array} users Array of existing user emails.
2310 loadPods: function(users
) {
2311 this.users_
= users
;
2317 * Scrolls focused user pod into view.
2319 scrollFocusedPodIntoView: function() {
2320 var pod
= this.focusedPod_
;
2324 // First check whether focused pod is already fully visible.
2325 var visibleArea
= $('scroll-container');
2326 var scrollTop
= visibleArea
.scrollTop
;
2327 var clientHeight
= visibleArea
.clientHeight
;
2328 var podTop
= $('oobe').offsetTop
+ pod
.offsetTop
;
2329 var padding
= USER_POD_KEYBOARD_MIN_PADDING
;
2330 if (podTop
+ pod
.height
+ padding
<= scrollTop
+ clientHeight
&&
2331 podTop
- padding
>= scrollTop
) {
2335 // Scroll so that user pod is as centered as possible.
2336 visibleArea
.scrollTop
= podTop
- (clientHeight
- pod
.offsetHeight
) / 2;
2340 * Rebuilds pod row using users_ and apps_ that were previously set or
2343 rebuildPods: function() {
2344 var emptyPodRow
= this.pods
.length
== 0;
2346 // Clear existing pods.
2347 this.innerHTML
= '';
2348 this.focusedPod_
= undefined;
2349 this.activatedPod_
= undefined;
2350 this.lastFocusedPod_
= undefined;
2352 // Switch off animation
2353 Oobe
.getInstance().toggleClass('flying-pods', false);
2355 // Populate the pod row.
2356 for (var i
= 0; i
< this.users_
.length
; ++i
)
2357 this.addUserPod(this.users_
[i
]);
2359 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
)
2360 this.podsWithPendingImages_
.push(pod
);
2362 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
2363 if (this.shouldShowApps_
) {
2364 for (var i
= 0; i
< this.apps_
.length
; ++i
)
2365 this.addUserPod(this.apps_
[i
]);
2368 // Make sure we eventually show the pod row, even if some image is stuck.
2369 setTimeout(function() {
2370 $('pod-row').classList
.remove('images-loading');
2371 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS
);
2373 var isAccountPicker
= $('login-header-bar').signinUIState
==
2374 SIGNIN_UI_STATE
.ACCOUNT_PICKER
;
2376 // Immediately recalculate pods layout only when current UI is account
2377 // picker. Otherwise postpone it.
2378 if (isAccountPicker
) {
2380 this.maybePreselectPod();
2382 // Without timeout changes in pods positions will be animated even
2383 // though it happened when 'flying-pods' class was disabled.
2384 setTimeout(function() {
2385 Oobe
.getInstance().toggleClass('flying-pods', true);
2388 this.podPlacementPostponed_
= true;
2390 // Update [Cancel] button state.
2391 if ($('login-header-bar').signinUIState
==
2392 SIGNIN_UI_STATE
.GAIA_SIGNIN
&&
2394 this.pods
.length
> 0) {
2395 login
.GaiaSigninScreen
.updateCancelButtonState();
2401 * Adds given apps to the pod row.
2402 * @param {array} apps Array of apps.
2404 setApps: function(apps
) {
2407 chrome
.send('kioskAppsLoaded');
2409 // Check whether there's a pending kiosk app error.
2410 window
.setTimeout(function() {
2411 chrome
.send('checkKioskAppLaunchError');
2416 * Sets whether should show app pods.
2417 * @param {boolean} shouldShowApps Whether app pods should be shown.
2419 setShouldShowApps: function(shouldShowApps
) {
2420 if (this.shouldShowApps_
== shouldShowApps
)
2423 this.shouldShowApps_
= shouldShowApps
;
2428 * Shows a custom icon on a user pod besides the input field.
2429 * @param {string} username Username of pod to add button
2430 * @param {!{id: !string,
2431 * hardlockOnClick: boolean,
2432 * isTrialRun: boolean,
2433 * ariaLabel: string | undefined,
2434 * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon
2435 * The icon parameters.
2437 showUserPodCustomIcon: function(username
, icon
) {
2438 var pod
= this.getPodWithUsername_(username
);
2440 console
.error('Unable to show user pod button for ' + username
+
2441 ': user pod not found.');
2445 if (!icon
.id
&& !icon
.tooltip
)
2449 pod
.customIconElement
.setIcon(icon
.id
);
2451 if (icon
.isTrialRun
) {
2452 pod
.customIconElement
.setInteractive(
2453 this.onDidClickLockIconDuringTrialRun_
.bind(this, username
));
2454 } else if (icon
.hardlockOnClick
) {
2455 pod
.customIconElement
.setInteractive(
2456 this.hardlockUserPod_
.bind(this, username
));
2458 pod
.customIconElement
.setInteractive(null);
2461 var ariaLabel
= icon
.ariaLabel
|| (icon
.tooltip
&& icon
.tooltip
.text
);
2463 pod
.customIconElement
.setAriaLabel(ariaLabel
);
2465 console
.warn('No ARIA label for user pod custom icon.');
2467 pod
.customIconElement
.show();
2469 // This has to be called after |show| in case the tooltip should be shown
2471 pod
.customIconElement
.setTooltip(
2472 icon
.tooltip
|| {text
: '', autoshow
: false});
2476 * Hard-locks user pod for the user. If user pod is hard-locked, it can be
2477 * only unlocked using password, and the authentication type cannot be
2479 * @param {!string} username The user's username.
2482 hardlockUserPod_: function(username
) {
2483 chrome
.send('hardlockPod', [username
]);
2487 * Records a metric indicating that the user clicked on the lock icon during
2488 * the trial run for Easy Unlock.
2489 * @param {!string} username The user's username.
2492 onDidClickLockIconDuringTrialRun_: function(username
) {
2493 chrome
.send('recordClickOnLockIcon', [username
]);
2497 * Hides the custom icon in the user pod added by showUserPodCustomIcon().
2498 * @param {string} username Username of pod to remove button
2500 hideUserPodCustomIcon: function(username
) {
2501 var pod
= this.getPodWithUsername_(username
);
2503 console
.error('Unable to hide user pod button for ' + username
+
2504 ': user pod not found.');
2508 // TODO(tengs): Allow option for a fading transition.
2509 pod
.customIconElement
.hide();
2513 * Sets the authentication type used to authenticate the user.
2514 * @param {string} username Username of selected user
2515 * @param {number} authType Authentication type, must be one of the
2516 * values listed in AUTH_TYPE enum.
2517 * @param {string} value The initial value to use for authentication.
2519 setAuthType: function(username
, authType
, value
) {
2520 var pod
= this.getPodWithUsername_(username
);
2522 console
.error('Unable to set auth type for ' + username
+
2523 ': user pod not found.');
2526 pod
.setAuthType(authType
, value
);
2530 * Sets the state of touch view mode.
2531 * @param {boolean} isTouchViewEnabled true if the mode is on.
2533 setTouchViewState: function(isTouchViewEnabled
) {
2534 this.touchViewEnabled_
= isTouchViewEnabled
;
2538 * Updates the display name shown on a public session pod.
2539 * @param {string} userID The user ID of the public session
2540 * @param {string} displayName The new display name
2542 setPublicSessionDisplayName: function(userID
, displayName
) {
2543 var pod
= this.getPodWithUsername_(userID
);
2545 pod
.setDisplayName(displayName
);
2549 * Updates the list of locales available for a public session.
2550 * @param {string} userID The user ID of the public session
2551 * @param {!Object} locales The list of available locales
2552 * @param {string} defaultLocale The locale to select by default
2553 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
2554 * two or more recommended locales
2556 setPublicSessionLocales: function(userID
,
2559 multipleRecommendedLocales
) {
2560 var pod
= this.getPodWithUsername_(userID
);
2562 pod
.populateLanguageSelect(locales
,
2564 multipleRecommendedLocales
);
2569 * Updates the list of available keyboard layouts for a public session pod.
2570 * @param {string} userID The user ID of the public session
2571 * @param {string} locale The locale to which this list of keyboard layouts
2573 * @param {!Object} list List of available keyboard layouts
2575 setPublicSessionKeyboardLayouts: function(userID
, locale
, list
) {
2576 var pod
= this.getPodWithUsername_(userID
);
2578 pod
.populateKeyboardSelect(locale
, list
);
2582 * Called when window was resized.
2584 onWindowResize: function() {
2585 var layout
= this.calculateLayout_();
2586 if (layout
.columns
!= this.columns
|| layout
.rows
!= this.rows
)
2589 if (Oobe
.getInstance().virtualKeyboardShown
)
2590 this.scrollFocusedPodIntoView();
2594 * Returns width of podrow having |columns| number of columns.
2597 columnsToWidth_: function(columns
) {
2598 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2599 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2600 var margin
= isDesktopUserManager
? DESKTOP_MARGIN_BY_COLUMNS
[columns
] :
2601 MARGIN_BY_COLUMNS
[columns
];
2602 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
2604 return 2 * rowPadding
+ columns
* this.userPodWidth_
+
2605 (columns
- 1) * margin
;
2609 * Returns height of podrow having |rows| number of rows.
2612 rowsToHeight_: function(rows
) {
2613 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2614 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2615 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
2617 return 2 * rowPadding
+ rows
* this.userPodHeight_
;
2621 * Calculates number of columns and rows that podrow should have in order to
2622 * hold as much its pods as possible for current screen size. Also it tries
2623 * to choose layout that looks good.
2624 * @return {{columns: number, rows: number}}
2626 calculateLayout_: function() {
2627 var preferredColumns
= this.pods
.length
< COLUMNS
.length
?
2628 COLUMNS
[this.pods
.length
] : COLUMNS
[COLUMNS
.length
- 1];
2629 var maxWidth
= Oobe
.getInstance().clientAreaSize
.width
;
2630 var columns
= preferredColumns
;
2631 while (maxWidth
< this.columnsToWidth_(columns
) && columns
> 1)
2633 var rows
= Math
.floor((this.pods
.length
- 1) / columns
) + 1;
2634 if (getComputedStyle(
2635 $('signin-banner'), null).getPropertyValue('display') != 'none') {
2636 rows
= Math
.min(rows
, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER
);
2638 var maxHeigth
= Oobe
.getInstance().clientAreaSize
.height
;
2639 while (maxHeigth
< this.rowsToHeight_(rows
) && rows
> 1)
2641 // One more iteration if it's not enough cells to place all pods.
2642 while (maxWidth
>= this.columnsToWidth_(columns
+ 1) &&
2643 columns
* rows
< this.pods
.length
&&
2644 columns
< MAX_NUMBER_OF_COLUMNS
) {
2647 return {columns
: columns
, rows
: rows
};
2651 * Places pods onto their positions onto pod grid.
2654 placePods_: function() {
2655 var layout
= this.calculateLayout_();
2656 var columns
= this.columns
= layout
.columns
;
2657 var rows
= this.rows
= layout
.rows
;
2658 var maxPodsNumber
= columns
* rows
;
2659 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2660 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2661 var margin
= isDesktopUserManager
? DESKTOP_MARGIN_BY_COLUMNS
[columns
] :
2662 MARGIN_BY_COLUMNS
[columns
];
2663 this.parentNode
.setPreferredSize(
2664 this.columnsToWidth_(columns
), this.rowsToHeight_(rows
));
2665 var height
= this.userPodHeight_
;
2666 var width
= this.userPodWidth_
;
2667 this.pods
.forEach(function(pod
, index
) {
2668 if (index
>= maxPodsNumber
) {
2673 if (pod
.offsetHeight
!= height
) {
2674 console
.error('Pod offsetHeight (' + pod
.offsetHeight
+
2675 ') and POD_HEIGHT (' + height
+ ') are not equal.');
2677 if (pod
.offsetWidth
!= width
) {
2678 console
.error('Pod offsetWidth (' + pod
.offsetWidth
+
2679 ') and POD_WIDTH (' + width
+ ') are not equal.');
2681 var column
= index
% columns
;
2682 var row
= Math
.floor(index
/ columns
);
2683 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
2685 pod
.left
= rowPadding
+ column
* (width
+ margin
);
2687 // On desktop, we want the rows to always be equally spaced.
2688 pod
.top
= isDesktopUserManager
? row
* (height
+ rowPadding
) :
2689 row
* height
+ rowPadding
;
2691 Oobe
.getInstance().updateScreenSize(this.parentNode
);
2695 * Number of columns.
2698 set columns(columns
) {
2699 // Cannot use 'columns' here.
2700 this.setAttribute('ncolumns', columns
);
2703 return parseInt(this.getAttribute('ncolumns'));
2711 // Cannot use 'rows' here.
2712 this.setAttribute('nrows', rows
);
2715 return parseInt(this.getAttribute('nrows'));
2719 * Whether the pod is currently focused.
2720 * @param {UserPod} pod Pod to check for focus.
2721 * @return {boolean} Pod focus status.
2723 isFocused: function(pod
) {
2724 return this.focusedPod_
== pod
;
2728 * Focuses a given user pod or clear focus when given null.
2729 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
2730 * @param {boolean=} opt_force If true, forces focus update even when
2731 * podToFocus is already focused.
2733 focusPod: function(podToFocus
, opt_force
) {
2734 if (this.isFocused(podToFocus
) && !opt_force
) {
2735 // Calling focusPod w/o podToFocus means reset.
2738 this.keyboardActivated_
= false;
2742 // Make sure there's only one focusPod operation happening at a time.
2743 if (this.insideFocusPod_
) {
2744 this.keyboardActivated_
= false;
2747 this.insideFocusPod_
= true;
2749 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2750 if (!this.alwaysFocusSinglePod
) {
2751 pod
.isActionBoxMenuActive
= false;
2753 if (pod
!= podToFocus
) {
2754 pod
.isActionBoxMenuHovered
= false;
2755 pod
.classList
.remove('focused');
2756 // On Desktop, the faded style is not set correctly, so we should
2757 // manually fade out non-focused pods if there is a focused pod.
2758 if (pod
.user
.isDesktopUser
&& podToFocus
)
2759 pod
.classList
.add('faded');
2761 pod
.classList
.remove('faded');
2766 // Clear any error messages for previous pod.
2767 if (!this.isFocused(podToFocus
))
2770 var hadFocus
= !!this.focusedPod_
;
2771 this.focusedPod_
= podToFocus
;
2773 podToFocus
.classList
.remove('faded');
2774 podToFocus
.classList
.add('focused');
2775 if (!podToFocus
.multiProfilesPolicyApplied
) {
2776 podToFocus
.classList
.toggle('signing-in', false);
2777 podToFocus
.focusInput();
2779 podToFocus
.userTypeBubbleElement
.classList
.add('bubble-shown');
2783 // focusPod() automatically loads wallpaper
2784 if (!podToFocus
.user
.isApp
)
2785 chrome
.send('focusPod', [podToFocus
.user
.username
]);
2786 this.firstShown_
= false;
2787 this.lastFocusedPod_
= podToFocus
;
2789 if (Oobe
.getInstance().virtualKeyboardShown
)
2790 this.scrollFocusedPodIntoView();
2792 this.insideFocusPod_
= false;
2793 this.keyboardActivated_
= false;
2797 * Resets wallpaper to the last active user's wallpaper, if any.
2799 loadLastWallpaper: function() {
2800 if (this.lastFocusedPod_
&& !this.lastFocusedPod_
.user
.isApp
)
2801 chrome
.send('loadWallpaper', [this.lastFocusedPod_
.user
.username
]);
2805 * Returns the currently activated pod.
2808 get activatedPod() {
2809 return this.activatedPod_
;
2813 * Sets currently activated pod.
2814 * @param {UserPod} pod Pod to check for focus.
2815 * @param {Event} e Event object.
2817 setActivatedPod: function(pod
, e
) {
2818 if (pod
&& pod
.activate(e
))
2819 this.activatedPod_
= pod
;
2823 * The pod of the signed-in user, if any; null otherwise.
2827 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2828 if (pod
.user
.signedIn
)
2835 * The pod that is preselected on user pod row show.
2838 get preselectedPod() {
2839 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2840 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2841 if (isDesktopUserManager
) {
2842 // On desktop, don't pre-select a pod if it's the only one.
2843 if (this.pods
.length
== 1)
2846 // The desktop User Manager can send the index of a pod that should be
2847 // initially focused in url hash.
2848 var podIndex
= parseInt(window
.location
.hash
.substr(1));
2849 if (isNaN(podIndex
) || podIndex
>= this.pods
.length
)
2851 return this.pods
[podIndex
];
2854 var lockedPod
= this.lockedPod
;
2857 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2858 if (!pod
.multiProfilesPolicyApplied
) {
2862 return this.pods
[0];
2867 * @param {boolean} takeFocus True to take focus.
2869 reset: function(takeFocus
) {
2870 this.disabled
= false;
2871 if (this.activatedPod_
)
2872 this.activatedPod_
.reset(takeFocus
);
2876 * Restores input focus to current selected pod, if there is any.
2878 refocusCurrentPod: function() {
2879 if (this.focusedPod_
&& !this.focusedPod_
.multiProfilesPolicyApplied
) {
2880 this.focusedPod_
.focusInput();
2885 * Clears focused pod password field.
2887 clearFocusedPod: function() {
2888 if (!this.disabled
&& this.focusedPod_
)
2889 this.focusedPod_
.reset(true);
2894 * @param {string} email Email for signin UI.
2896 showSigninUI: function(email
) {
2897 // Clear any error messages that might still be around.
2899 this.disabled
= true;
2900 this.lastFocusedPod_
= this.getPodWithUsername_(email
);
2901 Oobe
.showSigninUI(email
);
2905 * Updates current image of a user.
2906 * @param {string} username User for which to update the image.
2908 updateUserImage: function(username
) {
2909 var pod
= this.getPodWithUsername_(username
);
2911 pod
.updateUserImage();
2915 * Handler of click event.
2916 * @param {Event} e Click Event object.
2919 handleClick_: function(e
) {
2923 // Clear all menus if the click is outside pod menu and its
2925 if (!findAncestorByClass(e
.target
, 'action-box-menu') &&
2926 !findAncestorByClass(e
.target
, 'action-box-area')) {
2927 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
)
2928 pod
.isActionBoxMenuActive
= false;
2931 // Clears focus if not clicked on a pod and if there's more than one pod.
2932 var pod
= findAncestorByClass(e
.target
, 'pod');
2933 if ((!pod
|| pod
.parentNode
!= this) && !this.alwaysFocusSinglePod
) {
2938 pod
.isActionBoxMenuHovered
= true;
2940 // Return focus back to single pod.
2941 if (this.alwaysFocusSinglePod
&& !pod
) {
2942 this.focusPod(this.focusedPod_
, true /* force */);
2943 this.focusedPod_
.userTypeBubbleElement
.classList
.remove('bubble-shown');
2944 this.focusedPod_
.isActionBoxMenuHovered
= false;
2949 * Handler of mouse move event.
2950 * @param {Event} e Click Event object.
2953 handleMouseMove_: function(e
) {
2956 if (e
.webkitMovementX
== 0 && e
.webkitMovementY
== 0)
2959 // Defocus (thus hide) action box, if it is focused on a user pod
2960 // and the pointer is not hovering over it.
2961 var pod
= findAncestorByClass(e
.target
, 'pod');
2962 if (document
.activeElement
&&
2963 document
.activeElement
.parentNode
!= pod
&&
2964 document
.activeElement
.classList
.contains('action-box-area')) {
2965 document
.activeElement
.parentNode
.focus();
2969 pod
.isActionBoxMenuHovered
= true;
2971 // Hide action boxes on other user pods.
2972 for (var i
= 0, p
; p
= this.pods
[i
]; ++i
)
2973 if (p
!= pod
&& !p
.isActionBoxMenuActive
)
2974 p
.isActionBoxMenuHovered
= false;
2978 * Handles focus event.
2979 * @param {Event} e Focus Event object.
2982 handleFocus_: function(e
) {
2985 if (e
.target
.parentNode
== this) {
2987 if (e
.target
.classList
.contains('focused')) {
2988 if (!e
.target
.multiProfilesPolicyApplied
)
2989 e
.target
.focusInput();
2991 e
.target
.userTypeBubbleElement
.classList
.add('bubble-shown');
2993 this.focusPod(e
.target
);
2997 var pod
= findAncestorByClass(e
.target
, 'pod');
2998 if (pod
&& pod
.parentNode
== this) {
2999 // Focus on a control of a pod but not on the action area button.
3000 if (!pod
.classList
.contains('focused') &&
3001 !e
.target
.classList
.contains('action-box-button')) {
3003 pod
.userTypeBubbleElement
.classList
.remove('bubble-shown');
3009 // Clears pod focus when we reach here. It means new focus is neither
3010 // on a pod nor on a button/input for a pod.
3011 // Do not "defocus" user pod when it is a single pod.
3012 // That means that 'focused' class will not be removed and
3013 // input field/button will always be visible.
3014 if (!this.alwaysFocusSinglePod
)
3017 // Hide user-type-bubble in case this is one pod and we lost focus of
3019 this.focusedPod_
.userTypeBubbleElement
.classList
.remove('bubble-shown');
3024 * Handler of keydown event.
3025 * @param {Event} e KeyDown Event object.
3027 handleKeyDown: function(e
) {
3030 var editing
= e
.target
.tagName
== 'INPUT' && e
.target
.value
;
3031 switch (e
.keyIdentifier
) {
3034 this.keyboardActivated_
= true;
3035 if (this.focusedPod_
&& this.focusedPod_
.previousElementSibling
)
3036 this.focusPod(this.focusedPod_
.previousElementSibling
);
3038 this.focusPod(this.lastElementChild
);
3040 e
.stopPropagation();
3045 this.keyboardActivated_
= true;
3046 if (this.focusedPod_
&& this.focusedPod_
.nextElementSibling
)
3047 this.focusPod(this.focusedPod_
.nextElementSibling
);
3049 this.focusPod(this.firstElementChild
);
3051 e
.stopPropagation();
3055 if (this.focusedPod_
) {
3056 var targetTag
= e
.target
.tagName
;
3057 if (e
.target
== this.focusedPod_
.passwordElement
||
3058 (targetTag
!= 'INPUT' &&
3059 targetTag
!= 'BUTTON' &&
3060 targetTag
!= 'A')) {
3061 this.setActivatedPod(this.focusedPod_
, e
);
3062 e
.stopPropagation();
3066 case 'U+001B': // Esc
3067 if (!this.alwaysFocusSinglePod
)
3074 * Called right after the pod row is shown.
3076 handleAfterShow: function() {
3077 // Without timeout changes in pods positions will be animated even though
3078 // it happened when 'flying-pods' class was disabled.
3079 setTimeout(function() {
3080 Oobe
.getInstance().toggleClass('flying-pods', true);
3082 // Force input focus for user pod on show and once transition ends.
3083 if (this.focusedPod_
) {
3084 var focusedPod
= this.focusedPod_
;
3085 var screen
= this.parentNode
;
3087 focusedPod
.addEventListener('webkitTransitionEnd', function f(e
) {
3088 focusedPod
.removeEventListener('webkitTransitionEnd', f
);
3089 focusedPod
.reset(true);
3090 // Notify screen that it is ready.
3093 // Guard timer for 1 second -- it would conver all possible animations.
3094 ensureTransitionEndEvent(focusedPod
, 1000);
3099 * Called right before the pod row is shown.
3101 handleBeforeShow: function() {
3102 Oobe
.getInstance().toggleClass('flying-pods', false);
3103 for (var event
in this.listeners_
) {
3104 this.ownerDocument
.addEventListener(
3105 event
, this.listeners_
[event
][0], this.listeners_
[event
][1]);
3107 $('login-header-bar').buttonsTabIndex
= UserPodTabOrder
.HEADER_BAR
;
3109 if (this.podPlacementPostponed_
) {
3110 this.podPlacementPostponed_
= false;
3112 this.maybePreselectPod();
3117 * Called when the element is hidden.
3119 handleHide: function() {
3120 for (var event
in this.listeners_
) {
3121 this.ownerDocument
.removeEventListener(
3122 event
, this.listeners_
[event
][0], this.listeners_
[event
][1]);
3124 $('login-header-bar').buttonsTabIndex
= 0;
3128 * Called when a pod's user image finishes loading.
3130 handlePodImageLoad: function(pod
) {
3131 var index
= this.podsWithPendingImages_
.indexOf(pod
);
3136 this.podsWithPendingImages_
.splice(index
, 1);
3137 if (this.podsWithPendingImages_
.length
== 0) {
3138 this.classList
.remove('images-loading');
3143 * Preselects pod, if needed.
3145 maybePreselectPod: function() {
3146 var pod
= this.preselectedPod
;
3149 // Hide user-type-bubble in case all user pods are disabled and we focus
3151 if (pod
&& pod
.multiProfilesPolicyApplied
) {
3152 pod
.userTypeBubbleElement
.classList
.remove('bubble-shown');