1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview User pod row implementation.
9 cr
.define('login', function() {
11 * Number of displayed columns depending on user pod count.
12 * @type {Array.<number>}
15 var COLUMNS
= [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6];
18 * Mapping between number of columns in pod-row and margin between user pods
20 * @type {Array.<number>}
23 var MARGIN_BY_COLUMNS
= [undefined, 40, 40, 40, 40, 40, 12];
26 * Mapping between number of columns in the desktop pod-row and margin
27 * between user pods for such layout.
28 * @type {Array.<number>}
31 var DESKTOP_MARGIN_BY_COLUMNS
= [undefined, 15, 15, 15, 15, 15, 15];
34 * Maximal number of columns currently supported by pod-row.
38 var MAX_NUMBER_OF_COLUMNS
= 6;
41 * Maximal number of rows if sign-in banner is displayed alonside.
45 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER
= 2;
48 * Variables used for pod placement processing. Width and height should be
49 * synced with computed CSS sizes of pods.
52 var PUBLIC_EXPANDED_BASIC_WIDTH
= 500;
53 var PUBLIC_EXPANDED_ADVANCED_WIDTH
= 610;
54 var CROS_POD_HEIGHT
= 213;
55 var DESKTOP_POD_HEIGHT
= 226;
56 var POD_ROW_PADDING
= 10;
57 var DESKTOP_ROW_PADDING
= 15;
58 var CUSTOM_ICON_CONTAINER_SIZE
= 40;
61 * Minimal padding between user pod and virtual keyboard.
65 var USER_POD_KEYBOARD_MIN_PADDING
= 20;
68 * Maximum time for which the pod row remains hidden until all user images
73 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS
= 3000;
76 * Public session help topic identifier.
80 var HELP_TOPIC_PUBLIC_SESSION
= 3041033;
83 * Tab order for user pods. Update these when adding new controls.
87 var UserPodTabOrder
= {
88 POD_INPUT
: 1, // Password input fields (and whole pods themselves).
89 POD_CUSTOM_ICON
: 2, // Pod custom icon next to passwrod input field.
90 HEADER_BAR
: 3, // Buttons on the header bar (Shutdown, Add User).
91 ACTION_BOX
: 4, // Action box buttons.
92 PAD_MENU_ITEM
: 5 // User pad menu items (Remove this user).
96 * Supported authentication types. Keep in sync with the enum in
97 * chrome/browser/signin/screenlock_bridge.h
106 EXPAND_THEN_USER_CLICK
: 4,
107 FORCE_OFFLINE_PASSWORD
: 5
111 * Names of authentication types.
113 var AUTH_TYPE_NAMES
= {
114 0: 'offlinePassword',
118 4: 'expandThenUserClick',
119 5: 'forceOfflinePassword'
122 // Focus and tab order are organized as follows:
124 // (1) all user pods have tab index 1 so they are traversed first;
125 // (2) when a user pod is activated, its tab index is set to -1 and its
126 // main input field gets focus and tab index 1;
127 // (3) if user pod custom icon is interactive, it has tab index 2 so it
128 // follows the input.
129 // (4) buttons on the header bar have tab index 3 so they follow the custom
130 // icon, or user pod if custom icon is not interactive;
131 // (5) Action box buttons have tab index 4 and follow header bar buttons;
132 // (6) lastly, focus jumps to the Status Area and back to user pods.
134 // 'Focus' event is handled by a capture handler for the whole document
135 // and in some cases 'mousedown' event handlers are used instead of 'click'
136 // handlers where it's necessary to prevent 'focus' event from being fired.
139 * Helper function to remove a class from given element.
140 * @param {!HTMLElement} el Element whose class list to change.
141 * @param {string} cl Class to remove.
143 function removeClass(el
, cl
) {
144 el
.classList
.remove(cl
);
148 * Creates a user pod.
150 * @extends {HTMLDivElement}
152 var UserPod
= cr
.ui
.define(function() {
153 var node
= $('user-pod-template').cloneNode(true);
154 node
.removeAttribute('id');
159 * Stops event propagation from the any user pod child element.
160 * @param {Event} e Event to handle.
162 function stopEventPropagation(e
) {
163 // Prevent default so that we don't trigger a 'focus' event.
169 * Creates an element for custom icon shown in a user pod next to the input
172 * @extends {HTMLDivElement}
174 var UserPodCustomIcon
= cr
.ui
.define(function() {
175 var node
= document
.createElement('div');
176 node
.classList
.add('custom-icon-container');
179 // Create the actual icon element and add it as a child to the container.
180 var iconNode
= document
.createElement('div');
181 iconNode
.classList
.add('custom-icon');
182 node
.appendChild(iconNode
);
186 UserPodCustomIcon
.prototype = {
187 __proto__
: HTMLDivElement
.prototype,
204 * Tooltip to be shown when the user hovers over the icon. The icon
205 * properties may be set so the tooltip is shown automatically when the icon
206 * is updated. The tooltip is shown in a bubble attached to the icon
214 * Whether the tooltip is shown and the user is hovering over the icon.
218 tooltipActive_
: false,
221 * Whether the icon has been shown as a result of |autoshow| parameter begin
222 * set rather than user hovering over the icon.
223 * If this is set, the tooltip will not be removed when the mouse leaves the
228 tooltipAutoshown_
: false,
231 * A reference to the timeout for showing tooltip after mouse enters the
236 showTooltipTimeout_
: null,
239 * If animation is set, the current horizontal background offset for the
244 lastAnimationOffset_
: 0,
247 * The reference to interval for progressing the animation.
251 animationInterval_
: null,
254 * The width of the resource that contains representations for different
259 animationResourceSize_
: 0,
262 * When {@code fadeOut} is called, the element gets hidden using fadeout
263 * animation. This is reference to the listener for transition end added to
264 * the icon element while it's fading out.
265 * @type {?function(Event)}
268 hideTransitionListener_
: null,
271 * Callback for click and 'Enter' key events that gets set if the icon is
273 * @type {?function()}
276 actionHandler_
: null,
279 decorate: function() {
280 this.iconElement
.addEventListener('mouseover',
281 this.showTooltipSoon_
.bind(this));
282 this.iconElement
.addEventListener('mouseout',
283 this.hideTooltip_
.bind(this, false));
284 this.iconElement
.addEventListener('mousedown',
285 this.hideTooltip_
.bind(this, false));
286 this.iconElement
.addEventListener('click',
287 this.handleClick_
.bind(this));
288 this.iconElement
.addEventListener('keydown',
289 this.handleKeyDown_
.bind(this));
291 // When the icon is focused using mouse, there should be no outline shown.
292 // Preventing default mousedown event accomplishes this.
293 this.iconElement
.addEventListener('mousedown', function(e
) {
299 * Getter for the icon element's div.
300 * @return {HTMLDivElement}
303 return this.querySelector('.custom-icon');
307 * Set the icon's background image as image set with different
308 * representations for available screen scale factors.
309 * @param {!{scale1x: string, scale2x: string}} icon The icon
312 setIconAsImageSet: function(icon
) {
313 this.iconElement
.style
.backgroundImage
=
314 '-webkit-image-set(' +
315 'url(' + icon
.scale1x
+ ') 1x,' +
316 'url(' + icon
.scale2x
+ ') 2x)';
320 * Sets the icon background image to a chrome://theme URL.
321 * @param {!string} iconUrl The icon's background image URL.
323 setIconAsResourceUrl: function(iconUrl
) {
324 this.iconElement
.style
.backgroundImage
=
325 '-webkit-image-set(' +
326 'url(' + iconUrl
+ '@1x) 1x,' +
327 'url(' + iconUrl
+ '@2x) 2x)';
334 this.resetHideTransitionState_();
339 * Hides the icon using a fade-out animation.
341 fadeOut: function() {
342 // The icon is already being hidden.
343 if (this.iconElement
.classList
.contains('faded'))
346 this.hideTooltip_(true);
347 this.iconElement
.classList
.add('faded');
348 this.hideTransitionListener_
= this.hide_
.bind(this);
349 this.iconElement
.addEventListener('webkitTransitionEnd',
350 this.hideTransitionListener_
);
351 ensureTransitionEndEvent(this.iconElement
, 200);
355 * Sets the icon size element size.
356 * @param {!{width: number, height: number}} size The icon size.
358 setSize: function(size
) {
359 this.height_
= size
.height
< CUSTOM_ICON_CONTAINER_SIZE
?
360 size
.height
: CUSTOM_ICON_COTAINER_SIZE
;
361 this.iconElement
.style
.height
= this.height_
+ 'px';
363 this.width_
= size
.width
< CUSTOM_ICON_CONTAINER_SIZE
?
364 size
.width
: CUSTOM_ICON_COTAINER_SIZE
;
365 this.iconElement
.style
.width
= this.width_
+ 'px';
366 this.style
.width
= this.width_
+ 'px';
370 * Sets the icon opacity.
371 * @param {number} opacity The icon opacity in [0-100] scale.
373 setOpacity: function(opacity
) {
375 this.style
.opacity
= 1;
376 } else if (opacity
< 0) {
377 this.style
.opacity
= 0;
379 this.style
.opacity
= opacity
/ 100;
384 * Updates the icon tooltip. If {@code autoshow} parameter is set the
385 * tooltip is immediatelly shown. If tooltip text is not set, the method
386 * ensures the tooltip gets hidden. If tooltip is shown prior to this call,
387 * it remains shown, but the tooltip text is updated.
388 * @param {!{text: string, autoshow: boolean}} tooltip The tooltip
391 setTooltip: function(tooltip
) {
392 if (this.tooltip_
== tooltip
.text
&& !tooltip
.autoshow
)
394 this.tooltip_
= tooltip
.text
;
396 // If tooltip is already shown, just update the text.
397 if (tooltip
.text
&& this.tooltipActive_
&& !$('bubble').hidden
) {
398 // If both previous and the new tooltip are supposed to be shown
399 // automatically, keep the autoshown flag.
400 var markAutoshown
= this.tooltipAutoshown_
&& tooltip
.autoshow
;
401 this.hideTooltip_(true);
403 this.tooltipAutoshown_
= markAutoshown
;
407 // If autoshow flag is set, make sure the tooltip gets shown.
408 if (tooltip
.text
&& tooltip
.autoshow
) {
409 this.hideTooltip_(true);
411 this.tooltipAutoshown_
= true;
412 // Reset the tooltip active flag, which gets set in |showTooltip_|.
413 this.tooltipActive_
= false;
417 this.hideTooltip_(true);
420 this.iconElement
.setAttribute('aria-lablel', this.tooltip_
);
424 * Sets the icon animation parameter and starts the animation.
425 * The animation is set using the resource containing all animation frames
426 * concatenated horizontally. The animator offsets the background image in
428 * @param {?{resourceWidth: number, frameLengthMs: number}} animation
429 * |resourceWidth|: Total width for the resource containing the
431 * |frameLengthMs|: Time interval for which a single animation frame is
434 setAnimation: function(animation
) {
435 if (this.animationInterval_
)
436 clearInterval(this.animationInterval_
);
437 this.iconElement
.style
.backgroundPosition
= 'center';
440 this.lastAnimationOffset_
= 0;
441 this.animationResourceSize_
= animation
.resourceWidth
;
442 this.animationInterval_
= setInterval(this.progressAnimation_
.bind(this),
443 animation
.frameLengthMs
);
447 * Sets up icon tabIndex attribute and handler for click and 'Enter' key
449 * @param {?function()} callback If icon should be interactive, the
450 * function to get called on click and 'Enter' key down events. Should
451 * be null to make the icon non interactive.
453 setInteractive: function(callback
) {
454 // Update tabIndex property if needed.
455 if (!!this.actionHandler_
!= !!callback
) {
457 this.iconElement
.setAttribute('tabIndex',
458 UserPodTabOrder
.POD_CUSTOM_ICON
);
460 this.iconElement
.removeAttribute('tabIndex');
464 // Set the new action handler.
465 this.actionHandler_
= callback
;
469 * Hides the icon. Makes sure the tooltip is hidden and animation reset.
473 this.hideTooltip_(true);
475 this.setAnimation(null);
476 this.setInteractive(null);
477 this.resetHideTransitionState_();
481 * Ensures the icon's transition state potentially set by a call to
482 * {@code fadeOut} is cleared.
485 resetHideTransitionState_: function() {
486 if (this.hideTransitionListener_
) {
487 this.iconElement
.removeEventListener('webkitTransitionEnd',
488 this.hideTransitionListener_
);
489 this.hideTransitionListener_
= null;
491 this.iconElement
.classList
.toggle('faded', false);
495 * Handles click event on the icon element. No-op if
496 * {@code this.actionHandler_} is not set.
497 * @param {Event} e The click event.
500 handleClick_: function(e
) {
501 if (!this.actionHandler_
)
503 this.actionHandler_();
504 stopEventPropagation(e
);
508 * Handles key down event on the icon element. Only 'Enter' key is handled.
509 * No-op if {@code this.actionHandler_} is not set.
510 * @param {Event} e The key down event.
513 handleKeyDown_: function(e
) {
514 if (!this.actionHandler_
|| e
.keyIdentifier
!= 'Enter')
516 this.actionHandler_(e
);
517 stopEventPropagation(e
);
521 * Called when mouse enters the icon. It sets timeout for showing the
525 showTooltipSoon_: function() {
526 if (this.showTooltipTimeout_
|| this.tooltipActive_
)
528 this.showTooltipTimeout_
=
529 setTimeout(this.showTooltip_
.bind(this), 1000);
533 * Shows the current tooltip if one is set.
536 showTooltip_: function() {
537 if (this.hidden
|| !this.tooltip_
|| this.tooltipActive_
)
540 // If autoshown bubble got hidden, clear the autoshown flag.
541 if ($('bubble').hidden
&& this.tooltipAutoshown_
)
542 this.tooltipAutoshown_
= false;
544 // Show the tooltip bubble.
545 var bubbleContent
= document
.createElement('div');
546 bubbleContent
.textContent
= this.tooltip_
;
548 /** @const */ var BUBBLE_OFFSET
= CUSTOM_ICON_CONTAINER_SIZE
/ 2;
549 /** @const */ var BUBBLE_PADDING
= 8;
550 $('bubble').showContentForElement(this,
551 cr
.ui
.Bubble
.Attachment
.RIGHT
,
555 this.ensureTooltipTimeoutCleared_();
556 this.tooltipActive_
= true;
560 * Hides the tooltip. If the current tooltip was automatically shown, it
561 * will be hidden only if |force| is set.
562 * @param {boolean} Whether the tooltip should be hidden if it got shown
563 * because autoshow flag was set.
566 hideTooltip_: function(force
) {
567 this.tooltipActive_
= false;
568 this.ensureTooltipTimeoutCleared_();
569 if (!force
&& this.tooltipAutoshown_
)
571 $('bubble').hideForElement(this);
572 this.tooltipAutoshown_
= false;
573 this.iconElement
.removeAttribute('aria-label');
577 * Clears timaout for showing the tooltip if it's set.
580 ensureTooltipTimeoutCleared_: function() {
581 if (this.showTooltipTimeout_
) {
582 clearTimeout(this.showTooltipTimeout_
);
583 this.showTooltipTimeout_
= null;
588 * Horizontally offsets the animated icon's background for a single icon
592 progressAnimation_: function() {
593 this.lastAnimationOffset_
+= this.width_
;
594 if (this.lastAnimationOffset_
>= this.animationResourceSize_
)
595 this.lastAnimationOffset_
= 0;
596 this.iconElement
.style
.backgroundPosition
=
597 '-' + this.lastAnimationOffset_
+ 'px center';
602 * Unique salt added to user image URLs to prevent caching. Dictionary with
603 * user names as keys.
606 UserPod
.userImageSalt_
= {};
608 UserPod
.prototype = {
609 __proto__
: HTMLDivElement
.prototype,
612 decorate: function() {
613 this.tabIndex
= UserPodTabOrder
.POD_INPUT
;
614 this.actionBoxAreaElement
.tabIndex
= UserPodTabOrder
.ACTION_BOX
;
616 this.addEventListener('keydown', this.handlePodKeyDown_
.bind(this));
617 this.addEventListener('click', this.handleClickOnPod_
.bind(this));
619 this.signinButtonElement
.addEventListener('click',
620 this.activate
.bind(this));
622 this.actionBoxAreaElement
.addEventListener('mousedown',
623 stopEventPropagation
);
624 this.actionBoxAreaElement
.addEventListener('click',
625 this.handleActionAreaButtonClick_
.bind(this));
626 this.actionBoxAreaElement
.addEventListener('keydown',
627 this.handleActionAreaButtonKeyDown_
.bind(this));
629 this.actionBoxMenuRemoveElement
.addEventListener('click',
630 this.handleRemoveCommandClick_
.bind(this));
631 this.actionBoxMenuRemoveElement
.addEventListener('keydown',
632 this.handleRemoveCommandKeyDown_
.bind(this));
633 this.actionBoxMenuRemoveElement
.addEventListener('blur',
634 this.handleRemoveCommandBlur_
.bind(this));
635 this.actionBoxRemoveUserWarningButtonElement
.addEventListener(
637 this.handleRemoveUserConfirmationClick_
.bind(this));
638 this.actionBoxRemoveUserWarningButtonElement
.addEventListener(
640 this.handleRemoveUserConfirmationKeyDown_
.bind(this));
642 var customIcon
= this.customIconElement
;
643 customIcon
.parentNode
.replaceChild(new UserPodCustomIcon(), customIcon
);
647 * Initializes the pod after its properties set and added to a pod row.
649 initialize: function() {
650 this.passwordElement
.addEventListener('keydown',
651 this.parentNode
.handleKeyDown
.bind(this.parentNode
));
652 this.passwordElement
.addEventListener('keypress',
653 this.handlePasswordKeyPress_
.bind(this));
655 this.imageElement
.addEventListener('load',
656 this.parentNode
.handlePodImageLoad
.bind(this.parentNode
, this));
658 var initialAuthType
= this.user
.initialAuthType
||
659 AUTH_TYPE
.OFFLINE_PASSWORD
;
660 this.setAuthType(initialAuthType
, null);
664 * Resets tab order for pod elements to its initial state.
666 resetTabOrder: function() {
667 // Note: the |mainInput| can be the pod itself.
668 this.mainInput
.tabIndex
= -1;
669 this.tabIndex
= UserPodTabOrder
.POD_INPUT
;
673 * Handles keypress event (i.e. any textual input) on password input.
674 * @param {Event} e Keypress Event object.
677 handlePasswordKeyPress_: function(e
) {
678 // When tabbing from the system tray a tab key press is received. Suppress
679 // this so as not to type a tab character into the password field.
680 if (e
.keyCode
== 9) {
687 * Top edge margin number of pixels.
691 this.style
.top
= cr
.ui
.toCssPx(top
);
695 * Top edge margin number of pixels.
698 return parseInt(this.style
.top
);
702 * Left edge margin number of pixels.
706 this.style
.left
= cr
.ui
.toCssPx(left
);
710 * Left edge margin number of pixels.
713 return parseInt(this.style
.left
);
717 * Height number of pixels.
720 return this.offsetHeight
;
724 * Gets image element.
725 * @type {!HTMLImageElement}
728 return this.querySelector('.user-image');
733 * @type {!HTMLDivElement}
736 return this.querySelector('.name');
740 * Gets the container holding the password field.
741 * @type {!HTMLInputElement}
743 get passwordEntryContainerElement() {
744 return this.querySelector('.password-entry-container');
748 * Gets password field.
749 * @type {!HTMLInputElement}
751 get passwordElement() {
752 return this.querySelector('.password');
756 * Gets the password label, which is used to show a message where the
757 * password field is normally.
758 * @type {!HTMLInputElement}
760 get passwordLabelElement() {
761 return this.querySelector('.password-label');
765 * Gets user sign in button.
766 * @type {!HTMLButtonElement}
768 get signinButtonElement() {
769 return this.querySelector('.signin-button');
773 * Gets the container holding the launch app button.
774 * @type {!HTMLButtonElement}
776 get launchAppButtonContainerElement() {
777 return this.querySelector('.launch-app-button-container');
781 * Gets launch app button.
782 * @type {!HTMLButtonElement}
784 get launchAppButtonElement() {
785 return this.querySelector('.launch-app-button');
789 * Gets action box area.
790 * @type {!HTMLInputElement}
792 get actionBoxAreaElement() {
793 return this.querySelector('.action-box-area');
797 * Gets user type icon area.
798 * @type {!HTMLDivElement}
800 get userTypeIconAreaElement() {
801 return this.querySelector('.user-type-icon-area');
805 * Gets user type bubble like multi-profiles policy restriction message.
806 * @type {!HTMLDivElement}
808 get userTypeBubbleElement() {
809 return this.querySelector('.user-type-bubble');
813 * Gets action box menu.
814 * @type {!HTMLInputElement}
816 get actionBoxMenu() {
817 return this.querySelector('.action-box-menu');
821 * Gets action box menu title, user name item.
822 * @type {!HTMLInputElement}
824 get actionBoxMenuTitleNameElement() {
825 return this.querySelector('.action-box-menu-title-name');
829 * Gets action box menu title, user email item.
830 * @type {!HTMLInputElement}
832 get actionBoxMenuTitleEmailElement() {
833 return this.querySelector('.action-box-menu-title-email');
837 * Gets action box menu, remove user command item.
838 * @type {!HTMLInputElement}
840 get actionBoxMenuCommandElement() {
841 return this.querySelector('.action-box-menu-remove-command');
845 * Gets action box menu, remove user command item div.
846 * @type {!HTMLInputElement}
848 get actionBoxMenuRemoveElement() {
849 return this.querySelector('.action-box-menu-remove');
853 * Gets action box menu, remove user warning text div.
854 * @type {!HTMLInputElement}
856 get actionBoxRemoveUserWarningTextElement() {
857 return this.querySelector('.action-box-remove-user-warning-text');
861 * Gets action box menu, remove supervised user warning text div.
862 * @type {!HTMLInputElement}
864 get actionBoxRemoveSupervisedUserWarningTextElement() {
865 return this.querySelector(
866 '.action-box-remove-supervised-user-warning-text');
870 * Gets action box menu, remove user command item div.
871 * @type {!HTMLInputElement}
873 get actionBoxRemoveUserWarningElement() {
874 return this.querySelector('.action-box-remove-user-warning');
878 * Gets action box menu, remove user command item div.
879 * @type {!HTMLInputElement}
881 get actionBoxRemoveUserWarningButtonElement() {
882 return this.querySelector('.remove-warning-button');
886 * Gets the custom icon. This icon is normally hidden, but can be shown
887 * using the chrome.screenlockPrivate API.
888 * @type {!HTMLDivElement}
890 get customIconElement() {
891 return this.querySelector('.custom-icon-container');
895 * Updates the user pod element.
898 this.imageElement
.src
= 'chrome://userimage/' + this.user
.username
+
899 '?id=' + UserPod
.userImageSalt_
[this.user
.username
];
901 this.nameElement
.textContent
= this.user_
.displayName
;
902 this.classList
.toggle('signed-in', this.user_
.signedIn
);
904 if (this.isAuthTypeUserClick
)
905 this.passwordLabelElement
.textContent
= this.authValue
;
907 this.updateActionBoxArea();
909 this.passwordElement
.setAttribute('aria-label', loadTimeData
.getStringF(
910 'passwordFieldAccessibleName', this.user_
.emailAddress
));
912 this.customizeUserPodPerUserType();
915 updateActionBoxArea: function() {
916 if (this.user_
.publicAccount
|| this.user_
.isApp
) {
917 this.actionBoxAreaElement
.hidden
= true;
921 this.actionBoxMenuRemoveElement
.hidden
= !this.user_
.canRemove
;
923 this.actionBoxAreaElement
.setAttribute(
924 'aria-label', loadTimeData
.getStringF(
925 'podMenuButtonAccessibleName', this.user_
.emailAddress
));
926 this.actionBoxMenuRemoveElement
.setAttribute(
927 'aria-label', loadTimeData
.getString(
928 'podMenuRemoveItemAccessibleName'));
929 this.actionBoxMenuTitleNameElement
.textContent
= this.user_
.isOwner
?
930 loadTimeData
.getStringF('ownerUserPattern', this.user_
.displayName
) :
931 this.user_
.displayName
;
932 this.actionBoxMenuTitleEmailElement
.textContent
= this.user_
.emailAddress
;
933 this.actionBoxMenuTitleEmailElement
.hidden
= this.user_
.supervisedUser
;
935 this.actionBoxMenuCommandElement
.textContent
=
936 loadTimeData
.getString('removeUser');
939 customizeUserPodPerUserType: function() {
940 if (this.user_
.supervisedUser
&& !this.user_
.isDesktopUser
) {
941 this.setUserPodIconType('supervised');
942 } else if (this.multiProfilesPolicyApplied
) {
943 // Mark user pod as not focusable which in addition to the grayed out
944 // filter makes it look in disabled state.
945 this.classList
.add('multiprofiles-policy-applied');
946 this.setUserPodIconType('policy');
948 if (this.user
.multiProfilesPolicy
== 'primary-only')
949 this.querySelector('.mp-policy-primary-only-msg').hidden
= false;
950 else if (this.user
.multiProfilesPolicy
== 'owner-primary-only')
951 this.querySelector('.mp-owner-primary-only-msg').hidden
= false;
953 this.querySelector('.mp-policy-not-allowed-msg').hidden
= false;
954 } else if (this.user_
.isApp
) {
955 this.setUserPodIconType('app');
959 setUserPodIconType: function(userTypeClass
) {
960 this.userTypeIconAreaElement
.classList
.add(userTypeClass
);
961 this.userTypeIconAreaElement
.hidden
= false;
965 * The user that this pod represents.
973 this.user_
= userDict
;
978 * Returns true if multi-profiles sign in is currently active and this
979 * user pod is restricted per policy.
982 get multiProfilesPolicyApplied() {
983 var isMultiProfilesUI
=
984 (Oobe
.getInstance().displayType
== DISPLAY_TYPE
.USER_ADDING
);
985 return isMultiProfilesUI
&& !this.user_
.isMultiProfilesAllowed
;
989 * Gets main input element.
990 * @type {(HTMLButtonElement|HTMLInputElement)}
993 if (this.isAuthTypePassword
) {
994 return this.passwordElement
;
995 } else if (this.isAuthTypeOnlineSignIn
) {
996 return this.signinButtonElement
;
997 } else if (this.isAuthTypeUserClick
) {
1003 * Whether action box button is in active state.
1006 get isActionBoxMenuActive() {
1007 return this.actionBoxAreaElement
.classList
.contains('active');
1009 set isActionBoxMenuActive(active
) {
1010 if (active
== this.isActionBoxMenuActive
)
1014 this.actionBoxMenuRemoveElement
.hidden
= !this.user_
.canRemove
;
1015 this.actionBoxRemoveUserWarningElement
.hidden
= true;
1017 // Clear focus first if another pod is focused.
1018 if (!this.parentNode
.isFocused(this)) {
1019 this.parentNode
.focusPod(undefined, true);
1020 this.actionBoxAreaElement
.focus();
1023 // Hide user-type-bubble.
1024 this.userTypeBubbleElement
.classList
.remove('bubble-shown');
1026 this.actionBoxAreaElement
.classList
.add('active');
1028 this.actionBoxAreaElement
.classList
.remove('active');
1029 this.actionBoxAreaElement
.classList
.remove('menu-moved-up');
1030 this.actionBoxMenu
.classList
.remove('menu-moved-up');
1035 * Whether action box button is in hovered state.
1038 get isActionBoxMenuHovered() {
1039 return this.actionBoxAreaElement
.classList
.contains('hovered');
1041 set isActionBoxMenuHovered(hovered
) {
1042 if (hovered
== this.isActionBoxMenuHovered
)
1046 this.actionBoxAreaElement
.classList
.add('hovered');
1047 this.classList
.add('hovered');
1049 if (this.multiProfilesPolicyApplied
)
1050 this.userTypeBubbleElement
.classList
.remove('bubble-shown');
1051 this.actionBoxAreaElement
.classList
.remove('hovered');
1052 this.classList
.remove('hovered');
1057 * Set the authentication type for the pod.
1058 * @param {number} An auth type value defined in the AUTH_TYPE enum.
1059 * @param {string} authValue The initial value used for the auth type.
1061 setAuthType: function(authType
, authValue
) {
1062 this.authType_
= authType
;
1063 this.authValue_
= authValue
;
1064 this.setAttribute('auth-type', AUTH_TYPE_NAMES
[this.authType_
]);
1066 this.reset(this.parentNode
.isFocused(this));
1070 * The auth type of the user pod. This value is one of the enum
1071 * values in AUTH_TYPE.
1075 return this.authType_
;
1079 * The initial value used for the pod's authentication type.
1080 * eg. a prepopulated password input when using password authentication.
1083 return this.authValue_
;
1087 * True if the the user pod uses a password to authenticate.
1090 get isAuthTypePassword() {
1091 return this.authType_
== AUTH_TYPE
.OFFLINE_PASSWORD
||
1092 this.authType_
== AUTH_TYPE
.FORCE_OFFLINE_PASSWORD
;
1096 * True if the the user pod uses a user click to authenticate.
1099 get isAuthTypeUserClick() {
1100 return this.authType_
== AUTH_TYPE
.USER_CLICK
;
1104 * True if the the user pod uses a online sign in to authenticate.
1107 get isAuthTypeOnlineSignIn() {
1108 return this.authType_
== AUTH_TYPE
.ONLINE_SIGN_IN
;
1112 * Updates the image element of the user.
1114 updateUserImage: function() {
1115 UserPod
.userImageSalt_
[this.user
.username
] = new Date().getTime();
1120 * Focuses on input element.
1122 focusInput: function() {
1123 // Move tabIndex from the whole pod to the main input.
1124 // Note: the |mainInput| can be the pod itself.
1126 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1127 this.mainInput
.focus();
1131 * Activates the pod.
1132 * @param {Event} e Event object.
1133 * @return {boolean} True if activated successfully.
1135 activate: function(e
) {
1136 if (this.isAuthTypeOnlineSignIn
) {
1137 this.showSigninUI();
1138 } else if (this.isAuthTypeUserClick
) {
1139 Oobe
.disableSigninUI();
1140 chrome
.send('attemptUnlock', [this.user
.username
]);
1141 } else if (this.isAuthTypePassword
) {
1142 if (!this.passwordElement
.value
)
1144 Oobe
.disableSigninUI();
1145 chrome
.send('authenticateUser',
1146 [this.user
.username
, this.passwordElement
.value
]);
1148 console
.error('Activating user pod with invalid authentication type: ' +
1155 showSupervisedUserSigninWarning: function() {
1156 // Supervised user token has been invalidated.
1157 // Make sure that pod is focused i.e. "Sign in" button is seen.
1158 this.parentNode
.focusPod(this);
1160 var error
= document
.createElement('div');
1161 var messageDiv
= document
.createElement('div');
1162 messageDiv
.className
= 'error-message-bubble';
1163 messageDiv
.textContent
=
1164 loadTimeData
.getString('supervisedUserExpiredTokenWarning');
1165 error
.appendChild(messageDiv
);
1167 $('bubble').showContentForElement(
1168 this.signinButtonElement
,
1169 cr
.ui
.Bubble
.Attachment
.TOP
,
1171 this.signinButtonElement
.offsetWidth
/ 2,
1173 // Move warning bubble up if it overlaps the shelf.
1175 cr
.ui
.LoginUITools
.getMaxHeightBeforeShelfOverlapping($('bubble'));
1176 if (maxHeight
< $('bubble').offsetHeight
) {
1177 $('bubble').showContentForElement(
1178 this.signinButtonElement
,
1179 cr
.ui
.Bubble
.Attachment
.BOTTOM
,
1181 this.signinButtonElement
.offsetWidth
/ 2,
1187 * Shows signin UI for this user.
1189 showSigninUI: function() {
1190 if (this.user
.supervisedUser
&& !this.user
.isDesktopUser
) {
1191 this.showSupervisedUserSigninWarning();
1193 // Special case for multi-profiles sign in. We show users even if they
1194 // are not allowed per policy. Restrict those users from starting GAIA.
1195 if (this.multiProfilesPolicyApplied
)
1198 this.parentNode
.showSigninUI(this.user
.emailAddress
);
1203 * Resets the input field and updates the tab order of pod controls.
1204 * @param {boolean} takeFocus If true, input field takes focus.
1206 reset: function(takeFocus
) {
1207 this.passwordElement
.value
= '';
1209 if (!this.multiProfilesPolicyApplied
)
1210 this.focusInput(); // This will set a custom tab order.
1213 this.resetTabOrder();
1217 * Removes a user using the correct identifier based on user type.
1218 * @param {Object} user User to be removed.
1220 removeUser: function(user
) {
1221 chrome
.send('removeUser',
1222 [user
.isDesktopUser
? user
.profilePath
: user
.username
]);
1226 * Handles a click event on action area button.
1227 * @param {Event} e Click event.
1229 handleActionAreaButtonClick_: function(e
) {
1230 if (this.parentNode
.disabled
)
1232 this.isActionBoxMenuActive
= !this.isActionBoxMenuActive
;
1233 e
.stopPropagation();
1237 * Handles a keydown event on action area button.
1238 * @param {Event} e KeyDown event.
1240 handleActionAreaButtonKeyDown_: function(e
) {
1243 switch (e
.keyIdentifier
) {
1245 case 'U+0020': // Space
1246 if (this.parentNode
.focusedPod_
&& !this.isActionBoxMenuActive
)
1247 this.isActionBoxMenuActive
= true;
1248 e
.stopPropagation();
1252 if (this.isActionBoxMenuActive
) {
1253 this.actionBoxMenuRemoveElement
.tabIndex
=
1254 UserPodTabOrder
.PAD_MENU_ITEM
;
1255 this.actionBoxMenuRemoveElement
.focus();
1257 e
.stopPropagation();
1259 case 'U+001B': // Esc
1260 this.isActionBoxMenuActive
= false;
1261 e
.stopPropagation();
1263 case 'U+0009': // Tab
1264 if (!this.parentNode
.alwaysFocusSinglePod
)
1265 this.parentNode
.focusPod();
1267 this.isActionBoxMenuActive
= false;
1273 * Handles a click event on remove user command.
1274 * @param {Event} e Click event.
1276 handleRemoveCommandClick_: function(e
) {
1277 if (this.user
.supervisedUser
|| this.user
.isDesktopUser
) {
1278 this.showRemoveWarning_();
1281 if (this.isActionBoxMenuActive
)
1282 chrome
.send('removeUser', [this.user
.username
]);
1286 * Shows remove user warning. Used for supervised users on CrOS, and for all
1289 showRemoveWarning_: function() {
1290 this.actionBoxMenuRemoveElement
.hidden
= true;
1291 this.actionBoxRemoveUserWarningElement
.hidden
= false;
1292 this.actionBoxRemoveUserWarningButtonElement
.focus();
1294 // Move up the menu if it overlaps shelf.
1295 var maxHeight
= cr
.ui
.LoginUITools
.getMaxHeightBeforeShelfOverlapping(
1296 this.actionBoxMenu
);
1297 var actualHeight
= parseInt(
1298 window
.getComputedStyle(this.actionBoxMenu
).height
);
1299 if (maxHeight
< actualHeight
) {
1300 this.actionBoxMenu
.classList
.add('menu-moved-up');
1301 this.actionBoxAreaElement
.classList
.add('menu-moved-up');
1306 * Handles a click event on remove user confirmation button.
1307 * @param {Event} e Click event.
1309 handleRemoveUserConfirmationClick_: function(e
) {
1310 if (this.isActionBoxMenuActive
) {
1311 this.isActionBoxMenuActive
= false;
1312 this.removeUser(this.user
);
1313 e
.stopPropagation();
1318 * Handles a keydown event on remove user confirmation button.
1319 * @param {Event} e KeyDown event.
1321 handleRemoveUserConfirmationKeyDown_: function(e
) {
1322 if (!this.isActionBoxMenuActive
)
1325 // Only handle pressing 'Enter' or 'Space', and let all other events
1326 // bubble to the action box menu.
1327 if (e
.keyIdentifier
== 'Enter' || e
.keyIdentifier
== 'U+0020') {
1328 this.isActionBoxMenuActive
= false;
1329 this.removeUser(this.user
);
1330 e
.stopPropagation();
1331 // Prevent default so that we don't trigger a 'click' event.
1337 * Handles a keydown event on remove command.
1338 * @param {Event} e KeyDown event.
1340 handleRemoveCommandKeyDown_: function(e
) {
1343 switch (e
.keyIdentifier
) {
1345 if (this.user
.supervisedUser
|| this.user
.isDesktopUser
) {
1346 // Prevent default so that we don't trigger a 'click' event on the
1347 // remove button that will be focused.
1349 this.showRemoveWarning_();
1351 this.removeUser(this.user
);
1353 e
.stopPropagation();
1357 e
.stopPropagation();
1359 case 'U+001B': // Esc
1360 this.actionBoxAreaElement
.focus();
1361 this.isActionBoxMenuActive
= false;
1362 e
.stopPropagation();
1365 this.actionBoxAreaElement
.focus();
1366 this.isActionBoxMenuActive
= false;
1372 * Handles a blur event on remove command.
1373 * @param {Event} e Blur event.
1375 handleRemoveCommandBlur_: function(e
) {
1378 this.actionBoxMenuRemoveElement
.tabIndex
= -1;
1382 * Handles click event on a user pod.
1383 * @param {Event} e Click event.
1385 handleClickOnPod_: function(e
) {
1386 if (this.parentNode
.disabled
)
1389 if (!this.isActionBoxMenuActive
) {
1390 if (this.isAuthTypeOnlineSignIn
) {
1391 this.showSigninUI();
1392 } else if (this.isAuthTypeUserClick
) {
1393 this.parentNode
.setActivatedPod(this);
1396 if (this.multiProfilesPolicyApplied
)
1397 this.userTypeBubbleElement
.classList
.add('bubble-shown');
1399 // Prevent default so that we don't trigger 'focus' event.
1405 * Handles keydown event for a user pod.
1406 * @param {Event} e Key event.
1408 handlePodKeyDown_: function(e
) {
1409 if (!this.isAuthTypeUserClick
|| this.disabled
)
1411 switch (e
.keyIdentifier
) {
1413 case 'U+0020': // Space
1414 if (this.parentNode
.isFocused(this))
1415 this.parentNode
.setActivatedPod(this);
1422 * Creates a public account user pod.
1424 * @extends {UserPod}
1426 var PublicAccountUserPod
= cr
.ui
.define(function() {
1427 var node
= UserPod();
1429 var extras
= $('public-account-user-pod-extras-template').children
;
1430 for (var i
= 0; i
< extras
.length
; ++i
) {
1431 var el
= extras
[i
].cloneNode(true);
1432 node
.appendChild(el
);
1438 PublicAccountUserPod
.prototype = {
1439 __proto__
: UserPod
.prototype,
1442 * "Enter" button in expanded side pane.
1443 * @type {!HTMLButtonElement}
1445 get enterButtonElement() {
1446 return this.querySelector('.enter-button');
1450 * Boolean flag of whether the pod is showing the side pane. The flag
1451 * controls whether 'expanded' class is added to the pod's class list and
1452 * resets tab order because main input element changes when the 'expanded'
1457 return this.classList
.contains('expanded');
1460 set expanded(expanded
) {
1461 if (this.expanded
== expanded
)
1464 this.resetTabOrder();
1465 this.classList
.toggle('expanded', expanded
);
1467 // Show the advanced expanded pod directly if there are at least two
1468 // recommended locales. This will be the case in multilingual
1469 // environments where users are likely to want to choose among locales.
1470 if (this.querySelector('.language-select').multipleRecommendedLocales
)
1471 this.classList
.add('advanced');
1472 this.usualLeft
= this.left
;
1473 this.makeSpaceForExpandedPod_();
1474 } else if (typeof(this.usualLeft
) != 'undefined') {
1475 this.left
= this.usualLeft
;
1479 this.classList
.add('animating');
1480 this.addEventListener('webkitTransitionEnd', function f(e
) {
1481 self
.removeEventListener('webkitTransitionEnd', f
);
1482 self
.classList
.remove('animating');
1484 // Accessibility focus indicator does not move with the focused
1485 // element. Sends a 'focus' event on the currently focused element
1486 // so that accessibility focus indicator updates its location.
1487 if (document
.activeElement
)
1488 document
.activeElement
.dispatchEvent(new Event('focus'));
1490 // Guard timer set to animation duration + 20ms.
1491 ensureTransitionEndEvent(this, 200);
1495 return this.classList
.contains('advanced');
1501 return this.enterButtonElement
;
1503 return this.nameElement
;
1507 decorate: function() {
1508 UserPod
.prototype.decorate
.call(this);
1510 this.classList
.add('public-account');
1512 this.nameElement
.addEventListener('keydown', (function(e
) {
1513 if (e
.keyIdentifier
== 'Enter') {
1514 this.parentNode
.setActivatedPod(this, e
);
1515 // Stop this keydown event from bubbling up to PodRow handler.
1516 e
.stopPropagation();
1517 // Prevent default so that we don't trigger a 'click' event on the
1518 // newly focused "Enter" button.
1523 var learnMore
= this.querySelector('.learn-more');
1524 learnMore
.addEventListener('mousedown', stopEventPropagation
);
1525 learnMore
.addEventListener('click', this.handleLearnMoreEvent
);
1526 learnMore
.addEventListener('keydown', this.handleLearnMoreEvent
);
1528 learnMore
= this.querySelector('.expanded-pane-learn-more');
1529 learnMore
.addEventListener('click', this.handleLearnMoreEvent
);
1530 learnMore
.addEventListener('keydown', this.handleLearnMoreEvent
);
1532 var languageSelect
= this.querySelector('.language-select');
1533 languageSelect
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1534 languageSelect
.manuallyChanged
= false;
1535 languageSelect
.addEventListener(
1538 languageSelect
.manuallyChanged
= true;
1539 this.getPublicSessionKeyboardLayouts_();
1542 var keyboardSelect
= this.querySelector('.keyboard-select');
1543 keyboardSelect
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1544 keyboardSelect
.loadedLocale
= null;
1546 var languageAndInput
= this.querySelector('.language-and-input');
1547 languageAndInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1548 languageAndInput
.addEventListener('click',
1549 this.transitionToAdvanced_
.bind(this));
1551 this.enterButtonElement
.addEventListener('click', (function(e
) {
1552 this.enterButtonElement
.disabled
= true;
1553 var locale
= this.querySelector('.language-select').value
;
1554 var keyboardSelect
= this.querySelector('.keyboard-select');
1555 // The contents of |keyboardSelect| is updated asynchronously. If its
1556 // locale does not match |locale|, it has not updated yet and the
1557 // currently selected keyboard layout may not be applicable to |locale|.
1558 // Do not return any keyboard layout in this case and let the backend
1559 // choose a suitable layout.
1560 var keyboardLayout
=
1561 keyboardSelect
.loadedLocale
== locale
? keyboardSelect
.value
: '';
1562 chrome
.send('launchPublicSession',
1563 [this.user
.username
, locale
, keyboardLayout
]);
1568 initialize: function() {
1569 UserPod
.prototype.initialize
.call(this);
1571 id
= this.user
.username
+ '-keyboard';
1572 this.querySelector('.keyboard-select-label').htmlFor
= id
;
1573 this.querySelector('.keyboard-select').setAttribute('id', id
);
1575 var id
= this.user
.username
+ '-language';
1576 this.querySelector('.language-select-label').htmlFor
= id
;
1577 var languageSelect
= this.querySelector('.language-select');
1578 languageSelect
.setAttribute('id', id
);
1579 this.populateLanguageSelect(this.user
.initialLocales
,
1580 this.user
.initialLocale
,
1581 this.user
.initialMultipleRecommendedLocales
);
1585 update: function() {
1586 UserPod
.prototype.update
.call(this);
1587 this.querySelector('.expanded-pane-name').textContent
=
1588 this.user_
.displayName
;
1589 this.querySelector('.info').textContent
=
1590 loadTimeData
.getStringF('publicAccountInfoFormat',
1591 this.user_
.enterpriseDomain
);
1595 focusInput: function() {
1596 // Move tabIndex from the whole pod to the main input.
1598 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1599 this.mainInput
.focus();
1603 reset: function(takeFocus
) {
1605 this.expanded
= false;
1606 this.enterButtonElement
.disabled
= false;
1607 UserPod
.prototype.reset
.call(this, takeFocus
);
1611 activate: function(e
) {
1612 if (!this.expanded
) {
1613 this.expanded
= true;
1620 handleClickOnPod_: function(e
) {
1621 if (this.parentNode
.disabled
)
1624 this.parentNode
.focusPod(this);
1625 this.parentNode
.setActivatedPod(this, e
);
1626 // Prevent default so that we don't trigger 'focus' event.
1631 * Updates the display name shown on the pod.
1632 * @param {string} displayName The new display name
1634 setDisplayName: function(displayName
) {
1635 this.user_
.displayName
= displayName
;
1640 * Handle mouse and keyboard events for the learn more button. Triggering
1641 * the button causes information about public sessions to be shown.
1642 * @param {Event} event Mouse or keyboard event.
1644 handleLearnMoreEvent: function(event
) {
1645 switch (event
.type
) {
1646 // Show informaton on left click. Let any other clicks propagate.
1648 if (event
.button
!= 0)
1651 // Show informaton when <Return> or <Space> is pressed. Let any other
1652 // key presses propagate.
1654 switch (event
.keyCode
) {
1663 chrome
.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION
]);
1664 stopEventPropagation(event
);
1667 makeSpaceForExpandedPod_: function() {
1668 var width
= this.classList
.contains('advanced') ?
1669 PUBLIC_EXPANDED_ADVANCED_WIDTH
: PUBLIC_EXPANDED_BASIC_WIDTH
;
1670 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
1671 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
1672 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
1674 if (this.left
+ width
> $('pod-row').offsetWidth
- rowPadding
)
1675 this.left
= $('pod-row').offsetWidth
- rowPadding
- width
;
1679 * Transition the expanded pod from the basic to the advanced view.
1681 transitionToAdvanced_: function() {
1683 var languageAndInputSection
=
1684 this.querySelector('.language-and-input-section');
1685 this.classList
.add('transitioning-to-advanced');
1686 setTimeout(function() {
1687 pod
.classList
.add('advanced');
1688 pod
.makeSpaceForExpandedPod_();
1689 languageAndInputSection
.addEventListener('webkitTransitionEnd',
1690 function observer() {
1691 languageAndInputSection
.removeEventListener('webkitTransitionEnd',
1693 pod
.classList
.remove('transitioning-to-advanced');
1694 pod
.querySelector('.language-select').focus();
1696 // Guard timer set to animation duration + 20ms.
1697 ensureTransitionEndEvent(languageAndInputSection
, 380);
1702 * Retrieves the list of keyboard layouts available for the currently
1705 getPublicSessionKeyboardLayouts_: function() {
1706 var selectedLocale
= this.querySelector('.language-select').value
;
1707 if (selectedLocale
==
1708 this.querySelector('.keyboard-select').loadedLocale
) {
1709 // If the list of keyboard layouts was loaded for the currently selected
1710 // locale, it is already up to date.
1713 chrome
.send('getPublicSessionKeyboardLayouts',
1714 [this.user
.username
, selectedLocale
]);
1718 * Populates the keyboard layout "select" element with a list of layouts.
1719 * @param {string} locale The locale to which this list of keyboard layouts
1721 * @param {!Object} list List of available keyboard layouts
1723 populateKeyboardSelect: function(locale
, list
) {
1724 if (locale
!= this.querySelector('.language-select').value
) {
1725 // The selected locale has changed and the list of keyboard layouts is
1726 // not applicable. This method will be called again when a list of
1727 // keyboard layouts applicable to the selected locale is retrieved.
1731 var keyboardSelect
= this.querySelector('.keyboard-select');
1732 keyboardSelect
.loadedLocale
= locale
;
1733 keyboardSelect
.innerHTML
= '';
1734 for (var i
= 0; i
< list
.length
; ++i
) {
1736 keyboardSelect
.appendChild(
1737 new Option(item
.title
, item
.value
, item
.selected
, item
.selected
));
1742 * Populates the language "select" element with a list of locales.
1743 * @param {!Object} locales The list of available locales
1744 * @param {string} defaultLocale The locale to select by default
1745 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
1746 * two or more recommended locales
1748 populateLanguageSelect: function(locales
,
1750 multipleRecommendedLocales
) {
1751 var languageSelect
= this.querySelector('.language-select');
1752 // If the user manually selected a locale, do not change the selection.
1753 // Otherwise, select the new |defaultLocale|.
1755 languageSelect
.manuallyChanged
? languageSelect
.value
: defaultLocale
;
1756 languageSelect
.innerHTML
= '';
1757 var group
= languageSelect
;
1758 for (var i
= 0; i
< locales
.length
; ++i
) {
1759 var item
= locales
[i
];
1760 if (item
.optionGroupName
) {
1761 group
= document
.createElement('optgroup');
1762 group
.label
= item
.optionGroupName
;
1763 languageSelect
.appendChild(group
);
1765 group
.appendChild(new Option(item
.title
,
1767 item
.value
== selected
,
1768 item
.value
== selected
));
1771 languageSelect
.multipleRecommendedLocales
= multipleRecommendedLocales
;
1773 // Retrieve a list of keyboard layouts applicable to the locale that is
1775 this.getPublicSessionKeyboardLayouts_();
1780 * Creates a user pod to be used only in desktop chrome.
1782 * @extends {UserPod}
1784 var DesktopUserPod
= cr
.ui
.define(function() {
1785 // Don't just instantiate a UserPod(), as this will call decorate() on the
1786 // parent object, and add duplicate event listeners.
1787 var node
= $('user-pod-template').cloneNode(true);
1788 node
.removeAttribute('id');
1792 DesktopUserPod
.prototype = {
1793 __proto__
: UserPod
.prototype,
1797 if (this.user
.needsSignin
)
1798 return this.passwordElement
;
1800 return this.nameElement
;
1804 update: function() {
1805 this.imageElement
.src
= this.user
.userImage
;
1806 this.nameElement
.textContent
= this.user
.displayName
;
1808 var isLockedUser
= this.user
.needsSignin
;
1809 var isSupervisedUser
= this.user
.supervisedUser
;
1810 this.classList
.toggle('locked', isLockedUser
);
1811 this.classList
.toggle('supervised-user', isSupervisedUser
);
1813 if (this.isAuthTypeUserClick
)
1814 this.passwordLabelElement
.textContent
= this.authValue
;
1816 this.actionBoxRemoveUserWarningTextElement
.hidden
= isSupervisedUser
;
1817 this.actionBoxRemoveSupervisedUserWarningTextElement
.hidden
=
1820 UserPod
.prototype.updateActionBoxArea
.call(this);
1824 focusInput: function() {
1825 // Move tabIndex from the whole pod to the main input.
1827 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1828 this.mainInput
.focus();
1832 activate: function(e
) {
1833 if (!this.user
.needsSignin
) {
1834 Oobe
.launchUser(this.user
.emailAddress
, this.user
.displayName
);
1835 } else if (!this.passwordElement
.value
) {
1838 chrome
.send('authenticatedLaunchUser',
1839 [this.user
.emailAddress
,
1840 this.user
.displayName
,
1841 this.passwordElement
.value
]);
1843 this.passwordElement
.value
= '';
1848 handleClickOnPod_: function(e
) {
1849 if (this.parentNode
.disabled
)
1853 this.parentNode
.lastFocusedPod_
= this;
1855 // If this is an unlocked pod, then open a browser window. Otherwise
1856 // just activate the pod and show the password field.
1857 if (!this.user
.needsSignin
&& !this.isActionBoxMenuActive
)
1860 if (this.isAuthTypeUserClick
)
1861 chrome
.send('attemptUnlock', [this.user
.emailAddress
]);
1866 * Creates a user pod that represents kiosk app.
1868 * @extends {UserPod}
1870 var KioskAppPod
= cr
.ui
.define(function() {
1871 var node
= UserPod();
1875 KioskAppPod
.prototype = {
1876 __proto__
: UserPod
.prototype,
1879 decorate: function() {
1880 UserPod
.prototype.decorate
.call(this);
1881 this.launchAppButtonElement
.addEventListener('click',
1882 this.activate
.bind(this));
1886 update: function() {
1887 this.imageElement
.src
= this.user
.iconUrl
;
1888 this.imageElement
.alt
= this.user
.label
;
1889 this.imageElement
.title
= this.user
.label
;
1890 this.passwordEntryContainerElement
.hidden
= true;
1891 this.launchAppButtonContainerElement
.hidden
= false;
1892 this.nameElement
.textContent
= this.user
.label
;
1894 UserPod
.prototype.updateActionBoxArea
.call(this);
1895 UserPod
.prototype.customizeUserPodPerUserType
.call(this);
1900 return this.launchAppButtonElement
;
1904 focusInput: function() {
1905 // Move tabIndex from the whole pod to the main input.
1907 this.mainInput
.tabIndex
= UserPodTabOrder
.POD_INPUT
;
1908 this.mainInput
.focus();
1912 get forceOnlineSignin() {
1917 activate: function(e
) {
1918 var diagnosticMode
= e
&& e
.ctrlKey
;
1919 this.launchApp_(this.user
, diagnosticMode
);
1924 handleClickOnPod_: function(e
) {
1925 if (this.parentNode
.disabled
)
1929 this.parentNode
.lastFocusedPod_
= this;
1934 * Launch the app. If |diagnosticMode| is true, ask user to confirm.
1935 * @param {Object} app App data.
1936 * @param {boolean} diagnosticMode Whether to run the app in diagnostic
1939 launchApp_: function(app
, diagnosticMode
) {
1940 if (!diagnosticMode
) {
1941 chrome
.send('launchKioskApp', [app
.id
, false]);
1945 var oobe
= $('oobe');
1946 if (!oobe
.confirmDiagnosticMode_
) {
1947 oobe
.confirmDiagnosticMode_
=
1948 new cr
.ui
.dialogs
.ConfirmDialog(document
.body
);
1949 oobe
.confirmDiagnosticMode_
.setOkLabel(
1950 loadTimeData
.getString('confirmKioskAppDiagnosticModeYes'));
1951 oobe
.confirmDiagnosticMode_
.setCancelLabel(
1952 loadTimeData
.getString('confirmKioskAppDiagnosticModeNo'));
1955 oobe
.confirmDiagnosticMode_
.show(
1956 loadTimeData
.getStringF('confirmKioskAppDiagnosticModeFormat',
1959 chrome
.send('launchKioskApp', [app
.id
, true]);
1965 * Creates a new pod row element.
1967 * @extends {HTMLDivElement}
1969 var PodRow
= cr
.ui
.define('podrow');
1971 PodRow
.prototype = {
1972 __proto__
: HTMLDivElement
.prototype,
1974 // Whether this user pod row is shown for the first time.
1977 // True if inside focusPod().
1978 insideFocusPod_
: false,
1981 focusedPod_
: undefined,
1983 // Activated pod, i.e. the pod of current login attempt.
1984 activatedPod_
: undefined,
1986 // Pod that was most recently focused, if any.
1987 lastFocusedPod_
: undefined,
1989 // Pods whose initial images haven't been loaded yet.
1990 podsWithPendingImages_
: [],
1992 // Whether pod placement has been postponed.
1993 podPlacementPostponed_
: false,
1995 // Standard user pod height/width.
1999 // Array of apps that are shown in addition to other user pods.
2002 // True to show app pods along with user pods.
2003 shouldShowApps_
: true,
2005 // Array of users that are shown (public/supervised/regular).
2008 // If we're disabling single pod autofocus for Touch View.
2009 touchViewSinglePodExperimentOn_
: true,
2013 decorate: function() {
2014 // Event listeners that are installed for the time period during which
2015 // the element is visible.
2017 focus
: [this.handleFocus_
.bind(this), true /* useCapture */],
2018 click
: [this.handleClick_
.bind(this), true],
2019 mousemove
: [this.handleMouseMove_
.bind(this), false],
2020 keydown
: [this.handleKeyDown
.bind(this), false]
2023 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2024 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2025 this.userPodHeight_
= isDesktopUserManager
? DESKTOP_POD_HEIGHT
:
2027 // Same for Chrome OS and desktop.
2028 this.userPodWidth_
= POD_WIDTH
;
2032 * Returns all the pods in this pod row.
2036 return Array
.prototype.slice
.call(this.children
);
2040 * Return true if user pod row has only single user pod in it, which should
2041 * always be focused except desktop and touch view modes.
2044 get alwaysFocusSinglePod() {
2045 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2046 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2048 return (isDesktopUserManager
||
2049 (this.touchViewSinglePodExperimentOn_
&&
2050 this.touchViewEnabled_
)) ?
2051 false : this.children
.length
== 1;
2055 * Returns pod with the given app id.
2056 * @param {!string} app_id Application id to be matched.
2057 * @return {Object} Pod with the given app id. null if pod hasn't been
2060 getPodWithAppId_: function(app_id
) {
2061 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2062 if (pod
.user
.isApp
&& pod
.user
.id
== app_id
)
2069 * Returns pod with the given username (null if there is no such pod).
2070 * @param {string} username Username to be matched.
2071 * @return {Object} Pod with the given username. null if pod hasn't been
2074 getPodWithUsername_: function(username
) {
2075 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2076 if (pod
.user
.username
== username
)
2083 * True if the the pod row is disabled (handles no user interaction).
2088 return this.disabled_
;
2090 set disabled(value
) {
2091 this.disabled_
= value
;
2092 var controls
= this.querySelectorAll('button,input');
2093 for (var i
= 0, control
; control
= controls
[i
]; ++i
) {
2094 control
.disabled
= value
;
2099 * Creates a user pod from given email.
2100 * @param {!Object} user User info dictionary.
2102 createUserPod: function(user
) {
2104 if (user
.isDesktopUser
)
2105 userPod
= new DesktopUserPod({user
: user
});
2106 else if (user
.publicAccount
)
2107 userPod
= new PublicAccountUserPod({user
: user
});
2108 else if (user
.isApp
)
2109 userPod
= new KioskAppPod({user
: user
});
2111 userPod
= new UserPod({user
: user
});
2113 userPod
.hidden
= false;
2118 * Add an existing user pod to this pod row.
2119 * @param {!Object} user User info dictionary.
2121 addUserPod: function(user
) {
2122 var userPod
= this.createUserPod(user
);
2123 this.appendChild(userPod
);
2124 userPod
.initialize();
2128 * Runs app with a given id from the list of loaded apps.
2129 * @param {!string} app_id of an app to run.
2130 * @param {boolean=} opt_diagnostic_mode Whether to run the app in
2131 * diagnostic mode. Default is false.
2133 findAndRunAppForTesting: function(app_id
, opt_diagnostic_mode
) {
2134 var app
= this.getPodWithAppId_(app_id
);
2136 var activationEvent
= cr
.doc
.createEvent('MouseEvents');
2137 var ctrlKey
= opt_diagnostic_mode
;
2138 activationEvent
.initMouseEvent('click', true, true, null,
2139 0, 0, 0, 0, 0, ctrlKey
, false, false, false, 0, null);
2140 app
.dispatchEvent(activationEvent
);
2145 * Removes user pod from pod row.
2146 * @param {string} email User's email.
2148 removeUserPod: function(username
) {
2149 var podToRemove
= this.getPodWithUsername_(username
);
2150 if (podToRemove
== null) {
2151 console
.warn('Attempt to remove not existing pod for ' + username
+
2155 this.removeChild(podToRemove
);
2156 if (this.pods
.length
> 0)
2161 * Returns index of given pod or -1 if not found.
2162 * @param {UserPod} pod Pod to look up.
2165 indexOf_: function(pod
) {
2166 for (var i
= 0; i
< this.pods
.length
; ++i
) {
2167 if (pod
== this.pods
[i
])
2174 * Populates pod row with given existing users and start init animation.
2175 * @param {array} users Array of existing user emails.
2177 loadPods: function(users
) {
2178 this.users_
= users
;
2184 * Scrolls focused user pod into view.
2186 scrollFocusedPodIntoView: function() {
2187 var pod
= this.focusedPod_
;
2191 // First check whether focused pod is already fully visible.
2192 var visibleArea
= $('scroll-container');
2193 var scrollTop
= visibleArea
.scrollTop
;
2194 var clientHeight
= visibleArea
.clientHeight
;
2195 var podTop
= $('oobe').offsetTop
+ pod
.offsetTop
;
2196 var padding
= USER_POD_KEYBOARD_MIN_PADDING
;
2197 if (podTop
+ pod
.height
+ padding
<= scrollTop
+ clientHeight
&&
2198 podTop
- padding
>= scrollTop
) {
2202 // Scroll so that user pod is as centered as possible.
2203 visibleArea
.scrollTop
= podTop
- (clientHeight
- pod
.offsetHeight
) / 2;
2207 * Rebuilds pod row using users_ and apps_ that were previously set or
2210 rebuildPods: function() {
2211 var emptyPodRow
= this.pods
.length
== 0;
2213 // Clear existing pods.
2214 this.innerHTML
= '';
2215 this.focusedPod_
= undefined;
2216 this.activatedPod_
= undefined;
2217 this.lastFocusedPod_
= undefined;
2219 // Switch off animation
2220 Oobe
.getInstance().toggleClass('flying-pods', false);
2222 // Populate the pod row.
2223 for (var i
= 0; i
< this.users_
.length
; ++i
)
2224 this.addUserPod(this.users_
[i
]);
2226 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
)
2227 this.podsWithPendingImages_
.push(pod
);
2229 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
2230 if (this.shouldShowApps_
) {
2231 for (var i
= 0; i
< this.apps_
.length
; ++i
)
2232 this.addUserPod(this.apps_
[i
]);
2235 // Make sure we eventually show the pod row, even if some image is stuck.
2236 setTimeout(function() {
2237 $('pod-row').classList
.remove('images-loading');
2238 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS
);
2240 var isAccountPicker
= $('login-header-bar').signinUIState
==
2241 SIGNIN_UI_STATE
.ACCOUNT_PICKER
;
2243 // Immediately recalculate pods layout only when current UI is account
2244 // picker. Otherwise postpone it.
2245 if (isAccountPicker
) {
2247 this.maybePreselectPod();
2249 // Without timeout changes in pods positions will be animated even
2250 // though it happened when 'flying-pods' class was disabled.
2251 setTimeout(function() {
2252 Oobe
.getInstance().toggleClass('flying-pods', true);
2255 this.podPlacementPostponed_
= true;
2257 // Update [Cancel] button state.
2258 if ($('login-header-bar').signinUIState
==
2259 SIGNIN_UI_STATE
.GAIA_SIGNIN
&&
2261 this.pods
.length
> 0) {
2262 login
.GaiaSigninScreen
.updateCancelButtonState();
2268 * Adds given apps to the pod row.
2269 * @param {array} apps Array of apps.
2271 setApps: function(apps
) {
2274 chrome
.send('kioskAppsLoaded');
2276 // Check whether there's a pending kiosk app error.
2277 window
.setTimeout(function() {
2278 chrome
.send('checkKioskAppLaunchError');
2283 * Sets whether should show app pods.
2284 * @param {boolean} shouldShowApps Whether app pods should be shown.
2286 setShouldShowApps: function(shouldShowApps
) {
2287 if (this.shouldShowApps_
== shouldShowApps
)
2290 this.shouldShowApps_
= shouldShowApps
;
2295 * Shows a custom icon on a user pod besides the input field.
2296 * @param {string} username Username of pod to add button
2297 * @param {!{resourceUrl: (string | undefined),
2298 * data: ({scale1x: string, scale2x: string} | undefined),
2299 * size: ({width: number, height: number} | undefined),
2300 * animation: ({resourceWidth: number, frameLength: number} |
2302 * opacity: (number | undefined),
2303 * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon
2304 * The icon parameters.
2306 showUserPodCustomIcon: function(username
, icon
) {
2307 var pod
= this.getPodWithUsername_(username
);
2309 console
.error('Unable to show user pod button for ' + username
+
2310 ': user pod not found.');
2314 if (icon
.resourceUrl
) {
2315 pod
.customIconElement
.setIconAsResourceUrl(icon
.resourceUrl
);
2316 } else if (icon
.data
) {
2317 pod
.customIconElement
.setIconAsImageSet(icon
.data
);
2322 pod
.customIconElement
.setSize(icon
.size
|| {width
: 0, height
: 0});
2323 pod
.customIconElement
.setAnimation(icon
.animation
|| null);
2324 pod
.customIconElement
.setOpacity(icon
.opacity
|| 100);
2325 if (icon
.hardlockOnClick
) {
2326 pod
.customIconElement
.setInteractive(
2327 this.hardlockUserPod_
.bind(this, username
));
2329 pod
.customIconElement
.setInteractive(null);
2331 pod
.customIconElement
.show();
2332 // This has to be called after |show| in case the tooltip should be shown
2334 pod
.customIconElement
.setTooltip(
2335 icon
.tooltip
|| {text
: '', autoshow
: false});
2339 * Hard-locks user pod for the user. If user pod is hard-locked, it can be
2340 * only unlocked using password, and the authentication type cannot be
2342 * @param {!string} username The user's username.
2345 hardlockUserPod_: function(username
) {
2346 chrome
.send('hardlockPod', [username
]);
2350 * Hides the custom icon in the user pod added by showUserPodCustomIcon().
2351 * @param {string} username Username of pod to remove button
2353 hideUserPodCustomIcon: function(username
) {
2354 var pod
= this.getPodWithUsername_(username
);
2356 console
.error('Unable to hide user pod button for ' + username
+
2357 ': user pod not found.');
2361 pod
.customIconElement
.fadeOut();
2365 * Sets the authentication type used to authenticate the user.
2366 * @param {string} username Username of selected user
2367 * @param {number} authType Authentication type, must be one of the
2368 * values listed in AUTH_TYPE enum.
2369 * @param {string} value The initial value to use for authentication.
2371 setAuthType: function(username
, authType
, value
) {
2372 var pod
= this.getPodWithUsername_(username
);
2374 console
.error('Unable to set auth type for ' + username
+
2375 ': user pod not found.');
2378 pod
.setAuthType(authType
, value
);
2382 * Sets the state of touch view mode.
2383 * @param {boolean} isTouchViewEnabled true if the mode is on.
2385 setTouchViewState: function(isTouchViewEnabled
) {
2386 this.touchViewEnabled_
= isTouchViewEnabled
;
2390 * Updates the display name shown on a public session pod.
2391 * @param {string} userID The user ID of the public session
2392 * @param {string} displayName The new display name
2394 setPublicSessionDisplayName: function(userID
, displayName
) {
2395 var pod
= this.getPodWithUsername_(userID
);
2397 pod
.setDisplayName(displayName
);
2401 * Updates the list of locales available for a public session.
2402 * @param {string} userID The user ID of the public session
2403 * @param {!Object} locales The list of available locales
2404 * @param {string} defaultLocale The locale to select by default
2405 * @param {boolean} multipleRecommendedLocales Whether |locales| contains
2406 * two or more recommended locales
2408 setPublicSessionLocales: function(userID
,
2411 multipleRecommendedLocales
) {
2412 var pod
= this.getPodWithUsername_(userID
);
2414 pod
.populateLanguageSelect(locales
,
2416 multipleRecommendedLocales
);
2421 * Updates the list of available keyboard layouts for a public session pod.
2422 * @param {string} userID The user ID of the public session
2423 * @param {string} locale The locale to which this list of keyboard layouts
2425 * @param {!Object} list List of available keyboard layouts
2427 setPublicSessionKeyboardLayouts: function(userID
, locale
, list
) {
2428 var pod
= this.getPodWithUsername_(userID
);
2430 pod
.populateKeyboardSelect(locale
, list
);
2434 * Called when window was resized.
2436 onWindowResize: function() {
2437 var layout
= this.calculateLayout_();
2438 if (layout
.columns
!= this.columns
|| layout
.rows
!= this.rows
)
2441 if (Oobe
.getInstance().virtualKeyboardShown
)
2442 this.scrollFocusedPodIntoView();
2446 * Returns width of podrow having |columns| number of columns.
2449 columnsToWidth_: function(columns
) {
2450 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2451 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2452 var margin
= isDesktopUserManager
? DESKTOP_MARGIN_BY_COLUMNS
[columns
] :
2453 MARGIN_BY_COLUMNS
[columns
];
2454 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
2456 return 2 * rowPadding
+ columns
* this.userPodWidth_
+
2457 (columns
- 1) * margin
;
2461 * Returns height of podrow having |rows| number of rows.
2464 rowsToHeight_: function(rows
) {
2465 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2466 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2467 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
2469 return 2 * rowPadding
+ rows
* this.userPodHeight_
;
2473 * Calculates number of columns and rows that podrow should have in order to
2474 * hold as much its pods as possible for current screen size. Also it tries
2475 * to choose layout that looks good.
2476 * @return {{columns: number, rows: number}}
2478 calculateLayout_: function() {
2479 var preferredColumns
= this.pods
.length
< COLUMNS
.length
?
2480 COLUMNS
[this.pods
.length
] : COLUMNS
[COLUMNS
.length
- 1];
2481 var maxWidth
= Oobe
.getInstance().clientAreaSize
.width
;
2482 var columns
= preferredColumns
;
2483 while (maxWidth
< this.columnsToWidth_(columns
) && columns
> 1)
2485 var rows
= Math
.floor((this.pods
.length
- 1) / columns
) + 1;
2486 if (getComputedStyle(
2487 $('signin-banner'), null).getPropertyValue('display') != 'none') {
2488 rows
= Math
.min(rows
, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER
);
2490 var maxHeigth
= Oobe
.getInstance().clientAreaSize
.height
;
2491 while (maxHeigth
< this.rowsToHeight_(rows
) && rows
> 1)
2493 // One more iteration if it's not enough cells to place all pods.
2494 while (maxWidth
>= this.columnsToWidth_(columns
+ 1) &&
2495 columns
* rows
< this.pods
.length
&&
2496 columns
< MAX_NUMBER_OF_COLUMNS
) {
2499 return {columns
: columns
, rows
: rows
};
2503 * Places pods onto their positions onto pod grid.
2506 placePods_: function() {
2507 var layout
= this.calculateLayout_();
2508 var columns
= this.columns
= layout
.columns
;
2509 var rows
= this.rows
= layout
.rows
;
2510 var maxPodsNumber
= columns
* rows
;
2511 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2512 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2513 var margin
= isDesktopUserManager
? DESKTOP_MARGIN_BY_COLUMNS
[columns
] :
2514 MARGIN_BY_COLUMNS
[columns
];
2515 this.parentNode
.setPreferredSize(
2516 this.columnsToWidth_(columns
), this.rowsToHeight_(rows
));
2517 var height
= this.userPodHeight_
;
2518 var width
= this.userPodWidth_
;
2519 this.pods
.forEach(function(pod
, index
) {
2520 if (index
>= maxPodsNumber
) {
2525 if (pod
.offsetHeight
!= height
) {
2526 console
.error('Pod offsetHeight (' + pod
.offsetHeight
+
2527 ') and POD_HEIGHT (' + height
+ ') are not equal.');
2529 if (pod
.offsetWidth
!= width
) {
2530 console
.error('Pod offsetWidth (' + pod
.offsetWidth
+
2531 ') and POD_WIDTH (' + width
+ ') are not equal.');
2533 var column
= index
% columns
;
2534 var row
= Math
.floor(index
/ columns
);
2535 var rowPadding
= isDesktopUserManager
? DESKTOP_ROW_PADDING
:
2537 pod
.left
= rowPadding
+ column
* (width
+ margin
);
2539 // On desktop, we want the rows to always be equally spaced.
2540 pod
.top
= isDesktopUserManager
? row
* (height
+ rowPadding
) :
2541 row
* height
+ rowPadding
;
2543 Oobe
.getInstance().updateScreenSize(this.parentNode
);
2547 * Number of columns.
2550 set columns(columns
) {
2551 // Cannot use 'columns' here.
2552 this.setAttribute('ncolumns', columns
);
2555 return parseInt(this.getAttribute('ncolumns'));
2563 // Cannot use 'rows' here.
2564 this.setAttribute('nrows', rows
);
2567 return parseInt(this.getAttribute('nrows'));
2571 * Whether the pod is currently focused.
2572 * @param {UserPod} pod Pod to check for focus.
2573 * @return {boolean} Pod focus status.
2575 isFocused: function(pod
) {
2576 return this.focusedPod_
== pod
;
2580 * Focuses a given user pod or clear focus when given null.
2581 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
2582 * @param {boolean=} opt_force If true, forces focus update even when
2583 * podToFocus is already focused.
2585 focusPod: function(podToFocus
, opt_force
) {
2586 if (this.isFocused(podToFocus
) && !opt_force
) {
2587 // Calling focusPod w/o podToFocus means reset.
2590 this.keyboardActivated_
= false;
2594 // Make sure there's only one focusPod operation happening at a time.
2595 if (this.insideFocusPod_
) {
2596 this.keyboardActivated_
= false;
2599 this.insideFocusPod_
= true;
2601 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2602 if (!this.alwaysFocusSinglePod
) {
2603 pod
.isActionBoxMenuActive
= false;
2605 if (pod
!= podToFocus
) {
2606 pod
.isActionBoxMenuHovered
= false;
2607 pod
.classList
.remove('focused');
2608 // On Desktop, the faded style is not set correctly, so we should
2609 // manually fade out non-focused pods if there is a focused pod.
2610 if (pod
.user
.isDesktopUser
&& podToFocus
)
2611 pod
.classList
.add('faded');
2613 pod
.classList
.remove('faded');
2618 // Clear any error messages for previous pod.
2619 if (!this.isFocused(podToFocus
))
2622 var hadFocus
= !!this.focusedPod_
;
2623 this.focusedPod_
= podToFocus
;
2625 podToFocus
.classList
.remove('faded');
2626 podToFocus
.classList
.add('focused');
2627 if (!podToFocus
.multiProfilesPolicyApplied
)
2628 podToFocus
.reset(true); // Reset and give focus.
2630 podToFocus
.userTypeBubbleElement
.classList
.add('bubble-shown');
2634 // focusPod() automatically loads wallpaper
2635 if (!podToFocus
.user
.isApp
)
2636 chrome
.send('focusPod', [podToFocus
.user
.username
]);
2637 this.firstShown_
= false;
2638 this.lastFocusedPod_
= podToFocus
;
2640 if (Oobe
.getInstance().virtualKeyboardShown
)
2641 this.scrollFocusedPodIntoView();
2643 this.insideFocusPod_
= false;
2644 this.keyboardActivated_
= false;
2648 * Resets wallpaper to the last active user's wallpaper, if any.
2650 loadLastWallpaper: function() {
2651 if (this.lastFocusedPod_
&& !this.lastFocusedPod_
.user
.isApp
)
2652 chrome
.send('loadWallpaper', [this.lastFocusedPod_
.user
.username
]);
2656 * Returns the currently activated pod.
2659 get activatedPod() {
2660 return this.activatedPod_
;
2664 * Sets currently activated pod.
2665 * @param {UserPod} pod Pod to check for focus.
2666 * @param {Event} e Event object.
2668 setActivatedPod: function(pod
, e
) {
2669 if (pod
&& pod
.activate(e
))
2670 this.activatedPod_
= pod
;
2674 * The pod of the signed-in user, if any; null otherwise.
2678 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2679 if (pod
.user
.signedIn
)
2686 * The pod that is preselected on user pod row show.
2689 get preselectedPod() {
2690 var isDesktopUserManager
= Oobe
.getInstance().displayType
==
2691 DISPLAY_TYPE
.DESKTOP_USER_MANAGER
;
2692 if (isDesktopUserManager
) {
2693 // On desktop, don't pre-select a pod if it's the only one.
2694 if (this.pods
.length
== 1)
2697 // The desktop User Manager can send the index of a pod that should be
2698 // initially focused in url hash.
2699 var podIndex
= parseInt(window
.location
.hash
.substr(1));
2700 if (isNaN(podIndex
) || podIndex
>= this.pods
.length
)
2702 return this.pods
[podIndex
];
2705 var lockedPod
= this.lockedPod
;
2708 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
) {
2709 if (!pod
.multiProfilesPolicyApplied
) {
2713 return this.pods
[0];
2718 * @param {boolean} takeFocus True to take focus.
2720 reset: function(takeFocus
) {
2721 this.disabled
= false;
2722 if (this.activatedPod_
)
2723 this.activatedPod_
.reset(takeFocus
);
2727 * Restores input focus to current selected pod, if there is any.
2729 refocusCurrentPod: function() {
2730 if (this.focusedPod_
&& !this.focusedPod_
.multiProfilesPolicyApplied
) {
2731 this.focusedPod_
.focusInput();
2736 * Clears focused pod password field.
2738 clearFocusedPod: function() {
2739 if (!this.disabled
&& this.focusedPod_
)
2740 this.focusedPod_
.reset(true);
2745 * @param {string} email Email for signin UI.
2747 showSigninUI: function(email
) {
2748 // Clear any error messages that might still be around.
2750 this.disabled
= true;
2751 this.lastFocusedPod_
= this.getPodWithUsername_(email
);
2752 Oobe
.showSigninUI(email
);
2756 * Updates current image of a user.
2757 * @param {string} username User for which to update the image.
2759 updateUserImage: function(username
) {
2760 var pod
= this.getPodWithUsername_(username
);
2762 pod
.updateUserImage();
2766 * Handler of click event.
2767 * @param {Event} e Click Event object.
2770 handleClick_: function(e
) {
2774 // Clear all menus if the click is outside pod menu and its
2776 if (!findAncestorByClass(e
.target
, 'action-box-menu') &&
2777 !findAncestorByClass(e
.target
, 'action-box-area')) {
2778 for (var i
= 0, pod
; pod
= this.pods
[i
]; ++i
)
2779 pod
.isActionBoxMenuActive
= false;
2782 // Clears focus if not clicked on a pod and if there's more than one pod.
2783 var pod
= findAncestorByClass(e
.target
, 'pod');
2784 if ((!pod
|| pod
.parentNode
!= this) && !this.alwaysFocusSinglePod
) {
2789 pod
.isActionBoxMenuHovered
= true;
2791 // Return focus back to single pod.
2792 if (this.alwaysFocusSinglePod
&& !pod
) {
2793 this.focusPod(this.focusedPod_
, true /* force */);
2794 this.focusedPod_
.userTypeBubbleElement
.classList
.remove('bubble-shown');
2795 this.focusedPod_
.isActionBoxMenuHovered
= false;
2800 * Handler of mouse move event.
2801 * @param {Event} e Click Event object.
2804 handleMouseMove_: function(e
) {
2807 if (e
.webkitMovementX
== 0 && e
.webkitMovementY
== 0)
2810 // Defocus (thus hide) action box, if it is focused on a user pod
2811 // and the pointer is not hovering over it.
2812 var pod
= findAncestorByClass(e
.target
, 'pod');
2813 if (document
.activeElement
&&
2814 document
.activeElement
.parentNode
!= pod
&&
2815 document
.activeElement
.classList
.contains('action-box-area')) {
2816 document
.activeElement
.parentNode
.focus();
2820 pod
.isActionBoxMenuHovered
= true;
2822 // Hide action boxes on other user pods.
2823 for (var i
= 0, p
; p
= this.pods
[i
]; ++i
)
2824 if (p
!= pod
&& !p
.isActionBoxMenuActive
)
2825 p
.isActionBoxMenuHovered
= false;
2829 * Handles focus event.
2830 * @param {Event} e Focus Event object.
2833 handleFocus_: function(e
) {
2836 if (e
.target
.parentNode
== this) {
2838 if (e
.target
.classList
.contains('focused')) {
2839 if (!e
.target
.multiProfilesPolicyApplied
)
2840 e
.target
.focusInput();
2842 e
.target
.userTypeBubbleElement
.classList
.add('bubble-shown');
2844 this.focusPod(e
.target
);
2848 var pod
= findAncestorByClass(e
.target
, 'pod');
2849 if (pod
&& pod
.parentNode
== this) {
2850 // Focus on a control of a pod but not on the action area button.
2851 if (!pod
.classList
.contains('focused') &&
2852 !e
.target
.classList
.contains('action-box-button')) {
2854 pod
.userTypeBubbleElement
.classList
.remove('bubble-shown');
2860 // Clears pod focus when we reach here. It means new focus is neither
2861 // on a pod nor on a button/input for a pod.
2862 // Do not "defocus" user pod when it is a single pod.
2863 // That means that 'focused' class will not be removed and
2864 // input field/button will always be visible.
2865 if (!this.alwaysFocusSinglePod
)
2868 // Hide user-type-bubble in case this is one pod and we lost focus of
2870 this.focusedPod_
.userTypeBubbleElement
.classList
.remove('bubble-shown');
2875 * Handler of keydown event.
2876 * @param {Event} e KeyDown Event object.
2878 handleKeyDown: function(e
) {
2881 var editing
= e
.target
.tagName
== 'INPUT' && e
.target
.value
;
2882 switch (e
.keyIdentifier
) {
2885 this.keyboardActivated_
= true;
2886 if (this.focusedPod_
&& this.focusedPod_
.previousElementSibling
)
2887 this.focusPod(this.focusedPod_
.previousElementSibling
);
2889 this.focusPod(this.lastElementChild
);
2891 e
.stopPropagation();
2896 this.keyboardActivated_
= true;
2897 if (this.focusedPod_
&& this.focusedPod_
.nextElementSibling
)
2898 this.focusPod(this.focusedPod_
.nextElementSibling
);
2900 this.focusPod(this.firstElementChild
);
2902 e
.stopPropagation();
2906 if (this.focusedPod_
) {
2907 var targetTag
= e
.target
.tagName
;
2908 if (e
.target
== this.focusedPod_
.passwordElement
||
2909 (targetTag
!= 'INPUT' &&
2910 targetTag
!= 'BUTTON' &&
2911 targetTag
!= 'A')) {
2912 this.setActivatedPod(this.focusedPod_
, e
);
2913 e
.stopPropagation();
2917 case 'U+001B': // Esc
2918 if (!this.alwaysFocusSinglePod
)
2925 * Called right after the pod row is shown.
2927 handleAfterShow: function() {
2928 // Without timeout changes in pods positions will be animated even though
2929 // it happened when 'flying-pods' class was disabled.
2930 setTimeout(function() {
2931 Oobe
.getInstance().toggleClass('flying-pods', true);
2933 // Force input focus for user pod on show and once transition ends.
2934 if (this.focusedPod_
) {
2935 var focusedPod
= this.focusedPod_
;
2936 var screen
= this.parentNode
;
2938 focusedPod
.addEventListener('webkitTransitionEnd', function f(e
) {
2939 focusedPod
.removeEventListener('webkitTransitionEnd', f
);
2940 focusedPod
.reset(true);
2941 // Notify screen that it is ready.
2944 // Guard timer for 1 second -- it would conver all possible animations.
2945 ensureTransitionEndEvent(focusedPod
, 1000);
2950 * Called right before the pod row is shown.
2952 handleBeforeShow: function() {
2953 Oobe
.getInstance().toggleClass('flying-pods', false);
2954 for (var event
in this.listeners_
) {
2955 this.ownerDocument
.addEventListener(
2956 event
, this.listeners_
[event
][0], this.listeners_
[event
][1]);
2958 $('login-header-bar').buttonsTabIndex
= UserPodTabOrder
.HEADER_BAR
;
2960 if (this.podPlacementPostponed_
) {
2961 this.podPlacementPostponed_
= false;
2963 this.maybePreselectPod();
2968 * Called when the element is hidden.
2970 handleHide: function() {
2971 for (var event
in this.listeners_
) {
2972 this.ownerDocument
.removeEventListener(
2973 event
, this.listeners_
[event
][0], this.listeners_
[event
][1]);
2975 $('login-header-bar').buttonsTabIndex
= 0;
2979 * Called when a pod's user image finishes loading.
2981 handlePodImageLoad: function(pod
) {
2982 var index
= this.podsWithPendingImages_
.indexOf(pod
);
2987 this.podsWithPendingImages_
.splice(index
, 1);
2988 if (this.podsWithPendingImages_
.length
== 0) {
2989 this.classList
.remove('images-loading');
2994 * Preselects pod, if needed.
2996 maybePreselectPod: function() {
2997 var pod
= this.preselectedPod
;
3000 // Hide user-type-bubble in case all user pods are disabled and we focus
3002 if (pod
&& pod
.multiProfilesPolicyApplied
) {
3003 pod
.userTypeBubbleElement
.classList
.remove('bubble-shown');