| Index: ui/login/account_picker/md_user_pod_row.js
|
| diff --git a/ui/login/account_picker/md_user_pod_row.js b/ui/login/account_picker/md_user_pod_row.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cff4f544fb4623cc771de5eaeefab5df9be51bb1
|
| --- /dev/null
|
| +++ b/ui/login/account_picker/md_user_pod_row.js
|
| @@ -0,0 +1,3984 @@
|
| +// 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 User pod row implementation.
|
| + */
|
| +
|
| +cr.define('login', function() {
|
| + /**
|
| + * Number of displayed columns depending on user pod count.
|
| + * @type {Array<number>}
|
| + * @const
|
| + */
|
| + var COLUMNS = [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6];
|
| +
|
| + /**
|
| + * Mapping between number of columns in pod-row and margin between user pods
|
| + * for such layout.
|
| + * @type {Array<number>}
|
| + * @const
|
| + */
|
| + var MARGIN_BY_COLUMNS = [undefined, 40, 40, 40, 40, 40, 12];
|
| +
|
| + /**
|
| + * Mapping between number of columns in the desktop pod-row and margin
|
| + * between user pods for such layout.
|
| + * @type {Array<number>}
|
| + * @const
|
| + */
|
| + var DESKTOP_MARGIN_BY_COLUMNS = [undefined, 32, 32, 32, 32, 32, 32];
|
| +
|
| + /**
|
| + * Maximal number of columns currently supported by pod-row.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| + var MAX_NUMBER_OF_COLUMNS = 6;
|
| +
|
| + /**
|
| + * Maximal number of rows if sign-in banner is displayed alonside.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| + var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2;
|
| +
|
| + /**
|
| + * Variables used for pod placement processing. Width and height should be
|
| + * synced with computed CSS sizes of pods.
|
| + */
|
| + var CROS_POD_WIDTH = 180;
|
| + var DESKTOP_POD_WIDTH = 180;
|
| + var MD_DESKTOP_POD_WIDTH = 160;
|
| + var PUBLIC_EXPANDED_BASIC_WIDTH = 500;
|
| + var PUBLIC_EXPANDED_ADVANCED_WIDTH = 610;
|
| + var CROS_POD_HEIGHT = 213;
|
| + var DESKTOP_POD_HEIGHT = 226;
|
| + var MD_DESKTOP_POD_HEIGHT = 200;
|
| + var POD_ROW_PADDING = 10;
|
| + var DESKTOP_ROW_PADDING = 32;
|
| + var CUSTOM_ICON_CONTAINER_SIZE = 40;
|
| + var CROS_PIN_POD_HEIGHT = 417;
|
| +
|
| + /**
|
| + * Minimal padding between user pod and virtual keyboard.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| + var USER_POD_KEYBOARD_MIN_PADDING = 20;
|
| +
|
| + /**
|
| + * Maximum time for which the pod row remains hidden until all user images
|
| + * have been loaded.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| + var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000;
|
| +
|
| + /**
|
| + * Public session help topic identifier.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| + var HELP_TOPIC_PUBLIC_SESSION = 3041033;
|
| +
|
| + /**
|
| + * Tab order for user pods. Update these when adding new controls.
|
| + * @enum {number}
|
| + * @const
|
| + */
|
| + var UserPodTabOrder = {
|
| + POD_INPUT: 1, // Password input field, Action box menu button and
|
| + // the pod itself.
|
| + PIN_KEYBOARD: 2, // Pin keyboard below the password input field.
|
| + POD_CUSTOM_ICON: 3, // Pod custom icon next to password input field.
|
| + HEADER_BAR: 4, // Buttons on the header bar (Shutdown, Add User).
|
| + POD_MENU_ITEM: 5 // User pad menu items (User info, Remove user).
|
| + };
|
| +
|
| + /**
|
| + * Supported authentication types. Keep in sync with the enum in
|
| + * chrome/browser/signin/screenlock_bridge.h
|
| + * @enum {number}
|
| + * @const
|
| + */
|
| + var AUTH_TYPE = {
|
| + OFFLINE_PASSWORD: 0,
|
| + ONLINE_SIGN_IN: 1,
|
| + NUMERIC_PIN: 2,
|
| + USER_CLICK: 3,
|
| + EXPAND_THEN_USER_CLICK: 4,
|
| + FORCE_OFFLINE_PASSWORD: 5
|
| + };
|
| +
|
| + /**
|
| + * Names of authentication types.
|
| + */
|
| + var AUTH_TYPE_NAMES = {
|
| + 0: 'offlinePassword',
|
| + 1: 'onlineSignIn',
|
| + 2: 'numericPin',
|
| + 3: 'userClick',
|
| + 4: 'expandThenUserClick',
|
| + 5: 'forceOfflinePassword'
|
| + };
|
| +
|
| + /**
|
| + * Supported fingerprint unlock states.
|
| + * @enum {number}
|
| + * @const
|
| + */
|
| + var FINGERPRINT_STATES = {
|
| + HIDDEN: 0,
|
| + DEFAULT: 1,
|
| + SIGNIN: 2,
|
| + FAILED: 3,
|
| + };
|
| +
|
| + /**
|
| + * The fingerprint states to classes mapping.
|
| + * {@code state} properties indicate current fingerprint unlock state.
|
| + * {@code class} properties are CSS classes used to set the icons' background
|
| + * and password placeholder color.
|
| + * @const {Array<{type: !number, class: !string}>}
|
| + */
|
| + var FINGERPRINT_STATES_MAPPING = [
|
| + {state: FINGERPRINT_STATES.HIDDEN, class: 'hidden'},
|
| + {state: FINGERPRINT_STATES.DEFAULT, class: 'default'},
|
| + {state: FINGERPRINT_STATES.SIGNIN, class: 'signin'},
|
| + {state: FINGERPRINT_STATES.FAILED, class: 'failed'}
|
| + ];
|
| +
|
| + // Focus and tab order are organized as follows:
|
| + //
|
| + // (1) all user pods have tab index 1 so they are traversed first;
|
| + // (2) when a user pod is activated, its tab index is set to -1 and its
|
| + // main input field gets focus and tab index 1;
|
| + // (3) if user pod custom icon is interactive, it has tab index 2 so it
|
| + // follows the input.
|
| + // (4) buttons on the header bar have tab index 3 so they follow the custom
|
| + // icon, or user pod if custom icon is not interactive;
|
| + // (5) Action box buttons have tab index 4 and follow header bar buttons;
|
| + // (6) lastly, focus jumps to the Status Area and back to user pods.
|
| + //
|
| + // 'Focus' event is handled by a capture handler for the whole document
|
| + // and in some cases 'mousedown' event handlers are used instead of 'click'
|
| + // handlers where it's necessary to prevent 'focus' event from being fired.
|
| +
|
| + /**
|
| + * Helper function to remove a class from given element.
|
| + * @param {!HTMLElement} el Element whose class list to change.
|
| + * @param {string} cl Class to remove.
|
| + */
|
| + function removeClass(el, cl) {
|
| + el.classList.remove(cl);
|
| + }
|
| +
|
| + /**
|
| + * Creates a user pod.
|
| + * @constructor
|
| + * @extends {HTMLDivElement}
|
| + */
|
| + var UserPod = cr.ui.define(function() {
|
| + var node = $('user-pod-template').cloneNode(true);
|
| + node.removeAttribute('id');
|
| + return node;
|
| + });
|
| +
|
| + /**
|
| + * Stops event propagation from the any user pod child element.
|
| + * @param {Event} e Event to handle.
|
| + */
|
| + function stopEventPropagation(e) {
|
| + // Prevent default so that we don't trigger a 'focus' event.
|
| + e.preventDefault();
|
| + e.stopPropagation();
|
| + }
|
| +
|
| + /**
|
| + * Creates an element for custom icon shown in a user pod next to the input
|
| + * field.
|
| + * @constructor
|
| + * @extends {HTMLDivElement}
|
| + */
|
| + var UserPodCustomIcon = cr.ui.define(function() {
|
| + var node = document.createElement('div');
|
| + node.classList.add('custom-icon-container');
|
| + node.hidden = true;
|
| +
|
| + // Create the actual icon element and add it as a child to the container.
|
| + var iconNode = document.createElement('div');
|
| + iconNode.classList.add('custom-icon');
|
| + node.appendChild(iconNode);
|
| + return node;
|
| + });
|
| +
|
| + /**
|
| + * The supported user pod custom icons.
|
| + * {@code id} properties should be in sync with values set by C++ side.
|
| + * {@code class} properties are CSS classes used to set the icons' background.
|
| + * @const {Array<{id: !string, class: !string}>}
|
| + */
|
| + UserPodCustomIcon.ICONS = [
|
| + {id: 'locked', class: 'custom-icon-locked'},
|
| + {id: 'locked-to-be-activated',
|
| + class: 'custom-icon-locked-to-be-activated'},
|
| + {id: 'locked-with-proximity-hint',
|
| + class: 'custom-icon-locked-with-proximity-hint'},
|
| + {id: 'unlocked', class: 'custom-icon-unlocked'},
|
| + {id: 'hardlocked', class: 'custom-icon-hardlocked'},
|
| + {id: 'spinner', class: 'custom-icon-spinner'}
|
| + ];
|
| +
|
| + /**
|
| + * The hover state for the icon. When user hovers over the icon, a tooltip
|
| + * should be shown after a short delay. This enum is used to keep track of
|
| + * the tooltip status related to hover state.
|
| + * @enum {string}
|
| + */
|
| + UserPodCustomIcon.HoverState = {
|
| + /** The user is not hovering over the icon. */
|
| + NO_HOVER: 'no_hover',
|
| +
|
| + /** The user is hovering over the icon but the tooltip is not activated. */
|
| + HOVER: 'hover',
|
| +
|
| + /**
|
| + * User is hovering over the icon and the tooltip is activated due to the
|
| + * hover state (which happens with delay after user starts hovering).
|
| + */
|
| + HOVER_TOOLTIP: 'hover_tooltip'
|
| + };
|
| +
|
| + /**
|
| + * If the icon has a tooltip that should be automatically shown, the tooltip
|
| + * is shown even when there is no user action (i.e. user is not hovering over
|
| + * the icon), after a short delay. The tooltip should be hidden after some
|
| + * time. Note that the icon will not be considered autoshown if it was
|
| + * previously shown as a result of the user action.
|
| + * This enum is used to keep track of this state.
|
| + * @enum {string}
|
| + */
|
| + UserPodCustomIcon.TooltipAutoshowState = {
|
| + /** The tooltip should not be or was not automatically shown. */
|
| + DISABLED: 'disabled',
|
| +
|
| + /**
|
| + * The tooltip should be automatically shown, but the timeout for showing
|
| + * the tooltip has not yet passed.
|
| + */
|
| + ENABLED: 'enabled',
|
| +
|
| + /** The tooltip was automatically shown. */
|
| + ACTIVE : 'active'
|
| + };
|
| +
|
| + UserPodCustomIcon.prototype = {
|
| + __proto__: HTMLDivElement.prototype,
|
| +
|
| + /**
|
| + * The id of the icon being shown.
|
| + * @type {string}
|
| + * @private
|
| + */
|
| + iconId_: '',
|
| +
|
| + /**
|
| + * A reference to the timeout for updating icon hover state. Non-null
|
| + * only if there is an active timeout.
|
| + * @type {?number}
|
| + * @private
|
| + */
|
| + updateHoverStateTimeout_: null,
|
| +
|
| + /**
|
| + * A reference to the timeout for updating icon tooltip autoshow state.
|
| + * Non-null only if there is an active timeout.
|
| + * @type {?number}
|
| + * @private
|
| + */
|
| + updateTooltipAutoshowStateTimeout_: null,
|
| +
|
| + /**
|
| + * Callback for click and 'Enter' key events that gets set if the icon is
|
| + * interactive.
|
| + * @type {?function()}
|
| + * @private
|
| + */
|
| + actionHandler_: null,
|
| +
|
| + /**
|
| + * The current tooltip state.
|
| + * @type {{active: function(): boolean,
|
| + * autoshow: !UserPodCustomIcon.TooltipAutoshowState,
|
| + * hover: !UserPodCustomIcon.HoverState,
|
| + * text: string}}
|
| + * @private
|
| + */
|
| + tooltipState_: {
|
| + /**
|
| + * Utility method for determining whether the tooltip is active, either as
|
| + * a result of hover state or being autoshown.
|
| + * @return {boolean}
|
| + */
|
| + active: function() {
|
| + return this.autoshow == UserPodCustomIcon.TooltipAutoshowState.ACTIVE ||
|
| + this.hover == UserPodCustomIcon.HoverState.HOVER_TOOLTIP;
|
| + },
|
| +
|
| + /**
|
| + * @type {!UserPodCustomIcon.TooltipAutoshowState}
|
| + */
|
| + autoshow: UserPodCustomIcon.TooltipAutoshowState.DISABLED,
|
| +
|
| + /**
|
| + * @type {!UserPodCustomIcon.HoverState}
|
| + */
|
| + hover: UserPodCustomIcon.HoverState.NO_HOVER,
|
| +
|
| + /**
|
| + * The tooltip text.
|
| + * @type {string}
|
| + */
|
| + text: ''
|
| + },
|
| +
|
| + /** @override */
|
| + decorate: function() {
|
| + this.iconElement.addEventListener(
|
| + 'mouseover',
|
| + this.updateHoverState_.bind(this,
|
| + UserPodCustomIcon.HoverState.HOVER));
|
| + this.iconElement.addEventListener(
|
| + 'mouseout',
|
| + this.updateHoverState_.bind(this,
|
| + UserPodCustomIcon.HoverState.NO_HOVER));
|
| + this.iconElement.addEventListener('mousedown',
|
| + this.handleMouseDown_.bind(this));
|
| + this.iconElement.addEventListener('click',
|
| + this.handleClick_.bind(this));
|
| + this.iconElement.addEventListener('keydown',
|
| + this.handleKeyDown_.bind(this));
|
| +
|
| + // When the icon is focused using mouse, there should be no outline shown.
|
| + // Preventing default mousedown event accomplishes this.
|
| + this.iconElement.addEventListener('mousedown', function(e) {
|
| + e.preventDefault();
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Getter for the icon element's div.
|
| + * @return {HTMLDivElement}
|
| + */
|
| + get iconElement() {
|
| + return this.querySelector('.custom-icon');
|
| + },
|
| +
|
| + /**
|
| + * Updates the icon element class list to properly represent the provided
|
| + * icon.
|
| + * @param {!string} id The id of the icon that should be shown. Should be
|
| + * one of the ids listed in {@code UserPodCustomIcon.ICONS}.
|
| + */
|
| + setIcon: function(id) {
|
| + this.iconId_ = id;
|
| + UserPodCustomIcon.ICONS.forEach(function(icon) {
|
| + this.iconElement.classList.toggle(icon.class, id == icon.id);
|
| + }, this);
|
| + },
|
| +
|
| + /**
|
| + * Sets the ARIA label for the icon.
|
| + * @param {!string} ariaLabel
|
| + */
|
| + setAriaLabel: function(ariaLabel) {
|
| + this.iconElement.setAttribute('aria-label', ariaLabel);
|
| + },
|
| +
|
| + /**
|
| + * Shows the icon.
|
| + */
|
| + show: function() {
|
| + // Show the icon if the current iconId is valid.
|
| + var validIcon = false;
|
| + UserPodCustomIcon.ICONS.forEach(function(icon) {
|
| + validIcon = validIcon || this.iconId_ == icon.id;
|
| + }, this);
|
| + this.hidden = validIcon ? false : true;
|
| + },
|
| +
|
| + /**
|
| + * Updates the icon tooltip. If {@code autoshow} parameter is set the
|
| + * tooltip is immediatelly shown. If tooltip text is not set, the method
|
| + * ensures the tooltip gets hidden. If tooltip is shown prior to this call,
|
| + * it remains shown, but the tooltip text is updated.
|
| + * @param {!{text: string, autoshow: boolean}} tooltip The tooltip
|
| + * parameters.
|
| + */
|
| + setTooltip: function(tooltip) {
|
| + this.iconElement.classList.toggle('icon-with-tooltip', !!tooltip.text);
|
| +
|
| + this.updateTooltipAutoshowState_(
|
| + tooltip.autoshow ?
|
| + UserPodCustomIcon.TooltipAutoshowState.ENABLED :
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED);
|
| + this.tooltipState_.text = tooltip.text;
|
| + this.updateTooltip_();
|
| + },
|
| +
|
| + /**
|
| + * Sets up icon tabIndex attribute and handler for click and 'Enter' key
|
| + * down events.
|
| + * @param {?function()} callback If icon should be interactive, the
|
| + * function to get called on click and 'Enter' key down events. Should
|
| + * be null to make the icon non interactive.
|
| + */
|
| + setInteractive: function(callback) {
|
| + this.iconElement.classList.toggle('interactive-custom-icon', !!callback);
|
| +
|
| + // Update tabIndex property if needed.
|
| + if (!!this.actionHandler_ != !!callback) {
|
| + if (callback) {
|
| + this.iconElement.setAttribute('tabIndex',
|
| + UserPodTabOrder.POD_CUSTOM_ICON);
|
| + } else {
|
| + this.iconElement.removeAttribute('tabIndex');
|
| + }
|
| + }
|
| +
|
| + // Set the new action handler.
|
| + this.actionHandler_ = callback;
|
| + },
|
| +
|
| + /**
|
| + * Hides the icon and cleans its state.
|
| + */
|
| + hide: function() {
|
| + this.hideTooltip_();
|
| + this.clearUpdateHoverStateTimeout_();
|
| + this.clearUpdateTooltipAutoshowStateTimeout_();
|
| + this.setInteractive(null);
|
| + this.hidden = true;
|
| + },
|
| +
|
| + /**
|
| + * Clears timeout for showing a tooltip if one is set. Used to cancel
|
| + * showing the tooltip when the user starts typing the password.
|
| + */
|
| + cancelDelayedTooltipShow: function() {
|
| + this.updateTooltipAutoshowState_(
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED);
|
| + this.clearUpdateHoverStateTimeout_();
|
| + },
|
| +
|
| + /**
|
| + * Handles mouse down event in the icon element.
|
| + * @param {Event} e The mouse down event.
|
| + * @private
|
| + */
|
| + handleMouseDown_: function(e) {
|
| + this.updateHoverState_(UserPodCustomIcon.HoverState.NO_HOVER);
|
| + this.updateTooltipAutoshowState_(
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED);
|
| +
|
| + // Stop the event propagation so in the case the click ends up on the
|
| + // user pod (outside the custom icon) auth is not attempted.
|
| + stopEventPropagation(e);
|
| + },
|
| +
|
| + /**
|
| + * Handles click event on the icon element. No-op if
|
| + * {@code this.actionHandler_} is not set.
|
| + * @param {Event} e The click event.
|
| + * @private
|
| + */
|
| + handleClick_: function(e) {
|
| + if (!this.actionHandler_)
|
| + return;
|
| + this.actionHandler_();
|
| + stopEventPropagation(e);
|
| + },
|
| +
|
| + /**
|
| + * Handles key down event on the icon element. Only 'Enter' key is handled.
|
| + * No-op if {@code this.actionHandler_} is not set.
|
| + * @param {Event} e The key down event.
|
| + * @private
|
| + */
|
| + handleKeyDown_: function(e) {
|
| + if (!this.actionHandler_ || e.key != 'Enter')
|
| + return;
|
| + this.actionHandler_(e);
|
| + stopEventPropagation(e);
|
| + },
|
| +
|
| + /**
|
| + * Changes the tooltip hover state and updates tooltip visibility if needed.
|
| + * @param {!UserPodCustomIcon.HoverState} state
|
| + * @private
|
| + */
|
| + updateHoverState_: function(state) {
|
| + this.clearUpdateHoverStateTimeout_();
|
| + this.sanitizeTooltipStateIfBubbleHidden_();
|
| +
|
| + if (state == UserPodCustomIcon.HoverState.HOVER) {
|
| + if (this.tooltipState_.active()) {
|
| + this.tooltipState_.hover = UserPodCustomIcon.HoverState.HOVER_TOOLTIP;
|
| + } else {
|
| + this.updateHoverStateSoon_(
|
| + UserPodCustomIcon.HoverState.HOVER_TOOLTIP);
|
| + }
|
| + return;
|
| + }
|
| +
|
| + if (state != UserPodCustomIcon.HoverState.NO_HOVER &&
|
| + state != UserPodCustomIcon.HoverState.HOVER_TOOLTIP) {
|
| + console.error('Invalid hover state ' + state);
|
| + return;
|
| + }
|
| +
|
| + this.tooltipState_.hover = state;
|
| + this.updateTooltip_();
|
| + },
|
| +
|
| + /**
|
| + * Sets up a timeout for updating icon hover state.
|
| + * @param {!UserPodCustomIcon.HoverState} state
|
| + * @private
|
| + */
|
| + updateHoverStateSoon_: function(state) {
|
| + if (this.updateHoverStateTimeout_)
|
| + clearTimeout(this.updateHoverStateTimeout_);
|
| + this.updateHoverStateTimeout_ =
|
| + setTimeout(this.updateHoverState_.bind(this, state), 1000);
|
| + },
|
| +
|
| + /**
|
| + * Clears a timeout for updating icon hover state if there is one set.
|
| + * @private
|
| + */
|
| + clearUpdateHoverStateTimeout_: function() {
|
| + if (this.updateHoverStateTimeout_) {
|
| + clearTimeout(this.updateHoverStateTimeout_);
|
| + this.updateHoverStateTimeout_ = null;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Changes the tooltip autoshow state and changes tooltip visibility if
|
| + * needed.
|
| + * @param {!UserPodCustomIcon.TooltipAutoshowState} state
|
| + * @private
|
| + */
|
| + updateTooltipAutoshowState_: function(state) {
|
| + this.clearUpdateTooltipAutoshowStateTimeout_();
|
| + this.sanitizeTooltipStateIfBubbleHidden_();
|
| +
|
| + if (state == UserPodCustomIcon.TooltipAutoshowState.DISABLED) {
|
| + if (this.tooltipState_.autoshow != state) {
|
| + this.tooltipState_.autoshow = state;
|
| + this.updateTooltip_();
|
| + }
|
| + return;
|
| + }
|
| +
|
| + if (this.tooltipState_.active()) {
|
| + if (this.tooltipState_.autoshow !=
|
| + UserPodCustomIcon.TooltipAutoshowState.ACTIVE) {
|
| + this.tooltipState_.autoshow =
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED;
|
| + } else {
|
| + // If the tooltip is already automatically shown, the timeout for
|
| + // removing it should be reset.
|
| + this.updateTooltipAutoshowStateSoon_(
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED);
|
| + }
|
| + return;
|
| + }
|
| +
|
| + if (state == UserPodCustomIcon.TooltipAutoshowState.ENABLED) {
|
| + this.updateTooltipAutoshowStateSoon_(
|
| + UserPodCustomIcon.TooltipAutoshowState.ACTIVE);
|
| + } else if (state == UserPodCustomIcon.TooltipAutoshowState.ACTIVE) {
|
| + this.updateTooltipAutoshowStateSoon_(
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED);
|
| + }
|
| +
|
| + this.tooltipState_.autoshow = state;
|
| + this.updateTooltip_();
|
| + },
|
| +
|
| + /**
|
| + * Sets up a timeout for updating tooltip autoshow state.
|
| + * @param {!UserPodCustomIcon.TooltipAutoshowState} state
|
| + * @private
|
| + */
|
| + updateTooltipAutoshowStateSoon_: function(state) {
|
| + if (this.updateTooltipAutoshowStateTimeout_)
|
| + clearTimeout(this.updateTooltupAutoshowStateTimeout_);
|
| + var timeout =
|
| + state == UserPodCustomIcon.TooltipAutoshowState.DISABLED ?
|
| + 5000 : 1000;
|
| + this.updateTooltipAutoshowStateTimeout_ =
|
| + setTimeout(this.updateTooltipAutoshowState_.bind(this, state),
|
| + timeout);
|
| + },
|
| +
|
| + /**
|
| + * Clears the timeout for updating tooltip autoshow state if one is set.
|
| + * @private
|
| + */
|
| + clearUpdateTooltipAutoshowStateTimeout_: function() {
|
| + if (this.updateTooltipAutoshowStateTimeout_) {
|
| + clearTimeout(this.updateTooltipAutoshowStateTimeout_);
|
| + this.updateTooltipAutoshowStateTimeout_ = null;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * If tooltip bubble is hidden, this makes sure that hover and tooltip
|
| + * autoshow states are not the ones that imply an active tooltip.
|
| + * Used to handle a case where the tooltip bubble is hidden by an event that
|
| + * does not update one of the states (e.g. click outside the pod will not
|
| + * update tooltip autoshow state). Should be called before making
|
| + * tooltip state updates.
|
| + * @private
|
| + */
|
| + sanitizeTooltipStateIfBubbleHidden_: function() {
|
| + if (!$('bubble').hidden)
|
| + return;
|
| +
|
| + if (this.tooltipState_.hover ==
|
| + UserPodCustomIcon.HoverState.HOVER_TOOLTIP &&
|
| + this.tooltipState_.text) {
|
| + this.tooltipState_.hover = UserPodCustomIcon.HoverState.NO_HOVER;
|
| + this.clearUpdateHoverStateTimeout_();
|
| + }
|
| +
|
| + if (this.tooltipState_.autoshow ==
|
| + UserPodCustomIcon.TooltipAutoshowState.ACTIVE) {
|
| + this.tooltipState_.autoshow =
|
| + UserPodCustomIcon.TooltipAutoshowState.DISABLED;
|
| + this.clearUpdateTooltipAutoshowStateTimeout_();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Returns whether the user pod to which the custom icon belongs is focused.
|
| + * @return {boolean}
|
| + * @private
|
| + */
|
| + isParentPodFocused_: function() {
|
| + if ($('account-picker').hidden)
|
| + return false;
|
| + var parentPod = this.parentNode;
|
| + while (parentPod && !parentPod.classList.contains('pod'))
|
| + parentPod = parentPod.parentNode;
|
| + return parentPod && parentPod.parentNode.isFocused(parentPod);
|
| + },
|
| +
|
| + /**
|
| + * Depending on {@code this.tooltipState_}, it updates tooltip visibility
|
| + * and text.
|
| + * @private
|
| + */
|
| + updateTooltip_: function() {
|
| + if (this.hidden || !this.isParentPodFocused_())
|
| + return;
|
| +
|
| + if (!this.tooltipState_.active() || !this.tooltipState_.text) {
|
| + this.hideTooltip_();
|
| + return;
|
| + }
|
| +
|
| + // Show the tooltip bubble.
|
| + var bubbleContent = document.createElement('div');
|
| + bubbleContent.textContent = this.tooltipState_.text;
|
| +
|
| + /** @const */ var BUBBLE_OFFSET = CUSTOM_ICON_CONTAINER_SIZE / 2;
|
| + // TODO(tengs): Introduce a special reauth state for the account picker,
|
| + // instead of showing the tooltip bubble here (crbug.com/409427).
|
| + /** @const */ var BUBBLE_PADDING = 8 + (this.iconId_ ? 0 : 23);
|
| + $('bubble').showContentForElement(this,
|
| + cr.ui.Bubble.Attachment.LEFT,
|
| + bubbleContent,
|
| + BUBBLE_OFFSET,
|
| + BUBBLE_PADDING);
|
| + },
|
| +
|
| + /**
|
| + * Hides the tooltip.
|
| + * @private
|
| + */
|
| + hideTooltip_: function() {
|
| + $('bubble').hideForElement(this);
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Unique salt added to user image URLs to prevent caching. Dictionary with
|
| + * user names as keys.
|
| + * @type {Object}
|
| + */
|
| + UserPod.userImageSalt_ = {};
|
| +
|
| + UserPod.prototype = {
|
| + __proto__: HTMLDivElement.prototype,
|
| +
|
| + /**
|
| + * Whether click on the pod can issue a user click auth attempt. The
|
| + * attempt can be issued iff the pod was focused when the click
|
| + * started (i.e. on mouse down event).
|
| + * @type {boolean}
|
| + * @private
|
| + */
|
| + userClickAuthAllowed_: false,
|
| +
|
| + /**
|
| + * Whether the user has recently authenticated with fingerprint.
|
| + * @type {boolean}
|
| + * @private
|
| + */
|
| + fingerprintAuthenticated_: false,
|
| +
|
| + /**
|
| + * True iff the pod can display the pin keyboard. The pin keyboard may not
|
| + * always be displayed even if this is true, ie, if the virtual keyboard is
|
| + * also being displayed.
|
| + */
|
| + pinEnabled: false,
|
| +
|
| + /** @override */
|
| + decorate: function() {
|
| + this.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + this.actionBoxAreaElement.tabIndex = UserPodTabOrder.POD_INPUT;
|
| +
|
| + this.addEventListener('keydown', this.handlePodKeyDown_.bind(this));
|
| + this.addEventListener('click', this.handleClickOnPod_.bind(this));
|
| + this.addEventListener('mousedown', this.handlePodMouseDown_.bind(this));
|
| +
|
| + if (this.pinKeyboard) {
|
| + this.pinKeyboard.passwordElement = this.passwordElement;
|
| + this.pinKeyboard.addEventListener('pin-change',
|
| + this.handleInputChanged_.bind(this));
|
| + this.pinKeyboard.tabIndex = UserPodTabOrder.PIN_KEYBOARD;
|
| + }
|
| +
|
| + this.actionBoxAreaElement.addEventListener('mousedown',
|
| + stopEventPropagation);
|
| + this.actionBoxAreaElement.addEventListener('click',
|
| + this.handleActionAreaButtonClick_.bind(this));
|
| + this.actionBoxAreaElement.addEventListener('keydown',
|
| + this.handleActionAreaButtonKeyDown_.bind(this));
|
| +
|
| + this.actionBoxMenuTitleElement.addEventListener('keydown',
|
| + this.handleMenuTitleElementKeyDown_.bind(this));
|
| + this.actionBoxMenuTitleElement.addEventListener('blur',
|
| + this.handleMenuTitleElementBlur_.bind(this));
|
| +
|
| + this.actionBoxMenuRemoveElement.addEventListener('click',
|
| + this.handleRemoveCommandClick_.bind(this));
|
| + this.actionBoxMenuRemoveElement.addEventListener('keydown',
|
| + this.handleRemoveCommandKeyDown_.bind(this));
|
| + this.actionBoxMenuRemoveElement.addEventListener('blur',
|
| + this.handleRemoveCommandBlur_.bind(this));
|
| + this.actionBoxRemoveUserWarningButtonElement.addEventListener('click',
|
| + this.handleRemoveUserConfirmationClick_.bind(this));
|
| + this.actionBoxRemoveUserWarningButtonElement.addEventListener('keydown',
|
| + this.handleRemoveUserConfirmationKeyDown_.bind(this));
|
| +
|
| + if (this.fingerprintIconElement) {
|
| + this.fingerprintIconElement.addEventListener(
|
| + 'mouseover', this.handleFingerprintIconMouseOver_.bind(this));
|
| + this.fingerprintIconElement.addEventListener(
|
| + 'mouseout', this.handleFingerprintIconMouseOut_.bind(this));
|
| + this.fingerprintIconElement.addEventListener(
|
| + 'mousedown', stopEventPropagation);
|
| + }
|
| +
|
| + var customIcon = this.customIconElement;
|
| + customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon);
|
| + },
|
| +
|
| + /**
|
| + * Initializes the pod after its properties set and added to a pod row.
|
| + */
|
| + initialize: function() {
|
| + this.passwordElement.addEventListener('keydown',
|
| + this.parentNode.handleKeyDown.bind(this.parentNode));
|
| + this.passwordElement.addEventListener('keypress',
|
| + this.handlePasswordKeyPress_.bind(this));
|
| + this.passwordElement.addEventListener('input',
|
| + this.handleInputChanged_.bind(this));
|
| + this.passwordElement.addEventListener('mouseup',
|
| + this.handleInputMouseUp_.bind(this));
|
| +
|
| + if (this.submitButton) {
|
| + this.submitButton.addEventListener('click',
|
| + this.handleSubmitButtonClick_.bind(this));
|
| + }
|
| +
|
| + this.imageElement.addEventListener('load',
|
| + this.parentNode.handlePodImageLoad.bind(this.parentNode, this));
|
| +
|
| + var initialAuthType = this.user.initialAuthType ||
|
| + AUTH_TYPE.OFFLINE_PASSWORD;
|
| + this.setAuthType(initialAuthType, null);
|
| +
|
| + if (this.user.isActiveDirectory)
|
| + this.setAttribute('is-active-directory', '');
|
| +
|
| + this.userClickAuthAllowed_ = false;
|
| +
|
| + // Lazy load the assets needed for the polymer submit button.
|
| + var isLockScreen = (Oobe.getInstance().displayType == DISPLAY_TYPE.LOCK);
|
| + if (cr.isChromeOS && isLockScreen &&
|
| + !cr.ui.login.ResourceLoader.alreadyLoadedAssets(
|
| + 'custom-elements-user-pod')) {
|
| + cr.ui.login.ResourceLoader.registerAssets({
|
| + id: 'custom-elements-user-pod',
|
| + html: [{ url: 'custom_elements_user_pod.html' }]
|
| + });
|
| + cr.ui.login.ResourceLoader.loadAssetsOnIdle('custom-elements-user-pod');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Whether the user pod is disabled.
|
| + * @type {boolean}
|
| + */
|
| + disabled_: false,
|
| + get disabled() {
|
| + return this.disabled_;
|
| + },
|
| + set disabled(value) {
|
| + this.disabled_ = value;
|
| + this.querySelectorAll('button,input').forEach(function(element) {
|
| + element.disabled = value
|
| + });
|
| +
|
| + // Special handling for submit button - the submit button should be
|
| + // enabled only if there is the password value set.
|
| + var submitButton = this.submitButton;
|
| + if (submitButton)
|
| + submitButton.disabled = value || !this.passwordElement.value;
|
| + },
|
| +
|
| + /**
|
| + * Resets tab order for pod elements to its initial state.
|
| + */
|
| + resetTabOrder: function() {
|
| + // Note: the |mainInput| can be the pod itself.
|
| + this.mainInput.tabIndex = -1;
|
| + this.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + },
|
| +
|
| + /**
|
| + * Handles keypress event (i.e. any textual input) on password input.
|
| + * @param {Event} e Keypress Event object.
|
| + * @private
|
| + */
|
| + handlePasswordKeyPress_: function(e) {
|
| + // When tabbing from the system tray a tab key press is received. Suppress
|
| + // this so as not to type a tab character into the password field.
|
| + if (e.keyCode == 9) {
|
| + e.preventDefault();
|
| + return;
|
| + }
|
| + this.customIconElement.cancelDelayedTooltipShow();
|
| + },
|
| +
|
| + /**
|
| + * Handles a click event on submit button.
|
| + * @param {Event} e Click event.
|
| + */
|
| + handleSubmitButtonClick_: function(e) {
|
| + this.parentNode.setActivatedPod(this, e);
|
| + },
|
| +
|
| + /**
|
| + * Top edge margin number of pixels.
|
| + * @type {?number}
|
| + */
|
| + set top(top) {
|
| + this.style.top = cr.ui.toCssPx(top);
|
| + },
|
| +
|
| + /**
|
| + * Top edge margin number of pixels.
|
| + */
|
| + get top() {
|
| + return parseInt(this.style.top);
|
| + },
|
| +
|
| + /**
|
| + * Left edge margin number of pixels.
|
| + * @type {?number}
|
| + */
|
| + set left(left) {
|
| + this.style.left = cr.ui.toCssPx(left);
|
| + },
|
| +
|
| + /**
|
| + * Left edge margin number of pixels.
|
| + */
|
| + get left() {
|
| + return parseInt(this.style.left);
|
| + },
|
| +
|
| + /**
|
| + * Height number of pixels.
|
| + */
|
| + get height() {
|
| + return this.offsetHeight;
|
| + },
|
| +
|
| + /**
|
| + * Gets image element.
|
| + * @type {!HTMLImageElement}
|
| + */
|
| + get imageElement() {
|
| + return this.querySelector('.user-image');
|
| + },
|
| +
|
| + /**
|
| + * Gets name element.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get nameElement() {
|
| + return this.querySelector('.name');
|
| + },
|
| +
|
| + /**
|
| + * Gets reauth name hint element.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get reauthNameHintElement() {
|
| + return this.querySelector('.reauth-name-hint');
|
| + },
|
| +
|
| + /**
|
| + * Gets the container holding the password field.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get passwordEntryContainerElement() {
|
| + return this.querySelector('.password-entry-container');
|
| + },
|
| +
|
| + /**
|
| + * Gets password field.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get passwordElement() {
|
| + return this.querySelector('.password');
|
| + },
|
| +
|
| + /**
|
| + * Gets submit button.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get submitButton() {
|
| + return this.querySelector('.submit-button');
|
| + },
|
| +
|
| + /**
|
| + * Gets the password label, which is used to show a message where the
|
| + * password field is normally.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get passwordLabelElement() {
|
| + return this.querySelector('.password-label');
|
| + },
|
| +
|
| + get pinContainer() {
|
| + return this.querySelector('.pin-container');
|
| + },
|
| +
|
| + /**
|
| + * Gets the pin-keyboard of the pod.
|
| + * @type {!HTMLElement}
|
| + */
|
| + get pinKeyboard() {
|
| + return this.querySelector('pin-keyboard');
|
| + },
|
| +
|
| + /**
|
| + * Gets user online sign in hint element.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get reauthWarningElement() {
|
| + return this.querySelector('.reauth-hint-container');
|
| + },
|
| +
|
| + /**
|
| + * Gets the container holding the launch app button.
|
| + * @type {!HTMLButtonElement}
|
| + */
|
| + get launchAppButtonContainerElement() {
|
| + return this.querySelector('.launch-app-button-container');
|
| + },
|
| +
|
| + /**
|
| + * Gets launch app button.
|
| + * @type {!HTMLButtonElement}
|
| + */
|
| + get launchAppButtonElement() {
|
| + return this.querySelector('.launch-app-button');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box area.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get actionBoxAreaElement() {
|
| + return this.querySelector('.action-box-area');
|
| + },
|
| +
|
| + /**
|
| + * Gets user type icon area.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get userTypeIconAreaElement() {
|
| + return this.querySelector('.user-type-icon-area');
|
| + },
|
| +
|
| + /**
|
| + * Gets user type bubble like multi-profiles policy restriction message.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get userTypeBubbleElement() {
|
| + return this.querySelector('.user-type-bubble');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get actionBoxMenu() {
|
| + return this.querySelector('.action-box-menu');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu title (user name and email).
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get actionBoxMenuTitleElement() {
|
| + return this.querySelector('.action-box-menu-title');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu title, user name item.
|
| + * @type {!HTMLSpanElement}
|
| + */
|
| + get actionBoxMenuTitleNameElement() {
|
| + return this.querySelector('.action-box-menu-title-name');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu title, user email item.
|
| + * @type {!HTMLSpanElement}
|
| + */
|
| + get actionBoxMenuTitleEmailElement() {
|
| + return this.querySelector('.action-box-menu-title-email');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu, remove user command item.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get actionBoxMenuCommandElement() {
|
| + return this.querySelector('.action-box-menu-remove-command');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu, remove user command item div.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get actionBoxMenuRemoveElement() {
|
| + return this.querySelector('.action-box-menu-remove');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu, remove user command item div.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get actionBoxRemoveUserWarningElement() {
|
| + return this.querySelector('.action-box-remove-user-warning');
|
| + },
|
| +
|
| + /**
|
| + * Gets action box menu, remove user command item div.
|
| + * @type {!HTMLInputElement}
|
| + */
|
| + get actionBoxRemoveUserWarningButtonElement() {
|
| + return this.querySelector('.remove-warning-button');
|
| + },
|
| +
|
| + /**
|
| + * Gets the custom icon. This icon is normally hidden, but can be shown
|
| + * using the chrome.screenlockPrivate API.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get customIconElement() {
|
| + return this.querySelector('.custom-icon-container');
|
| + },
|
| +
|
| + /**
|
| + * Gets the elements used for statistics display.
|
| + * @type {Object.<string, !HTMLDivElement>}
|
| + */
|
| + get statsMapElements() {
|
| + return {
|
| + 'BrowsingHistory':
|
| + this.querySelector('.action-box-remove-user-warning-history'),
|
| + 'Passwords':
|
| + this.querySelector('.action-box-remove-user-warning-passwords'),
|
| + 'Bookmarks':
|
| + this.querySelector('.action-box-remove-user-warning-bookmarks'),
|
| + 'Settings':
|
| + this.querySelector('.action-box-remove-user-warning-settings')
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Gets the fingerprint icon area.
|
| + * @type {!HTMLDivElement}
|
| + */
|
| + get fingerprintIconElement() {
|
| + return this.querySelector('.fingerprint-icon-container');
|
| + },
|
| +
|
| + /**
|
| + * Updates the user pod element.
|
| + */
|
| + update: function() {
|
| + this.imageElement.src = 'chrome://userimage/' + this.user.username +
|
| + '?id=' + UserPod.userImageSalt_[this.user.username];
|
| +
|
| + this.nameElement.textContent = this.user_.displayName;
|
| + this.reauthNameHintElement.textContent = this.user_.displayName;
|
| + this.classList.toggle('signed-in', this.user_.signedIn);
|
| +
|
| + if (this.isAuthTypeUserClick)
|
| + this.passwordLabelElement.textContent = this.authValue;
|
| +
|
| + this.updateActionBoxArea();
|
| +
|
| + this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
|
| + 'passwordFieldAccessibleName', this.user_.emailAddress));
|
| +
|
| + this.customizeUserPodPerUserType();
|
| + },
|
| +
|
| + updateActionBoxArea: function() {
|
| + if (this.user_.publicAccount || this.user_.isApp) {
|
| + this.actionBoxAreaElement.hidden = true;
|
| + return;
|
| + }
|
| +
|
| + this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
|
| +
|
| + this.actionBoxAreaElement.setAttribute(
|
| + 'aria-label', loadTimeData.getStringF(
|
| + 'podMenuButtonAccessibleName', this.user_.emailAddress));
|
| + this.actionBoxMenuRemoveElement.setAttribute(
|
| + 'aria-label', loadTimeData.getString(
|
| + 'podMenuRemoveItemAccessibleName'));
|
| + this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ?
|
| + loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) :
|
| + this.user_.displayName;
|
| + this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress;
|
| +
|
| + this.actionBoxMenuTitleEmailElement.hidden =
|
| + this.user_.legacySupervisedUser;
|
| +
|
| + this.actionBoxMenuCommandElement.textContent =
|
| + loadTimeData.getString('removeUser');
|
| + },
|
| +
|
| + customizeUserPodPerUserType: function() {
|
| + if (this.user_.childUser && !this.user_.isDesktopUser) {
|
| + this.setUserPodIconType('child');
|
| + } else if (this.user_.legacySupervisedUser && !this.user_.isDesktopUser) {
|
| + this.setUserPodIconType('legacySupervised');
|
| + this.classList.add('legacy-supervised');
|
| + } else if (this.multiProfilesPolicyApplied) {
|
| + // Mark user pod as not focusable which in addition to the grayed out
|
| + // filter makes it look in disabled state.
|
| + this.classList.add('multiprofiles-policy-applied');
|
| + this.setUserPodIconType('policy');
|
| +
|
| + if (this.user.multiProfilesPolicy == 'primary-only')
|
| + this.querySelector('.mp-policy-primary-only-msg').hidden = false;
|
| + else if (this.user.multiProfilesPolicy == 'owner-primary-only')
|
| + this.querySelector('.mp-owner-primary-only-msg').hidden = false;
|
| + else
|
| + this.querySelector('.mp-policy-not-allowed-msg').hidden = false;
|
| + } else if (this.user_.isApp) {
|
| + this.setUserPodIconType('app');
|
| + }
|
| + },
|
| +
|
| + isPinReady: function() {
|
| + return this.pinKeyboard && this.pinKeyboard.offsetHeight > 0;
|
| + },
|
| +
|
| + set showError(visible) {
|
| + if (this.submitButton)
|
| + this.submitButton.classList.toggle('error-shown', visible);
|
| + },
|
| +
|
| + updatePinClass_: function(element, enable) {
|
| + element.classList.toggle('pin-enabled', enable);
|
| + element.classList.toggle('pin-disabled', !enable);
|
| + },
|
| +
|
| + setPinVisibility: function(visible) {
|
| + if (this.isPinShown() == visible)
|
| + return;
|
| +
|
| + // Do not show pin if virtual keyboard is there.
|
| + if (visible && Oobe.getInstance().virtualKeyboardShown)
|
| + return;
|
| +
|
| + // Do not show pin keyboard if the pod does not have pin enabled.
|
| + if (visible && !this.pinEnabled)
|
| + return;
|
| +
|
| + var elements = this.getElementsByClassName('pin-tag');
|
| + for (var i = 0; i < elements.length; ++i)
|
| + this.updatePinClass_(elements[i], visible);
|
| + this.updatePinClass_(this, visible);
|
| +
|
| + // Set the focus to the input element after showing/hiding pin keyboard.
|
| + this.mainInput.focus();
|
| +
|
| + // Change the password placeholder based on pin keyboard visibility.
|
| + this.passwordElement.placeholder = loadTimeData.getString(visible ?
|
| + 'pinKeyboardPlaceholderPinPassword' : 'passwordHint');
|
| +
|
| + chrome.send('setForceDisableVirtualKeyboard', [visible]);
|
| + },
|
| +
|
| + isPinShown: function() {
|
| + return this.classList.contains('pin-enabled');
|
| + },
|
| +
|
| + setUserPodIconType: function(userTypeClass) {
|
| + this.userTypeIconAreaElement.classList.add(userTypeClass);
|
| + this.userTypeIconAreaElement.hidden = false;
|
| + },
|
| +
|
| + isFingerprintIconShown: function() {
|
| + return this.fingerprintIconElement && !this.fingerprintIconElement.hidden;
|
| + },
|
| +
|
| + /**
|
| + * The user that this pod represents.
|
| + * @type {!Object}
|
| + */
|
| + user_: undefined,
|
| + get user() {
|
| + return this.user_;
|
| + },
|
| + set user(userDict) {
|
| + this.user_ = userDict;
|
| + this.update();
|
| + },
|
| +
|
| + /**
|
| + * Returns true if multi-profiles sign in is currently active and this
|
| + * user pod is restricted per policy.
|
| + * @type {boolean}
|
| + */
|
| + get multiProfilesPolicyApplied() {
|
| + var isMultiProfilesUI =
|
| + (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING);
|
| + return isMultiProfilesUI && !this.user_.isMultiProfilesAllowed;
|
| + },
|
| +
|
| + /**
|
| + * Gets main input element.
|
| + * @type {(HTMLButtonElement|HTMLInputElement)}
|
| + */
|
| + get mainInput() {
|
| + if (this.isAuthTypePassword) {
|
| + return this.passwordElement;
|
| + } else if (this.isAuthTypeOnlineSignIn) {
|
| + return this;
|
| + } else if (this.isAuthTypeUserClick) {
|
| + return this.passwordLabelElement;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Whether action box button is in active state.
|
| + * @type {boolean}
|
| + */
|
| + get isActionBoxMenuActive() {
|
| + return this.actionBoxAreaElement.classList.contains('active');
|
| + },
|
| + set isActionBoxMenuActive(active) {
|
| + if (active == this.isActionBoxMenuActive)
|
| + return;
|
| +
|
| + if (active) {
|
| + this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove;
|
| + this.actionBoxRemoveUserWarningElement.hidden = true;
|
| +
|
| + // Clear focus first if another pod is focused.
|
| + if (!this.parentNode.isFocused(this)) {
|
| + this.parentNode.focusPod(undefined, true);
|
| + this.actionBoxAreaElement.focus();
|
| + }
|
| +
|
| + // Hide user-type-bubble.
|
| + this.userTypeBubbleElement.classList.remove('bubble-shown');
|
| +
|
| + this.actionBoxAreaElement.classList.add('active');
|
| +
|
| + // Invisible focus causes ChromeVox to read user name and email.
|
| + this.actionBoxMenuTitleElement.tabIndex = UserPodTabOrder.POD_MENU_ITEM;
|
| + this.actionBoxMenuTitleElement.focus();
|
| +
|
| + // If the user pod is on either edge of the screen, then the menu
|
| + // could be displayed partially ofscreen.
|
| + this.actionBoxMenu.classList.remove('left-edge-offset');
|
| + this.actionBoxMenu.classList.remove('right-edge-offset');
|
| +
|
| + var offsetLeft =
|
| + cr.ui.login.DisplayManager.getOffset(this.actionBoxMenu).left;
|
| + var menuWidth = this.actionBoxMenu.offsetWidth;
|
| + if (offsetLeft < 0)
|
| + this.actionBoxMenu.classList.add('left-edge-offset');
|
| + else if (offsetLeft + menuWidth > window.innerWidth)
|
| + this.actionBoxMenu.classList.add('right-edge-offset');
|
| + } else {
|
| + this.actionBoxAreaElement.classList.remove('active');
|
| + this.actionBoxAreaElement.classList.remove('menu-moved-up');
|
| + this.actionBoxMenu.classList.remove('menu-moved-up');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Whether action box button is in hovered state.
|
| + * @type {boolean}
|
| + */
|
| + get isActionBoxMenuHovered() {
|
| + return this.actionBoxAreaElement.classList.contains('hovered');
|
| + },
|
| + set isActionBoxMenuHovered(hovered) {
|
| + if (hovered == this.isActionBoxMenuHovered)
|
| + return;
|
| +
|
| + if (hovered) {
|
| + this.actionBoxAreaElement.classList.add('hovered');
|
| + this.classList.add('hovered');
|
| + } else {
|
| + if (this.multiProfilesPolicyApplied)
|
| + this.userTypeBubbleElement.classList.remove('bubble-shown');
|
| + this.actionBoxAreaElement.classList.remove('hovered');
|
| + this.classList.remove('hovered');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Set the authentication type for the pod.
|
| + * @param {number} An auth type value defined in the AUTH_TYPE enum.
|
| + * @param {string} authValue The initial value used for the auth type.
|
| + */
|
| + setAuthType: function(authType, authValue) {
|
| + this.authType_ = authType;
|
| + this.authValue_ = authValue;
|
| + this.setAttribute('auth-type', AUTH_TYPE_NAMES[this.authType_]);
|
| + this.update();
|
| + this.reset(this.parentNode.isFocused(this));
|
| + },
|
| +
|
| + /**
|
| + * The auth type of the user pod. This value is one of the enum
|
| + * values in AUTH_TYPE.
|
| + * @type {number}
|
| + */
|
| + get authType() {
|
| + return this.authType_;
|
| + },
|
| +
|
| + /**
|
| + * The initial value used for the pod's authentication type.
|
| + * eg. a prepopulated password input when using password authentication.
|
| + */
|
| + get authValue() {
|
| + return this.authValue_;
|
| + },
|
| +
|
| + /**
|
| + * True if the the user pod uses a password to authenticate.
|
| + * @type {bool}
|
| + */
|
| + get isAuthTypePassword() {
|
| + return this.authType_ == AUTH_TYPE.OFFLINE_PASSWORD ||
|
| + this.authType_ == AUTH_TYPE.FORCE_OFFLINE_PASSWORD;
|
| + },
|
| +
|
| + /**
|
| + * True if the the user pod uses a user click to authenticate.
|
| + * @type {bool}
|
| + */
|
| + get isAuthTypeUserClick() {
|
| + return this.authType_ == AUTH_TYPE.USER_CLICK;
|
| + },
|
| +
|
| + /**
|
| + * True if the the user pod uses a online sign in to authenticate.
|
| + * @type {bool}
|
| + */
|
| + get isAuthTypeOnlineSignIn() {
|
| + return this.authType_ == AUTH_TYPE.ONLINE_SIGN_IN;
|
| + },
|
| +
|
| + /**
|
| + * Updates the image element of the user.
|
| + */
|
| + updateUserImage: function() {
|
| + UserPod.userImageSalt_[this.user.username] = new Date().getTime();
|
| + this.update();
|
| + },
|
| +
|
| + /**
|
| + * Focuses on input element.
|
| + */
|
| + focusInput: function() {
|
| + // Move tabIndex from the whole pod to the main input.
|
| + // Note: the |mainInput| can be the pod itself.
|
| + this.tabIndex = -1;
|
| + this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + this.mainInput.focus();
|
| + },
|
| +
|
| + /**
|
| + * Activates the pod.
|
| + * @param {Event} e Event object.
|
| + * @return {boolean} True if activated successfully.
|
| + */
|
| + activate: function(e) {
|
| + if (this.isAuthTypeOnlineSignIn) {
|
| + this.showSigninUI();
|
| + } else if (this.isAuthTypeUserClick) {
|
| + Oobe.disableSigninUI();
|
| + this.classList.toggle('signing-in', true);
|
| + chrome.send('attemptUnlock', [this.user.username]);
|
| + } else if (this.isAuthTypePassword) {
|
| + if (this.fingerprintAuthenticated_) {
|
| + this.fingerprintAuthenticated_ = false;
|
| + return true;
|
| + }
|
| + var pinValue = this.pinKeyboard ? this.pinKeyboard.value : '';
|
| + var password = this.passwordElement.value || pinValue;
|
| + if (!password)
|
| + return false;
|
| + Oobe.disableSigninUI();
|
| + chrome.send('authenticateUser', [this.user.username, password,
|
| + this.isPinShown()]);
|
| + } else {
|
| + console.error('Activating user pod with invalid authentication type: ' +
|
| + this.authType);
|
| + }
|
| +
|
| + return true;
|
| + },
|
| +
|
| + showSupervisedUserSigninWarning: function() {
|
| + // Legacy supervised user token has been invalidated.
|
| + // Make sure that pod is focused i.e. "Sign in" button is seen.
|
| + this.parentNode.focusPod(this);
|
| +
|
| + var error = document.createElement('div');
|
| + var messageDiv = document.createElement('div');
|
| + messageDiv.className = 'error-message-bubble';
|
| + messageDiv.textContent =
|
| + loadTimeData.getString('supervisedUserExpiredTokenWarning');
|
| + error.appendChild(messageDiv);
|
| +
|
| + $('bubble').showContentForElement(
|
| + this.reauthWarningElement,
|
| + cr.ui.Bubble.Attachment.TOP,
|
| + error,
|
| + this.reauthWarningElement.offsetWidth / 2,
|
| + 4);
|
| + // Move warning bubble up if it overlaps the shelf.
|
| + var maxHeight =
|
| + cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping($('bubble'));
|
| + if (maxHeight < $('bubble').offsetHeight) {
|
| + $('bubble').showContentForElement(
|
| + this.reauthWarningElement,
|
| + cr.ui.Bubble.Attachment.BOTTOM,
|
| + error,
|
| + this.reauthWarningElement.offsetWidth / 2,
|
| + 4);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Shows signin UI for this user.
|
| + */
|
| + showSigninUI: function() {
|
| + if (this.user.legacySupervisedUser && !this.user.isDesktopUser) {
|
| + this.showSupervisedUserSigninWarning();
|
| + } else {
|
| + // Special case for multi-profiles sign in. We show users even if they
|
| + // are not allowed per policy. Restrict those users from starting GAIA.
|
| + if (this.multiProfilesPolicyApplied)
|
| + return;
|
| +
|
| + this.parentNode.showSigninUI(this.user.emailAddress);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Resets the input field and updates the tab order of pod controls.
|
| + * @param {boolean} takeFocus If true, input field takes focus.
|
| + */
|
| + reset: function(takeFocus) {
|
| + this.passwordElement.value = '';
|
| + if (this.pinKeyboard)
|
| + this.pinKeyboard.value = '';
|
| + this.updateInput_();
|
| + this.classList.toggle('signing-in', false);
|
| + if (takeFocus) {
|
| + if (!this.multiProfilesPolicyApplied)
|
| + this.focusInput(); // This will set a custom tab order.
|
| + }
|
| + else
|
| + this.resetTabOrder();
|
| + },
|
| +
|
| + /**
|
| + * Removes a user using the correct identifier based on user type.
|
| + * @param {Object} user User to be removed.
|
| + */
|
| + removeUser: function(user) {
|
| + chrome.send('removeUser',
|
| + [user.isDesktopUser ? user.profilePath : user.username]);
|
| + },
|
| +
|
| + /**
|
| + * Handles a click event on action area button.
|
| + * @param {Event} e Click event.
|
| + */
|
| + handleActionAreaButtonClick_: function(e) {
|
| + if (this.parentNode.disabled)
|
| + return;
|
| + this.isActionBoxMenuActive = !this.isActionBoxMenuActive;
|
| + e.stopPropagation();
|
| + },
|
| +
|
| + /**
|
| + * Handles a keydown event on action area button.
|
| + * @param {Event} e KeyDown event.
|
| + */
|
| + handleActionAreaButtonKeyDown_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + switch (e.key) {
|
| + case 'Enter':
|
| + case ' ':
|
| + if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive)
|
| + this.isActionBoxMenuActive = true;
|
| + e.stopPropagation();
|
| + break;
|
| + case 'ArrowUp':
|
| + case 'ArrowDown':
|
| + if (this.isActionBoxMenuActive) {
|
| + this.actionBoxMenuRemoveElement.tabIndex =
|
| + UserPodTabOrder.POD_MENU_ITEM;
|
| + this.actionBoxMenuRemoveElement.focus();
|
| + }
|
| + e.stopPropagation();
|
| + break;
|
| + // Ignore these two, so ChromeVox hotkeys don't close the menu before
|
| + // they can navigate through it.
|
| + case 'Shift':
|
| + case 'Meta':
|
| + break;
|
| + case 'Escape':
|
| + this.actionBoxAreaElement.focus();
|
| + this.isActionBoxMenuActive = false;
|
| + e.stopPropagation();
|
| + break;
|
| + case 'Tab':
|
| + if (!this.parentNode.alwaysFocusSinglePod)
|
| + this.parentNode.focusPod();
|
| + default:
|
| + this.isActionBoxMenuActive = false;
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles a keydown event on menu title.
|
| + * @param {Event} e KeyDown event.
|
| + */
|
| + handleMenuTitleElementKeyDown_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| +
|
| + if (e.key != 'Tab') {
|
| + this.handleActionAreaButtonKeyDown_(e);
|
| + return;
|
| + }
|
| +
|
| + if (e.shiftKey == false) {
|
| + if (this.actionBoxMenuRemoveElement.hidden) {
|
| + this.isActionBoxMenuActive = false;
|
| + } else {
|
| + this.actionBoxMenuRemoveElement.tabIndex =
|
| + UserPodTabOrder.POD_MENU_ITEM;
|
| + this.actionBoxMenuRemoveElement.focus();
|
| + e.preventDefault();
|
| + }
|
| + } else {
|
| + this.isActionBoxMenuActive = false;
|
| + this.focusInput();
|
| + e.preventDefault();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles a blur event on menu title.
|
| + * @param {Event} e Blur event.
|
| + */
|
| + handleMenuTitleElementBlur_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + this.actionBoxMenuTitleElement.tabIndex = -1;
|
| + },
|
| +
|
| + /**
|
| + * Handles a click event on remove user command.
|
| + * @param {Event} e Click event.
|
| + */
|
| + handleRemoveCommandClick_: function(e) {
|
| + this.showRemoveWarning_();
|
| + },
|
| +
|
| + /**
|
| + * Move the action box menu up if needed.
|
| + */
|
| + moveActionMenuUpIfNeeded_: function() {
|
| + // Skip checking (computationally expensive) if already moved up.
|
| + if (this.actionBoxMenu.classList.contains('menu-moved-up'))
|
| + return;
|
| +
|
| + // Move up the menu if it overlaps shelf.
|
| + var maxHeight = cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping(
|
| + this.actionBoxMenu, true);
|
| + var actualHeight = parseInt(
|
| + window.getComputedStyle(this.actionBoxMenu).height);
|
| + if (maxHeight < actualHeight) {
|
| + this.actionBoxMenu.classList.add('menu-moved-up');
|
| + this.actionBoxAreaElement.classList.add('menu-moved-up');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Shows remove user warning. Used for legacy supervised users
|
| + * and non-device-owner on CrOS, and for all users on desktop.
|
| + */
|
| + showRemoveWarning_: function() {
|
| + this.actionBoxMenuRemoveElement.hidden = true;
|
| + this.actionBoxRemoveUserWarningElement.hidden = false;
|
| +
|
| + if (!this.user.isDesktopUser) {
|
| + this.moveActionMenuUpIfNeeded_();
|
| + if (!this.user.legacySupervisedUser) {
|
| + this.querySelector(
|
| + '.action-box-remove-user-warning-text').style.display = 'none';
|
| + this.querySelector(
|
| + '.action-box-remove-user-warning-table-nonsync').style.display
|
| + = 'none';
|
| + var message = loadTimeData.getString('removeNonOwnerUserWarningText');
|
| + this.updateRemoveNonOwnerUserWarningMessage_(this.user.profilePath,
|
| + message);
|
| + }
|
| + } else {
|
| + // Show extra statistics information for desktop users
|
| + this.querySelector(
|
| + '.action-box-remove-non-owner-user-warning-text').hidden = true;
|
| + this.RemoveWarningDialogSetMessage_(true, false);
|
| + // set a global handler for the callback
|
| + window.updateRemoveWarningDialog =
|
| + this.updateRemoveWarningDialog_.bind(this);
|
| + chrome.send('removeUserWarningLoadStats', [this.user.profilePath]);
|
| + }
|
| + chrome.send('logRemoveUserWarningShown');
|
| + },
|
| +
|
| + /**
|
| + * Refresh the statistics in the remove user warning dialog.
|
| + * @param {string} profilePath The filepath of the URL (must be verified).
|
| + * @param {Object} profileStats Statistics associated with profileURL.
|
| + */
|
| + updateRemoveWarningDialog_: function(profilePath, profileStats) {
|
| + if (profilePath !== this.user.profilePath)
|
| + return;
|
| +
|
| + var stats_elements = this.statsMapElements;
|
| + // Update individual statistics
|
| + var hasErrors = false;
|
| + for (var key in profileStats) {
|
| + if (stats_elements.hasOwnProperty(key)) {
|
| + if (profileStats[key].success) {
|
| + this.user.statistics[key] = profileStats[key];
|
| + } else if (!this.user.statistics[key].success) {
|
| + hasErrors = true;
|
| + stats_elements[key].textContent = '';
|
| + }
|
| + }
|
| + }
|
| +
|
| + this.RemoveWarningDialogSetMessage_(false, hasErrors);
|
| + },
|
| +
|
| + /**
|
| + * Set the new message in the dialog.
|
| + * @param {boolean} Whether this is the first output, that requires setting
|
| + * a in-progress message.
|
| + * @param {boolean} Whether any actual query to the statistics have failed.
|
| + * Should be true only if there is an error and the corresponding statistic
|
| + * is also unavailable in ProfileAttributesStorage.
|
| + */
|
| + RemoveWarningDialogSetMessage_: function(isInitial, hasErrors) {
|
| + var stats_elements = this.statsMapElements;
|
| + var total_count = 0;
|
| + var num_stats_loaded = 0;
|
| + for (var key in stats_elements) {
|
| + if (this.user.statistics[key].success) {
|
| + var count = this.user.statistics[key].count;
|
| + stats_elements[key].textContent = count;
|
| + total_count += count;
|
| + num_stats_loaded++;
|
| + }
|
| + }
|
| +
|
| + // this.classList is used for selecting the appropriate dialog.
|
| + if (total_count)
|
| + this.classList.remove('has-no-stats');
|
| +
|
| + var is_synced_user = this.user.emailAddress !== "";
|
| + // Write total number if all statistics are loaded.
|
| + if (num_stats_loaded === Object.keys(stats_elements).length) {
|
| + if (!total_count) {
|
| + this.classList.add('has-no-stats');
|
| + var message = loadTimeData.getString(
|
| + is_synced_user ? 'removeUserWarningTextSyncNoStats' :
|
| + 'removeUserWarningTextNonSyncNoStats');
|
| + this.updateRemoveWarningDialogSetMessage_(this.user.profilePath,
|
| + message);
|
| + } else {
|
| + window.updateRemoveWarningDialogSetMessage =
|
| + this.updateRemoveWarningDialogSetMessage_.bind(this);
|
| + chrome.send('getRemoveWarningDialogMessage',[{
|
| + profilePath: this.user.profilePath,
|
| + isSyncedUser: is_synced_user,
|
| + hasErrors: hasErrors,
|
| + totalCount: total_count
|
| + }]);
|
| + }
|
| + } else if (isInitial) {
|
| + if (!this.user.isProfileLoaded) {
|
| + message = loadTimeData.getString(
|
| + is_synced_user ? 'removeUserWarningTextSyncNoStats' :
|
| + 'removeUserWarningTextNonSyncNoStats');
|
| + this.updateRemoveWarningDialogSetMessage_(this.user.profilePath,
|
| + message);
|
| + } else {
|
| + message = loadTimeData.getString(
|
| + is_synced_user ? 'removeUserWarningTextSyncCalculating' :
|
| + 'removeUserWarningTextNonSyncCalculating');
|
| + substitute = loadTimeData.getString(
|
| + 'removeUserWarningTextCalculating');
|
| + this.updateRemoveWarningDialogSetMessage_(this.user.profilePath,
|
| + message, substitute);
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Refresh the message in the remove user warning dialog.
|
| + * @param {string} profilePath The filepath of the URL (must be verified).
|
| + * @param {string} message The message to be written.
|
| + * @param {number|string=} count The number or string to replace $1 in
|
| + * |message|. Can be omitted if $1 is not present in |message|.
|
| + */
|
| + updateRemoveWarningDialogSetMessage_: function(profilePath, message,
|
| + count) {
|
| + if (profilePath !== this.user.profilePath)
|
| + return;
|
| + // Add localized messages where $1 will be replaced with
|
| + // <span class="total-count"></span> and $2 will be replaced with
|
| + // <span class="email"></span>.
|
| + var element = this.querySelector('.action-box-remove-user-warning-text');
|
| + element.textContent = '';
|
| +
|
| + messageParts = message.split(/(\$[12])/);
|
| + var numParts = messageParts.length;
|
| + for (var j = 0; j < numParts; j++) {
|
| + if (messageParts[j] === '$1') {
|
| + var elementToAdd = document.createElement('span');
|
| + elementToAdd.classList.add('total-count');
|
| + elementToAdd.textContent = count;
|
| + element.appendChild(elementToAdd);
|
| + } else if (messageParts[j] === '$2') {
|
| + var elementToAdd = document.createElement('span');
|
| + elementToAdd.classList.add('email');
|
| + elementToAdd.textContent = this.user.emailAddress;
|
| + element.appendChild(elementToAdd);
|
| + } else {
|
| + element.appendChild(document.createTextNode(messageParts[j]));
|
| + }
|
| + }
|
| + this.moveActionMenuUpIfNeeded_();
|
| + },
|
| +
|
| + /**
|
| + * Update the message in the "remove non-owner user warning" dialog on CrOS.
|
| + * @param {string} profilePath The filepath of the URL (must be verified).
|
| + * @param (string) message The message to be written.
|
| + */
|
| + updateRemoveNonOwnerUserWarningMessage_: function(profilePath, message) {
|
| + if (profilePath !== this.user.profilePath)
|
| + return;
|
| + // Add localized messages where $1 will be replaced with
|
| + // <span class="email"></span>.
|
| + var element = this.querySelector(
|
| + '.action-box-remove-non-owner-user-warning-text');
|
| + element.textContent = '';
|
| +
|
| + messageParts = message.split(/(\$[1])/);
|
| + var numParts = messageParts.length;
|
| + for (var j = 0; j < numParts; j++) {
|
| + if (messageParts[j] == '$1') {
|
| + var elementToAdd = document.createElement('span');
|
| + elementToAdd.classList.add('email');
|
| + elementToAdd.textContent = this.user.emailAddress;
|
| + element.appendChild(elementToAdd);
|
| + } else {
|
| + element.appendChild(document.createTextNode(messageParts[j]));
|
| + }
|
| + }
|
| + this.moveActionMenuUpIfNeeded_();
|
| + },
|
| +
|
| + /**
|
| + * Handles a click event on remove user confirmation button.
|
| + * @param {Event} e Click event.
|
| + */
|
| + handleRemoveUserConfirmationClick_: function(e) {
|
| + if (this.isActionBoxMenuActive) {
|
| + this.isActionBoxMenuActive = false;
|
| + this.removeUser(this.user);
|
| + e.stopPropagation();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles mouseover event on fingerprint icon.
|
| + * @param {Event} e MouseOver event.
|
| + */
|
| + handleFingerprintIconMouseOver_: function(e) {
|
| + var bubbleContent = document.createElement('div');
|
| + bubbleContent.textContent =
|
| + loadTimeData.getString('fingerprintIconMessage');
|
| + this.passwordElement.placeholder =
|
| + loadTimeData.getString('fingerprintHint');
|
| +
|
| + /** @const */ var BUBBLE_OFFSET = 25;
|
| + /** @const */ var BUBBLE_PADDING = -8;
|
| + var attachment = this.isPinShown() ? cr.ui.Bubble.Attachment.RIGHT :
|
| + cr.ui.Bubble.Attachment.BOTTOM;
|
| + var bubbleAnchor = this.getBubbleAnchorForFingerprintIcon_();
|
| + $('bubble').showContentForElement(
|
| + bubbleAnchor, attachment, bubbleContent, BUBBLE_OFFSET,
|
| + BUBBLE_PADDING, true);
|
| + },
|
| +
|
| + /**
|
| + * Handles mouseout event on fingerprint icon.
|
| + * @param {Event} e MouseOut event.
|
| + */
|
| + handleFingerprintIconMouseOut_: function(e) {
|
| + var bubbleAnchor = this.getBubbleAnchorForFingerprintIcon_();
|
| + $('bubble').hideForElement(bubbleAnchor);
|
| + this.passwordElement.placeholder = loadTimeData.getString(
|
| + this.isPinShown() ? 'pinKeyboardPlaceholderPinPassword' :
|
| + 'passwordHint');
|
| + },
|
| +
|
| + /**
|
| + * Returns bubble anchor of the fingerprint icon.
|
| + * @return {!HTMLElement} Anchor element of the bubble.
|
| + */
|
| + getBubbleAnchorForFingerprintIcon_: function() {
|
| + var bubbleAnchor = this;
|
| + if (this.isPinShown())
|
| + bubbleAnchor = (this.getElementsByClassName('auth-container'))[0];
|
| + return bubbleAnchor;
|
| + },
|
| +
|
| + /**
|
| + * Handles a keydown event on remove user confirmation button.
|
| + * @param {Event} e KeyDown event.
|
| + */
|
| + handleRemoveUserConfirmationKeyDown_: function(e) {
|
| + if (!this.isActionBoxMenuActive)
|
| + return;
|
| +
|
| + // Only handle pressing 'Enter' or 'Space', and let all other events
|
| + // bubble to the action box menu.
|
| + if (e.key == 'Enter' || e.key == ' ') {
|
| + this.isActionBoxMenuActive = false;
|
| + this.removeUser(this.user);
|
| + e.stopPropagation();
|
| + // Prevent default so that we don't trigger a 'click' event.
|
| + e.preventDefault();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles a keydown event on remove command.
|
| + * @param {Event} e KeyDown event.
|
| + */
|
| + handleRemoveCommandKeyDown_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + switch (e.key) {
|
| + case 'Enter':
|
| + e.preventDefault();
|
| + this.showRemoveWarning_();
|
| + e.stopPropagation();
|
| + break;
|
| + case 'ArrowUp':
|
| + case 'ArrowDown':
|
| + e.stopPropagation();
|
| + break;
|
| + // Ignore these two, so ChromeVox hotkeys don't close the menu before
|
| + // they can navigate through it.
|
| + case 'Shift':
|
| + case 'Meta':
|
| + break;
|
| + case 'Escape':
|
| + this.actionBoxAreaElement.focus();
|
| + this.isActionBoxMenuActive = false;
|
| + e.stopPropagation();
|
| + break;
|
| + default:
|
| + this.actionBoxAreaElement.focus();
|
| + this.isActionBoxMenuActive = false;
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles a blur event on remove command.
|
| + * @param {Event} e Blur event.
|
| + */
|
| + handleRemoveCommandBlur_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + this.actionBoxMenuRemoveElement.tabIndex = -1;
|
| + },
|
| +
|
| + /**
|
| + * Handles mouse down event. It sets whether the user click auth will be
|
| + * allowed on the next mouse click event. The auth is allowed iff the pod
|
| + * was focused on the mouse down event starting the click.
|
| + * @param {Event} e The mouse down event.
|
| + */
|
| + handlePodMouseDown_: function(e) {
|
| + this.userClickAuthAllowed_ = this.parentNode.isFocused(this);
|
| + },
|
| +
|
| + /**
|
| + * Called when the input of the password element changes. Updates the submit
|
| + * button color and state and hides the error popup bubble.
|
| + */
|
| + updateInput_: function() {
|
| + if (this.submitButton) {
|
| + this.submitButton.disabled = this.passwordElement.value.length == 0;
|
| + if (this.isFingerprintIconShown()) {
|
| + this.submitButton.hidden = this.passwordElement.value.length == 0;
|
| + } else {
|
| + this.submitButton.hidden = false;
|
| + }
|
| + }
|
| + this.showError = false;
|
| + $('bubble').hide();
|
| + },
|
| +
|
| + /**
|
| + * Handles input event on the password element.
|
| + * @param {Event} e Input event.
|
| + */
|
| + handleInputChanged_: function(e) {
|
| + this.updateInput_();
|
| + },
|
| +
|
| + /**
|
| + * Handles mouse up event on the password element.
|
| + * @param {Event} e Mouse up event.
|
| + */
|
| + handleInputMouseUp_: function(e) {
|
| + // If the PIN keyboard is shown and the user clicks on the password
|
| + // element, the virtual keyboard should pop up if it is enabled, so we
|
| + // must disable the virtual keyboard override.
|
| + if (this.isPinShown()) {
|
| + chrome.send('setForceDisableVirtualKeyboard', [false]);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles click event on a user pod.
|
| + * @param {Event} e Click event.
|
| + */
|
| + handleClickOnPod_: function(e) {
|
| + if (this.parentNode.disabled)
|
| + return;
|
| +
|
| + if (!this.isActionBoxMenuActive) {
|
| + if (this.isAuthTypeOnlineSignIn) {
|
| + this.showSigninUI();
|
| + } else if (this.isAuthTypeUserClick && this.userClickAuthAllowed_) {
|
| + // Note that this.userClickAuthAllowed_ is set in mouse down event
|
| + // handler.
|
| + this.parentNode.setActivatedPod(this);
|
| + } else if (this.pinKeyboard &&
|
| + e.target == this.pinKeyboard.submitButton) {
|
| + // Sets the pod as activated if the submit button is clicked so that
|
| + // it simulates what the enter button does for the password/pin.
|
| + this.parentNode.setActivatedPod(this);
|
| + }
|
| +
|
| + if (this.multiProfilesPolicyApplied)
|
| + this.userTypeBubbleElement.classList.add('bubble-shown');
|
| +
|
| + // Prevent default so that we don't trigger 'focus' event and
|
| + // stop propagation so that the 'click' event does not bubble
|
| + // up and accidentally closes the bubble tooltip.
|
| + stopEventPropagation(e);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handles keydown event for a user pod.
|
| + * @param {Event} e Key event.
|
| + */
|
| + handlePodKeyDown_: function(e) {
|
| + if (!this.isAuthTypeUserClick || this.disabled)
|
| + return;
|
| + switch (e.key) {
|
| + case 'Enter':
|
| + case ' ':
|
| + if (this.parentNode.isFocused(this))
|
| + this.parentNode.setActivatedPod(this);
|
| + break;
|
| + }
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Creates a public account user pod.
|
| + * @constructor
|
| + * @extends {UserPod}
|
| + */
|
| + var PublicAccountUserPod = cr.ui.define(function() {
|
| + var node = UserPod();
|
| +
|
| + var extras = $('public-account-user-pod-extras-template').children;
|
| + for (var i = 0; i < extras.length; ++i) {
|
| + var el = extras[i].cloneNode(true);
|
| + node.appendChild(el);
|
| + }
|
| +
|
| + return node;
|
| + });
|
| +
|
| + PublicAccountUserPod.prototype = {
|
| + __proto__: UserPod.prototype,
|
| +
|
| + /**
|
| + * "Enter" button in expanded side pane.
|
| + * @type {!HTMLButtonElement}
|
| + */
|
| + get enterButtonElement() {
|
| + return this.querySelector('.enter-button');
|
| + },
|
| +
|
| + /**
|
| + * Boolean flag of whether the pod is showing the side pane. The flag
|
| + * controls whether 'expanded' class is added to the pod's class list and
|
| + * resets tab order because main input element changes when the 'expanded'
|
| + * state changes.
|
| + * @type {boolean}
|
| + */
|
| + get expanded() {
|
| + return this.classList.contains('expanded');
|
| + },
|
| +
|
| + set expanded(expanded) {
|
| + if (this.expanded == expanded)
|
| + return;
|
| +
|
| + this.resetTabOrder();
|
| + this.classList.toggle('expanded', expanded);
|
| + if (expanded) {
|
| + // Show the advanced expanded pod directly if there are at least two
|
| + // recommended locales. This will be the case in multilingual
|
| + // environments where users are likely to want to choose among locales.
|
| + if (this.querySelector('.language-select').multipleRecommendedLocales)
|
| + this.classList.add('advanced');
|
| + this.usualLeft = this.left;
|
| + this.makeSpaceForExpandedPod_();
|
| + } else if (typeof(this.usualLeft) != 'undefined') {
|
| + this.left = this.usualLeft;
|
| + }
|
| +
|
| + var self = this;
|
| + this.classList.add('animating');
|
| + this.addEventListener('transitionend', function f(e) {
|
| + self.removeEventListener('transitionend', f);
|
| + self.classList.remove('animating');
|
| +
|
| + // Accessibility focus indicator does not move with the focused
|
| + // element. Sends a 'focus' event on the currently focused element
|
| + // so that accessibility focus indicator updates its location.
|
| + if (document.activeElement)
|
| + document.activeElement.dispatchEvent(new Event('focus'));
|
| + });
|
| + // Guard timer set to animation duration + 20ms.
|
| + ensureTransitionEndEvent(this, 200);
|
| + },
|
| +
|
| + get advanced() {
|
| + return this.classList.contains('advanced');
|
| + },
|
| +
|
| + /** @override */
|
| + get mainInput() {
|
| + if (this.expanded)
|
| + return this.enterButtonElement;
|
| + else
|
| + return this.nameElement;
|
| + },
|
| +
|
| + /** @override */
|
| + decorate: function() {
|
| + UserPod.prototype.decorate.call(this);
|
| +
|
| + this.classList.add('public-account');
|
| +
|
| + this.nameElement.addEventListener('keydown', (function(e) {
|
| + if (e.key == 'Enter') {
|
| + this.parentNode.setActivatedPod(this, e);
|
| + // Stop this keydown event from bubbling up to PodRow handler.
|
| + e.stopPropagation();
|
| + // Prevent default so that we don't trigger a 'click' event on the
|
| + // newly focused "Enter" button.
|
| + e.preventDefault();
|
| + }
|
| + }).bind(this));
|
| +
|
| + var learnMore = this.querySelector('.learn-more');
|
| + learnMore.addEventListener('mousedown', stopEventPropagation);
|
| + learnMore.addEventListener('click', this.handleLearnMoreEvent);
|
| + learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
|
| +
|
| + learnMore = this.querySelector('.expanded-pane-learn-more');
|
| + learnMore.addEventListener('click', this.handleLearnMoreEvent);
|
| + learnMore.addEventListener('keydown', this.handleLearnMoreEvent);
|
| +
|
| + var languageSelect = this.querySelector('.language-select');
|
| + languageSelect.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + languageSelect.manuallyChanged = false;
|
| + languageSelect.addEventListener(
|
| + 'change',
|
| + function() {
|
| + languageSelect.manuallyChanged = true;
|
| + this.getPublicSessionKeyboardLayouts_();
|
| + }.bind(this));
|
| +
|
| + var keyboardSelect = this.querySelector('.keyboard-select');
|
| + keyboardSelect.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + keyboardSelect.loadedLocale = null;
|
| +
|
| + var languageAndInput = this.querySelector('.language-and-input');
|
| + languageAndInput.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + languageAndInput.addEventListener('click',
|
| + this.transitionToAdvanced_.bind(this));
|
| +
|
| + var monitoringLearnMore = this.querySelector('.monitoring-learn-more');
|
| + monitoringLearnMore.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + monitoringLearnMore.addEventListener(
|
| + 'click', this.onMonitoringLearnMoreClicked_.bind(this));
|
| +
|
| + this.enterButtonElement.addEventListener('click', (function(e) {
|
| + this.enterButtonElement.disabled = true;
|
| + var locale = this.querySelector('.language-select').value;
|
| + var keyboardSelect = this.querySelector('.keyboard-select');
|
| + // The contents of |keyboardSelect| is updated asynchronously. If its
|
| + // locale does not match |locale|, it has not updated yet and the
|
| + // currently selected keyboard layout may not be applicable to |locale|.
|
| + // Do not return any keyboard layout in this case and let the backend
|
| + // choose a suitable layout.
|
| + var keyboardLayout =
|
| + keyboardSelect.loadedLocale == locale ? keyboardSelect.value : '';
|
| + chrome.send('launchPublicSession',
|
| + [this.user.username, locale, keyboardLayout]);
|
| + }).bind(this));
|
| + },
|
| +
|
| + /** @override **/
|
| + initialize: function() {
|
| + UserPod.prototype.initialize.call(this);
|
| +
|
| + id = this.user.username + '-keyboard';
|
| + this.querySelector('.keyboard-select-label').htmlFor = id;
|
| + this.querySelector('.keyboard-select').setAttribute('id', id);
|
| +
|
| + var id = this.user.username + '-language';
|
| + this.querySelector('.language-select-label').htmlFor = id;
|
| + var languageSelect = this.querySelector('.language-select');
|
| + languageSelect.setAttribute('id', id);
|
| + this.populateLanguageSelect(this.user.initialLocales,
|
| + this.user.initialLocale,
|
| + this.user.initialMultipleRecommendedLocales);
|
| + },
|
| +
|
| + /** @override **/
|
| + update: function() {
|
| + UserPod.prototype.update.call(this);
|
| + this.querySelector('.expanded-pane-name').textContent =
|
| + this.user_.displayName;
|
| + this.querySelector('.info').textContent =
|
| + loadTimeData.getStringF('publicAccountInfoFormat',
|
| + this.user_.enterpriseDomain);
|
| + },
|
| +
|
| + /** @override */
|
| + focusInput: function() {
|
| + // Move tabIndex from the whole pod to the main input.
|
| + this.tabIndex = -1;
|
| + this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + this.mainInput.focus();
|
| + },
|
| +
|
| + /** @override */
|
| + reset: function(takeFocus) {
|
| + if (!takeFocus)
|
| + this.expanded = false;
|
| + this.enterButtonElement.disabled = false;
|
| + UserPod.prototype.reset.call(this, takeFocus);
|
| + },
|
| +
|
| + /** @override */
|
| + activate: function(e) {
|
| + if (!this.expanded) {
|
| + this.expanded = true;
|
| + this.focusInput();
|
| + }
|
| + return true;
|
| + },
|
| +
|
| + /** @override */
|
| + handleClickOnPod_: function(e) {
|
| + if (this.parentNode.disabled)
|
| + return;
|
| +
|
| + this.parentNode.focusPod(this);
|
| + this.parentNode.setActivatedPod(this, e);
|
| + // Prevent default so that we don't trigger 'focus' event.
|
| + e.preventDefault();
|
| + },
|
| +
|
| + /**
|
| + * Updates the display name shown on the pod.
|
| + * @param {string} displayName The new display name
|
| + */
|
| + setDisplayName: function(displayName) {
|
| + this.user_.displayName = displayName;
|
| + this.update();
|
| + },
|
| +
|
| + /**
|
| + * Handle mouse and keyboard events for the learn more button. Triggering
|
| + * the button causes information about public sessions to be shown.
|
| + * @param {Event} event Mouse or keyboard event.
|
| + */
|
| + handleLearnMoreEvent: function(event) {
|
| + switch (event.type) {
|
| + // Show informaton on left click. Let any other clicks propagate.
|
| + case 'click':
|
| + if (event.button != 0)
|
| + return;
|
| + break;
|
| + // Show informaton when <Return> or <Space> is pressed. Let any other
|
| + // key presses propagate.
|
| + case 'keydown':
|
| + switch (event.keyCode) {
|
| + case 13: // Return.
|
| + case 32: // Space.
|
| + break;
|
| + default:
|
| + return;
|
| + }
|
| + break;
|
| + }
|
| + chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]);
|
| + stopEventPropagation(event);
|
| + },
|
| +
|
| + makeSpaceForExpandedPod_: function() {
|
| + var width = this.classList.contains('advanced') ?
|
| + PUBLIC_EXPANDED_ADVANCED_WIDTH : PUBLIC_EXPANDED_BASIC_WIDTH;
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| + var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
|
| + POD_ROW_PADDING;
|
| + if (this.left + width > $('pod-row').offsetWidth - rowPadding)
|
| + this.left = $('pod-row').offsetWidth - rowPadding - width;
|
| + },
|
| +
|
| + /**
|
| + * Transition the expanded pod from the basic to the advanced view.
|
| + */
|
| + transitionToAdvanced_: function() {
|
| + var pod = this;
|
| + var languageAndInputSection =
|
| + this.querySelector('.language-and-input-section');
|
| + this.classList.add('transitioning-to-advanced');
|
| + setTimeout(function() {
|
| + pod.classList.add('advanced');
|
| + pod.makeSpaceForExpandedPod_();
|
| + languageAndInputSection.addEventListener('transitionend',
|
| + function observer() {
|
| + languageAndInputSection.removeEventListener('transitionend',
|
| + observer);
|
| + pod.classList.remove('transitioning-to-advanced');
|
| + pod.querySelector('.language-select').focus();
|
| + });
|
| + // Guard timer set to animation duration + 20ms.
|
| + ensureTransitionEndEvent(languageAndInputSection, 380);
|
| + }, 0);
|
| + },
|
| +
|
| + /**
|
| + * Show a dialog when user clicks on learn more (monitoring) button.
|
| + */
|
| + onMonitoringLearnMoreClicked_: function() {
|
| + if (!this.dialogContainer_) {
|
| + this.dialogContainer_ = document.createElement('div');
|
| + this.dialogContainer_.classList.add('monitoring-dialog-container');
|
| + var topContainer = document.querySelector('#scroll-container');
|
| + topContainer.appendChild(this.dialogContainer_);
|
| + }
|
| + // Public Session POD in advanced view has a different size so add a dummy
|
| + // parent element to enable different CSS settings.
|
| + this.dialogContainer_.classList.toggle(
|
| + 'advanced', this.classList.contains('advanced'))
|
| + var html = '';
|
| + var infoItems = ['publicAccountMonitoringInfoItem1',
|
| + 'publicAccountMonitoringInfoItem2',
|
| + 'publicAccountMonitoringInfoItem3',
|
| + 'publicAccountMonitoringInfoItem4'];
|
| + for (item of infoItems) {
|
| + html += '<p class="cr-dialog-item">';
|
| + html += loadTimeData.getString(item);
|
| + html += '</p>';
|
| + }
|
| + var title = loadTimeData.getString('publicAccountMonitoringInfo');
|
| + this.dialog_ = new cr.ui.dialogs.BaseDialog(this.dialogContainer_);
|
| + this.dialog_.showHtml(title, html, undefined,
|
| + this.onMonitoringDialogClosed_.bind(this));
|
| + this.parentNode.disabled = true;
|
| + },
|
| +
|
| + /**
|
| + * Cleanup after the monitoring warning dialog is closed.
|
| + */
|
| + onMonitoringDialogClosed_: function() {
|
| + this.parentNode.disabled = false;
|
| + this.dialog_ = undefined;
|
| + },
|
| +
|
| + /**
|
| + * Retrieves the list of keyboard layouts available for the currently
|
| + * selected locale.
|
| + */
|
| + getPublicSessionKeyboardLayouts_: function() {
|
| + var selectedLocale = this.querySelector('.language-select').value;
|
| + if (selectedLocale ==
|
| + this.querySelector('.keyboard-select').loadedLocale) {
|
| + // If the list of keyboard layouts was loaded for the currently selected
|
| + // locale, it is already up to date.
|
| + return;
|
| + }
|
| + chrome.send('getPublicSessionKeyboardLayouts',
|
| + [this.user.username, selectedLocale]);
|
| + },
|
| +
|
| + /**
|
| + * Populates the keyboard layout "select" element with a list of layouts.
|
| + * @param {string} locale The locale to which this list of keyboard layouts
|
| + * applies
|
| + * @param {!Object} list List of available keyboard layouts
|
| + */
|
| + populateKeyboardSelect: function(locale, list) {
|
| + if (locale != this.querySelector('.language-select').value) {
|
| + // The selected locale has changed and the list of keyboard layouts is
|
| + // not applicable. This method will be called again when a list of
|
| + // keyboard layouts applicable to the selected locale is retrieved.
|
| + return;
|
| + }
|
| +
|
| + var keyboardSelect = this.querySelector('.keyboard-select');
|
| + keyboardSelect.loadedLocale = locale;
|
| + keyboardSelect.innerHTML = '';
|
| + for (var i = 0; i < list.length; ++i) {
|
| + var item = list[i];
|
| + keyboardSelect.appendChild(
|
| + new Option(item.title, item.value, item.selected, item.selected));
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Populates the language "select" element with a list of locales.
|
| + * @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
|
| + */
|
| + populateLanguageSelect: function(locales,
|
| + defaultLocale,
|
| + multipleRecommendedLocales) {
|
| + var languageSelect = this.querySelector('.language-select');
|
| + // If the user manually selected a locale, do not change the selection.
|
| + // Otherwise, select the new |defaultLocale|.
|
| + var selected =
|
| + languageSelect.manuallyChanged ? languageSelect.value : defaultLocale;
|
| + languageSelect.innerHTML = '';
|
| + var group = languageSelect;
|
| + for (var i = 0; i < locales.length; ++i) {
|
| + var item = locales[i];
|
| + if (item.optionGroupName) {
|
| + group = document.createElement('optgroup');
|
| + group.label = item.optionGroupName;
|
| + languageSelect.appendChild(group);
|
| + } else {
|
| + group.appendChild(new Option(item.title,
|
| + item.value,
|
| + item.value == selected,
|
| + item.value == selected));
|
| + }
|
| + }
|
| + languageSelect.multipleRecommendedLocales = multipleRecommendedLocales;
|
| +
|
| + // Retrieve a list of keyboard layouts applicable to the locale that is
|
| + // now selected.
|
| + this.getPublicSessionKeyboardLayouts_();
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Creates a user pod to be used only in desktop chrome.
|
| + * @constructor
|
| + * @extends {UserPod}
|
| + */
|
| + var DesktopUserPod = cr.ui.define(function() {
|
| + // Don't just instantiate a UserPod(), as this will call decorate() on the
|
| + // parent object, and add duplicate event listeners.
|
| + var node = $('user-pod-template').cloneNode(true);
|
| + node.removeAttribute('id');
|
| + return node;
|
| + });
|
| +
|
| + DesktopUserPod.prototype = {
|
| + __proto__: UserPod.prototype,
|
| +
|
| + /** @override */
|
| + initialize: function() {
|
| + if (this.user.needsSignin) {
|
| + if (this.user.hasLocalCreds) {
|
| + this.user.initialAuthType = AUTH_TYPE.OFFLINE_PASSWORD;
|
| + } else {
|
| + this.user.initialAuthType = AUTH_TYPE.ONLINE_SIGN_IN;
|
| + }
|
| + }
|
| + UserPod.prototype.initialize.call(this);
|
| + },
|
| +
|
| + /** @override */
|
| + get mainInput() {
|
| + if (this.user.needsSignin)
|
| + return this.passwordElement;
|
| + else
|
| + return this.nameElement;
|
| + },
|
| +
|
| + /** @override */
|
| + update: function() {
|
| + this.imageElement.src = this.user.userImage;
|
| + this.nameElement.textContent = this.user.displayName;
|
| + this.reauthNameHintElement.textContent = this.user.displayName;
|
| +
|
| + var isLockedUser = this.user.needsSignin;
|
| + var isLegacySupervisedUser = this.user.legacySupervisedUser;
|
| + var isChildUser = this.user.childUser;
|
| + var isSyncedUser = this.user.emailAddress !== "";
|
| + var isProfileLoaded = this.user.isProfileLoaded;
|
| + this.classList.toggle('locked', isLockedUser);
|
| + this.classList.toggle('legacy-supervised', isLegacySupervisedUser);
|
| + this.classList.toggle('child', isChildUser);
|
| + this.classList.toggle('synced', isSyncedUser);
|
| + this.classList.toggle('has-no-stats',
|
| + !isProfileLoaded && !this.user.statistics.length);
|
| +
|
| + if (this.isAuthTypeUserClick)
|
| + this.passwordLabelElement.textContent = this.authValue;
|
| +
|
| + this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF(
|
| + 'passwordFieldAccessibleName', this.user_.emailAddress));
|
| +
|
| + UserPod.prototype.updateActionBoxArea.call(this);
|
| + },
|
| +
|
| + /** @override */
|
| + activate: function(e) {
|
| + if (!this.user.needsSignin) {
|
| + Oobe.launchUser(this.user.profilePath);
|
| + } else if (this.user.hasLocalCreds && !this.passwordElement.value) {
|
| + return false;
|
| + } else {
|
| + chrome.send('authenticatedLaunchUser',
|
| + [this.user.profilePath,
|
| + this.user.emailAddress,
|
| + this.passwordElement.value]);
|
| + }
|
| + this.passwordElement.value = '';
|
| + return true;
|
| + },
|
| +
|
| + /** @override */
|
| + handleClickOnPod_: function(e) {
|
| + if (this.parentNode.disabled)
|
| + return;
|
| +
|
| + Oobe.clearErrors();
|
| + this.parentNode.lastFocusedPod_ = this;
|
| +
|
| + // If this is a locked pod and there are local credentials, show the
|
| + // password field. Otherwise call activate() which will open up a browser
|
| + // window or show the reauth dialog, as needed.
|
| + if (!(this.user.needsSignin && this.user.hasLocalCreds) &&
|
| + !this.isActionBoxMenuActive) {
|
| + this.activate(e);
|
| + }
|
| +
|
| + if (this.isAuthTypeUserClick)
|
| + chrome.send('attemptUnlock', [this.user.emailAddress]);
|
| + },
|
| + };
|
| +
|
| + /**
|
| + * Creates a user pod that represents kiosk app.
|
| + * @constructor
|
| + * @extends {UserPod}
|
| + */
|
| + var KioskAppPod = cr.ui.define(function() {
|
| + var node = UserPod();
|
| + return node;
|
| + });
|
| +
|
| + KioskAppPod.prototype = {
|
| + __proto__: UserPod.prototype,
|
| +
|
| + /** @override */
|
| + decorate: function() {
|
| + UserPod.prototype.decorate.call(this);
|
| + this.launchAppButtonElement.addEventListener('click',
|
| + this.activate.bind(this));
|
| + },
|
| +
|
| + /** @override */
|
| + update: function() {
|
| + this.imageElement.src = this.user.iconUrl;
|
| + this.imageElement.alt = this.user.label;
|
| + this.imageElement.title = this.user.label;
|
| + this.passwordEntryContainerElement.hidden = true;
|
| + this.launchAppButtonContainerElement.hidden = false;
|
| + this.nameElement.textContent = this.user.label;
|
| + this.reauthNameHintElement.textContent = this.user.label;
|
| +
|
| + UserPod.prototype.updateActionBoxArea.call(this);
|
| + UserPod.prototype.customizeUserPodPerUserType.call(this);
|
| + },
|
| +
|
| + /** @override */
|
| + get mainInput() {
|
| + return this.launchAppButtonElement;
|
| + },
|
| +
|
| + /** @override */
|
| + focusInput: function() {
|
| + // Move tabIndex from the whole pod to the main input.
|
| + this.tabIndex = -1;
|
| + this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT;
|
| + this.mainInput.focus();
|
| + },
|
| +
|
| + /** @override */
|
| + get forceOnlineSignin() {
|
| + return false;
|
| + },
|
| +
|
| + /** @override */
|
| + activate: function(e) {
|
| + var diagnosticMode = e && e.ctrlKey;
|
| + this.launchApp_(this.user, diagnosticMode);
|
| + return true;
|
| + },
|
| +
|
| + /** @override */
|
| + handleClickOnPod_: function(e) {
|
| + if (this.parentNode.disabled)
|
| + return;
|
| +
|
| + Oobe.clearErrors();
|
| + this.parentNode.lastFocusedPod_ = this;
|
| + this.activate(e);
|
| + },
|
| +
|
| + /**
|
| + * Launch the app. If |diagnosticMode| is true, ask user to confirm.
|
| + * @param {Object} app App data.
|
| + * @param {boolean} diagnosticMode Whether to run the app in diagnostic
|
| + * mode.
|
| + */
|
| + launchApp_: function(app, diagnosticMode) {
|
| + if (!diagnosticMode) {
|
| + chrome.send('launchKioskApp', [app.id, false]);
|
| + return;
|
| + }
|
| +
|
| + var oobe = $('oobe');
|
| + if (!oobe.confirmDiagnosticMode_) {
|
| + oobe.confirmDiagnosticMode_ =
|
| + new cr.ui.dialogs.ConfirmDialog(document.body);
|
| + oobe.confirmDiagnosticMode_.setOkLabel(
|
| + loadTimeData.getString('confirmKioskAppDiagnosticModeYes'));
|
| + oobe.confirmDiagnosticMode_.setCancelLabel(
|
| + loadTimeData.getString('confirmKioskAppDiagnosticModeNo'));
|
| + }
|
| +
|
| + oobe.confirmDiagnosticMode_.show(
|
| + loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat',
|
| + app.label),
|
| + function() {
|
| + chrome.send('launchKioskApp', [app.id, true]);
|
| + });
|
| + },
|
| + };
|
| +
|
| + /**
|
| + * Creates a new pod row element.
|
| + * @constructor
|
| + * @extends {HTMLDivElement}
|
| + */
|
| + var PodRow = cr.ui.define('podrow');
|
| +
|
| + PodRow.prototype = {
|
| + __proto__: HTMLDivElement.prototype,
|
| +
|
| + // Whether this user pod row is shown for the first time.
|
| + firstShown_: true,
|
| +
|
| + // True if inside focusPod().
|
| + insideFocusPod_: false,
|
| +
|
| + // Focused pod.
|
| + focusedPod_: undefined,
|
| +
|
| + // Activated pod, i.e. the pod of current login attempt.
|
| + activatedPod_: undefined,
|
| +
|
| + // Pod that was most recently focused, if any.
|
| + lastFocusedPod_: undefined,
|
| +
|
| + // Pods whose initial images haven't been loaded yet.
|
| + podsWithPendingImages_: [],
|
| +
|
| + // Whether pod placement has been postponed.
|
| + podPlacementPostponed_: false,
|
| +
|
| + // Standard user pod height/width.
|
| + userPodHeight_: 0,
|
| + userPodWidth_: 0,
|
| +
|
| + // Array of apps that are shown in addition to other user pods.
|
| + apps_: [],
|
| +
|
| + // True to show app pods along with user pods.
|
| + shouldShowApps_: true,
|
| +
|
| + // Array of users that are shown (public/supervised/regular).
|
| + users_: [],
|
| +
|
| + // If we're in Touch View mode.
|
| + touchViewEnabled_: false,
|
| +
|
| + /** @override */
|
| + decorate: function() {
|
| + // Event listeners that are installed for the time period during which
|
| + // the element is visible.
|
| + this.listeners_ = {
|
| + focus: [this.handleFocus_.bind(this), true /* useCapture */],
|
| + click: [this.handleClick_.bind(this), true],
|
| + mousemove: [this.handleMouseMove_.bind(this), false],
|
| + keydown: [this.handleKeyDown.bind(this), false]
|
| + };
|
| +
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| + var isNewDesktopUserManager = Oobe.getInstance().newDesktopUserManager;
|
| + this.userPodHeight_ = isDesktopUserManager ?
|
| + isNewDesktopUserManager ? MD_DESKTOP_POD_HEIGHT :
|
| + DESKTOP_POD_HEIGHT :
|
| + CROS_POD_HEIGHT;
|
| + this.userPodWidth_ = isDesktopUserManager ?
|
| + isNewDesktopUserManager ? MD_DESKTOP_POD_WIDTH :
|
| + DESKTOP_POD_WIDTH :
|
| + CROS_POD_WIDTH;
|
| + },
|
| +
|
| + /**
|
| + * Returns all the pods in this pod row.
|
| + * @type {NodeList}
|
| + */
|
| + get pods() {
|
| + return Array.prototype.slice.call(this.children);
|
| + },
|
| +
|
| + /**
|
| + * Return true if user pod row has only single user pod in it, which should
|
| + * always be focused except desktop and touch view modes.
|
| + * @type {boolean}
|
| + */
|
| + get alwaysFocusSinglePod() {
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| +
|
| + return (isDesktopUserManager || this.touchViewEnabled_) ?
|
| + false : this.children.length == 1;
|
| + },
|
| +
|
| + /**
|
| + * Returns pod with the given app id.
|
| + * @param {!string} app_id Application id to be matched.
|
| + * @return {Object} Pod with the given app id. null if pod hasn't been
|
| + * found.
|
| + */
|
| + getPodWithAppId_: function(app_id) {
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i) {
|
| + if (pod.user.isApp && pod.user.id == app_id)
|
| + return pod;
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Returns pod with the given username (null if there is no such pod).
|
| + * @param {string} username Username to be matched.
|
| + * @return {Object} Pod with the given username. null if pod hasn't been
|
| + * found.
|
| + */
|
| + getPodWithUsername_: function(username) {
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i) {
|
| + if (pod.user.username == username)
|
| + return pod;
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * True if the the pod row is disabled (handles no user interaction).
|
| + * @type {boolean}
|
| + */
|
| + disabled_: false,
|
| + get disabled() {
|
| + return this.disabled_;
|
| + },
|
| + set disabled(value) {
|
| + this.disabled_ = value;
|
| + this.pods.forEach(function(pod) {
|
| + pod.disabled = value;
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Creates a user pod from given email.
|
| + * @param {!Object} user User info dictionary.
|
| + */
|
| + createUserPod: function(user) {
|
| + var userPod;
|
| + if (user.isDesktopUser)
|
| + userPod = new DesktopUserPod({user: user});
|
| + else if (user.publicAccount)
|
| + userPod = new PublicAccountUserPod({user: user});
|
| + else if (user.isApp)
|
| + userPod = new KioskAppPod({user: user});
|
| + else
|
| + userPod = new UserPod({user: user});
|
| +
|
| + userPod.hidden = false;
|
| + return userPod;
|
| + },
|
| +
|
| + /**
|
| + * Add an existing user pod to this pod row.
|
| + * @param {!Object} user User info dictionary.
|
| + */
|
| + addUserPod: function(user) {
|
| + var userPod = this.createUserPod(user);
|
| + this.appendChild(userPod);
|
| + userPod.initialize();
|
| + },
|
| +
|
| + /**
|
| + * Performs visual changes on the user pod if there is an error.
|
| + * @param {boolean} visible Whether to show or hide the display.
|
| + */
|
| + setFocusedPodErrorDisplay: function(visible) {
|
| + if (this.focusedPod_)
|
| + this.focusedPod_.showError = visible;
|
| + },
|
| +
|
| + /**
|
| + * Shows or hides the pin keyboard for the current focused pod.
|
| + * @param {boolean} visible
|
| + */
|
| + setFocusedPodPinVisibility: function(visible) {
|
| + if (this.focusedPod_)
|
| + this.focusedPod_.setPinVisibility(visible);
|
| + },
|
| +
|
| + /**
|
| + * Runs app with a given id from the list of loaded apps.
|
| + * @param {!string} app_id of an app to run.
|
| + * @param {boolean=} opt_diagnosticMode Whether to run the app in
|
| + * diagnostic mode. Default is false.
|
| + */
|
| + findAndRunAppForTesting: function(app_id, opt_diagnosticMode) {
|
| + var app = this.getPodWithAppId_(app_id);
|
| + if (app) {
|
| + var activationEvent = cr.doc.createEvent('MouseEvents');
|
| + var ctrlKey = opt_diagnosticMode;
|
| + activationEvent.initMouseEvent('click', true, true, null,
|
| + 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null);
|
| + app.dispatchEvent(activationEvent);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Enables or disables the pin keyboard for the given user. A disabled pin
|
| + * keyboard will never be displayed.
|
| + *
|
| + * If the user's pod is focused, then enabling the pin keyboard will display
|
| + * it; disabling the pin keyboard will hide it.
|
| + * @param {!string} username
|
| + * @param {boolean} enabled
|
| + */
|
| + setPinEnabled: function(username, enabled) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (!pod) {
|
| + console.error('Attempt to enable/disable pin keyboard of missing pod.');
|
| + return;
|
| + }
|
| +
|
| + // Make sure to set |pinEnabled| before toggling visiblity to avoid
|
| + // validation errors.
|
| + pod.pinEnabled = enabled;
|
| +
|
| + if (this.focusedPod_ == pod) {
|
| + if (enabled) {
|
| + ensurePinKeyboardLoaded(
|
| + this.setPinVisibility.bind(this, username, true));
|
| + } else {
|
| + this.setPinVisibility(username, false);
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Shows or hides the pin keyboard from the pod with the given |username|.
|
| + * This is only a visibility change; the pin keyboard can be reshown.
|
| + *
|
| + * Use setPinEnabled if the pin keyboard should be disabled for the given
|
| + * user.
|
| + * @param {!user} username
|
| + * @param {boolean} visible
|
| + */
|
| + setPinVisibility: function(username, visible) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (!pod) {
|
| + console.error('Attempt to show/hide pin keyboard of missing pod.');
|
| + return;
|
| + }
|
| + if (visible && pod.pinEnabled === false) {
|
| + console.error('Attempt to show disabled pin keyboard');
|
| + return;
|
| + }
|
| + if (visible && this.focusedPod_ != pod) {
|
| + console.error('Attempt to show pin keyboard on non-focused pod');
|
| + return;
|
| + }
|
| +
|
| + pod.setPinVisibility(visible);
|
| + },
|
| +
|
| + /**
|
| + * Removes user pod from pod row.
|
| + * @param {!user} username
|
| + */
|
| + removeUserPod: function(username) {
|
| + var podToRemove = this.getPodWithUsername_(username);
|
| + if (podToRemove == null) {
|
| + console.warn('Attempt to remove pod that does not exist');
|
| + return;
|
| + }
|
| + this.removeChild(podToRemove);
|
| + if (this.pods.length > 0)
|
| + this.placePods_();
|
| + },
|
| +
|
| + /**
|
| + * Returns index of given pod or -1 if not found.
|
| + * @param {UserPod} pod Pod to look up.
|
| + * @private
|
| + */
|
| + indexOf_: function(pod) {
|
| + for (var i = 0; i < this.pods.length; ++i) {
|
| + if (pod == this.pods[i])
|
| + return i;
|
| + }
|
| + return -1;
|
| + },
|
| +
|
| + /**
|
| + * Populates pod row with given existing users and start init animation.
|
| + * @param {array} users Array of existing user emails.
|
| + */
|
| + loadPods: function(users) {
|
| + this.users_ = users;
|
| +
|
| + this.rebuildPods();
|
| + },
|
| +
|
| + /**
|
| + * Scrolls focused user pod into view.
|
| + */
|
| + scrollFocusedPodIntoView: function() {
|
| + var pod = this.focusedPod_;
|
| + if (!pod)
|
| + return;
|
| +
|
| + // First check whether focused pod is already fully visible.
|
| + var visibleArea = $('scroll-container');
|
| + // Visible area may not defined at user manager screen on all platforms.
|
| + // Windows, Mac and Linux do not have visible area.
|
| + if (!visibleArea)
|
| + return;
|
| + var scrollTop = visibleArea.scrollTop;
|
| + var clientHeight = visibleArea.clientHeight;
|
| + var podTop = $('oobe').offsetTop + pod.offsetTop;
|
| + var padding = USER_POD_KEYBOARD_MIN_PADDING;
|
| + if (podTop + pod.height + padding <= scrollTop + clientHeight &&
|
| + podTop - padding >= scrollTop) {
|
| + return;
|
| + }
|
| +
|
| + // Scroll so that user pod is as centered as possible.
|
| + visibleArea.scrollTop = podTop - (clientHeight - pod.offsetHeight) / 2;
|
| + },
|
| +
|
| + /**
|
| + * Rebuilds pod row using users_ and apps_ that were previously set or
|
| + * updated.
|
| + */
|
| + rebuildPods: function() {
|
| + var emptyPodRow = this.pods.length == 0;
|
| +
|
| + // Clear existing pods.
|
| + this.innerHTML = '';
|
| + this.focusedPod_ = undefined;
|
| + this.activatedPod_ = undefined;
|
| + this.lastFocusedPod_ = undefined;
|
| +
|
| + // Switch off animation
|
| + Oobe.getInstance().toggleClass('flying-pods', false);
|
| +
|
| + // Populate the pod row.
|
| + for (var i = 0; i < this.users_.length; ++i)
|
| + this.addUserPod(this.users_[i]);
|
| +
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i)
|
| + this.podsWithPendingImages_.push(pod);
|
| +
|
| + // TODO(nkostylev): Edge case handling when kiosk apps are not fitting.
|
| + if (this.shouldShowApps_) {
|
| + for (var i = 0; i < this.apps_.length; ++i)
|
| + this.addUserPod(this.apps_[i]);
|
| + }
|
| +
|
| + // Make sure we eventually show the pod row, even if some image is stuck.
|
| + setTimeout(function() {
|
| + $('pod-row').classList.remove('images-loading');
|
| + }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS);
|
| +
|
| + var isAccountPicker = $('login-header-bar').signinUIState ==
|
| + SIGNIN_UI_STATE.ACCOUNT_PICKER;
|
| +
|
| + // Immediately recalculate pods layout only when current UI is account
|
| + // picker. Otherwise postpone it.
|
| + if (isAccountPicker) {
|
| + this.placePods_();
|
| + this.maybePreselectPod();
|
| +
|
| + // Without timeout changes in pods positions will be animated even
|
| + // though it happened when 'flying-pods' class was disabled.
|
| + setTimeout(function() {
|
| + Oobe.getInstance().toggleClass('flying-pods', true);
|
| + }, 0);
|
| + } else {
|
| + this.podPlacementPostponed_ = true;
|
| +
|
| + // Update [Cancel] button state.
|
| + if ($('login-header-bar').signinUIState ==
|
| + SIGNIN_UI_STATE.GAIA_SIGNIN &&
|
| + emptyPodRow &&
|
| + this.pods.length > 0) {
|
| + login.GaiaSigninScreen.updateControlsState();
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Adds given apps to the pod row.
|
| + * @param {array} apps Array of apps.
|
| + */
|
| + setApps: function(apps) {
|
| + this.apps_ = apps;
|
| + this.rebuildPods();
|
| + chrome.send('kioskAppsLoaded');
|
| +
|
| + // Check whether there's a pending kiosk app error.
|
| + window.setTimeout(function() {
|
| + chrome.send('checkKioskAppLaunchError');
|
| + }, 500);
|
| + },
|
| +
|
| + /**
|
| + * Sets whether should show app pods.
|
| + * @param {boolean} shouldShowApps Whether app pods should be shown.
|
| + */
|
| + setShouldShowApps: function(shouldShowApps) {
|
| + if (this.shouldShowApps_ == shouldShowApps)
|
| + return;
|
| +
|
| + this.shouldShowApps_ = shouldShowApps;
|
| + this.rebuildPods();
|
| + },
|
| +
|
| + /**
|
| + * Shows a custom icon on a user pod besides the input field.
|
| + * @param {string} username Username of pod to add button
|
| + * @param {!{id: !string,
|
| + * hardlockOnClick: boolean,
|
| + * isTrialRun: boolean,
|
| + * ariaLabel: string | undefined,
|
| + * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon
|
| + * The icon parameters.
|
| + */
|
| + showUserPodCustomIcon: function(username, icon) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (pod == null) {
|
| + console.error('Unable to show user pod button: user pod not found.');
|
| + return;
|
| + }
|
| +
|
| + if (!icon.id && !icon.tooltip)
|
| + return;
|
| +
|
| + if (icon.id)
|
| + pod.customIconElement.setIcon(icon.id);
|
| +
|
| + if (icon.isTrialRun) {
|
| + pod.customIconElement.setInteractive(
|
| + this.onDidClickLockIconDuringTrialRun_.bind(this, username));
|
| + } else if (icon.hardlockOnClick) {
|
| + pod.customIconElement.setInteractive(
|
| + this.hardlockUserPod_.bind(this, username));
|
| + } else {
|
| + pod.customIconElement.setInteractive(null);
|
| + }
|
| +
|
| + var ariaLabel = icon.ariaLabel || (icon.tooltip && icon.tooltip.text);
|
| + if (ariaLabel)
|
| + pod.customIconElement.setAriaLabel(ariaLabel);
|
| + else
|
| + console.warn('No ARIA label for user pod custom icon.');
|
| +
|
| + pod.customIconElement.show();
|
| +
|
| + // This has to be called after |show| in case the tooltip should be shown
|
| + // immediatelly.
|
| + pod.customIconElement.setTooltip(
|
| + icon.tooltip || {text: '', autoshow: false});
|
| +
|
| + // Hide fingerprint icon when custom icon is shown.
|
| + this.setUserPodFingerprintIcon(username, FINGERPRINT_STATES.HIDDEN);
|
| + },
|
| +
|
| + /**
|
| + * Hard-locks user pod for the user. If user pod is hard-locked, it can be
|
| + * only unlocked using password, and the authentication type cannot be
|
| + * changed.
|
| + * @param {!string} username The user's username.
|
| + * @private
|
| + */
|
| + hardlockUserPod_: function(username) {
|
| + chrome.send('hardlockPod', [username]);
|
| + },
|
| +
|
| + /**
|
| + * Records a metric indicating that the user clicked on the lock icon during
|
| + * the trial run for Easy Unlock.
|
| + * @param {!string} username The user's username.
|
| + * @private
|
| + */
|
| + onDidClickLockIconDuringTrialRun_: function(username) {
|
| + chrome.send('recordClickOnLockIcon', [username]);
|
| + },
|
| +
|
| + /**
|
| + * Hides the custom icon in the user pod added by showUserPodCustomIcon().
|
| + * @param {string} username Username of pod to remove button
|
| + */
|
| + hideUserPodCustomIcon: function(username) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (pod == null) {
|
| + console.error('Unable to hide user pod button: user pod not found.');
|
| + return;
|
| + }
|
| +
|
| + // TODO(tengs): Allow option for a fading transition.
|
| + pod.customIconElement.hide();
|
| +
|
| + // Show fingerprint icon if applicable.
|
| + this.setUserPodFingerprintIcon(username, FINGERPRINT_STATES.DEFAULT);
|
| + },
|
| +
|
| + /**
|
| + * 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) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (pod == null) {
|
| + console.error(
|
| + 'Unable to set user pod fingerprint icon: user pod not found.');
|
| + return;
|
| + }
|
| + pod.fingerprintAuthenticated_ = false;
|
| + if (!pod.fingerprintIconElement)
|
| + return;
|
| + if (!pod.user.allowFingerprint || state == FINGERPRINT_STATES.HIDDEN ||
|
| + !pod.customIconElement.hidden) {
|
| + pod.fingerprintIconElement.hidden = true;
|
| + pod.submitButton.hidden = false;
|
| + return;
|
| + }
|
| +
|
| + FINGERPRINT_STATES_MAPPING.forEach(function(icon) {
|
| + pod.fingerprintIconElement.classList.toggle(
|
| + icon.class, state == icon.state);
|
| + });
|
| + pod.fingerprintIconElement.hidden = false;
|
| + pod.submitButton.hidden = pod.passwordElement.value.length == 0;
|
| + this.updatePasswordField_(pod, state);
|
| + if (state == FINGERPRINT_STATES.DEFAULT)
|
| + return;
|
| +
|
| + pod.fingerprintAuthenticated_ = true;
|
| + this.setActivatedPod(pod);
|
| + if (state == FINGERPRINT_STATES.FAILED) {
|
| + /** @const */ var RESET_ICON_TIMEOUT_MS = 500;
|
| + setTimeout(
|
| + this.resetIconAndPasswordField_.bind(this, pod),
|
| + RESET_ICON_TIMEOUT_MS);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Reset the fingerprint icon and password field.
|
| + * @param {UserPod} pod Pod to reset.
|
| + */
|
| + resetIconAndPasswordField_: function(pod) {
|
| + if (!pod.fingerprintIconElement)
|
| + return;
|
| + this.setUserPodFingerprintIcon(
|
| + pod.user.username, FINGERPRINT_STATES.DEFAULT);
|
| + },
|
| +
|
| + /**
|
| + * Remove the fingerprint icon in the user pod.
|
| + * @param {string} username Username of the selected user
|
| + */
|
| + removeUserPodFingerprintIcon: function(username) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (pod == null) {
|
| + console.error('No user pod found (when removing fingerprint icon).');
|
| + return;
|
| + }
|
| + this.resetIconAndPasswordField_(pod);
|
| + if (pod.fingerprintIconElement) {
|
| + pod.fingerprintIconElement.parentNode.removeChild(
|
| + pod.fingerprintIconElement);
|
| + }
|
| + pod.submitButton.hidden = false;
|
| + },
|
| +
|
| + /**
|
| + * Updates the password field in the user pod.
|
| + * @param {UserPod} pod Pod to update.
|
| + * @param {number} state Fingerprint unlock state
|
| + */
|
| + updatePasswordField_: function(pod, state) {
|
| + FINGERPRINT_STATES_MAPPING.forEach(function(item) {
|
| + pod.passwordElement.classList.toggle(item.class, state == item.state);
|
| + });
|
| + var placeholderStr = loadTimeData.getString(
|
| + pod.isPinShown() ? 'pinKeyboardPlaceholderPinPassword' :
|
| + 'passwordHint');
|
| + if (state == FINGERPRINT_STATES.SIGNIN) {
|
| + placeholderStr = loadTimeData.getString('fingerprintSigningin');
|
| + } else if (state == FINGERPRINT_STATES.FAILED) {
|
| + placeholderStr = loadTimeData.getString('fingerprintSigninFailed');
|
| + }
|
| + pod.passwordElement.placeholder = placeholderStr;
|
| + },
|
| +
|
| + /**
|
| + * Sets the authentication type used to authenticate the user.
|
| + * @param {string} username Username of selected user
|
| + * @param {number} authType Authentication type, must be one of the
|
| + * values listed in AUTH_TYPE enum.
|
| + * @param {string} value The initial value to use for authentication.
|
| + */
|
| + setAuthType: function(username, authType, value) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (pod == null) {
|
| + console.error('Unable to set auth type: user pod not found.');
|
| + return;
|
| + }
|
| + pod.setAuthType(authType, value);
|
| + },
|
| +
|
| + /**
|
| + * Sets the state of touch view mode.
|
| + * @param {boolean} isTouchViewEnabled true if the mode is on.
|
| + */
|
| + setTouchViewState: function(isTouchViewEnabled) {
|
| + this.touchViewEnabled_ = isTouchViewEnabled;
|
| + this.pods.forEach(function(pod, index) {
|
| + pod.actionBoxAreaElement.classList.toggle('forced', isTouchViewEnabled);
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * 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) {
|
| + var pod = this.getPodWithUsername_(userID);
|
| + if (pod != null)
|
| + pod.setDisplayName(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) {
|
| + var pod = this.getPodWithUsername_(userID);
|
| + if (pod != null) {
|
| + pod.populateLanguageSelect(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) {
|
| + var pod = this.getPodWithUsername_(userID);
|
| + if (pod != null)
|
| + pod.populateKeyboardSelect(locale, list);
|
| + },
|
| +
|
| + /**
|
| + * Called when window was resized.
|
| + */
|
| + onWindowResize: function() {
|
| + var layout = this.calculateLayout_();
|
| + if (layout.columns != this.columns || layout.rows != this.rows)
|
| + this.placePods_();
|
| +
|
| + // Wrap this in a set timeout so the function is called after the pod is
|
| + // finished transitioning so that we work with the final pod dimensions.
|
| + // If there is no focused pod that may be transitioning when this function
|
| + // is called, we can call scrollFocusedPodIntoView() right away.
|
| + var timeOut = 0;
|
| + if (this.focusedPod_) {
|
| + var style = getComputedStyle(this.focusedPod_);
|
| + timeOut = parseFloat(style.transitionDuration) * 1000;
|
| + }
|
| +
|
| + setTimeout(function() {
|
| + this.scrollFocusedPodIntoView();
|
| + }.bind(this), timeOut);
|
| + },
|
| +
|
| + /**
|
| + * Returns width of podrow having |columns| number of columns.
|
| + * @private
|
| + */
|
| + columnsToWidth_: function(columns) {
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| + var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
|
| + MARGIN_BY_COLUMNS[columns];
|
| + var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
|
| + POD_ROW_PADDING;
|
| + return 2 * rowPadding + columns * this.userPodWidth_ +
|
| + (columns - 1) * margin;
|
| + },
|
| +
|
| + /**
|
| + * Returns height of podrow having |rows| number of rows.
|
| + * @private
|
| + */
|
| + rowsToHeight_: function(rows) {
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| + var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
|
| + POD_ROW_PADDING;
|
| + return 2 * rowPadding + rows * this.userPodHeight_;
|
| + },
|
| +
|
| + /**
|
| + * Calculates number of columns and rows that podrow should have in order to
|
| + * hold as much its pods as possible for current screen size. Also it tries
|
| + * to choose layout that looks good.
|
| + * @return {{columns: number, rows: number}}
|
| + */
|
| + calculateLayout_: function() {
|
| + var preferredColumns = this.pods.length < COLUMNS.length ?
|
| + COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1];
|
| + var maxWidth = Oobe.getInstance().clientAreaSize.width;
|
| + var columns = preferredColumns;
|
| + while (maxWidth < this.columnsToWidth_(columns) && columns > 1)
|
| + --columns;
|
| + var rows = Math.floor((this.pods.length - 1) / columns) + 1;
|
| + if (getComputedStyle(
|
| + $('signin-banner'), null).getPropertyValue('display') != 'none') {
|
| + rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER);
|
| + }
|
| + if (!Oobe.getInstance().newDesktopUserManager) {
|
| + var maxHeigth = Oobe.getInstance().clientAreaSize.height;
|
| + while (maxHeigth < this.rowsToHeight_(rows) && rows > 1)
|
| + --rows;
|
| + }
|
| + // One more iteration if it's not enough cells to place all pods.
|
| + while (maxWidth >= this.columnsToWidth_(columns + 1) &&
|
| + columns * rows < this.pods.length &&
|
| + columns < MAX_NUMBER_OF_COLUMNS) {
|
| + ++columns;
|
| + }
|
| + return {columns: columns, rows: rows};
|
| + },
|
| +
|
| + /**
|
| + * Places pods onto their positions onto pod grid.
|
| + * @private
|
| + */
|
| + placePods_: function() {
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| + if (isDesktopUserManager && !Oobe.getInstance().userPodsPageVisible)
|
| + return;
|
| +
|
| + var layout = this.calculateLayout_();
|
| + var columns = this.columns = layout.columns;
|
| + var rows = this.rows = layout.rows;
|
| + var maxPodsNumber = columns * rows;
|
| + var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] :
|
| + MARGIN_BY_COLUMNS[columns];
|
| + this.parentNode.setPreferredSize(
|
| + this.columnsToWidth_(columns), this.rowsToHeight_(rows));
|
| + var height = this.userPodHeight_;
|
| + var width = this.userPodWidth_;
|
| + var pinPodLocation = { column: columns + 1, row: rows + 1 };
|
| + if (this.focusedPod_ && this.focusedPod_.isPinShown())
|
| + pinPodLocation = this.findPodLocation_(this.focusedPod_, columns, rows);
|
| +
|
| + this.pods.forEach(function(pod, index) {
|
| + if (index >= maxPodsNumber) {
|
| + pod.hidden = true;
|
| + return;
|
| + }
|
| + pod.hidden = false;
|
| + if (pod.offsetHeight != height &&
|
| + pod.offsetHeight != CROS_PIN_POD_HEIGHT) {
|
| + console.error('Pod offsetHeight (' + pod.offsetHeight +
|
| + ') and POD_HEIGHT (' + height + ') are not equal.');
|
| + }
|
| + if (pod.offsetWidth != width) {
|
| + console.error('Pod offsetWidth (' + pod.offsetWidth +
|
| + ') and POD_WIDTH (' + width + ') are not equal.');
|
| + }
|
| + var column = index % columns;
|
| + var row = Math.floor(index / columns);
|
| +
|
| + var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING :
|
| + POD_ROW_PADDING;
|
| + pod.left = rowPadding + column * (width + margin);
|
| +
|
| + // On desktop, we want the rows to always be equally spaced.
|
| + pod.top = isDesktopUserManager ? row * (height + rowPadding) :
|
| + row * height + rowPadding;
|
| + });
|
| + Oobe.getInstance().updateScreenSize(this.parentNode);
|
| + },
|
| +
|
| + /**
|
| + * Number of columns.
|
| + * @type {?number}
|
| + */
|
| + set columns(columns) {
|
| + // Cannot use 'columns' here.
|
| + this.setAttribute('ncolumns', columns);
|
| + },
|
| + get columns() {
|
| + return parseInt(this.getAttribute('ncolumns'));
|
| + },
|
| +
|
| + /**
|
| + * Number of rows.
|
| + * @type {?number}
|
| + */
|
| + set rows(rows) {
|
| + // Cannot use 'rows' here.
|
| + this.setAttribute('nrows', rows);
|
| + },
|
| + get rows() {
|
| + return parseInt(this.getAttribute('nrows'));
|
| + },
|
| +
|
| + /**
|
| + * Whether the pod is currently focused.
|
| + * @param {UserPod} pod Pod to check for focus.
|
| + * @return {boolean} Pod focus status.
|
| + */
|
| + isFocused: function(pod) {
|
| + return this.focusedPod_ == pod;
|
| + },
|
| +
|
| + /**
|
| + * Focuses a given user pod or clear focus when given null.
|
| + * @param {UserPod=} podToFocus User pod to focus (undefined clears focus).
|
| + * @param {boolean=} opt_force If true, forces focus update even when
|
| + * podToFocus is already focused.
|
| + * @param {boolean=} opt_skipInputFocus If true, don't focus on the input
|
| + * box of user pod.
|
| + */
|
| + focusPod: function(podToFocus, opt_force, opt_skipInputFocus) {
|
| + if (this.isFocused(podToFocus) && !opt_force) {
|
| + // Calling focusPod w/o podToFocus means reset.
|
| + if (!podToFocus)
|
| + Oobe.clearErrors();
|
| + return;
|
| + }
|
| +
|
| + // Make sure there's only one focusPod operation happening at a time.
|
| + if (this.insideFocusPod_) {
|
| + return;
|
| + }
|
| + this.insideFocusPod_ = true;
|
| +
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i) {
|
| + if (!this.alwaysFocusSinglePod) {
|
| + pod.isActionBoxMenuActive = false;
|
| + }
|
| + if (pod != podToFocus) {
|
| + pod.isActionBoxMenuHovered = false;
|
| + pod.classList.remove('focused');
|
| + pod.setPinVisibility(false);
|
| + this.setUserPodFingerprintIcon(
|
| + pod.user.username, FINGERPRINT_STATES.HIDDEN);
|
| + // On Desktop, the faded style is not set correctly, so we should
|
| + // manually fade out non-focused pods if there is a focused pod.
|
| + if (pod.user.isDesktopUser && podToFocus)
|
| + pod.classList.add('faded');
|
| + else
|
| + pod.classList.remove('faded');
|
| + pod.reset(false);
|
| + }
|
| + }
|
| +
|
| + // Clear any error messages for previous pod.
|
| + if (!this.isFocused(podToFocus))
|
| + Oobe.clearErrors();
|
| +
|
| + this.focusedPod_ = podToFocus;
|
| + if (podToFocus) {
|
| + // Only show the keyboard if it is fully loaded.
|
| + if (podToFocus.isPinReady())
|
| + podToFocus.setPinVisibility(true);
|
| + podToFocus.classList.remove('faded');
|
| + podToFocus.classList.add('focused');
|
| + if (!podToFocus.multiProfilesPolicyApplied) {
|
| + podToFocus.classList.toggle('signing-in', false);
|
| + if (!opt_skipInputFocus)
|
| + podToFocus.focusInput();
|
| + } else {
|
| + podToFocus.userTypeBubbleElement.classList.add('bubble-shown');
|
| + // Note it is not necessary to skip this focus request when
|
| + // |opt_skipInputFocus| is true. When |multiProfilesPolicyApplied|
|
| + // is false, it doesn't focus on the password input box by default.
|
| + podToFocus.focus();
|
| + }
|
| +
|
| + // focusPod() automatically loads wallpaper
|
| + if (!podToFocus.user.isApp)
|
| + chrome.send('focusPod', [podToFocus.user.username]);
|
| + this.firstShown_ = false;
|
| + this.lastFocusedPod_ = podToFocus;
|
| + this.scrollFocusedPodIntoView();
|
| + this.setUserPodFingerprintIcon(
|
| + podToFocus.user.username, FINGERPRINT_STATES.DEFAULT);
|
| + } else {
|
| + chrome.send('noPodFocused');
|
| + }
|
| + this.insideFocusPod_ = false;
|
| + },
|
| +
|
| + /**
|
| + * Resets wallpaper to the last active user's wallpaper, if any.
|
| + */
|
| + loadLastWallpaper: function() {
|
| + if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp)
|
| + chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]);
|
| + },
|
| +
|
| + /**
|
| + * Returns the currently activated pod.
|
| + * @type {UserPod}
|
| + */
|
| + get activatedPod() {
|
| + return this.activatedPod_;
|
| + },
|
| +
|
| + /**
|
| + * Sets currently activated pod.
|
| + * @param {UserPod} pod Pod to check for focus.
|
| + * @param {Event} e Event object.
|
| + */
|
| + setActivatedPod: function(pod, e) {
|
| + if (this.disabled) {
|
| + console.error('Cannot activate pod while sign-in UI is disabled.');
|
| + return;
|
| + }
|
| + if (pod && pod.activate(e))
|
| + this.activatedPod_ = pod;
|
| + },
|
| +
|
| + /**
|
| + * The pod of the signed-in user, if any; null otherwise.
|
| + * @type {?UserPod}
|
| + */
|
| + get lockedPod() {
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i) {
|
| + if (pod.user.signedIn)
|
| + return pod;
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * The pod that is preselected on user pod row show.
|
| + * @type {?UserPod}
|
| + */
|
| + get preselectedPod() {
|
| + var isDesktopUserManager = Oobe.getInstance().displayType ==
|
| + DISPLAY_TYPE.DESKTOP_USER_MANAGER;
|
| + if (isDesktopUserManager) {
|
| + // On desktop, don't pre-select a pod if it's the only one.
|
| + if (this.pods.length == 1)
|
| + return null;
|
| +
|
| + // The desktop User Manager can send an URI encoded profile path in the
|
| + // url hash, that indicates a pod that should be initially focused.
|
| + var focusedProfilePath =
|
| + decodeURIComponent(window.location.hash.substr(1));
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i) {
|
| + if (focusedProfilePath === pod.user.profilePath)
|
| + return pod;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + var lockedPod = this.lockedPod;
|
| + if (lockedPod)
|
| + return lockedPod;
|
| + for (i = 0; pod = this.pods[i]; ++i) {
|
| + if (!pod.multiProfilesPolicyApplied)
|
| + return pod;
|
| + }
|
| + return this.pods[0];
|
| + },
|
| +
|
| + /**
|
| + * Resets input UI.
|
| + * @param {boolean} takeFocus True to take focus.
|
| + */
|
| + reset: function(takeFocus) {
|
| + this.disabled = false;
|
| + if (this.activatedPod_)
|
| + this.activatedPod_.reset(takeFocus);
|
| + },
|
| +
|
| + /**
|
| + * Restores input focus to current selected pod, if there is any.
|
| + */
|
| + refocusCurrentPod: function() {
|
| + if (this.focusedPod_ && !this.focusedPod_.multiProfilesPolicyApplied) {
|
| + this.focusedPod_.focusInput();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Clears focused pod password field.
|
| + */
|
| + clearFocusedPod: function() {
|
| + if (!this.disabled && this.focusedPod_)
|
| + this.focusedPod_.reset(true);
|
| + },
|
| +
|
| + /**
|
| + * Shows signin UI.
|
| + * @param {string} email Email for signin UI.
|
| + */
|
| + showSigninUI: function(email) {
|
| + // Clear any error messages that might still be around.
|
| + Oobe.clearErrors();
|
| + this.disabled = true;
|
| + this.lastFocusedPod_ = this.getPodWithUsername_(email);
|
| + Oobe.showSigninUI(email);
|
| + },
|
| +
|
| + /**
|
| + * Updates current image of a user.
|
| + * @param {string} username User for which to update the image.
|
| + */
|
| + updateUserImage: function(username) {
|
| + var pod = this.getPodWithUsername_(username);
|
| + if (pod)
|
| + pod.updateUserImage();
|
| + },
|
| +
|
| + /**
|
| + * Handler of click event.
|
| + * @param {Event} e Click Event object.
|
| + * @private
|
| + */
|
| + handleClick_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| +
|
| + // Clear all menus if the click is outside pod menu and its
|
| + // button area.
|
| + if (!findAncestorByClass(e.target, 'action-box-menu') &&
|
| + !findAncestorByClass(e.target, 'action-box-area')) {
|
| + for (var i = 0, pod; pod = this.pods[i]; ++i)
|
| + pod.isActionBoxMenuActive = false;
|
| + }
|
| +
|
| + // Clears focus if not clicked on a pod and if there's more than one pod.
|
| + var pod = findAncestorByClass(e.target, 'pod');
|
| + if ((!pod || pod.parentNode != this) && !this.alwaysFocusSinglePod) {
|
| + this.focusPod();
|
| + }
|
| +
|
| + if (pod)
|
| + pod.isActionBoxMenuHovered = true;
|
| +
|
| + // Return focus back to single pod.
|
| + if (this.alwaysFocusSinglePod && !pod) {
|
| + if ($('login-header-bar').contains(e.target))
|
| + return;
|
| + this.focusPod(this.focusedPod_, true /* force */);
|
| + this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
|
| + this.focusedPod_.isActionBoxMenuHovered = false;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handler of mouse move event.
|
| + * @param {Event} e Click Event object.
|
| + * @private
|
| + */
|
| + handleMouseMove_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + if (e.movementX == 0 && e.movementY == 0)
|
| + return;
|
| +
|
| + // Defocus (thus hide) action box, if it is focused on a user pod
|
| + // and the pointer is not hovering over it.
|
| + var pod = findAncestorByClass(e.target, 'pod');
|
| + if (document.activeElement &&
|
| + document.activeElement.parentNode != pod &&
|
| + document.activeElement.classList.contains('action-box-area')) {
|
| + document.activeElement.parentNode.focus();
|
| + }
|
| +
|
| + if (pod)
|
| + pod.isActionBoxMenuHovered = true;
|
| +
|
| + // Hide action boxes on other user pods.
|
| + for (var i = 0, p; p = this.pods[i]; ++i)
|
| + if (p != pod && !p.isActionBoxMenuActive)
|
| + p.isActionBoxMenuHovered = false;
|
| + },
|
| +
|
| + /**
|
| + * Handles focus event.
|
| + * @param {Event} e Focus Event object.
|
| + * @private
|
| + */
|
| + handleFocus_: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + if (e.target.parentNode == this) {
|
| + // Focus on a pod
|
| + if (e.target.classList.contains('focused')) {
|
| + if (!e.target.multiProfilesPolicyApplied)
|
| + e.target.focusInput();
|
| + else
|
| + e.target.userTypeBubbleElement.classList.add('bubble-shown');
|
| + } else
|
| + this.focusPod(e.target);
|
| + return;
|
| + }
|
| +
|
| + var pod = findAncestorByClass(e.target, 'pod');
|
| + if (pod && pod.parentNode == this) {
|
| + // Focus on a control of a pod but not on the action area button.
|
| + if (!pod.classList.contains('focused')) {
|
| + if (e.target.classList.contains('action-box-area') ||
|
| + e.target.classList.contains('remove-warning-button')) {
|
| + // focusPod usually moves focus on the password input box which
|
| + // triggers virtual keyboard to show up. But the focus may move to a
|
| + // non text input element shortly by e.target.focus. Hence, a
|
| + // virtual keyboard flicking might be observed. We need to manually
|
| + // prevent focus on password input box to avoid virtual keyboard
|
| + // flicking in this case. See crbug.com/396016 for details.
|
| + this.focusPod(pod, false, true /* opt_skipInputFocus */);
|
| + } else {
|
| + this.focusPod(pod);
|
| + }
|
| + pod.userTypeBubbleElement.classList.remove('bubble-shown');
|
| + e.target.focus();
|
| + }
|
| + return;
|
| + }
|
| +
|
| + // Clears pod focus when we reach here. It means new focus is neither
|
| + // on a pod nor on a button/input for a pod.
|
| + // Do not "defocus" user pod when it is a single pod.
|
| + // That means that 'focused' class will not be removed and
|
| + // input field/button will always be visible.
|
| + if (!this.alwaysFocusSinglePod)
|
| + this.focusPod();
|
| + else {
|
| + // Hide user-type-bubble in case this is one pod and we lost focus of
|
| + // it.
|
| + this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Handler of keydown event.
|
| + * @param {Event} e KeyDown Event object.
|
| + */
|
| + handleKeyDown: function(e) {
|
| + if (this.disabled)
|
| + return;
|
| + var editing = e.target.tagName == 'INPUT' && e.target.value;
|
| + switch (e.key) {
|
| + case 'ArrowLeft':
|
| + if (!editing) {
|
| + if (this.focusedPod_ && this.focusedPod_.previousElementSibling)
|
| + this.focusPod(this.focusedPod_.previousElementSibling);
|
| + else
|
| + this.focusPod(this.lastElementChild);
|
| +
|
| + e.stopPropagation();
|
| + }
|
| + break;
|
| + case 'ArrowRight':
|
| + if (!editing) {
|
| + if (this.focusedPod_ && this.focusedPod_.nextElementSibling)
|
| + this.focusPod(this.focusedPod_.nextElementSibling);
|
| + else
|
| + this.focusPod(this.firstElementChild);
|
| +
|
| + e.stopPropagation();
|
| + }
|
| + break;
|
| + case 'Enter':
|
| + if (this.focusedPod_) {
|
| + var targetTag = e.target.tagName;
|
| + if (e.target == this.focusedPod_.passwordElement ||
|
| + (this.focusedPod_.pinKeyboard &&
|
| + e.target == this.focusedPod_.pinKeyboard.inputElement) ||
|
| + (targetTag != 'INPUT' &&
|
| + targetTag != 'BUTTON' &&
|
| + targetTag != 'A')) {
|
| + this.setActivatedPod(this.focusedPod_, e);
|
| + e.stopPropagation();
|
| + }
|
| + }
|
| + break;
|
| + case 'Escape':
|
| + if (!this.alwaysFocusSinglePod)
|
| + this.focusPod();
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called right after the pod row is shown.
|
| + */
|
| + handleAfterShow: function() {
|
| + var focusedPod = this.focusedPod_;
|
| +
|
| + // Without timeout changes in pods positions will be animated even though
|
| + // it happened when 'flying-pods' class was disabled.
|
| + setTimeout(function() {
|
| + Oobe.getInstance().toggleClass('flying-pods', true);
|
| + if (focusedPod)
|
| + ensureTransitionEndEvent(focusedPod);
|
| + }, 0);
|
| +
|
| + // Force input focus for user pod on show and once transition ends.
|
| + if (focusedPod) {
|
| + var screen = this.parentNode;
|
| + var self = this;
|
| + focusedPod.addEventListener('transitionend', function f(e) {
|
| + focusedPod.removeEventListener('transitionend', f);
|
| + focusedPod.reset(true);
|
| + // Notify screen that it is ready.
|
| + screen.onShow();
|
| + });
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called right before the pod row is shown.
|
| + */
|
| + handleBeforeShow: function() {
|
| + Oobe.getInstance().toggleClass('flying-pods', false);
|
| + for (var event in this.listeners_) {
|
| + this.ownerDocument.addEventListener(
|
| + event, this.listeners_[event][0], this.listeners_[event][1]);
|
| + }
|
| + $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR;
|
| +
|
| + if (this.podPlacementPostponed_) {
|
| + this.podPlacementPostponed_ = false;
|
| + this.placePods_();
|
| + this.maybePreselectPod();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Called when the element is hidden.
|
| + */
|
| + handleHide: function() {
|
| + for (var event in this.listeners_) {
|
| + this.ownerDocument.removeEventListener(
|
| + event, this.listeners_[event][0], this.listeners_[event][1]);
|
| + }
|
| + $('login-header-bar').buttonsTabIndex = 0;
|
| + },
|
| +
|
| + /**
|
| + * Called when a pod's user image finishes loading.
|
| + */
|
| + handlePodImageLoad: function(pod) {
|
| + var index = this.podsWithPendingImages_.indexOf(pod);
|
| + if (index == -1) {
|
| + return;
|
| + }
|
| +
|
| + this.podsWithPendingImages_.splice(index, 1);
|
| + if (this.podsWithPendingImages_.length == 0) {
|
| + this.classList.remove('images-loading');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Preselects pod, if needed.
|
| + */
|
| + maybePreselectPod: function() {
|
| + var pod = this.preselectedPod;
|
| + this.focusPod(pod);
|
| +
|
| + // Hide user-type-bubble in case all user pods are disabled and we focus
|
| + // first pod.
|
| + if (pod && pod.multiProfilesPolicyApplied) {
|
| + pod.userTypeBubbleElement.classList.remove('bubble-shown');
|
| + }
|
| + }
|
| + };
|
| +
|
| + return {
|
| + PodRow: PodRow
|
| + };
|
| +});
|
|
|