summaryrefslogtreecommitdiff
path: root/chromium/ui/login/account_picker/screen_account_picker.js
blob: dd272b971c4f4b57884ce5fae5b1f9654a1263af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Account picker screen implementation.
 */

login.createScreen('AccountPickerScreen', 'account-picker', function() {
  /**
   * Maximum number of offline login failures before online login.
   * @type {number}
   * @const
   */
  var MAX_LOGIN_ATTEMPTS_IN_POD = 3;

  /**
   * Distance between error bubble and user POD.
   * @type {number}
   * @const
   */
   var BUBBLE_POD_OFFSET = 4;

  return {
    EXTERNAL_API: [
      'loadUsers',
      'runAppForTesting',
      'setApps',
      'setShouldShowApps',
      'showAppError',
      'updateUserImage',
      'setCapsLockState',
      'forceLockedUserPodFocus',
      'removeUser',
      'showBannerMessage',
      'showUserPodCustomIcon',
      'hideUserPodCustomIcon',
      'setUserPodFingerprintIcon',
      'removeUserPodFingerprintIcon',
      'setPinEnabledForUser',
      'setAuthType',
      'setTabletModeState',
      'setPublicSessionDisplayName',
      'setPublicSessionLocales',
      'setPublicSessionKeyboardLayouts',
      'setLockScreenAppsState',
    ],

    preferredWidth_: 0,
    preferredHeight_: 0,

    // Whether this screen is shown for the first time.
    firstShown_: true,

    // Whether this screen is currently being shown.
    showing_: false,

    // Last reported lock screen app activity state.
    lockScreenAppsState_: LOCK_SCREEN_APPS_STATE.NONE,

    /** @override */
    decorate: function() {
      login.PodRow.decorate($('pod-row'));
      this.ownerDocument.addEventListener('click',
          this.handleOwnerDocClick_.bind(this));
    },

    /** @override */
    getPreferredSize: function() {
      return {width: this.preferredWidth_, height: this.preferredHeight_};
    },

    /** @override */
    onWindowResize: function() {
      $('pod-row').onWindowResize();

      // Reposition the error bubble, if it is showing. Since we are just
      // moving the bubble, the number of login attempts tried doesn't matter.
      var errorBubble = $('bubble');
      if (errorBubble && !errorBubble.hidden)
        this.showErrorBubble(0, undefined  /* Reuses the existing message. */);
    },

    /**
     * Sets preferred size for account picker screen.
     */
    setPreferredSize: function(width, height) {
      this.preferredWidth_ = width;
      this.preferredHeight_ = height;
    },

    /**
     * When the account picker is being used to lock the screen, pressing the
     * exit accelerator key will sign out the active user as it would when
     * they are signed in.
     */
    exit: function() {
      // Check and disable the sign out button so that we can never have two
      // sign out requests generated in a row.
      if ($('pod-row').lockedPod && !$('sign-out-user-button').disabled) {
        $('sign-out-user-button').disabled = true;
        chrome.send('signOutUser');
      }
    },

    /* Cancel user adding if ESC was pressed.
     */
    cancel: function() {
      if (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING)
        chrome.send('cancelUserAdding');
    },

    /**
     * Event handler that is invoked just after the frame is shown.
     * @param {string} data Screen init payload.
     */
    onAfterShow: function(data) {
      $('pod-row').handleAfterShow();
    },

    /**
     * Event handler that is invoked just before the frame is shown.
     * @param {string} data Screen init payload.
     */
    onBeforeShow: function(data) {
      this.showing_ = true;
      chrome.send('loginUIStateChanged', ['account-picker', true]);
      $('login-header-bar').signinUIState = SIGNIN_UI_STATE.ACCOUNT_PICKER;
      // Header bar should be always visible on Account Picker screen.
      Oobe.getInstance().headerHidden = false;
      chrome.send('hideCaptivePortal');
      var podRow = $('pod-row');
      podRow.handleBeforeShow();

      // In case of the preselected pod onShow will be called once pod
      // receives focus.
      if (!podRow.preselectedPod)
        this.onShow();
    },

    /**
     * Event handler invoked when the page is shown and ready.
     */
    onShow: function() {
      if (!this.showing_) {
        // This method may be called asynchronously when the pod row finishes
        // initializing. However, at that point, the screen may have been hidden
        // again already. If that happens, ignore the onShow() call.
        return;
      }
      chrome.send('getTabletModeState');
      if (!this.firstShown_) return;
      this.firstShown_ = false;

      // Ensure that login is actually visible.
      window.requestAnimationFrame(function() {
        chrome.send('accountPickerReady');
        chrome.send('loginVisible', ['account-picker']);
      });
    },

    /**
     * Event handler that is invoked just before the frame is hidden.
     */
    onBeforeHide: function() {
      $('pod-row').clearFocusedPod();
      this.showing_ = false;
      chrome.send('loginUIStateChanged', ['account-picker', false]);
      $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
      $('pod-row').handleHide();
    },

    /**
     * Shows sign-in error bubble.
     * @param {number} loginAttempts Number of login attemps tried.
     * @param {HTMLElement} content Content to show in bubble.
     */
    showErrorBubble: function(loginAttempts, error) {
      var activatedPod = $('pod-row').activatedPod;
      if (!activatedPod) {
        $('bubble').showContentForElement($('pod-row'),
                                          cr.ui.Bubble.Attachment.RIGHT,
                                          error);
        return;
      }
      // Show web authentication if this is not a supervised user.
      if (loginAttempts > MAX_LOGIN_ATTEMPTS_IN_POD &&
          !activatedPod.user.supervisedUser) {
        chrome.send('maxIncorrectPasswordAttempts',
            [activatedPod.user.emailAddress]);
        activatedPod.showSigninUI();
      } else {
        if (loginAttempts == 1) {
          chrome.send('firstIncorrectPasswordAttempt',
              [activatedPod.user.emailAddress]);
        }
        // Update the pod row display if incorrect password.
        $('pod-row').setFocusedPodErrorDisplay(true);

        /** @const */ var BUBBLE_OFFSET = 25;
        // -8 = 4(BUBBLE_POD_OFFSET) - 2(bubble margin)
        //      - 10(internal bubble adjustment)
        var bubblePositioningPadding = -8;

        var bubbleAnchor;
        var attachment;
        if (activatedPod.pinContainer &&
            activatedPod.pinContainer.style.visibility == 'visible') {
          // Anchor the bubble to the input field.
          bubbleAnchor = (
              activatedPod.getElementsByClassName('auth-container'))[0];
          if (!bubbleAnchor) {
            console.error('auth-container not found!');
            bubbleAnchor = activatedPod.mainInput;
          }
          attachment = cr.ui.Bubble.Attachment.RIGHT;
        } else {
          // Anchor the bubble to the pod instead of the input.
          bubbleAnchor = activatedPod;
          attachment = cr.ui.Bubble.Attachment.BOTTOM;
        }

        var bubble = $('bubble');

        // Cannot use cr.ui.LoginUITools.get* on bubble until it is attached to
        // the element. getMaxHeight/Width rely on the correct up/left element
        // side positioning that doesn't happen until bubble is attached.
        var maxHeight =
            cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping(bubbleAnchor)
          - bubbleAnchor.offsetHeight - BUBBLE_POD_OFFSET;
        var maxWidth = cr.ui.LoginUITools.getMaxWidthToFit(bubbleAnchor)
          - bubbleAnchor.offsetWidth - BUBBLE_POD_OFFSET;

        // Change bubble visibility temporary to calculate height.
        var bubbleVisibility = bubble.style.visibility;
        bubble.style.visibility = 'hidden';
        bubble.hidden = false;
        // Now we need the bubble to have the new content before calculating
        // size. Undefined |error| == reuse old content.
        if (error !== undefined)
          bubble.replaceContent(error);

        // Get bubble size.
        var bubbleOffsetHeight = parseInt(bubble.offsetHeight);
        var bubbleOffsetWidth = parseInt(bubble.offsetWidth);
        // Restore attributes.
        bubble.style.visibility = bubbleVisibility;
        bubble.hidden = true;

        if (attachment == cr.ui.Bubble.Attachment.BOTTOM) {
          // Move error bubble if it overlaps the shelf.
          if (maxHeight < bubbleOffsetHeight)
            attachment = cr.ui.Bubble.Attachment.TOP;
        } else {
          // Move error bubble if it doesn't fit screen.
          if (maxWidth < bubbleOffsetWidth) {
            bubblePositioningPadding = 2;
            attachment = cr.ui.Bubble.Attachment.LEFT;
          }
        }
        var showBubbleCallback = function() {
          activatedPod.removeEventListener("transitionend",
              showBubbleCallback);
          $('bubble').showContentForElement(bubbleAnchor,
                                            attachment,
                                            error,
                                            BUBBLE_OFFSET,
                                            bubblePositioningPadding, true);
        };
        activatedPod.addEventListener("transitionend",
                                      showBubbleCallback);
        ensureTransitionEndEvent(activatedPod);
      }
    },

   /**
     * Loads given users in pod row.
     * @param {array} users Array of user.
     * @param {boolean} showGuest Whether to show guest session button.
     */
    loadUsers: function(users, showGuest) {
      $('pod-row').loadPods(users);
      $('login-header-bar').showGuestButton = showGuest;
      // On Desktop, #login-header-bar has a shadow if there are 8+ profiles.
      if (Oobe.getInstance().displayType == DISPLAY_TYPE.DESKTOP_USER_MANAGER)
        $('login-header-bar').classList.toggle('shadow', users.length > 8);
    },

    /**
     * Runs app with a given id from the list of loaded apps.
     * @param {!string} app_id of an app to run.
     * @param {boolean=} opt_diagnostic_mode Whether to run the app in
     *     diagnostic mode.  Default is false.
     */
    runAppForTesting: function(app_id, opt_diagnostic_mode) {
      $('pod-row').findAndRunAppForTesting(app_id, opt_diagnostic_mode);
    },

    /**
     * Adds given apps to the pod row.
     * @param {array} apps Array of apps.
     */
    setApps: function(apps) {
      $('pod-row').setApps(apps);
    },

    /**
     * Sets the flag of whether app pods should be visible.
     * @param {boolean} shouldShowApps Whether to show app pods.
     */
    setShouldShowApps: function(shouldShowApps) {
      $('pod-row').setShouldShowApps(shouldShowApps);
    },

    /**
     * Shows the given kiosk app error message.
     * @param {!string} message Error message to show.
     */
    showAppError: function(message) {
      // TODO(nkostylev): Figure out a way to show kiosk app launch error
      // pointing to the kiosk app pod.
      /** @const */ var BUBBLE_PADDING = 12;
      $('bubble').showTextForElement($('pod-row'),
                                     message,
                                     cr.ui.Bubble.Attachment.BOTTOM,
                                     $('pod-row').offsetWidth / 2,
                                     BUBBLE_PADDING);
    },

    /**
     * Updates current image of a user.
     * @param {string} username User for which to update the image.
     */
    updateUserImage: function(username) {
      $('pod-row').updateUserImage(username);
    },

    /**
     * Updates Caps Lock state (for Caps Lock hint in password input field).
     * @param {boolean} enabled Whether Caps Lock is on.
     */
    setCapsLockState: function(enabled) {
      $('pod-row').classList.toggle('capslock-on', enabled);
    },

    /**
     * Enforces focus on user pod of locked user.
     */
    forceLockedUserPodFocus: function() {
      var row = $('pod-row');
      if (row.lockedPod)
        row.focusPod(row.lockedPod, true);
    },

    /**
     * Remove given user from pod row if it is there.
     * @param {string} user name.
     */
    removeUser: function(username) {
      $('pod-row').removeUserPod(username);
    },

    /**
     * Displays a banner containing |message|. If the banner is already present
     * this function updates the message in the banner. This function is used
     * by the chrome.screenlockPrivate.showMessage API.
     * @param {string} message Text to be displayed or empty to hide the banner.
     */
    showBannerMessage: function(message) {
      var banner = $('signin-banner');
      banner.textContent = message;
      banner.classList.toggle('message-set', !!message);
    },

    /**
     * Shows a custom icon in the user pod of |username|. This function
     * is used by the chrome.screenlockPrivate API.
     * @param {string} username Username of pod to add button
     * @param {!{id: !string,
     *           hardlockOnClick: boolean,
     *           isTrialRun: boolean,
     *           tooltip: ({text: string, autoshow: boolean} | undefined)}} icon
     *     The icon parameters.
     */
    showUserPodCustomIcon: function(username, icon) {
      $('pod-row').showUserPodCustomIcon(username, icon);
    },

    /**
     * Hides the custom icon in the user pod of |username| added by
     * showUserPodCustomIcon(). This function is used by the
     * chrome.screenlockPrivate API.
     * @param {string} username Username of pod to remove button
     */
    hideUserPodCustomIcon: function(username) {
      $('pod-row').hideUserPodCustomIcon(username);
    },

    /**
     * Set a fingerprint icon in the user pod of |username|.
     * @param {string} username Username of the selected user
     * @param {number} state Fingerprint unlock state
     */
    setUserPodFingerprintIcon: function(username, state) {
      $('pod-row').setUserPodFingerprintIcon(username, state);
    },

    /**
     * Removes the fingerprint icon in the user pod of |username|.
     * @param {string} username Username of the selected user.
     */
    removeUserPodFingerprintIcon: function(username) {
      $('pod-row').removeUserPodFingerprintIcon(username);
    },

    /**
     * Sets the authentication type used to authenticate the user.
     * @param {string} username Username of selected user
     * @param {number} authType Authentication type, must be a valid value in
     *                          the AUTH_TYPE enum in user_pod_row.js.
     * @param {string} value The initial value to use for authentication.
     */
    setAuthType: function(username, authType, value) {
      $('pod-row').setAuthType(username, authType, value);
    },

    /**
     * Sets the state of tablet mode.
     * @param {boolean} isTabletModeEnabled true if the mode is on.
     */
    setTabletModeState: function(isTabletModeEnabled) {
      $('pod-row').setTabletModeState(isTabletModeEnabled);
    },

    /**
     * Enables or disables the pin keyboard for the given user. This may change
     * pin keyboard visibility.
     * @param {!string} user
     * @param {boolean} enabled
     */
    setPinEnabledForUser: function(user, enabled) {
      $('pod-row').setPinEnabled(user, enabled);
    },

    /**
     * Updates the display name shown on a public session pod.
     * @param {string} userID The user ID of the public session
     * @param {string} displayName The new display name
     */
    setPublicSessionDisplayName: function(userID, displayName) {
      $('pod-row').setPublicSessionDisplayName(userID, displayName);
    },

    /**
     * Updates the list of locales available for a public session.
     * @param {string} userID The user ID of the public session
     * @param {!Object} locales The list of available locales
     * @param {string} defaultLocale The locale to select by default
     * @param {boolean} multipleRecommendedLocales Whether |locales| contains
     *     two or more recommended locales
     */
    setPublicSessionLocales: function(userID,
                                      locales,
                                      defaultLocale,
                                      multipleRecommendedLocales) {
      $('pod-row').setPublicSessionLocales(userID,
                                           locales,
                                           defaultLocale,
                                           multipleRecommendedLocales);
    },

    /**
     * Updates the list of available keyboard layouts for a public session pod.
     * @param {string} userID The user ID of the public session
     * @param {string} locale The locale to which this list of keyboard layouts
     *     applies
     * @param {!Object} list List of available keyboard layouts
     */
    setPublicSessionKeyboardLayouts: function(userID, locale, list) {
      $('pod-row').setPublicSessionKeyboardLayouts(userID, locale, list);
    },

    /**
     * Updates UI based on the provided lock screen apps state.
     *
     * @param {LOCK_SCREEN_APPS_STATE} state The current lock screen apps state.
     */
    setLockScreenAppsState: function(state) {
      if (Oobe.getInstance().displayType != DISPLAY_TYPE.LOCK ||
          state == this.lockScreenAppsState_) {
        return;
      }

      this.lockScreenAppsState_ = state;
      $('login-header-bar').lockScreenAppsState = state;
      // When an lock screen app window is in background - i.e. visible behind
      // the lock screen UI - dim the lock screen background, so it's more
      // noticeable that the app widow in background is not actionable.
      $('background').classList.toggle(
          'dimmed-background', state == LOCK_SCREEN_APPS_STATE.BACKGROUND);

      if (state === LOCK_SCREEN_APPS_STATE.FOREGROUND)
        $('pod-row').clearFocusedPod();

    },

    /**
     * Handles clicks on the document which displays the account picker UI.
     * If the click event target is outer container - i.e. background portion of
     * UI with no other UI elements, and lock screen apps are in background, a
     * request is issued to chrome to move lock screen apps to foreground.
     * @param {Event} event The click event.
     */
    handleOwnerDocClick_: function(event) {
      if (this.lockScreenAppsState_  != LOCK_SCREEN_APPS_STATE.BACKGROUND ||
          event.target != $('outer-container')) {
        return;
      }
      chrome.send('setLockScreenAppsState',
                  [LOCK_SCREEN_APPS_STATE.FOREGROUND]);

      event.preventDefault();
      event.stopPropagation();
    },
  };
});