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