Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(621)

Side by Side Diff: chrome/browser/resources/login/user_pod_row.js

Issue 402403005: Move common account picker/user pod js/css/html out of src/chrome (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix html include on win Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 /**
6 * @fileoverview User pod row implementation.
7 */
8
9 cr.define('login', function() {
10 /**
11 * Number of displayed columns depending on user pod count.
12 * @type {Array.<number>}
13 * @const
14 */
15 var COLUMNS = [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6];
16
17 /**
18 * Mapping between number of columns in pod-row and margin between user pods
19 * for such layout.
20 * @type {Array.<number>}
21 * @const
22 */
23 var MARGIN_BY_COLUMNS = [undefined, 40, 40, 40, 40, 40, 12];
24
25 /**
26 * Mapping between number of columns in the desktop pod-row and margin
27 * between user pods for such layout.
28 * @type {Array.<number>}
29 * @const
30 */
31 var DESKTOP_MARGIN_BY_COLUMNS = [undefined, 15, 15, 15, 15, 15, 15];
32
33 /**
34 * Maximal number of columns currently supported by pod-row.
35 * @type {number}
36 * @const
37 */
38 var MAX_NUMBER_OF_COLUMNS = 6;
39
40 /**
41 * Maximal number of rows if sign-in banner is displayed alonside.
42 * @type {number}
43 * @const
44 */
45 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2;
46
47 /**
48 * Variables used for pod placement processing. Width and height should be
49 * synced with computed CSS sizes of pods.
50 */
51 var POD_WIDTH = 180;
52 var PUBLIC_EXPANDED_BASIC_WIDTH = 500;
53 var CROS_POD_HEIGHT = 213;
54 var DESKTOP_POD_HEIGHT = 226;
55 var POD_ROW_PADDING = 10;
56 var DESKTOP_ROW_PADDING = 15;
57
58 /**
59 * Minimal padding between user pod and virtual keyboard.
60 * @type {number}
61 * @const
62 */
63 var USER_POD_KEYBOARD_MIN_PADDING = 20;
64
65 /**
66 * Whether to preselect the first pod automatically on login screen.
67 * @type {boolean}
68 * @const
69 */
70 var PRESELECT_FIRST_POD = true;
71
72 /**
73 * Maximum time for which the pod row remains hidden until all user images
74 * have been loaded.
75 * @type {number}
76 * @const
77 */
78 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000;
79
80 /**
81 * Public session help topic identifier.
82 * @type {number}
83 * @const
84 */
85 var HELP_TOPIC_PUBLIC_SESSION = 3041033;
86
87 /**
88 * Tab order for user pods. Update these when adding new controls.
89 * @enum {number}
90 * @const
91 */
92 var UserPodTabOrder = {
93 POD_INPUT: 1, // Password input fields (and whole pods themselves).
94 HEADER_BAR: 2, // Buttons on the header bar (Shutdown, Add User).
95 ACTION_BOX: 3, // Action box buttons.
96 PAD_MENU_ITEM: 4 // User pad menu items (Remove this user).
97 };
98
99 /**
100 * Supported authentication types. Keep in sync with the enum in
101 * chrome/browser/signin/screenlock_bridge.h
102 * @enum {number}
103 * @const
104 */
105 var AUTH_TYPE = {
106 OFFLINE_PASSWORD: 0,
107 ONLINE_SIGN_IN: 1,
108 NUMERIC_PIN: 2,
109 USER_CLICK: 3,
110 EXPAND_THEN_USER_CLICK: 4,
111 };
112
113 /**
114 * Names of authentication types.
115 */
116 var AUTH_TYPE_NAMES = {
117 0: 'offlinePassword',
118 1: 'onlineSignIn',
119 2: 'numericPin',
120 3: 'userClick',
121 4: 'expandThenUserClick',
122 };
123
124 // Focus and tab order are organized as follows:
125 //
126 // (1) all user pods have tab index 1 so they are traversed first;
127 // (2) when a user pod is activated, its tab index is set to -1 and its
128 // main input field gets focus and tab index 1;
129 // (3) buttons on the header bar have tab index 2 so they follow user pods;
130 // (4) Action box buttons have tab index 3 and follow header bar buttons;
131 // (5) lastly, focus jumps to the Status Area and back to user pods.
132 //
133 // 'Focus' event is handled by a capture handler for the whole document
134 // and in some cases 'mousedown' event handlers are used instead of 'click'
135 // handlers where it's necessary to prevent 'focus' event from being fired.
136
137 /**
138 * Helper function to remove a class from given element.
139 * @param {!HTMLElement} el Element whose class list to change.
140 * @param {string} cl Class to remove.
141 */
142 function removeClass(el, cl) {
143 el.classList.remove(cl);
144 }
145
146 /**
147 * Creates a user pod.
148 * @constructor
149 * @extends {HTMLDivElement}
150 */
151 var UserPod = cr.ui.define(function() {
152 var node = $('user-pod-template').cloneNode(true);
153 node.removeAttribute('id');
154 return node;
155 });
156
157 /**
158 * Stops event propagation from the any user pod child element.
159 * @param {Event} e Event to handle.
160 */
161 function stopEventPropagation(e) {
162 // Prevent default so that we don't trigger a 'focus' event.
163 e.preventDefault();
164 e.stopPropagation();
165 }
166
167 /**
168 * Unique salt added to user image URLs to prevent caching. Dictionary with
169 * user names as keys.
170 * @type {Object}
171 */
172 UserPod.userImageSalt_ = {};
173
174 UserPod.prototype = {
175 __proto__: HTMLDivElement.prototype,
176
177 /** @override */
178 decorate: function() {
179 this.tabIndex = UserPodTabOrder.POD_INPUT;
180 this.actionBoxAreaElement.tabIndex = UserPodTabOrder.ACTION_BOX;
181
182 this.addEventListener('keydown', this.handlePodKeyDown_.bind(this));
183 this.addEventListener('click', this.handleClickOnPod_.bind(this));
184
185 this.signinButtonElement.addEventListener('click',
186 this.activate.bind(this));
187
188 this.actionBoxAreaElement.addEventListener('mousedown',
189 stopEventPropagation);
190 this.actionBoxAreaElement.addEventListener('click',
191 this.handleActionAreaButtonClick_.bind(this));
192 this.actionBoxAreaElement.addEventListener('keydown',
193 this.handleActionAreaButtonKeyDown_.bind(this));
194
195 this.actionBoxMenuRemoveElement.addEventListener('click',
196 this.handleRemoveCommandClick_.bind(this));
197 this.actionBoxMenuRemoveElement.addEventListener('keydown',
198 this.handleRemoveCommandKeyDown_.bind(this));
199 this.actionBoxMenuRemoveElement.addEventListener('blur',
200 this.handleRemoveCommandBlur_.bind(this));
201 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
202 'click',
203 this.handleRemoveUserConfirmationClick_.bind(this));
204 this.actionBoxRemoveUserWarningButtonElement.addEventListener(
205 'keydown',
206 this.handleRemoveUserConfirmationKeyDown_.bind(this));
207 },
208
209 /**
210 * Initializes the pod after its properties set and added to a pod row.
211 */
212 initialize: function() {
213 this.passwordElement.addEventListener('keydown',
214 this.parentNode.handleKeyDown.bind(this.parentNode));
215 this.passwordElement.addEventListener('keypress',
216 this.handlePasswordKeyPress_.bind(this));
217
218 this.imageElement.addEventListener('load',
219 this.parentNode.handlePodImageLoad.bind(this.parentNode, this));
220
221 var initialAuthType = this.user.initialAuthType ||
222 AUTH_TYPE.OFFLINE_PASSWORD;
223 this.setAuthType(initialAuthType, null);
224 },
225
226 /**
227 * Resets tab order for pod elements to its initial state.
228 */
229 resetTabOrder: function() {
230 // Note: the |mainInput| can be the pod itself.
231 this.mainInput.tabIndex = -1;
232 this.tabIndex = UserPodTabOrder.POD_INPUT;
233 },
234
235 /**
236 * Handles keypress event (i.e. any textual input) on password input.
237 * @param {Event} e Keypress Event object.
238 * @private
239 */
240 handlePasswordKeyPress_: function(e) {
241 // When tabbing from the system tray a tab key press is received. Suppress
242 // this so as not to type a tab character into the password field.
243 if (e.keyCode == 9) {
244 e.preventDefault();
245 return;
246 }
247 },
248
249 /**
250 * Top edge margin number of pixels.
251 * @type {?number}
252 */
253 set top(top) {
254 this.style.top = cr.ui.toCssPx(top);
255 },
256
257 /**
258 * Top edge margin number of pixels.
259 */
260 get top() {
261 return parseInt(this.style.top);
262 },
263
264 /**
265 * Left edge margin number of pixels.
266 * @type {?number}
267 */
268 set left(left) {
269 this.style.left = cr.ui.toCssPx(left);
270 },
271
272 /**
273 * Left edge margin number of pixels.
274 */
275 get left() {
276 return parseInt(this.style.left);
277 },
278
279 /**
280 * Height number of pixels.
281 */
282 get height() {
283 return this.offsetHeight;
284 },
285
286 /**
287 * Gets image element.
288 * @type {!HTMLImageElement}
289 */
290 get imageElement() {
291 return this.querySelector('.user-image');
292 },
293
294 /**
295 * Gets name element.
296 * @type {!HTMLDivElement}
297 */
298 get nameElement() {
299 return this.querySelector('.name');
300 },
301
302 /**
303 * Gets the container holding the password field.
304 * @type {!HTMLInputElement}
305 */
306 get passwordEntryContainerElement() {
307 return this.querySelector('.password-entry-container');
308 },
309
310 /**
311 * Gets password field.
312 * @type {!HTMLInputElement}
313 */
314 get passwordElement() {
315 return this.querySelector('.password');
316 },
317
318 /**
319 * Gets the password label, which is used to show a message where the
320 * password field is normally.
321 * @type {!HTMLInputElement}
322 */
323 get passwordLabelElement() {
324 return this.querySelector('.password-label');
325 },
326
327 /**
328 * Gets user sign in button.
329 * @type {!HTMLButtonElement}
330 */
331 get signinButtonElement() {
332 return this.querySelector('.signin-button');
333 },
334
335 /**
336 * Gets the container holding the launch app button.
337 * @type {!HTMLButtonElement}
338 */
339 get launchAppButtonContainerElement() {
340 return this.querySelector('.launch-app-button-container');
341 },
342
343 /**
344 * Gets launch app button.
345 * @type {!HTMLButtonElement}
346 */
347 get launchAppButtonElement() {
348 return this.querySelector('.launch-app-button');
349 },
350
351 /**
352 * Gets action box area.
353 * @type {!HTMLInputElement}
354 */
355 get actionBoxAreaElement() {
356 return this.querySelector('.action-box-area');
357 },
358
359 /**
360 * Gets user type icon area.
361 * @type {!HTMLDivElement}
362 */
363 get userTypeIconAreaElement() {
364 return this.querySelector('.user-type-icon-area');
365 },
366
367 /**
368 * Gets user type bubble like multi-profiles policy restriction message.
369 * @type {!HTMLDivElement}
370 */
371 get userTypeBubbleElement() {
372 return this.querySelector('.user-type-bubble');
373 },
374
375 /**
376 * Gets action box menu title, user name item.
377 * @type {!HTMLInputElement}
378 */
379 get actionBoxMenuTitleNameElement() {
380 return this.querySelector('.action-box-menu-title-name');
381 },
382
383 /**
384 * Gets action box menu title, user email item.
385 * @type {!HTMLInputElement}
386 */
387 get actionBoxMenuTitleEmailElement() {
388 return this.querySelector('.action-box-menu-title-email');
389 },
390
391 /**
392 * Gets action box menu, remove user command item.
393 * @type {!HTMLInputElement}
394 */
395 get actionBoxMenuCommandElement() {
396 return this.querySelector('.action-box-menu-remove-command');
397 },
398
399 /**
400 * Gets action box menu, remove user command item div.
401 * @type {!HTMLInputElement}
402 */
403 get actionBoxMenuRemoveElement() {
404 return this.querySelector('.action-box-menu-remove');
405 },
406
407 /**
408 * Gets action box menu, remove user warning text div.
409 * @type {!HTMLInputElement}
410 */
411 get actionBoxRemoveUserWarningTextElement() {
412 return this.querySelector('.action-box-remove-user-warning-text');
413 },
414
415 /**
416 * Gets action box menu, remove supervised user warning text div.
417 * @type {!HTMLInputElement}
418 */
419 get actionBoxRemoveSupervisedUserWarningTextElement() {
420 return this.querySelector(
421 '.action-box-remove-supervised-user-warning-text');
422 },
423
424 /**
425 * Gets action box menu, remove user command item div.
426 * @type {!HTMLInputElement}
427 */
428 get actionBoxRemoveUserWarningElement() {
429 return this.querySelector('.action-box-remove-user-warning');
430 },
431
432 /**
433 * Gets action box menu, remove user command item div.
434 * @type {!HTMLInputElement}
435 */
436 get actionBoxRemoveUserWarningButtonElement() {
437 return this.querySelector('.remove-warning-button');
438 },
439
440 /**
441 * Gets the custom icon. This icon is normally hidden, but can be shown
442 * using the chrome.screenlockPrivate API.
443 * @type {!HTMLDivElement}
444 */
445 get customIconElement() {
446 return this.querySelector('.custom-icon');
447 },
448
449 /**
450 * Updates the user pod element.
451 */
452 update: function() {
453 this.imageElement.src = 'chrome://userimage/' + this.user.username +
454 '?id=' + UserPod.userImageSalt_[this.user.username];
455
456 this.nameElement.textContent = this.user_.displayName;
457 this.classList.toggle('signed-in', this.user_.signedIn);
458
459 if (this.isAuthTypeUserClick)
460 this.passwordLabelElement.textContent = this.authValue;
461
462 this.updateActionBoxArea();
463
464 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
465 'passwordFieldAccessibleName', this.user_.emailAddress));
466
467 this.customizeUserPodPerUserType();
468 },
469
470 updateActionBoxArea: function() {
471 if (this.user_.publicAccount || this.user_.isApp) {
472 this.actionBoxAreaElement.hidden = true;
473 return;
474 }
475
476 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
477
478 this.actionBoxAreaElement.setAttribute(
479 'aria-label', loadTimeData.getStringF(
480 'podMenuButtonAccessibleName', this.user_.emailAddress));
481 this.actionBoxMenuRemoveElement.setAttribute(
482 'aria-label', loadTimeData.getString(
483 'podMenuRemoveItemAccessibleName'));
484 this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ?
485 loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) :
486 this.user_.displayName;
487 this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress;
488 this.actionBoxMenuTitleEmailElement.hidden =
489 this.user_.locallyManagedUser;
490
491 this.actionBoxMenuCommandElement.textContent =
492 loadTimeData.getString('removeUser');
493 },
494
495 customizeUserPodPerUserType: function() {
496 if (this.user_.locallyManagedUser && !this.user_.isDesktopUser) {
497 this.setUserPodIconType('supervised');
498 } else if (this.multiProfilesPolicyApplied) {
499 // Mark user pod as not focusable which in addition to the grayed out
500 // filter makes it look in disabled state.
501 this.classList.add('multiprofiles-policy-applied');
502 this.setUserPodIconType('policy');
503
504 if (this.user.multiProfilesPolicy == 'primary-only')
505 this.querySelector('.mp-policy-primary-only-msg').hidden = false;
506 else if (this.user.multiProfilesPolicy == 'owner-primary-only')
507 this.querySelector('.mp-owner-primary-only-msg').hidden = false;
508 else
509 this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
510 } else if (this.user_.isApp) {
511 this.setUserPodIconType('app');
512 }
513 },
514
515 setUserPodIconType: function(userTypeClass) {
516 this.userTypeIconAreaElement.classList.add(userTypeClass);
517 this.userTypeIconAreaElement.hidden = false;
518 },
519
520 /**
521 * The user that this pod represents.
522 * @type {!Object}
523 */
524 user_: undefined,
525 get user() {
526 return this.user_;
527 },
528 set user(userDict) {
529 this.user_ = userDict;
530 this.update();
531 },
532
533 /**
534 * Returns true if multi-profiles sign in is currently active and this
535 * user pod is restricted per policy.
536 * @type {boolean}
537 */
538 get multiProfilesPolicyApplied() {
539 var isMultiProfilesUI =
540 (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
541 return isMultiProfilesUI && !this.user_.isMultiProfilesAllowed;
542 },
543
544 /**
545 * Gets main input element.
546 * @type {(HTMLButtonElement|HTMLInputElement)}
547 */
548 get mainInput() {
549 if (this.isAuthTypePassword) {
550 return this.passwordElement;
551 } else if (this.isAuthTypeOnlineSignIn) {
552 return this.signinButtonElement;
553 } else if (this.isAuthTypeUserClick) {
554 return this;
555 }
556 },
557
558 /**
559 * Whether action box button is in active state.
560 * @type {boolean}
561 */
562 get isActionBoxMenuActive() {
563 return this.actionBoxAreaElement.classList.contains('active');
564 },
565 set isActionBoxMenuActive(active) {
566 if (active == this.isActionBoxMenuActive)
567 return;
568
569 if (active) {
570 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
571 this.actionBoxRemoveUserWarningElement.hidden = true;
572
573 // Clear focus first if another pod is focused.
574 if (!this.parentNode.isFocused(this)) {
575 this.parentNode.focusPod(undefined, true);
576 this.actionBoxAreaElement.focus();
577 }
578
579 // Hide user-type-bubble.
580 this.userTypeBubbleElement.classList.remove('bubble-shown');
581
582 this.actionBoxAreaElement.classList.add('active');
583 } else {
584 this.actionBoxAreaElement.classList.remove('active');
585 }
586 },
587
588 /**
589 * Whether action box button is in hovered state.
590 * @type {boolean}
591 */
592 get isActionBoxMenuHovered() {
593 return this.actionBoxAreaElement.classList.contains('hovered');
594 },
595 set isActionBoxMenuHovered(hovered) {
596 if (hovered == this.isActionBoxMenuHovered)
597 return;
598
599 if (hovered) {
600 this.actionBoxAreaElement.classList.add('hovered');
601 this.classList.add('hovered');
602 } else {
603 if (this.multiProfilesPolicyApplied)
604 this.userTypeBubbleElement.classList.remove('bubble-shown');
605 this.actionBoxAreaElement.classList.remove('hovered');
606 this.classList.remove('hovered');
607 }
608 },
609
610 /**
611 * Set the authentication type for the pod.
612 * @param {number} An auth type value defined in the AUTH_TYPE enum.
613 * @param {string} authValue The initial value used for the auth type.
614 */
615 setAuthType: function(authType, authValue) {
616 this.authType_ = authType;
617 this.authValue_ = authValue;
618 this.setAttribute('auth-type', AUTH_TYPE_NAMES[this.authType_]);
619 this.update();
620 this.reset(this.parentNode.isFocused(this));
621 },
622
623 /**
624 * The auth type of the user pod. This value is one of the enum
625 * values in AUTH_TYPE.
626 * @type {number}
627 */
628 get authType() {
629 return this.authType_;
630 },
631
632 /**
633 * The initial value used for the pod's authentication type.
634 * eg. a prepopulated password input when using password authentication.
635 */
636 get authValue() {
637 return this.authValue_;
638 },
639
640 /**
641 * True if the the user pod uses a password to authenticate.
642 * @type {bool}
643 */
644 get isAuthTypePassword() {
645 return this.authType_ == AUTH_TYPE.OFFLINE_PASSWORD;
646 },
647
648 /**
649 * True if the the user pod uses a user click to authenticate.
650 * @type {bool}
651 */
652 get isAuthTypeUserClick() {
653 return this.authType_ == AUTH_TYPE.USER_CLICK;
654 },
655
656 /**
657 * True if the the user pod uses a online sign in to authenticate.
658 * @type {bool}
659 */
660 get isAuthTypeOnlineSignIn() {
661 return this.authType_ == AUTH_TYPE.ONLINE_SIGN_IN;
662 },
663
664 /**
665 * Updates the image element of the user.
666 */
667 updateUserImage: function() {
668 UserPod.userImageSalt_[this.user.username] = new Date().getTime();
669 this.update();
670 },
671
672 /**
673 * Focuses on input element.
674 */
675 focusInput: function() {
676 // Move tabIndex from the whole pod to the main input.
677 // Note: the |mainInput| can be the pod itself.
678 this.tabIndex = -1;
679 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
680 this.mainInput.focus();
681 },
682
683 /**
684 * Activates the pod.
685 * @param {Event} e Event object.
686 * @return {boolean} True if activated successfully.
687 */
688 activate: function(e) {
689 if (this.isAuthTypeOnlineSignIn) {
690 this.showSigninUI();
691 } else if (this.isAuthTypeUserClick) {
692 Oobe.disableSigninUI();
693 chrome.send('attemptUnlock', [this.user.username]);
694 } else if (this.isAuthTypePassword) {
695 if (!this.passwordElement.value)
696 return false;
697 Oobe.disableSigninUI();
698 chrome.send('authenticateUser',
699 [this.user.username, this.passwordElement.value]);
700 } else {
701 console.error('Activating user pod with invalid authentication type: ' +
702 this.authType);
703 }
704
705 return true;
706 },
707
708 showSupervisedUserSigninWarning: function() {
709 // Locally managed user token has been invalidated.
710 // Make sure that pod is focused i.e. "Sign in" button is seen.
711 this.parentNode.focusPod(this);
712
713 var error = document.createElement('div');
714 var messageDiv = document.createElement('div');
715 messageDiv.className = 'error-message-bubble';
716 messageDiv.textContent =
717 loadTimeData.getString('supervisedUserExpiredTokenWarning');
718 error.appendChild(messageDiv);
719
720 $('bubble').showContentForElement(
721 this.signinButtonElement,
722 cr.ui.Bubble.Attachment.TOP,
723 error,
724 this.signinButtonElement.offsetWidth / 2,
725 4);
726 },
727
728 /**
729 * Shows signin UI for this user.
730 */
731 showSigninUI: function() {
732 if (this.user.locallyManagedUser && !this.user.isDesktopUser) {
733 this.showSupervisedUserSigninWarning();
734 } else {
735 // Special case for multi-profiles sign in. We show users even if they
736 // are not allowed per policy. Restrict those users from starting GAIA.
737 if (this.multiProfilesPolicyApplied)
738 return;
739
740 this.parentNode.showSigninUI(this.user.emailAddress);
741 }
742 },
743
744 /**
745 * Resets the input field and updates the tab order of pod controls.
746 * @param {boolean} takeFocus If true, input field takes focus.
747 */
748 reset: function(takeFocus) {
749 this.passwordElement.value = '';
750 if (takeFocus) {
751 if (!this.multiProfilesPolicyApplied)
752 this.focusInput(); // This will set a custom tab order.
753 }
754 else
755 this.resetTabOrder();
756 },
757
758 /**
759 * Removes a user using the correct identifier based on user type.
760 * @param {Object} user User to be removed.
761 */
762 removeUser: function(user) {
763 chrome.send('removeUser',
764 [user.isDesktopUser ? user.profilePath : user.username]);
765 },
766
767 /**
768 * Handles a click event on action area button.
769 * @param {Event} e Click event.
770 */
771 handleActionAreaButtonClick_: function(e) {
772 if (this.parentNode.disabled)
773 return;
774 this.isActionBoxMenuActive = !this.isActionBoxMenuActive;
775 e.stopPropagation();
776 },
777
778 /**
779 * Handles a keydown event on action area button.
780 * @param {Event} e KeyDown event.
781 */
782 handleActionAreaButtonKeyDown_: function(e) {
783 if (this.disabled)
784 return;
785 switch (e.keyIdentifier) {
786 case 'Enter':
787 case 'U+0020': // Space
788 if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive)
789 this.isActionBoxMenuActive = true;
790 e.stopPropagation();
791 break;
792 case 'Up':
793 case 'Down':
794 if (this.isActionBoxMenuActive) {
795 this.actionBoxMenuRemoveElement.tabIndex =
796 UserPodTabOrder.PAD_MENU_ITEM;
797 this.actionBoxMenuRemoveElement.focus();
798 }
799 e.stopPropagation();
800 break;
801 case 'U+001B': // Esc
802 this.isActionBoxMenuActive = false;
803 e.stopPropagation();
804 break;
805 case 'U+0009': // Tab
806 if (!this.parentNode.alwaysFocusSinglePod)
807 this.parentNode.focusPod();
808 default:
809 this.isActionBoxMenuActive = false;
810 break;
811 }
812 },
813
814 /**
815 * Handles a click event on remove user command.
816 * @param {Event} e Click event.
817 */
818 handleRemoveCommandClick_: function(e) {
819 if (this.user.locallyManagedUser || this.user.isDesktopUser) {
820 this.showRemoveWarning_();
821 return;
822 }
823 if (this.isActionBoxMenuActive)
824 chrome.send('removeUser', [this.user.username]);
825 },
826
827 /**
828 * Shows remove user warning. Used for supervised users on CrOS, and for all
829 * users on desktop.
830 */
831 showRemoveWarning_: function() {
832 this.actionBoxMenuRemoveElement.hidden = true;
833 this.actionBoxRemoveUserWarningElement.hidden = false;
834 this.actionBoxRemoveUserWarningButtonElement.focus();
835 },
836
837 /**
838 * Handles a click event on remove user confirmation button.
839 * @param {Event} e Click event.
840 */
841 handleRemoveUserConfirmationClick_: function(e) {
842 if (this.isActionBoxMenuActive) {
843 this.isActionBoxMenuActive = false;
844 this.removeUser(this.user);
845 e.stopPropagation();
846 }
847 },
848
849 /**
850 * Handles a keydown event on remove user confirmation button.
851 * @param {Event} e KeyDown event.
852 */
853 handleRemoveUserConfirmationKeyDown_: function(e) {
854 if (!this.isActionBoxMenuActive)
855 return;
856
857 // Only handle pressing 'Enter' or 'Space', and let all other events
858 // bubble to the action box menu.
859 if (e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020') {
860 this.isActionBoxMenuActive = false;
861 this.removeUser(this.user);
862 e.stopPropagation();
863 // Prevent default so that we don't trigger a 'click' event.
864 e.preventDefault();
865 }
866 },
867
868 /**
869 * Handles a keydown event on remove command.
870 * @param {Event} e KeyDown event.
871 */
872 handleRemoveCommandKeyDown_: function(e) {
873 if (this.disabled)
874 return;
875 switch (e.keyIdentifier) {
876 case 'Enter':
877 if (this.user.locallyManagedUser || this.user.isDesktopUser) {
878 // Prevent default so that we don't trigger a 'click' event on the
879 // remove button that will be focused.
880 e.preventDefault();
881 this.showRemoveWarning_();
882 } else {
883 this.removeUser(this.user);
884 }
885 e.stopPropagation();
886 break;
887 case 'Up':
888 case 'Down':
889 e.stopPropagation();
890 break;
891 case 'U+001B': // Esc
892 this.actionBoxAreaElement.focus();
893 this.isActionBoxMenuActive = false;
894 e.stopPropagation();
895 break;
896 default:
897 this.actionBoxAreaElement.focus();
898 this.isActionBoxMenuActive = false;
899 break;
900 }
901 },
902
903 /**
904 * Handles a blur event on remove command.
905 * @param {Event} e Blur event.
906 */
907 handleRemoveCommandBlur_: function(e) {
908 if (this.disabled)
909 return;
910 this.actionBoxMenuRemoveElement.tabIndex = -1;
911 },
912
913 /**
914 * Handles click event on a user pod.
915 * @param {Event} e Click event.
916 */
917 handleClickOnPod_: function(e) {
918 if (this.parentNode.disabled)
919 return;
920
921 if (!this.isActionBoxMenuActive) {
922 if (this.isAuthTypeOnlineSignIn) {
923 this.showSigninUI();
924 } else if (this.isAuthTypeUserClick) {
925 this.parentNode.setActivatedPod(this);
926 }
927
928 if (this.multiProfilesPolicyApplied)
929 this.userTypeBubbleElement.classList.add('bubble-shown');
930
931 // Prevent default so that we don't trigger 'focus' event.
932 e.preventDefault();
933 }
934 },
935
936 /**
937 * Handles keydown event for a user pod.
938 * @param {Event} e Key event.
939 */
940 handlePodKeyDown_: function(e) {
941 if (!this.isAuthTypeUserClick || this.disabled)
942 return;
943 switch (e.keyIdentifier) {
944 case 'Enter':
945 case 'U+0020': // Space
946 if (this.parentNode.isFocused(this))
947 this.parentNode.setActivatedPod(this);
948 break;
949 }
950 }
951 };
952
953 /**
954 * Creates a public account user pod.
955 * @constructor
956 * @extends {UserPod}
957 */
958 var PublicAccountUserPod = cr.ui.define(function() {
959 var node = UserPod();
960
961 var extras = $('public-account-user-pod-extras-template').children;
962 for (var i = 0; i < extras.length; ++i) {
963 var el = extras[i].cloneNode(true);
964 node.appendChild(el);
965 }
966
967 return node;
968 });
969
970 PublicAccountUserPod.prototype = {
971 __proto__: UserPod.prototype,
972
973 /**
974 * "Enter" button in expanded side pane.
975 * @type {!HTMLButtonElement}
976 */
977 get enterButtonElement() {
978 return this.querySelector('.enter-button');
979 },
980
981 /**
982 * Boolean flag of whether the pod is showing the side pane. The flag
983 * controls whether 'expanded' class is added to the pod's class list and
984 * resets tab order because main input element changes when the 'expanded'
985 * state changes.
986 * @type {boolean}
987 */
988 get expanded() {
989 return this.classList.contains('expanded');
990 },
991
992 set expanded(expanded) {
993 if (this.expanded == expanded)
994 return;
995
996 this.resetTabOrder();
997 this.classList.toggle('expanded', expanded);
998 if (expanded) {
999 var isDesktopUserManager = Oobe.getInstance().displayType ==
1000 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1001 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
1002 POD_ROW_PADDING;
1003 this.usualLeft = this.left;
1004 this.usualTop = this.top;
1005 if (this.left + PUBLIC_EXPANDED_BASIC_WIDTH >
1006 $('pod-row').offsetWidth - rowPadding)
1007 this.left = $('pod-row').offsetWidth - rowPadding -
1008 PUBLIC_EXPANDED_BASIC_WIDTH;
1009 } else {
1010 if (typeof(this.usualLeft) != 'undefined')
1011 this.left = this.usualLeft;
1012 if (typeof(this.usualTop) != 'undefined')
1013 this.top = this.usualTop;
1014 }
1015
1016 var self = this;
1017 this.classList.add('animating');
1018 this.addEventListener('webkitTransitionEnd', function f(e) {
1019 self.removeEventListener('webkitTransitionEnd', f);
1020 self.classList.remove('animating');
1021
1022 // Accessibility focus indicator does not move with the focused
1023 // element. Sends a 'focus' event on the currently focused element
1024 // so that accessibility focus indicator updates its location.
1025 if (document.activeElement)
1026 document.activeElement.dispatchEvent(new Event('focus'));
1027 });
1028 },
1029
1030 /** @override */
1031 get mainInput() {
1032 if (this.expanded)
1033 return this.enterButtonElement;
1034 else
1035 return this.nameElement;
1036 },
1037
1038 /** @override */
1039 decorate: function() {
1040 UserPod.prototype.decorate.call(this);
1041
1042 this.classList.add('public-account');
1043
1044 this.nameElement.addEventListener('keydown', (function(e) {
1045 if (e.keyIdentifier == 'Enter') {
1046 this.parentNode.setActivatedPod(this, e);
1047 // Stop this keydown event from bubbling up to PodRow handler.
1048 e.stopPropagation();
1049 // Prevent default so that we don't trigger a 'click' event on the
1050 // newly focused "Enter" button.
1051 e.preventDefault();
1052 }
1053 }).bind(this));
1054
1055 var learnMore = this.querySelector('.learn-more');
1056 learnMore.addEventListener('mousedown', stopEventPropagation);
1057 learnMore.addEventListener('click', this.handleLearnMoreEvent);
1058 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
1059
1060 learnMore = this.querySelector('.expanded-pane-learn-more');
1061 learnMore.addEventListener('click', this.handleLearnMoreEvent);
1062 learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
1063
1064 this.enterButtonElement.addEventListener('click', (function(e) {
1065 this.enterButtonElement.disabled = true;
1066 chrome.send('launchPublicAccount', [this.user.username]);
1067 }).bind(this));
1068 },
1069
1070 /** @override **/
1071 update: function() {
1072 UserPod.prototype.update.call(this);
1073 this.querySelector('.expanded-pane-name').textContent =
1074 this.user_.displayName;
1075 this.querySelector('.info').textContent =
1076 loadTimeData.getStringF('publicAccountInfoFormat',
1077 this.user_.enterpriseDomain);
1078 },
1079
1080 /** @override */
1081 focusInput: function() {
1082 // Move tabIndex from the whole pod to the main input.
1083 this.tabIndex = -1;
1084 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1085 this.mainInput.focus();
1086 },
1087
1088 /** @override */
1089 reset: function(takeFocus) {
1090 if (!takeFocus)
1091 this.expanded = false;
1092 this.enterButtonElement.disabled = false;
1093 UserPod.prototype.reset.call(this, takeFocus);
1094 },
1095
1096 /** @override */
1097 activate: function(e) {
1098 this.expanded = true;
1099 this.focusInput();
1100 return true;
1101 },
1102
1103 /** @override */
1104 handleClickOnPod_: function(e) {
1105 if (this.parentNode.disabled)
1106 return;
1107
1108 this.parentNode.focusPod(this);
1109 this.parentNode.setActivatedPod(this, e);
1110 // Prevent default so that we don't trigger 'focus' event.
1111 e.preventDefault();
1112 },
1113
1114 /**
1115 * Handle mouse and keyboard events for the learn more button. Triggering
1116 * the button causes information about public sessions to be shown.
1117 * @param {Event} event Mouse or keyboard event.
1118 */
1119 handleLearnMoreEvent: function(event) {
1120 switch (event.type) {
1121 // Show informaton on left click. Let any other clicks propagate.
1122 case 'click':
1123 if (event.button != 0)
1124 return;
1125 break;
1126 // Show informaton when <Return> or <Space> is pressed. Let any other
1127 // key presses propagate.
1128 case 'keydown':
1129 switch (event.keyCode) {
1130 case 13: // Return.
1131 case 32: // Space.
1132 break;
1133 default:
1134 return;
1135 }
1136 break;
1137 }
1138 chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]);
1139 stopEventPropagation(event);
1140 },
1141 };
1142
1143 /**
1144 * Creates a user pod to be used only in desktop chrome.
1145 * @constructor
1146 * @extends {UserPod}
1147 */
1148 var DesktopUserPod = cr.ui.define(function() {
1149 // Don't just instantiate a UserPod(), as this will call decorate() on the
1150 // parent object, and add duplicate event listeners.
1151 var node = $('user-pod-template').cloneNode(true);
1152 node.removeAttribute('id');
1153 return node;
1154 });
1155
1156 DesktopUserPod.prototype = {
1157 __proto__: UserPod.prototype,
1158
1159 /** @override */
1160 get mainInput() {
1161 if (this.user.needsSignin)
1162 return this.passwordElement;
1163 else
1164 return this.nameElement;
1165 },
1166
1167 /** @override */
1168 update: function() {
1169 this.imageElement.src = this.user.userImage;
1170 this.nameElement.textContent = this.user.displayName;
1171
1172 var isLockedUser = this.user.needsSignin;
1173 var isSupervisedUser = this.user.locallyManagedUser;
1174 this.classList.toggle('locked', isLockedUser);
1175 this.classList.toggle('supervised-user', isSupervisedUser);
1176
1177 if (this.isAuthTypeUserClick)
1178 this.passwordLabelElement.textContent = this.authValue;
1179
1180 this.actionBoxRemoveUserWarningTextElement.hidden = isSupervisedUser;
1181 this.actionBoxRemoveSupervisedUserWarningTextElement.hidden =
1182 !isSupervisedUser;
1183
1184 UserPod.prototype.updateActionBoxArea.call(this);
1185 },
1186
1187 /** @override */
1188 focusInput: function() {
1189 // Move tabIndex from the whole pod to the main input.
1190 this.tabIndex = -1;
1191 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1192 this.mainInput.focus();
1193 },
1194
1195 /** @override */
1196 activate: function(e) {
1197 if (!this.user.needsSignin) {
1198 Oobe.launchUser(this.user.emailAddress, this.user.displayName);
1199 } else if (!this.passwordElement.value) {
1200 return false;
1201 } else {
1202 chrome.send('authenticatedLaunchUser',
1203 [this.user.emailAddress,
1204 this.user.displayName,
1205 this.passwordElement.value]);
1206 }
1207 this.passwordElement.value = '';
1208 return true;
1209 },
1210
1211 /** @override */
1212 handleClickOnPod_: function(e) {
1213 if (this.parentNode.disabled)
1214 return;
1215
1216 Oobe.clearErrors();
1217 this.parentNode.lastFocusedPod_ = this;
1218
1219 // If this is an unlocked pod, then open a browser window. Otherwise
1220 // just activate the pod and show the password field.
1221 if (!this.user.needsSignin && !this.isActionBoxMenuActive)
1222 this.activate(e);
1223
1224 if (this.isAuthTypeUserClick)
1225 chrome.send('attemptUnlock', [this.user.emailAddress]);
1226 },
1227 };
1228
1229 /**
1230 * Creates a user pod that represents kiosk app.
1231 * @constructor
1232 * @extends {UserPod}
1233 */
1234 var KioskAppPod = cr.ui.define(function() {
1235 var node = UserPod();
1236 return node;
1237 });
1238
1239 KioskAppPod.prototype = {
1240 __proto__: UserPod.prototype,
1241
1242 /** @override */
1243 decorate: function() {
1244 UserPod.prototype.decorate.call(this);
1245 this.launchAppButtonElement.addEventListener('click',
1246 this.activate.bind(this));
1247 },
1248
1249 /** @override */
1250 update: function() {
1251 this.imageElement.src = this.user.iconUrl;
1252 this.imageElement.alt = this.user.label;
1253 this.imageElement.title = this.user.label;
1254 this.passwordEntryContainerElement.hidden = true;
1255 this.launchAppButtonContainerElement.hidden = false;
1256 this.nameElement.textContent = this.user.label;
1257
1258 UserPod.prototype.updateActionBoxArea.call(this);
1259 UserPod.prototype.customizeUserPodPerUserType.call(this);
1260 },
1261
1262 /** @override */
1263 get mainInput() {
1264 return this.launchAppButtonElement;
1265 },
1266
1267 /** @override */
1268 focusInput: function() {
1269 // Move tabIndex from the whole pod to the main input.
1270 this.tabIndex = -1;
1271 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
1272 this.mainInput.focus();
1273 },
1274
1275 /** @override */
1276 get forceOnlineSignin() {
1277 return false;
1278 },
1279
1280 /** @override */
1281 activate: function(e) {
1282 var diagnosticMode = e && e.ctrlKey;
1283 this.launchApp_(this.user, diagnosticMode);
1284 return true;
1285 },
1286
1287 /** @override */
1288 handleClickOnPod_: function(e) {
1289 if (this.parentNode.disabled)
1290 return;
1291
1292 Oobe.clearErrors();
1293 this.parentNode.lastFocusedPod_ = this;
1294 this.activate(e);
1295 },
1296
1297 /**
1298 * Launch the app. If |diagnosticMode| is true, ask user to confirm.
1299 * @param {Object} app App data.
1300 * @param {boolean} diagnosticMode Whether to run the app in diagnostic
1301 * mode.
1302 */
1303 launchApp_: function(app, diagnosticMode) {
1304 if (!diagnosticMode) {
1305 chrome.send('launchKioskApp', [app.id, false]);
1306 return;
1307 }
1308
1309 var oobe = $('oobe');
1310 if (!oobe.confirmDiagnosticMode_) {
1311 oobe.confirmDiagnosticMode_ =
1312 new cr.ui.dialogs.ConfirmDialog(document.body);
1313 oobe.confirmDiagnosticMode_.setOkLabel(
1314 loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
1315 oobe.confirmDiagnosticMode_.setCancelLabel(
1316 loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
1317 }
1318
1319 oobe.confirmDiagnosticMode_.show(
1320 loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
1321 app.label),
1322 function() {
1323 chrome.send('launchKioskApp', [app.id, true]);
1324 });
1325 },
1326 };
1327
1328 /**
1329 * Creates a new pod row element.
1330 * @constructor
1331 * @extends {HTMLDivElement}
1332 */
1333 var PodRow = cr.ui.define('podrow');
1334
1335 PodRow.prototype = {
1336 __proto__: HTMLDivElement.prototype,
1337
1338 // Whether this user pod row is shown for the first time.
1339 firstShown_: true,
1340
1341 // True if inside focusPod().
1342 insideFocusPod_: false,
1343
1344 // Focused pod.
1345 focusedPod_: undefined,
1346
1347 // Activated pod, i.e. the pod of current login attempt.
1348 activatedPod_: undefined,
1349
1350 // Pod that was most recently focused, if any.
1351 lastFocusedPod_: undefined,
1352
1353 // Pods whose initial images haven't been loaded yet.
1354 podsWithPendingImages_: [],
1355
1356 // Whether pod placement has been postponed.
1357 podPlacementPostponed_: false,
1358
1359 // Standard user pod height/width.
1360 userPodHeight_: 0,
1361 userPodWidth_: 0,
1362
1363 // Array of apps that are shown in addition to other user pods.
1364 apps_: [],
1365
1366 // True to show app pods along with user pods.
1367 shouldShowApps_: true,
1368
1369 // Array of users that are shown (public/supervised/regular).
1370 users_: [],
1371
1372 /** @override */
1373 decorate: function() {
1374 // Event listeners that are installed for the time period during which
1375 // the element is visible.
1376 this.listeners_ = {
1377 focus: [this.handleFocus_.bind(this), true /* useCapture */],
1378 click: [this.handleClick_.bind(this), true],
1379 mousemove: [this.handleMouseMove_.bind(this), false],
1380 keydown: [this.handleKeyDown.bind(this), false]
1381 };
1382
1383 var isDesktopUserManager = Oobe.getInstance().displayType ==
1384 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1385 this.userPodHeight_ = isDesktopUserManager ? DESKTOP_POD_HEIGHT :
1386 CROS_POD_HEIGHT;
1387 // Same for Chrome OS and desktop.
1388 this.userPodWidth_ = POD_WIDTH;
1389 },
1390
1391 /**
1392 * Returns all the pods in this pod row.
1393 * @type {NodeList}
1394 */
1395 get pods() {
1396 return Array.prototype.slice.call(this.children);
1397 },
1398
1399 /**
1400 * Return true if user pod row has only single user pod in it, which should
1401 * always be focused.
1402 * @type {boolean}
1403 */
1404 get alwaysFocusSinglePod() {
1405 var isDesktopUserManager = Oobe.getInstance().displayType ==
1406 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1407
1408 return isDesktopUserManager ? false : this.children.length == 1;
1409 },
1410
1411 /**
1412 * Returns pod with the given app id.
1413 * @param {!string} app_id Application id to be matched.
1414 * @return {Object} Pod with the given app id. null if pod hasn't been
1415 * found.
1416 */
1417 getPodWithAppId_: function(app_id) {
1418 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1419 if (pod.user.isApp && pod.user.id == app_id)
1420 return pod;
1421 }
1422 return null;
1423 },
1424
1425 /**
1426 * Returns pod with the given username (null if there is no such pod).
1427 * @param {string} username Username to be matched.
1428 * @return {Object} Pod with the given username. null if pod hasn't been
1429 * found.
1430 */
1431 getPodWithUsername_: function(username) {
1432 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1433 if (pod.user.username == username)
1434 return pod;
1435 }
1436 return null;
1437 },
1438
1439 /**
1440 * True if the the pod row is disabled (handles no user interaction).
1441 * @type {boolean}
1442 */
1443 disabled_: false,
1444 get disabled() {
1445 return this.disabled_;
1446 },
1447 set disabled(value) {
1448 this.disabled_ = value;
1449 var controls = this.querySelectorAll('button,input');
1450 for (var i = 0, control; control = controls[i]; ++i) {
1451 control.disabled = value;
1452 }
1453 },
1454
1455 /**
1456 * Creates a user pod from given email.
1457 * @param {!Object} user User info dictionary.
1458 */
1459 createUserPod: function(user) {
1460 var userPod;
1461 if (user.isDesktopUser)
1462 userPod = new DesktopUserPod({user: user});
1463 else if (user.publicAccount)
1464 userPod = new PublicAccountUserPod({user: user});
1465 else if (user.isApp)
1466 userPod = new KioskAppPod({user: user});
1467 else
1468 userPod = new UserPod({user: user});
1469
1470 userPod.hidden = false;
1471 return userPod;
1472 },
1473
1474 /**
1475 * Add an existing user pod to this pod row.
1476 * @param {!Object} user User info dictionary.
1477 */
1478 addUserPod: function(user) {
1479 var userPod = this.createUserPod(user);
1480 this.appendChild(userPod);
1481 userPod.initialize();
1482 },
1483
1484 /**
1485 * Runs app with a given id from the list of loaded apps.
1486 * @param {!string} app_id of an app to run.
1487 * @param {boolean=} opt_diagnostic_mode Whether to run the app in
1488 * diagnostic mode. Default is false.
1489 */
1490 findAndRunAppForTesting: function(app_id, opt_diagnostic_mode) {
1491 var app = this.getPodWithAppId_(app_id);
1492 if (app) {
1493 var activationEvent = cr.doc.createEvent('MouseEvents');
1494 var ctrlKey = opt_diagnostic_mode;
1495 activationEvent.initMouseEvent('click', true, true, null,
1496 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
1497 app.dispatchEvent(activationEvent);
1498 }
1499 },
1500
1501 /**
1502 * Removes user pod from pod row.
1503 * @param {string} email User's email.
1504 */
1505 removeUserPod: function(username) {
1506 var podToRemove = this.getPodWithUsername_(username);
1507 if (podToRemove == null) {
1508 console.warn('Attempt to remove not existing pod for ' + username +
1509 '.');
1510 return;
1511 }
1512 this.removeChild(podToRemove);
1513 if (this.pods.length > 0)
1514 this.placePods_();
1515 },
1516
1517 /**
1518 * Returns index of given pod or -1 if not found.
1519 * @param {UserPod} pod Pod to look up.
1520 * @private
1521 */
1522 indexOf_: function(pod) {
1523 for (var i = 0; i < this.pods.length; ++i) {
1524 if (pod == this.pods[i])
1525 return i;
1526 }
1527 return -1;
1528 },
1529
1530 /**
1531 * Populates pod row with given existing users and start init animation.
1532 * @param {array} users Array of existing user emails.
1533 */
1534 loadPods: function(users) {
1535 this.users_ = users;
1536
1537 this.rebuildPods();
1538 },
1539
1540 /**
1541 * Scrolls focused user pod into view.
1542 */
1543 scrollFocusedPodIntoView: function() {
1544 var pod = this.focusedPod_;
1545 if (!pod)
1546 return;
1547
1548 // First check whether focused pod is already fully visible.
1549 var visibleArea = $('scroll-container');
1550 var scrollTop = visibleArea.scrollTop;
1551 var clientHeight = visibleArea.clientHeight;
1552 var podTop = $('oobe').offsetTop + pod.offsetTop;
1553 var padding = USER_POD_KEYBOARD_MIN_PADDING;
1554 if (podTop + pod.height + padding <= scrollTop + clientHeight &&
1555 podTop - padding >= scrollTop) {
1556 return;
1557 }
1558
1559 // Scroll so that user pod is as centered as possible.
1560 visibleArea.scrollTop = podTop - (clientHeight - pod.offsetHeight) / 2;
1561 },
1562
1563 /**
1564 * Rebuilds pod row using users_ and apps_ that were previously set or
1565 * updated.
1566 */
1567 rebuildPods: function() {
1568 var emptyPodRow = this.pods.length == 0;
1569
1570 // Clear existing pods.
1571 this.innerHTML = '';
1572 this.focusedPod_ = undefined;
1573 this.activatedPod_ = undefined;
1574 this.lastFocusedPod_ = undefined;
1575
1576 // Switch off animation
1577 Oobe.getInstance().toggleClass('flying-pods', false);
1578
1579 // Populate the pod row.
1580 for (var i = 0; i < this.users_.length; ++i)
1581 this.addUserPod(this.users_[i]);
1582
1583 for (var i = 0, pod; pod = this.pods[i]; ++i)
1584 this.podsWithPendingImages_.push(pod);
1585
1586 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
1587 if (this.shouldShowApps_) {
1588 for (var i = 0; i < this.apps_.length; ++i)
1589 this.addUserPod(this.apps_[i]);
1590 }
1591
1592 // Make sure we eventually show the pod row, even if some image is stuck.
1593 setTimeout(function() {
1594 $('pod-row').classList.remove('images-loading');
1595 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS);
1596
1597 var isCrosAccountPicker = $('login-header-bar').signinUIState ==
1598 SIGNIN_UI_STATE.ACCOUNT_PICKER;
1599 var isDesktopUserManager = Oobe.getInstance().displayType ==
1600 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1601
1602 // Chrome OS: immediately recalculate pods layout only when current UI
1603 // is account picker. Otherwise postpone it.
1604 // Desktop: recalculate pods layout right away.
1605 if (isDesktopUserManager || isCrosAccountPicker) {
1606 this.placePods_();
1607
1608 // Without timeout changes in pods positions will be animated even
1609 // though it happened when 'flying-pods' class was disabled.
1610 setTimeout(function() {
1611 Oobe.getInstance().toggleClass('flying-pods', true);
1612 }, 0);
1613
1614 // On desktop, don't pre-select a pod if it's the only one.
1615 if (isDesktopUserManager && this.pods.length == 1)
1616 this.focusPod();
1617 else
1618 this.focusPod(this.preselectedPod);
1619 } else {
1620 this.podPlacementPostponed_ = true;
1621
1622 // Update [Cancel] button state.
1623 if ($('login-header-bar').signinUIState ==
1624 SIGNIN_UI_STATE.GAIA_SIGNIN &&
1625 emptyPodRow &&
1626 this.pods.length > 0) {
1627 login.GaiaSigninScreen.updateCancelButtonState();
1628 }
1629 }
1630 },
1631
1632 /**
1633 * Adds given apps to the pod row.
1634 * @param {array} apps Array of apps.
1635 */
1636 setApps: function(apps) {
1637 this.apps_ = apps;
1638 this.rebuildPods();
1639 chrome.send('kioskAppsLoaded');
1640
1641 // Check whether there's a pending kiosk app error.
1642 window.setTimeout(function() {
1643 chrome.send('checkKioskAppLaunchError');
1644 }, 500);
1645 },
1646
1647 /**
1648 * Sets whether should show app pods.
1649 * @param {boolean} shouldShowApps Whether app pods should be shown.
1650 */
1651 setShouldShowApps: function(shouldShowApps) {
1652 if (this.shouldShowApps_ == shouldShowApps)
1653 return;
1654
1655 this.shouldShowApps_ = shouldShowApps;
1656 this.rebuildPods();
1657 },
1658
1659 /**
1660 * Shows a custom icon on a user pod besides the input field.
1661 * @param {string} username Username of pod to add button
1662 * @param {{scale1x: string, scale2x: string}} icon Dictionary of URLs of
1663 * the custom icon's representations for 1x and 2x scale factors.
1664 */
1665 showUserPodCustomIcon: function(username, icon) {
1666 var pod = this.getPodWithUsername_(username);
1667 if (pod == null) {
1668 console.error('Unable to show user pod button for ' + username +
1669 ': user pod not found.');
1670 return;
1671 }
1672
1673 pod.customIconElement.hidden = false;
1674 pod.customIconElement.style.backgroundImage =
1675 '-webkit-image-set(' +
1676 'url(' + icon.scale1x + ') 1x,' +
1677 'url(' + icon.scale2x + ') 2x)';
1678 },
1679
1680 /**
1681 * Hides the custom icon in the user pod added by showUserPodCustomIcon().
1682 * @param {string} username Username of pod to remove button
1683 */
1684 hideUserPodCustomIcon: function(username) {
1685 var pod = this.getPodWithUsername_(username);
1686 if (pod == null) {
1687 console.error('Unable to hide user pod button for ' + username +
1688 ': user pod not found.');
1689 return;
1690 }
1691
1692 pod.customIconElement.hidden = true;
1693 },
1694
1695 /**
1696 * Sets the authentication type used to authenticate the user.
1697 * @param {string} username Username of selected user
1698 * @param {number} authType Authentication type, must be one of the
1699 * values listed in AUTH_TYPE enum.
1700 * @param {string} value The initial value to use for authentication.
1701 */
1702 setAuthType: function(username, authType, value) {
1703 var pod = this.getPodWithUsername_(username);
1704 if (pod == null) {
1705 console.error('Unable to set auth type for ' + username +
1706 ': user pod not found.');
1707 return;
1708 }
1709 pod.setAuthType(authType, value);
1710 },
1711
1712 /**
1713 * Shows a tooltip bubble explaining Easy Unlock for the focused pod.
1714 */
1715 showEasyUnlockBubble: function() {
1716 if (!this.focusedPod_) {
1717 console.error('No focused pod to show Easy Unlock bubble.');
1718 return;
1719 }
1720
1721 var bubbleContent = document.createElement('div');
1722 bubbleContent.classList.add('easy-unlock-button-content');
1723 bubbleContent.textContent = loadTimeData.getString('easyUnlockTooltip');
1724
1725 var attachElement = this.focusedPod_.customIconElement;
1726 /** @const */ var BUBBLE_OFFSET = 20;
1727 /** @const */ var BUBBLE_PADDING = 8;
1728 $('bubble').showContentForElement(attachElement,
1729 cr.ui.Bubble.Attachment.RIGHT,
1730 bubbleContent,
1731 BUBBLE_OFFSET,
1732 BUBBLE_PADDING);
1733 },
1734
1735 /**
1736 * Called when window was resized.
1737 */
1738 onWindowResize: function() {
1739 var layout = this.calculateLayout_();
1740 if (layout.columns != this.columns || layout.rows != this.rows)
1741 this.placePods_();
1742
1743 if (Oobe.getInstance().virtualKeyboardShown)
1744 this.scrollFocusedPodIntoView();
1745 },
1746
1747 /**
1748 * Returns width of podrow having |columns| number of columns.
1749 * @private
1750 */
1751 columnsToWidth_: function(columns) {
1752 var isDesktopUserManager = Oobe.getInstance().displayType ==
1753 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1754 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
1755 MARGIN_BY_COLUMNS[columns];
1756 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
1757 POD_ROW_PADDING;
1758 return 2 * rowPadding + columns * this.userPodWidth_ +
1759 (columns - 1) * margin;
1760 },
1761
1762 /**
1763 * Returns height of podrow having |rows| number of rows.
1764 * @private
1765 */
1766 rowsToHeight_: function(rows) {
1767 var isDesktopUserManager = Oobe.getInstance().displayType ==
1768 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1769 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
1770 POD_ROW_PADDING;
1771 return 2 * rowPadding + rows * this.userPodHeight_;
1772 },
1773
1774 /**
1775 * Calculates number of columns and rows that podrow should have in order to
1776 * hold as much its pods as possible for current screen size. Also it tries
1777 * to choose layout that looks good.
1778 * @return {{columns: number, rows: number}}
1779 */
1780 calculateLayout_: function() {
1781 var preferredColumns = this.pods.length < COLUMNS.length ?
1782 COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1];
1783 var maxWidth = Oobe.getInstance().clientAreaSize.width;
1784 var columns = preferredColumns;
1785 while (maxWidth < this.columnsToWidth_(columns) && columns > 1)
1786 --columns;
1787 var rows = Math.floor((this.pods.length - 1) / columns) + 1;
1788 if (getComputedStyle(
1789 $('signin-banner'), null).getPropertyValue('display') != 'none') {
1790 rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER);
1791 }
1792 var maxHeigth = Oobe.getInstance().clientAreaSize.height;
1793 while (maxHeigth < this.rowsToHeight_(rows) && rows > 1)
1794 --rows;
1795 // One more iteration if it's not enough cells to place all pods.
1796 while (maxWidth >= this.columnsToWidth_(columns + 1) &&
1797 columns * rows < this.pods.length &&
1798 columns < MAX_NUMBER_OF_COLUMNS) {
1799 ++columns;
1800 }
1801 return {columns: columns, rows: rows};
1802 },
1803
1804 /**
1805 * Places pods onto their positions onto pod grid.
1806 * @private
1807 */
1808 placePods_: function() {
1809 var layout = this.calculateLayout_();
1810 var columns = this.columns = layout.columns;
1811 var rows = this.rows = layout.rows;
1812 var maxPodsNumber = columns * rows;
1813 var isDesktopUserManager = Oobe.getInstance().displayType ==
1814 DISPLAY_TYPE.DESKTOP_USER_MANAGER;
1815 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
1816 MARGIN_BY_COLUMNS[columns];
1817 this.parentNode.setPreferredSize(
1818 this.columnsToWidth_(columns), this.rowsToHeight_(rows));
1819 var height = this.userPodHeight_;
1820 var width = this.userPodWidth_;
1821 this.pods.forEach(function(pod, index) {
1822 if (pod.offsetHeight != height) {
1823 console.error('Pod offsetHeight (' + pod.offsetHeight +
1824 ') and POD_HEIGHT (' + height + ') are not equal.');
1825 }
1826 if (pod.offsetWidth != width) {
1827 console.error('Pod offsetWidth (' + pod.offsetWidth +
1828 ') and POD_WIDTH (' + width + ') are not equal.');
1829 }
1830 if (index >= maxPodsNumber) {
1831 pod.hidden = true;
1832 return;
1833 }
1834 pod.hidden = false;
1835 var column = index % columns;
1836 var row = Math.floor(index / columns);
1837 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
1838 POD_ROW_PADDING;
1839 pod.left = rowPadding + column * (width + margin);
1840
1841 // On desktop, we want the rows to always be equally spaced.
1842 pod.top = isDesktopUserManager ? row * (height + rowPadding) :
1843 row * height + rowPadding;
1844 });
1845 Oobe.getInstance().updateScreenSize(this.parentNode);
1846 },
1847
1848 /**
1849 * Number of columns.
1850 * @type {?number}
1851 */
1852 set columns(columns) {
1853 // Cannot use 'columns' here.
1854 this.setAttribute('ncolumns', columns);
1855 },
1856 get columns() {
1857 return parseInt(this.getAttribute('ncolumns'));
1858 },
1859
1860 /**
1861 * Number of rows.
1862 * @type {?number}
1863 */
1864 set rows(rows) {
1865 // Cannot use 'rows' here.
1866 this.setAttribute('nrows', rows);
1867 },
1868 get rows() {
1869 return parseInt(this.getAttribute('nrows'));
1870 },
1871
1872 /**
1873 * Whether the pod is currently focused.
1874 * @param {UserPod} pod Pod to check for focus.
1875 * @return {boolean} Pod focus status.
1876 */
1877 isFocused: function(pod) {
1878 return this.focusedPod_ == pod;
1879 },
1880
1881 /**
1882 * Focuses a given user pod or clear focus when given null.
1883 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
1884 * @param {boolean=} opt_force If true, forces focus update even when
1885 * podToFocus is already focused.
1886 */
1887 focusPod: function(podToFocus, opt_force) {
1888 if (this.isFocused(podToFocus) && !opt_force) {
1889 // Calling focusPod w/o podToFocus means reset.
1890 if (!podToFocus)
1891 Oobe.clearErrors();
1892 this.keyboardActivated_ = false;
1893 return;
1894 }
1895
1896 // Make sure there's only one focusPod operation happening at a time.
1897 if (this.insideFocusPod_) {
1898 this.keyboardActivated_ = false;
1899 return;
1900 }
1901 this.insideFocusPod_ = true;
1902
1903 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1904 if (!this.alwaysFocusSinglePod) {
1905 pod.isActionBoxMenuActive = false;
1906 }
1907 if (pod != podToFocus) {
1908 pod.isActionBoxMenuHovered = false;
1909 pod.classList.remove('focused');
1910 // On Desktop, the faded style is not set correctly, so we should
1911 // manually fade out non-focused pods if there is a focused pod.
1912 if (pod.user.isDesktopUser && podToFocus)
1913 pod.classList.add('faded');
1914 else
1915 pod.classList.remove('faded');
1916 pod.reset(false);
1917 }
1918 }
1919
1920 // Clear any error messages for previous pod.
1921 if (!this.isFocused(podToFocus))
1922 Oobe.clearErrors();
1923
1924 var hadFocus = !!this.focusedPod_;
1925 this.focusedPod_ = podToFocus;
1926 if (podToFocus) {
1927 podToFocus.classList.remove('faded');
1928 podToFocus.classList.add('focused');
1929 if (!podToFocus.multiProfilesPolicyApplied)
1930 podToFocus.reset(true); // Reset and give focus.
1931 else {
1932 podToFocus.userTypeBubbleElement.classList.add('bubble-shown');
1933 podToFocus.focus();
1934 }
1935
1936 // focusPod() automatically loads wallpaper
1937 if (!podToFocus.user.isApp)
1938 chrome.send('focusPod', [podToFocus.user.username]);
1939 this.firstShown_ = false;
1940 this.lastFocusedPod_ = podToFocus;
1941
1942 if (Oobe.getInstance().virtualKeyboardShown)
1943 this.scrollFocusedPodIntoView();
1944 }
1945 this.insideFocusPod_ = false;
1946 this.keyboardActivated_ = false;
1947 },
1948
1949 /**
1950 * Focuses a given user pod by index or clear focus when given null.
1951 * @param {int=} podToFocus index of User pod to focus.
1952 * @param {boolean=} opt_force If true, forces focus update even when
1953 * podToFocus is already focused.
1954 */
1955 focusPodByIndex: function(podToFocus, opt_force) {
1956 if (podToFocus < this.pods.length)
1957 this.focusPod(this.pods[podToFocus], opt_force);
1958 },
1959
1960 /**
1961 * Resets wallpaper to the last active user's wallpaper, if any.
1962 */
1963 loadLastWallpaper: function() {
1964 if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp)
1965 chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]);
1966 },
1967
1968 /**
1969 * Returns the currently activated pod.
1970 * @type {UserPod}
1971 */
1972 get activatedPod() {
1973 return this.activatedPod_;
1974 },
1975
1976 /**
1977 * Sets currently activated pod.
1978 * @param {UserPod} pod Pod to check for focus.
1979 * @param {Event} e Event object.
1980 */
1981 setActivatedPod: function(pod, e) {
1982 if (pod && pod.activate(e))
1983 this.activatedPod_ = pod;
1984 },
1985
1986 /**
1987 * The pod of the signed-in user, if any; null otherwise.
1988 * @type {?UserPod}
1989 */
1990 get lockedPod() {
1991 for (var i = 0, pod; pod = this.pods[i]; ++i) {
1992 if (pod.user.signedIn)
1993 return pod;
1994 }
1995 return null;
1996 },
1997
1998 /**
1999 * The pod that is preselected on user pod row show.
2000 * @type {?UserPod}
2001 */
2002 get preselectedPod() {
2003 var lockedPod = this.lockedPod;
2004 if (lockedPod || !PRESELECT_FIRST_POD)
2005 return lockedPod;
2006 for (var i = 0, pod; pod = this.pods[i]; ++i) {
2007 if (!pod.multiProfilesPolicyApplied) {
2008 return pod;
2009 }
2010 }
2011 return this.pods[0];
2012 },
2013
2014 /**
2015 * Resets input UI.
2016 * @param {boolean} takeFocus True to take focus.
2017 */
2018 reset: function(takeFocus) {
2019 this.disabled = false;
2020 if (this.activatedPod_)
2021 this.activatedPod_.reset(takeFocus);
2022 },
2023
2024 /**
2025 * Restores input focus to current selected pod, if there is any.
2026 */
2027 refocusCurrentPod: function() {
2028 if (this.focusedPod_ && !this.focusedPod_.multiProfilesPolicyApplied) {
2029 this.focusedPod_.focusInput();
2030 }
2031 },
2032
2033 /**
2034 * Clears focused pod password field.
2035 */
2036 clearFocusedPod: function() {
2037 if (!this.disabled && this.focusedPod_)
2038 this.focusedPod_.reset(true);
2039 },
2040
2041 /**
2042 * Shows signin UI.
2043 * @param {string} email Email for signin UI.
2044 */
2045 showSigninUI: function(email) {
2046 // Clear any error messages that might still be around.
2047 Oobe.clearErrors();
2048 this.disabled = true;
2049 this.lastFocusedPod_ = this.getPodWithUsername_(email);
2050 Oobe.showSigninUI(email);
2051 },
2052
2053 /**
2054 * Updates current image of a user.
2055 * @param {string} username User for which to update the image.
2056 */
2057 updateUserImage: function(username) {
2058 var pod = this.getPodWithUsername_(username);
2059 if (pod)
2060 pod.updateUserImage();
2061 },
2062
2063 /**
2064 * Handler of click event.
2065 * @param {Event} e Click Event object.
2066 * @private
2067 */
2068 handleClick_: function(e) {
2069 if (this.disabled)
2070 return;
2071
2072 // Clear all menus if the click is outside pod menu and its
2073 // button area.
2074 if (!findAncestorByClass(e.target, 'action-box-menu') &&
2075 !findAncestorByClass(e.target, 'action-box-area')) {
2076 for (var i = 0, pod; pod = this.pods[i]; ++i)
2077 pod.isActionBoxMenuActive = false;
2078 }
2079
2080 // Clears focus if not clicked on a pod and if there's more than one pod.
2081 var pod = findAncestorByClass(e.target, 'pod');
2082 if ((!pod || pod.parentNode != this) && !this.alwaysFocusSinglePod) {
2083 this.focusPod();
2084 }
2085
2086 if (pod)
2087 pod.isActionBoxMenuHovered = true;
2088
2089 // Return focus back to single pod.
2090 if (this.alwaysFocusSinglePod) {
2091 this.focusPod(this.focusedPod_, true /* force */);
2092 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
2093 if (!pod)
2094 this.focusedPod_.isActionBoxMenuHovered = false;
2095 }
2096 },
2097
2098 /**
2099 * Handler of mouse move event.
2100 * @param {Event} e Click Event object.
2101 * @private
2102 */
2103 handleMouseMove_: function(e) {
2104 if (this.disabled)
2105 return;
2106 if (e.webkitMovementX == 0 && e.webkitMovementY == 0)
2107 return;
2108
2109 // Defocus (thus hide) action box, if it is focused on a user pod
2110 // and the pointer is not hovering over it.
2111 var pod = findAncestorByClass(e.target, 'pod');
2112 if (document.activeElement &&
2113 document.activeElement.parentNode != pod &&
2114 document.activeElement.classList.contains('action-box-area')) {
2115 document.activeElement.parentNode.focus();
2116 }
2117
2118 if (pod)
2119 pod.isActionBoxMenuHovered = true;
2120
2121 // Hide action boxes on other user pods.
2122 for (var i = 0, p; p = this.pods[i]; ++i)
2123 if (p != pod && !p.isActionBoxMenuActive)
2124 p.isActionBoxMenuHovered = false;
2125 },
2126
2127 /**
2128 * Handles focus event.
2129 * @param {Event} e Focus Event object.
2130 * @private
2131 */
2132 handleFocus_: function(e) {
2133 if (this.disabled)
2134 return;
2135 if (e.target.parentNode == this) {
2136 // Focus on a pod
2137 if (e.target.classList.contains('focused')) {
2138 if (!e.target.multiProfilesPolicyApplied)
2139 e.target.focusInput();
2140 else
2141 e.target.userTypeBubbleElement.classList.add('bubble-shown');
2142 } else
2143 this.focusPod(e.target);
2144 return;
2145 }
2146
2147 var pod = findAncestorByClass(e.target, 'pod');
2148 if (pod && pod.parentNode == this) {
2149 // Focus on a control of a pod but not on the action area button.
2150 if (!pod.classList.contains('focused') &&
2151 !e.target.classList.contains('action-box-button')) {
2152 this.focusPod(pod);
2153 pod.userTypeBubbleElement.classList.remove('bubble-shown');
2154 e.target.focus();
2155 }
2156 return;
2157 }
2158
2159 // Clears pod focus when we reach here. It means new focus is neither
2160 // on a pod nor on a button/input for a pod.
2161 // Do not "defocus" user pod when it is a single pod.
2162 // That means that 'focused' class will not be removed and
2163 // input field/button will always be visible.
2164 if (!this.alwaysFocusSinglePod)
2165 this.focusPod();
2166 else {
2167 // Hide user-type-bubble in case this is one pod and we lost focus of
2168 // it.
2169 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
2170 }
2171 },
2172
2173 /**
2174 * Handler of keydown event.
2175 * @param {Event} e KeyDown Event object.
2176 */
2177 handleKeyDown: function(e) {
2178 if (this.disabled)
2179 return;
2180 var editing = e.target.tagName == 'INPUT' && e.target.value;
2181 switch (e.keyIdentifier) {
2182 case 'Left':
2183 if (!editing) {
2184 this.keyboardActivated_ = true;
2185 if (this.focusedPod_ && this.focusedPod_.previousElementSibling)
2186 this.focusPod(this.focusedPod_.previousElementSibling);
2187 else
2188 this.focusPod(this.lastElementChild);
2189
2190 e.stopPropagation();
2191 }
2192 break;
2193 case 'Right':
2194 if (!editing) {
2195 this.keyboardActivated_ = true;
2196 if (this.focusedPod_ && this.focusedPod_.nextElementSibling)
2197 this.focusPod(this.focusedPod_.nextElementSibling);
2198 else
2199 this.focusPod(this.firstElementChild);
2200
2201 e.stopPropagation();
2202 }
2203 break;
2204 case 'Enter':
2205 if (this.focusedPod_) {
2206 var targetTag = e.target.tagName;
2207 if (e.target == this.focusedPod_.passwordElement ||
2208 (targetTag != 'INPUT' &&
2209 targetTag != 'BUTTON' &&
2210 targetTag != 'A')) {
2211 this.setActivatedPod(this.focusedPod_, e);
2212 e.stopPropagation();
2213 }
2214 }
2215 break;
2216 case 'U+001B': // Esc
2217 if (!this.alwaysFocusSinglePod)
2218 this.focusPod();
2219 break;
2220 }
2221 },
2222
2223 /**
2224 * Called right after the pod row is shown.
2225 */
2226 handleAfterShow: function() {
2227 // Without timeout changes in pods positions will be animated even though
2228 // it happened when 'flying-pods' class was disabled.
2229 setTimeout(function() {
2230 Oobe.getInstance().toggleClass('flying-pods', true);
2231 }, 0);
2232 // Force input focus for user pod on show and once transition ends.
2233 if (this.focusedPod_) {
2234 var focusedPod = this.focusedPod_;
2235 var screen = this.parentNode;
2236 var self = this;
2237 focusedPod.addEventListener('webkitTransitionEnd', function f(e) {
2238 focusedPod.removeEventListener('webkitTransitionEnd', f);
2239 focusedPod.reset(true);
2240 // Notify screen that it is ready.
2241 screen.onShow();
2242 });
2243 // Guard timer for 1 second -- it would conver all possible animations.
2244 ensureTransitionEndEvent(focusedPod, 1000);
2245 }
2246 },
2247
2248 /**
2249 * Called right before the pod row is shown.
2250 */
2251 handleBeforeShow: function() {
2252 Oobe.getInstance().toggleClass('flying-pods', false);
2253 for (var event in this.listeners_) {
2254 this.ownerDocument.addEventListener(
2255 event, this.listeners_[event][0], this.listeners_[event][1]);
2256 }
2257 $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR;
2258
2259 if (this.podPlacementPostponed_) {
2260 this.podPlacementPostponed_ = false;
2261 this.placePods_();
2262 pod = this.preselectedPod;
2263 this.focusPod(pod);
2264 // Hide user-type-bubble in case all user pods are disabled and we focus
2265 // first pod.
2266 if (pod && pod.multiProfilesPolicyApplied) {
2267 pod.userTypeBubbleElement.classList.remove('bubble-shown');
2268 }
2269 }
2270 },
2271
2272 /**
2273 * Called when the element is hidden.
2274 */
2275 handleHide: function() {
2276 for (var event in this.listeners_) {
2277 this.ownerDocument.removeEventListener(
2278 event, this.listeners_[event][0], this.listeners_[event][1]);
2279 }
2280 $('login-header-bar').buttonsTabIndex = 0;
2281 },
2282
2283 /**
2284 * Called when a pod's user image finishes loading.
2285 */
2286 handlePodImageLoad: function(pod) {
2287 var index = this.podsWithPendingImages_.indexOf(pod);
2288 if (index == -1) {
2289 return;
2290 }
2291
2292 this.podsWithPendingImages_.splice(index, 1);
2293 if (this.podsWithPendingImages_.length == 0) {
2294 this.classList.remove('images-loading');
2295 }
2296 }
2297 };
2298
2299 return {
2300 PodRow: PodRow
2301 };
2302 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698