OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview User pod row implementation. |
| 7 */ |
| 8 |
| 9 cr.define('login', function() { |
| 10 /** |
| 11 * Number of displayed columns depending on user pod count. |
| 12 * @type {Array<number>} |
| 13 * @const |
| 14 */ |
| 15 var COLUMNS = [0, 1, 2, 3, 4, 5, 4, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 6, 6]; |
| 16 |
| 17 /** |
| 18 * Mapping between number of columns in pod-row and margin between user pods |
| 19 * for such layout. |
| 20 * @type {Array<number>} |
| 21 * @const |
| 22 */ |
| 23 var MARGIN_BY_COLUMNS = [undefined, 40, 40, 40, 40, 40, 12]; |
| 24 |
| 25 /** |
| 26 * Mapping between number of columns in the desktop pod-row and margin |
| 27 * between user pods for such layout. |
| 28 * @type {Array<number>} |
| 29 * @const |
| 30 */ |
| 31 var DESKTOP_MARGIN_BY_COLUMNS = [undefined, 32, 32, 32, 32, 32, 32]; |
| 32 |
| 33 /** |
| 34 * Maximal number of columns currently supported by pod-row. |
| 35 * @type {number} |
| 36 * @const |
| 37 */ |
| 38 var MAX_NUMBER_OF_COLUMNS = 6; |
| 39 |
| 40 /** |
| 41 * Maximal number of rows if sign-in banner is displayed alonside. |
| 42 * @type {number} |
| 43 * @const |
| 44 */ |
| 45 var MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER = 2; |
| 46 |
| 47 /** |
| 48 * Variables used for pod placement processing. Width and height should be |
| 49 * synced with computed CSS sizes of pods. |
| 50 */ |
| 51 var CROS_POD_WIDTH = 180; |
| 52 var DESKTOP_POD_WIDTH = 180; |
| 53 var MD_DESKTOP_POD_WIDTH = 160; |
| 54 var PUBLIC_EXPANDED_BASIC_WIDTH = 500; |
| 55 var PUBLIC_EXPANDED_ADVANCED_WIDTH = 610; |
| 56 var CROS_POD_HEIGHT = 213; |
| 57 var DESKTOP_POD_HEIGHT = 226; |
| 58 var MD_DESKTOP_POD_HEIGHT = 200; |
| 59 var POD_ROW_PADDING = 10; |
| 60 var DESKTOP_ROW_PADDING = 32; |
| 61 var CUSTOM_ICON_CONTAINER_SIZE = 40; |
| 62 var CROS_PIN_POD_HEIGHT = 417; |
| 63 |
| 64 /** |
| 65 * Minimal padding between user pod and virtual keyboard. |
| 66 * @type {number} |
| 67 * @const |
| 68 */ |
| 69 var USER_POD_KEYBOARD_MIN_PADDING = 20; |
| 70 |
| 71 /** |
| 72 * Maximum time for which the pod row remains hidden until all user images |
| 73 * have been loaded. |
| 74 * @type {number} |
| 75 * @const |
| 76 */ |
| 77 var POD_ROW_IMAGES_LOAD_TIMEOUT_MS = 3000; |
| 78 |
| 79 /** |
| 80 * Public session help topic identifier. |
| 81 * @type {number} |
| 82 * @const |
| 83 */ |
| 84 var HELP_TOPIC_PUBLIC_SESSION = 3041033; |
| 85 |
| 86 /** |
| 87 * Tab order for user pods. Update these when adding new controls. |
| 88 * @enum {number} |
| 89 * @const |
| 90 */ |
| 91 var UserPodTabOrder = { |
| 92 POD_INPUT: 1, // Password input field, Action box menu button and |
| 93 // the pod itself. |
| 94 PIN_KEYBOARD: 2, // Pin keyboard below the password input field. |
| 95 POD_CUSTOM_ICON: 3, // Pod custom icon next to password input field. |
| 96 HEADER_BAR: 4, // Buttons on the header bar (Shutdown, Add User). |
| 97 POD_MENU_ITEM: 5 // User pad menu items (User info, Remove user). |
| 98 }; |
| 99 |
| 100 /** |
| 101 * Supported authentication types. Keep in sync with the enum in |
| 102 * chrome/browser/signin/screenlock_bridge.h |
| 103 * @enum {number} |
| 104 * @const |
| 105 */ |
| 106 var AUTH_TYPE = { |
| 107 OFFLINE_PASSWORD: 0, |
| 108 ONLINE_SIGN_IN: 1, |
| 109 NUMERIC_PIN: 2, |
| 110 USER_CLICK: 3, |
| 111 EXPAND_THEN_USER_CLICK: 4, |
| 112 FORCE_OFFLINE_PASSWORD: 5 |
| 113 }; |
| 114 |
| 115 /** |
| 116 * Names of authentication types. |
| 117 */ |
| 118 var AUTH_TYPE_NAMES = { |
| 119 0: 'offlinePassword', |
| 120 1: 'onlineSignIn', |
| 121 2: 'numericPin', |
| 122 3: 'userClick', |
| 123 4: 'expandThenUserClick', |
| 124 5: 'forceOfflinePassword' |
| 125 }; |
| 126 |
| 127 /** |
| 128 * Supported fingerprint unlock states. |
| 129 * @enum {number} |
| 130 * @const |
| 131 */ |
| 132 var FINGERPRINT_STATES = { |
| 133 HIDDEN: 0, |
| 134 DEFAULT: 1, |
| 135 SIGNIN: 2, |
| 136 FAILED: 3, |
| 137 }; |
| 138 |
| 139 /** |
| 140 * The fingerprint states to classes mapping. |
| 141 * {@code state} properties indicate current fingerprint unlock state. |
| 142 * {@code class} properties are CSS classes used to set the icons' background |
| 143 * and password placeholder color. |
| 144 * @const {Array<{type: !number, class: !string}>} |
| 145 */ |
| 146 var FINGERPRINT_STATES_MAPPING = [ |
| 147 {state: FINGERPRINT_STATES.HIDDEN, class: 'hidden'}, |
| 148 {state: FINGERPRINT_STATES.DEFAULT, class: 'default'}, |
| 149 {state: FINGERPRINT_STATES.SIGNIN, class: 'signin'}, |
| 150 {state: FINGERPRINT_STATES.FAILED, class: 'failed'} |
| 151 ]; |
| 152 |
| 153 // Focus and tab order are organized as follows: |
| 154 // |
| 155 // (1) all user pods have tab index 1 so they are traversed first; |
| 156 // (2) when a user pod is activated, its tab index is set to -1 and its |
| 157 // main input field gets focus and tab index 1; |
| 158 // (3) if user pod custom icon is interactive, it has tab index 2 so it |
| 159 // follows the input. |
| 160 // (4) buttons on the header bar have tab index 3 so they follow the custom |
| 161 // icon, or user pod if custom icon is not interactive; |
| 162 // (5) Action box buttons have tab index 4 and follow header bar buttons; |
| 163 // (6) lastly, focus jumps to the Status Area and back to user pods. |
| 164 // |
| 165 // 'Focus' event is handled by a capture handler for the whole document |
| 166 // and in some cases 'mousedown' event handlers are used instead of 'click' |
| 167 // handlers where it's necessary to prevent 'focus' event from being fired. |
| 168 |
| 169 /** |
| 170 * Helper function to remove a class from given element. |
| 171 * @param {!HTMLElement} el Element whose class list to change. |
| 172 * @param {string} cl Class to remove. |
| 173 */ |
| 174 function removeClass(el, cl) { |
| 175 el.classList.remove(cl); |
| 176 } |
| 177 |
| 178 /** |
| 179 * Creates a user pod. |
| 180 * @constructor |
| 181 * @extends {HTMLDivElement} |
| 182 */ |
| 183 var UserPod = cr.ui.define(function() { |
| 184 var node = $('user-pod-template').cloneNode(true); |
| 185 node.removeAttribute('id'); |
| 186 return node; |
| 187 }); |
| 188 |
| 189 /** |
| 190 * Stops event propagation from the any user pod child element. |
| 191 * @param {Event} e Event to handle. |
| 192 */ |
| 193 function stopEventPropagation(e) { |
| 194 // Prevent default so that we don't trigger a 'focus' event. |
| 195 e.preventDefault(); |
| 196 e.stopPropagation(); |
| 197 } |
| 198 |
| 199 /** |
| 200 * Creates an element for custom icon shown in a user pod next to the input |
| 201 * field. |
| 202 * @constructor |
| 203 * @extends {HTMLDivElement} |
| 204 */ |
| 205 var UserPodCustomIcon = cr.ui.define(function() { |
| 206 var node = document.createElement('div'); |
| 207 node.classList.add('custom-icon-container'); |
| 208 node.hidden = true; |
| 209 |
| 210 // Create the actual icon element and add it as a child to the container. |
| 211 var iconNode = document.createElement('div'); |
| 212 iconNode.classList.add('custom-icon'); |
| 213 node.appendChild(iconNode); |
| 214 return node; |
| 215 }); |
| 216 |
| 217 /** |
| 218 * The supported user pod custom icons. |
| 219 * {@code id} properties should be in sync with values set by C++ side. |
| 220 * {@code class} properties are CSS classes used to set the icons' background. |
| 221 * @const {Array<{id: !string, class: !string}>} |
| 222 */ |
| 223 UserPodCustomIcon.ICONS = [ |
| 224 {id: 'locked', class: 'custom-icon-locked'}, |
| 225 {id: 'locked-to-be-activated', |
| 226 class: 'custom-icon-locked-to-be-activated'}, |
| 227 {id: 'locked-with-proximity-hint', |
| 228 class: 'custom-icon-locked-with-proximity-hint'}, |
| 229 {id: 'unlocked', class: 'custom-icon-unlocked'}, |
| 230 {id: 'hardlocked', class: 'custom-icon-hardlocked'}, |
| 231 {id: 'spinner', class: 'custom-icon-spinner'} |
| 232 ]; |
| 233 |
| 234 /** |
| 235 * The hover state for the icon. When user hovers over the icon, a tooltip |
| 236 * should be shown after a short delay. This enum is used to keep track of |
| 237 * the tooltip status related to hover state. |
| 238 * @enum {string} |
| 239 */ |
| 240 UserPodCustomIcon.HoverState = { |
| 241 /** The user is not hovering over the icon. */ |
| 242 NO_HOVER: 'no_hover', |
| 243 |
| 244 /** The user is hovering over the icon but the tooltip is not activated. */ |
| 245 HOVER: 'hover', |
| 246 |
| 247 /** |
| 248 * User is hovering over the icon and the tooltip is activated due to the |
| 249 * hover state (which happens with delay after user starts hovering). |
| 250 */ |
| 251 HOVER_TOOLTIP: 'hover_tooltip' |
| 252 }; |
| 253 |
| 254 /** |
| 255 * If the icon has a tooltip that should be automatically shown, the tooltip |
| 256 * is shown even when there is no user action (i.e. user is not hovering over |
| 257 * the icon), after a short delay. The tooltip should be hidden after some |
| 258 * time. Note that the icon will not be considered autoshown if it was |
| 259 * previously shown as a result of the user action. |
| 260 * This enum is used to keep track of this state. |
| 261 * @enum {string} |
| 262 */ |
| 263 UserPodCustomIcon.TooltipAutoshowState = { |
| 264 /** The tooltip should not be or was not automatically shown. */ |
| 265 DISABLED: 'disabled', |
| 266 |
| 267 /** |
| 268 * The tooltip should be automatically shown, but the timeout for showing |
| 269 * the tooltip has not yet passed. |
| 270 */ |
| 271 ENABLED: 'enabled', |
| 272 |
| 273 /** The tooltip was automatically shown. */ |
| 274 ACTIVE : 'active' |
| 275 }; |
| 276 |
| 277 UserPodCustomIcon.prototype = { |
| 278 __proto__: HTMLDivElement.prototype, |
| 279 |
| 280 /** |
| 281 * The id of the icon being shown. |
| 282 * @type {string} |
| 283 * @private |
| 284 */ |
| 285 iconId_: '', |
| 286 |
| 287 /** |
| 288 * A reference to the timeout for updating icon hover state. Non-null |
| 289 * only if there is an active timeout. |
| 290 * @type {?number} |
| 291 * @private |
| 292 */ |
| 293 updateHoverStateTimeout_: null, |
| 294 |
| 295 /** |
| 296 * A reference to the timeout for updating icon tooltip autoshow state. |
| 297 * Non-null only if there is an active timeout. |
| 298 * @type {?number} |
| 299 * @private |
| 300 */ |
| 301 updateTooltipAutoshowStateTimeout_: null, |
| 302 |
| 303 /** |
| 304 * Callback for click and 'Enter' key events that gets set if the icon is |
| 305 * interactive. |
| 306 * @type {?function()} |
| 307 * @private |
| 308 */ |
| 309 actionHandler_: null, |
| 310 |
| 311 /** |
| 312 * The current tooltip state. |
| 313 * @type {{active: function(): boolean, |
| 314 * autoshow: !UserPodCustomIcon.TooltipAutoshowState, |
| 315 * hover: !UserPodCustomIcon.HoverState, |
| 316 * text: string}} |
| 317 * @private |
| 318 */ |
| 319 tooltipState_: { |
| 320 /** |
| 321 * Utility method for determining whether the tooltip is active, either as |
| 322 * a result of hover state or being autoshown. |
| 323 * @return {boolean} |
| 324 */ |
| 325 active: function() { |
| 326 return this.autoshow == UserPodCustomIcon.TooltipAutoshowState.ACTIVE || |
| 327 this.hover == UserPodCustomIcon.HoverState.HOVER_TOOLTIP; |
| 328 }, |
| 329 |
| 330 /** |
| 331 * @type {!UserPodCustomIcon.TooltipAutoshowState} |
| 332 */ |
| 333 autoshow: UserPodCustomIcon.TooltipAutoshowState.DISABLED, |
| 334 |
| 335 /** |
| 336 * @type {!UserPodCustomIcon.HoverState} |
| 337 */ |
| 338 hover: UserPodCustomIcon.HoverState.NO_HOVER, |
| 339 |
| 340 /** |
| 341 * The tooltip text. |
| 342 * @type {string} |
| 343 */ |
| 344 text: '' |
| 345 }, |
| 346 |
| 347 /** @override */ |
| 348 decorate: function() { |
| 349 this.iconElement.addEventListener( |
| 350 'mouseover', |
| 351 this.updateHoverState_.bind(this, |
| 352 UserPodCustomIcon.HoverState.HOVER)); |
| 353 this.iconElement.addEventListener( |
| 354 'mouseout', |
| 355 this.updateHoverState_.bind(this, |
| 356 UserPodCustomIcon.HoverState.NO_HOVER)); |
| 357 this.iconElement.addEventListener('mousedown', |
| 358 this.handleMouseDown_.bind(this)); |
| 359 this.iconElement.addEventListener('click', |
| 360 this.handleClick_.bind(this)); |
| 361 this.iconElement.addEventListener('keydown', |
| 362 this.handleKeyDown_.bind(this)); |
| 363 |
| 364 // When the icon is focused using mouse, there should be no outline shown. |
| 365 // Preventing default mousedown event accomplishes this. |
| 366 this.iconElement.addEventListener('mousedown', function(e) { |
| 367 e.preventDefault(); |
| 368 }); |
| 369 }, |
| 370 |
| 371 /** |
| 372 * Getter for the icon element's div. |
| 373 * @return {HTMLDivElement} |
| 374 */ |
| 375 get iconElement() { |
| 376 return this.querySelector('.custom-icon'); |
| 377 }, |
| 378 |
| 379 /** |
| 380 * Updates the icon element class list to properly represent the provided |
| 381 * icon. |
| 382 * @param {!string} id The id of the icon that should be shown. Should be |
| 383 * one of the ids listed in {@code UserPodCustomIcon.ICONS}. |
| 384 */ |
| 385 setIcon: function(id) { |
| 386 this.iconId_ = id; |
| 387 UserPodCustomIcon.ICONS.forEach(function(icon) { |
| 388 this.iconElement.classList.toggle(icon.class, id == icon.id); |
| 389 }, this); |
| 390 }, |
| 391 |
| 392 /** |
| 393 * Sets the ARIA label for the icon. |
| 394 * @param {!string} ariaLabel |
| 395 */ |
| 396 setAriaLabel: function(ariaLabel) { |
| 397 this.iconElement.setAttribute('aria-label', ariaLabel); |
| 398 }, |
| 399 |
| 400 /** |
| 401 * Shows the icon. |
| 402 */ |
| 403 show: function() { |
| 404 // Show the icon if the current iconId is valid. |
| 405 var validIcon = false; |
| 406 UserPodCustomIcon.ICONS.forEach(function(icon) { |
| 407 validIcon = validIcon || this.iconId_ == icon.id; |
| 408 }, this); |
| 409 this.hidden = validIcon ? false : true; |
| 410 }, |
| 411 |
| 412 /** |
| 413 * Updates the icon tooltip. If {@code autoshow} parameter is set the |
| 414 * tooltip is immediatelly shown. If tooltip text is not set, the method |
| 415 * ensures the tooltip gets hidden. If tooltip is shown prior to this call, |
| 416 * it remains shown, but the tooltip text is updated. |
| 417 * @param {!{text: string, autoshow: boolean}} tooltip The tooltip |
| 418 * parameters. |
| 419 */ |
| 420 setTooltip: function(tooltip) { |
| 421 this.iconElement.classList.toggle('icon-with-tooltip', !!tooltip.text); |
| 422 |
| 423 this.updateTooltipAutoshowState_( |
| 424 tooltip.autoshow ? |
| 425 UserPodCustomIcon.TooltipAutoshowState.ENABLED : |
| 426 UserPodCustomIcon.TooltipAutoshowState.DISABLED); |
| 427 this.tooltipState_.text = tooltip.text; |
| 428 this.updateTooltip_(); |
| 429 }, |
| 430 |
| 431 /** |
| 432 * Sets up icon tabIndex attribute and handler for click and 'Enter' key |
| 433 * down events. |
| 434 * @param {?function()} callback If icon should be interactive, the |
| 435 * function to get called on click and 'Enter' key down events. Should |
| 436 * be null to make the icon non interactive. |
| 437 */ |
| 438 setInteractive: function(callback) { |
| 439 this.iconElement.classList.toggle('interactive-custom-icon', !!callback); |
| 440 |
| 441 // Update tabIndex property if needed. |
| 442 if (!!this.actionHandler_ != !!callback) { |
| 443 if (callback) { |
| 444 this.iconElement.setAttribute('tabIndex', |
| 445 UserPodTabOrder.POD_CUSTOM_ICON); |
| 446 } else { |
| 447 this.iconElement.removeAttribute('tabIndex'); |
| 448 } |
| 449 } |
| 450 |
| 451 // Set the new action handler. |
| 452 this.actionHandler_ = callback; |
| 453 }, |
| 454 |
| 455 /** |
| 456 * Hides the icon and cleans its state. |
| 457 */ |
| 458 hide: function() { |
| 459 this.hideTooltip_(); |
| 460 this.clearUpdateHoverStateTimeout_(); |
| 461 this.clearUpdateTooltipAutoshowStateTimeout_(); |
| 462 this.setInteractive(null); |
| 463 this.hidden = true; |
| 464 }, |
| 465 |
| 466 /** |
| 467 * Clears timeout for showing a tooltip if one is set. Used to cancel |
| 468 * showing the tooltip when the user starts typing the password. |
| 469 */ |
| 470 cancelDelayedTooltipShow: function() { |
| 471 this.updateTooltipAutoshowState_( |
| 472 UserPodCustomIcon.TooltipAutoshowState.DISABLED); |
| 473 this.clearUpdateHoverStateTimeout_(); |
| 474 }, |
| 475 |
| 476 /** |
| 477 * Handles mouse down event in the icon element. |
| 478 * @param {Event} e The mouse down event. |
| 479 * @private |
| 480 */ |
| 481 handleMouseDown_: function(e) { |
| 482 this.updateHoverState_(UserPodCustomIcon.HoverState.NO_HOVER); |
| 483 this.updateTooltipAutoshowState_( |
| 484 UserPodCustomIcon.TooltipAutoshowState.DISABLED); |
| 485 |
| 486 // Stop the event propagation so in the case the click ends up on the |
| 487 // user pod (outside the custom icon) auth is not attempted. |
| 488 stopEventPropagation(e); |
| 489 }, |
| 490 |
| 491 /** |
| 492 * Handles click event on the icon element. No-op if |
| 493 * {@code this.actionHandler_} is not set. |
| 494 * @param {Event} e The click event. |
| 495 * @private |
| 496 */ |
| 497 handleClick_: function(e) { |
| 498 if (!this.actionHandler_) |
| 499 return; |
| 500 this.actionHandler_(); |
| 501 stopEventPropagation(e); |
| 502 }, |
| 503 |
| 504 /** |
| 505 * Handles key down event on the icon element. Only 'Enter' key is handled. |
| 506 * No-op if {@code this.actionHandler_} is not set. |
| 507 * @param {Event} e The key down event. |
| 508 * @private |
| 509 */ |
| 510 handleKeyDown_: function(e) { |
| 511 if (!this.actionHandler_ || e.key != 'Enter') |
| 512 return; |
| 513 this.actionHandler_(e); |
| 514 stopEventPropagation(e); |
| 515 }, |
| 516 |
| 517 /** |
| 518 * Changes the tooltip hover state and updates tooltip visibility if needed. |
| 519 * @param {!UserPodCustomIcon.HoverState} state |
| 520 * @private |
| 521 */ |
| 522 updateHoverState_: function(state) { |
| 523 this.clearUpdateHoverStateTimeout_(); |
| 524 this.sanitizeTooltipStateIfBubbleHidden_(); |
| 525 |
| 526 if (state == UserPodCustomIcon.HoverState.HOVER) { |
| 527 if (this.tooltipState_.active()) { |
| 528 this.tooltipState_.hover = UserPodCustomIcon.HoverState.HOVER_TOOLTIP; |
| 529 } else { |
| 530 this.updateHoverStateSoon_( |
| 531 UserPodCustomIcon.HoverState.HOVER_TOOLTIP); |
| 532 } |
| 533 return; |
| 534 } |
| 535 |
| 536 if (state != UserPodCustomIcon.HoverState.NO_HOVER && |
| 537 state != UserPodCustomIcon.HoverState.HOVER_TOOLTIP) { |
| 538 console.error('Invalid hover state ' + state); |
| 539 return; |
| 540 } |
| 541 |
| 542 this.tooltipState_.hover = state; |
| 543 this.updateTooltip_(); |
| 544 }, |
| 545 |
| 546 /** |
| 547 * Sets up a timeout for updating icon hover state. |
| 548 * @param {!UserPodCustomIcon.HoverState} state |
| 549 * @private |
| 550 */ |
| 551 updateHoverStateSoon_: function(state) { |
| 552 if (this.updateHoverStateTimeout_) |
| 553 clearTimeout(this.updateHoverStateTimeout_); |
| 554 this.updateHoverStateTimeout_ = |
| 555 setTimeout(this.updateHoverState_.bind(this, state), 1000); |
| 556 }, |
| 557 |
| 558 /** |
| 559 * Clears a timeout for updating icon hover state if there is one set. |
| 560 * @private |
| 561 */ |
| 562 clearUpdateHoverStateTimeout_: function() { |
| 563 if (this.updateHoverStateTimeout_) { |
| 564 clearTimeout(this.updateHoverStateTimeout_); |
| 565 this.updateHoverStateTimeout_ = null; |
| 566 } |
| 567 }, |
| 568 |
| 569 /** |
| 570 * Changes the tooltip autoshow state and changes tooltip visibility if |
| 571 * needed. |
| 572 * @param {!UserPodCustomIcon.TooltipAutoshowState} state |
| 573 * @private |
| 574 */ |
| 575 updateTooltipAutoshowState_: function(state) { |
| 576 this.clearUpdateTooltipAutoshowStateTimeout_(); |
| 577 this.sanitizeTooltipStateIfBubbleHidden_(); |
| 578 |
| 579 if (state == UserPodCustomIcon.TooltipAutoshowState.DISABLED) { |
| 580 if (this.tooltipState_.autoshow != state) { |
| 581 this.tooltipState_.autoshow = state; |
| 582 this.updateTooltip_(); |
| 583 } |
| 584 return; |
| 585 } |
| 586 |
| 587 if (this.tooltipState_.active()) { |
| 588 if (this.tooltipState_.autoshow != |
| 589 UserPodCustomIcon.TooltipAutoshowState.ACTIVE) { |
| 590 this.tooltipState_.autoshow = |
| 591 UserPodCustomIcon.TooltipAutoshowState.DISABLED; |
| 592 } else { |
| 593 // If the tooltip is already automatically shown, the timeout for |
| 594 // removing it should be reset. |
| 595 this.updateTooltipAutoshowStateSoon_( |
| 596 UserPodCustomIcon.TooltipAutoshowState.DISABLED); |
| 597 } |
| 598 return; |
| 599 } |
| 600 |
| 601 if (state == UserPodCustomIcon.TooltipAutoshowState.ENABLED) { |
| 602 this.updateTooltipAutoshowStateSoon_( |
| 603 UserPodCustomIcon.TooltipAutoshowState.ACTIVE); |
| 604 } else if (state == UserPodCustomIcon.TooltipAutoshowState.ACTIVE) { |
| 605 this.updateTooltipAutoshowStateSoon_( |
| 606 UserPodCustomIcon.TooltipAutoshowState.DISABLED); |
| 607 } |
| 608 |
| 609 this.tooltipState_.autoshow = state; |
| 610 this.updateTooltip_(); |
| 611 }, |
| 612 |
| 613 /** |
| 614 * Sets up a timeout for updating tooltip autoshow state. |
| 615 * @param {!UserPodCustomIcon.TooltipAutoshowState} state |
| 616 * @private |
| 617 */ |
| 618 updateTooltipAutoshowStateSoon_: function(state) { |
| 619 if (this.updateTooltipAutoshowStateTimeout_) |
| 620 clearTimeout(this.updateTooltupAutoshowStateTimeout_); |
| 621 var timeout = |
| 622 state == UserPodCustomIcon.TooltipAutoshowState.DISABLED ? |
| 623 5000 : 1000; |
| 624 this.updateTooltipAutoshowStateTimeout_ = |
| 625 setTimeout(this.updateTooltipAutoshowState_.bind(this, state), |
| 626 timeout); |
| 627 }, |
| 628 |
| 629 /** |
| 630 * Clears the timeout for updating tooltip autoshow state if one is set. |
| 631 * @private |
| 632 */ |
| 633 clearUpdateTooltipAutoshowStateTimeout_: function() { |
| 634 if (this.updateTooltipAutoshowStateTimeout_) { |
| 635 clearTimeout(this.updateTooltipAutoshowStateTimeout_); |
| 636 this.updateTooltipAutoshowStateTimeout_ = null; |
| 637 } |
| 638 }, |
| 639 |
| 640 /** |
| 641 * If tooltip bubble is hidden, this makes sure that hover and tooltip |
| 642 * autoshow states are not the ones that imply an active tooltip. |
| 643 * Used to handle a case where the tooltip bubble is hidden by an event that |
| 644 * does not update one of the states (e.g. click outside the pod will not |
| 645 * update tooltip autoshow state). Should be called before making |
| 646 * tooltip state updates. |
| 647 * @private |
| 648 */ |
| 649 sanitizeTooltipStateIfBubbleHidden_: function() { |
| 650 if (!$('bubble').hidden) |
| 651 return; |
| 652 |
| 653 if (this.tooltipState_.hover == |
| 654 UserPodCustomIcon.HoverState.HOVER_TOOLTIP && |
| 655 this.tooltipState_.text) { |
| 656 this.tooltipState_.hover = UserPodCustomIcon.HoverState.NO_HOVER; |
| 657 this.clearUpdateHoverStateTimeout_(); |
| 658 } |
| 659 |
| 660 if (this.tooltipState_.autoshow == |
| 661 UserPodCustomIcon.TooltipAutoshowState.ACTIVE) { |
| 662 this.tooltipState_.autoshow = |
| 663 UserPodCustomIcon.TooltipAutoshowState.DISABLED; |
| 664 this.clearUpdateTooltipAutoshowStateTimeout_(); |
| 665 } |
| 666 }, |
| 667 |
| 668 /** |
| 669 * Returns whether the user pod to which the custom icon belongs is focused. |
| 670 * @return {boolean} |
| 671 * @private |
| 672 */ |
| 673 isParentPodFocused_: function() { |
| 674 if ($('account-picker').hidden) |
| 675 return false; |
| 676 var parentPod = this.parentNode; |
| 677 while (parentPod && !parentPod.classList.contains('pod')) |
| 678 parentPod = parentPod.parentNode; |
| 679 return parentPod && parentPod.parentNode.isFocused(parentPod); |
| 680 }, |
| 681 |
| 682 /** |
| 683 * Depending on {@code this.tooltipState_}, it updates tooltip visibility |
| 684 * and text. |
| 685 * @private |
| 686 */ |
| 687 updateTooltip_: function() { |
| 688 if (this.hidden || !this.isParentPodFocused_()) |
| 689 return; |
| 690 |
| 691 if (!this.tooltipState_.active() || !this.tooltipState_.text) { |
| 692 this.hideTooltip_(); |
| 693 return; |
| 694 } |
| 695 |
| 696 // Show the tooltip bubble. |
| 697 var bubbleContent = document.createElement('div'); |
| 698 bubbleContent.textContent = this.tooltipState_.text; |
| 699 |
| 700 /** @const */ var BUBBLE_OFFSET = CUSTOM_ICON_CONTAINER_SIZE / 2; |
| 701 // TODO(tengs): Introduce a special reauth state for the account picker, |
| 702 // instead of showing the tooltip bubble here (crbug.com/409427). |
| 703 /** @const */ var BUBBLE_PADDING = 8 + (this.iconId_ ? 0 : 23); |
| 704 $('bubble').showContentForElement(this, |
| 705 cr.ui.Bubble.Attachment.LEFT, |
| 706 bubbleContent, |
| 707 BUBBLE_OFFSET, |
| 708 BUBBLE_PADDING); |
| 709 }, |
| 710 |
| 711 /** |
| 712 * Hides the tooltip. |
| 713 * @private |
| 714 */ |
| 715 hideTooltip_: function() { |
| 716 $('bubble').hideForElement(this); |
| 717 } |
| 718 }; |
| 719 |
| 720 /** |
| 721 * Unique salt added to user image URLs to prevent caching. Dictionary with |
| 722 * user names as keys. |
| 723 * @type {Object} |
| 724 */ |
| 725 UserPod.userImageSalt_ = {}; |
| 726 |
| 727 UserPod.prototype = { |
| 728 __proto__: HTMLDivElement.prototype, |
| 729 |
| 730 /** |
| 731 * Whether click on the pod can issue a user click auth attempt. The |
| 732 * attempt can be issued iff the pod was focused when the click |
| 733 * started (i.e. on mouse down event). |
| 734 * @type {boolean} |
| 735 * @private |
| 736 */ |
| 737 userClickAuthAllowed_: false, |
| 738 |
| 739 /** |
| 740 * Whether the user has recently authenticated with fingerprint. |
| 741 * @type {boolean} |
| 742 * @private |
| 743 */ |
| 744 fingerprintAuthenticated_: false, |
| 745 |
| 746 /** |
| 747 * True iff the pod can display the pin keyboard. The pin keyboard may not |
| 748 * always be displayed even if this is true, ie, if the virtual keyboard is |
| 749 * also being displayed. |
| 750 */ |
| 751 pinEnabled: false, |
| 752 |
| 753 /** @override */ |
| 754 decorate: function() { |
| 755 this.tabIndex = UserPodTabOrder.POD_INPUT; |
| 756 this.actionBoxAreaElement.tabIndex = UserPodTabOrder.POD_INPUT; |
| 757 |
| 758 this.addEventListener('keydown', this.handlePodKeyDown_.bind(this)); |
| 759 this.addEventListener('click', this.handleClickOnPod_.bind(this)); |
| 760 this.addEventListener('mousedown', this.handlePodMouseDown_.bind(this)); |
| 761 |
| 762 if (this.pinKeyboard) { |
| 763 this.pinKeyboard.passwordElement = this.passwordElement; |
| 764 this.pinKeyboard.addEventListener('pin-change', |
| 765 this.handleInputChanged_.bind(this)); |
| 766 this.pinKeyboard.tabIndex = UserPodTabOrder.PIN_KEYBOARD; |
| 767 } |
| 768 |
| 769 this.actionBoxAreaElement.addEventListener('mousedown', |
| 770 stopEventPropagation); |
| 771 this.actionBoxAreaElement.addEventListener('click', |
| 772 this.handleActionAreaButtonClick_.bind(this)); |
| 773 this.actionBoxAreaElement.addEventListener('keydown', |
| 774 this.handleActionAreaButtonKeyDown_.bind(this)); |
| 775 |
| 776 this.actionBoxMenuTitleElement.addEventListener('keydown', |
| 777 this.handleMenuTitleElementKeyDown_.bind(this)); |
| 778 this.actionBoxMenuTitleElement.addEventListener('blur', |
| 779 this.handleMenuTitleElementBlur_.bind(this)); |
| 780 |
| 781 this.actionBoxMenuRemoveElement.addEventListener('click', |
| 782 this.handleRemoveCommandClick_.bind(this)); |
| 783 this.actionBoxMenuRemoveElement.addEventListener('keydown', |
| 784 this.handleRemoveCommandKeyDown_.bind(this)); |
| 785 this.actionBoxMenuRemoveElement.addEventListener('blur', |
| 786 this.handleRemoveCommandBlur_.bind(this)); |
| 787 this.actionBoxRemoveUserWarningButtonElement.addEventListener('click', |
| 788 this.handleRemoveUserConfirmationClick_.bind(this)); |
| 789 this.actionBoxRemoveUserWarningButtonElement.addEventListener('keydown', |
| 790 this.handleRemoveUserConfirmationKeyDown_.bind(this)); |
| 791 |
| 792 if (this.fingerprintIconElement) { |
| 793 this.fingerprintIconElement.addEventListener( |
| 794 'mouseover', this.handleFingerprintIconMouseOver_.bind(this)); |
| 795 this.fingerprintIconElement.addEventListener( |
| 796 'mouseout', this.handleFingerprintIconMouseOut_.bind(this)); |
| 797 this.fingerprintIconElement.addEventListener( |
| 798 'mousedown', stopEventPropagation); |
| 799 } |
| 800 |
| 801 var customIcon = this.customIconElement; |
| 802 customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon); |
| 803 }, |
| 804 |
| 805 /** |
| 806 * Initializes the pod after its properties set and added to a pod row. |
| 807 */ |
| 808 initialize: function() { |
| 809 this.passwordElement.addEventListener('keydown', |
| 810 this.parentNode.handleKeyDown.bind(this.parentNode)); |
| 811 this.passwordElement.addEventListener('keypress', |
| 812 this.handlePasswordKeyPress_.bind(this)); |
| 813 this.passwordElement.addEventListener('input', |
| 814 this.handleInputChanged_.bind(this)); |
| 815 this.passwordElement.addEventListener('mouseup', |
| 816 this.handleInputMouseUp_.bind(this)); |
| 817 |
| 818 if (this.submitButton) { |
| 819 this.submitButton.addEventListener('click', |
| 820 this.handleSubmitButtonClick_.bind(this)); |
| 821 } |
| 822 |
| 823 this.imageElement.addEventListener('load', |
| 824 this.parentNode.handlePodImageLoad.bind(this.parentNode, this)); |
| 825 |
| 826 var initialAuthType = this.user.initialAuthType || |
| 827 AUTH_TYPE.OFFLINE_PASSWORD; |
| 828 this.setAuthType(initialAuthType, null); |
| 829 |
| 830 if (this.user.isActiveDirectory) |
| 831 this.setAttribute('is-active-directory', ''); |
| 832 |
| 833 this.userClickAuthAllowed_ = false; |
| 834 |
| 835 // Lazy load the assets needed for the polymer submit button. |
| 836 var isLockScreen = (Oobe.getInstance().displayType == DISPLAY_TYPE.LOCK); |
| 837 if (cr.isChromeOS && isLockScreen && |
| 838 !cr.ui.login.ResourceLoader.alreadyLoadedAssets( |
| 839 'custom-elements-user-pod')) { |
| 840 cr.ui.login.ResourceLoader.registerAssets({ |
| 841 id: 'custom-elements-user-pod', |
| 842 html: [{ url: 'custom_elements_user_pod.html' }] |
| 843 }); |
| 844 cr.ui.login.ResourceLoader.loadAssetsOnIdle('custom-elements-user-pod'); |
| 845 } |
| 846 }, |
| 847 |
| 848 /** |
| 849 * Whether the user pod is disabled. |
| 850 * @type {boolean} |
| 851 */ |
| 852 disabled_: false, |
| 853 get disabled() { |
| 854 return this.disabled_; |
| 855 }, |
| 856 set disabled(value) { |
| 857 this.disabled_ = value; |
| 858 this.querySelectorAll('button,input').forEach(function(element) { |
| 859 element.disabled = value |
| 860 }); |
| 861 |
| 862 // Special handling for submit button - the submit button should be |
| 863 // enabled only if there is the password value set. |
| 864 var submitButton = this.submitButton; |
| 865 if (submitButton) |
| 866 submitButton.disabled = value || !this.passwordElement.value; |
| 867 }, |
| 868 |
| 869 /** |
| 870 * Resets tab order for pod elements to its initial state. |
| 871 */ |
| 872 resetTabOrder: function() { |
| 873 // Note: the |mainInput| can be the pod itself. |
| 874 this.mainInput.tabIndex = -1; |
| 875 this.tabIndex = UserPodTabOrder.POD_INPUT; |
| 876 }, |
| 877 |
| 878 /** |
| 879 * Handles keypress event (i.e. any textual input) on password input. |
| 880 * @param {Event} e Keypress Event object. |
| 881 * @private |
| 882 */ |
| 883 handlePasswordKeyPress_: function(e) { |
| 884 // When tabbing from the system tray a tab key press is received. Suppress |
| 885 // this so as not to type a tab character into the password field. |
| 886 if (e.keyCode == 9) { |
| 887 e.preventDefault(); |
| 888 return; |
| 889 } |
| 890 this.customIconElement.cancelDelayedTooltipShow(); |
| 891 }, |
| 892 |
| 893 /** |
| 894 * Handles a click event on submit button. |
| 895 * @param {Event} e Click event. |
| 896 */ |
| 897 handleSubmitButtonClick_: function(e) { |
| 898 this.parentNode.setActivatedPod(this, e); |
| 899 }, |
| 900 |
| 901 /** |
| 902 * Top edge margin number of pixels. |
| 903 * @type {?number} |
| 904 */ |
| 905 set top(top) { |
| 906 this.style.top = cr.ui.toCssPx(top); |
| 907 }, |
| 908 |
| 909 /** |
| 910 * Top edge margin number of pixels. |
| 911 */ |
| 912 get top() { |
| 913 return parseInt(this.style.top); |
| 914 }, |
| 915 |
| 916 /** |
| 917 * Left edge margin number of pixels. |
| 918 * @type {?number} |
| 919 */ |
| 920 set left(left) { |
| 921 this.style.left = cr.ui.toCssPx(left); |
| 922 }, |
| 923 |
| 924 /** |
| 925 * Left edge margin number of pixels. |
| 926 */ |
| 927 get left() { |
| 928 return parseInt(this.style.left); |
| 929 }, |
| 930 |
| 931 /** |
| 932 * Height number of pixels. |
| 933 */ |
| 934 get height() { |
| 935 return this.offsetHeight; |
| 936 }, |
| 937 |
| 938 /** |
| 939 * Gets image element. |
| 940 * @type {!HTMLImageElement} |
| 941 */ |
| 942 get imageElement() { |
| 943 return this.querySelector('.user-image'); |
| 944 }, |
| 945 |
| 946 /** |
| 947 * Gets name element. |
| 948 * @type {!HTMLDivElement} |
| 949 */ |
| 950 get nameElement() { |
| 951 return this.querySelector('.name'); |
| 952 }, |
| 953 |
| 954 /** |
| 955 * Gets reauth name hint element. |
| 956 * @type {!HTMLDivElement} |
| 957 */ |
| 958 get reauthNameHintElement() { |
| 959 return this.querySelector('.reauth-name-hint'); |
| 960 }, |
| 961 |
| 962 /** |
| 963 * Gets the container holding the password field. |
| 964 * @type {!HTMLInputElement} |
| 965 */ |
| 966 get passwordEntryContainerElement() { |
| 967 return this.querySelector('.password-entry-container'); |
| 968 }, |
| 969 |
| 970 /** |
| 971 * Gets password field. |
| 972 * @type {!HTMLInputElement} |
| 973 */ |
| 974 get passwordElement() { |
| 975 return this.querySelector('.password'); |
| 976 }, |
| 977 |
| 978 /** |
| 979 * Gets submit button. |
| 980 * @type {!HTMLInputElement} |
| 981 */ |
| 982 get submitButton() { |
| 983 return this.querySelector('.submit-button'); |
| 984 }, |
| 985 |
| 986 /** |
| 987 * Gets the password label, which is used to show a message where the |
| 988 * password field is normally. |
| 989 * @type {!HTMLInputElement} |
| 990 */ |
| 991 get passwordLabelElement() { |
| 992 return this.querySelector('.password-label'); |
| 993 }, |
| 994 |
| 995 get pinContainer() { |
| 996 return this.querySelector('.pin-container'); |
| 997 }, |
| 998 |
| 999 /** |
| 1000 * Gets the pin-keyboard of the pod. |
| 1001 * @type {!HTMLElement} |
| 1002 */ |
| 1003 get pinKeyboard() { |
| 1004 return this.querySelector('pin-keyboard'); |
| 1005 }, |
| 1006 |
| 1007 /** |
| 1008 * Gets user online sign in hint element. |
| 1009 * @type {!HTMLDivElement} |
| 1010 */ |
| 1011 get reauthWarningElement() { |
| 1012 return this.querySelector('.reauth-hint-container'); |
| 1013 }, |
| 1014 |
| 1015 /** |
| 1016 * Gets the container holding the launch app button. |
| 1017 * @type {!HTMLButtonElement} |
| 1018 */ |
| 1019 get launchAppButtonContainerElement() { |
| 1020 return this.querySelector('.launch-app-button-container'); |
| 1021 }, |
| 1022 |
| 1023 /** |
| 1024 * Gets launch app button. |
| 1025 * @type {!HTMLButtonElement} |
| 1026 */ |
| 1027 get launchAppButtonElement() { |
| 1028 return this.querySelector('.launch-app-button'); |
| 1029 }, |
| 1030 |
| 1031 /** |
| 1032 * Gets action box area. |
| 1033 * @type {!HTMLInputElement} |
| 1034 */ |
| 1035 get actionBoxAreaElement() { |
| 1036 return this.querySelector('.action-box-area'); |
| 1037 }, |
| 1038 |
| 1039 /** |
| 1040 * Gets user type icon area. |
| 1041 * @type {!HTMLDivElement} |
| 1042 */ |
| 1043 get userTypeIconAreaElement() { |
| 1044 return this.querySelector('.user-type-icon-area'); |
| 1045 }, |
| 1046 |
| 1047 /** |
| 1048 * Gets user type bubble like multi-profiles policy restriction message. |
| 1049 * @type {!HTMLDivElement} |
| 1050 */ |
| 1051 get userTypeBubbleElement() { |
| 1052 return this.querySelector('.user-type-bubble'); |
| 1053 }, |
| 1054 |
| 1055 /** |
| 1056 * Gets action box menu. |
| 1057 * @type {!HTMLDivElement} |
| 1058 */ |
| 1059 get actionBoxMenu() { |
| 1060 return this.querySelector('.action-box-menu'); |
| 1061 }, |
| 1062 |
| 1063 /** |
| 1064 * Gets action box menu title (user name and email). |
| 1065 * @type {!HTMLDivElement} |
| 1066 */ |
| 1067 get actionBoxMenuTitleElement() { |
| 1068 return this.querySelector('.action-box-menu-title'); |
| 1069 }, |
| 1070 |
| 1071 /** |
| 1072 * Gets action box menu title, user name item. |
| 1073 * @type {!HTMLSpanElement} |
| 1074 */ |
| 1075 get actionBoxMenuTitleNameElement() { |
| 1076 return this.querySelector('.action-box-menu-title-name'); |
| 1077 }, |
| 1078 |
| 1079 /** |
| 1080 * Gets action box menu title, user email item. |
| 1081 * @type {!HTMLSpanElement} |
| 1082 */ |
| 1083 get actionBoxMenuTitleEmailElement() { |
| 1084 return this.querySelector('.action-box-menu-title-email'); |
| 1085 }, |
| 1086 |
| 1087 /** |
| 1088 * Gets action box menu, remove user command item. |
| 1089 * @type {!HTMLInputElement} |
| 1090 */ |
| 1091 get actionBoxMenuCommandElement() { |
| 1092 return this.querySelector('.action-box-menu-remove-command'); |
| 1093 }, |
| 1094 |
| 1095 /** |
| 1096 * Gets action box menu, remove user command item div. |
| 1097 * @type {!HTMLInputElement} |
| 1098 */ |
| 1099 get actionBoxMenuRemoveElement() { |
| 1100 return this.querySelector('.action-box-menu-remove'); |
| 1101 }, |
| 1102 |
| 1103 /** |
| 1104 * Gets action box menu, remove user command item div. |
| 1105 * @type {!HTMLInputElement} |
| 1106 */ |
| 1107 get actionBoxRemoveUserWarningElement() { |
| 1108 return this.querySelector('.action-box-remove-user-warning'); |
| 1109 }, |
| 1110 |
| 1111 /** |
| 1112 * Gets action box menu, remove user command item div. |
| 1113 * @type {!HTMLInputElement} |
| 1114 */ |
| 1115 get actionBoxRemoveUserWarningButtonElement() { |
| 1116 return this.querySelector('.remove-warning-button'); |
| 1117 }, |
| 1118 |
| 1119 /** |
| 1120 * Gets the custom icon. This icon is normally hidden, but can be shown |
| 1121 * using the chrome.screenlockPrivate API. |
| 1122 * @type {!HTMLDivElement} |
| 1123 */ |
| 1124 get customIconElement() { |
| 1125 return this.querySelector('.custom-icon-container'); |
| 1126 }, |
| 1127 |
| 1128 /** |
| 1129 * Gets the elements used for statistics display. |
| 1130 * @type {Object.<string, !HTMLDivElement>} |
| 1131 */ |
| 1132 get statsMapElements() { |
| 1133 return { |
| 1134 'BrowsingHistory': |
| 1135 this.querySelector('.action-box-remove-user-warning-history'), |
| 1136 'Passwords': |
| 1137 this.querySelector('.action-box-remove-user-warning-passwords'), |
| 1138 'Bookmarks': |
| 1139 this.querySelector('.action-box-remove-user-warning-bookmarks'), |
| 1140 'Settings': |
| 1141 this.querySelector('.action-box-remove-user-warning-settings') |
| 1142 } |
| 1143 }, |
| 1144 |
| 1145 /** |
| 1146 * Gets the fingerprint icon area. |
| 1147 * @type {!HTMLDivElement} |
| 1148 */ |
| 1149 get fingerprintIconElement() { |
| 1150 return this.querySelector('.fingerprint-icon-container'); |
| 1151 }, |
| 1152 |
| 1153 /** |
| 1154 * Updates the user pod element. |
| 1155 */ |
| 1156 update: function() { |
| 1157 this.imageElement.src = 'chrome://userimage/' + this.user.username + |
| 1158 '?id=' + UserPod.userImageSalt_[this.user.username]; |
| 1159 |
| 1160 this.nameElement.textContent = this.user_.displayName; |
| 1161 this.reauthNameHintElement.textContent = this.user_.displayName; |
| 1162 this.classList.toggle('signed-in', this.user_.signedIn); |
| 1163 |
| 1164 if (this.isAuthTypeUserClick) |
| 1165 this.passwordLabelElement.textContent = this.authValue; |
| 1166 |
| 1167 this.updateActionBoxArea(); |
| 1168 |
| 1169 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF( |
| 1170 'passwordFieldAccessibleName', this.user_.emailAddress)); |
| 1171 |
| 1172 this.customizeUserPodPerUserType(); |
| 1173 }, |
| 1174 |
| 1175 updateActionBoxArea: function() { |
| 1176 if (this.user_.publicAccount || this.user_.isApp) { |
| 1177 this.actionBoxAreaElement.hidden = true; |
| 1178 return; |
| 1179 } |
| 1180 |
| 1181 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove; |
| 1182 |
| 1183 this.actionBoxAreaElement.setAttribute( |
| 1184 'aria-label', loadTimeData.getStringF( |
| 1185 'podMenuButtonAccessibleName', this.user_.emailAddress)); |
| 1186 this.actionBoxMenuRemoveElement.setAttribute( |
| 1187 'aria-label', loadTimeData.getString( |
| 1188 'podMenuRemoveItemAccessibleName')); |
| 1189 this.actionBoxMenuTitleNameElement.textContent = this.user_.isOwner ? |
| 1190 loadTimeData.getStringF('ownerUserPattern', this.user_.displayName) : |
| 1191 this.user_.displayName; |
| 1192 this.actionBoxMenuTitleEmailElement.textContent = this.user_.emailAddress; |
| 1193 |
| 1194 this.actionBoxMenuTitleEmailElement.hidden = |
| 1195 this.user_.legacySupervisedUser; |
| 1196 |
| 1197 this.actionBoxMenuCommandElement.textContent = |
| 1198 loadTimeData.getString('removeUser'); |
| 1199 }, |
| 1200 |
| 1201 customizeUserPodPerUserType: function() { |
| 1202 if (this.user_.childUser && !this.user_.isDesktopUser) { |
| 1203 this.setUserPodIconType('child'); |
| 1204 } else if (this.user_.legacySupervisedUser && !this.user_.isDesktopUser) { |
| 1205 this.setUserPodIconType('legacySupervised'); |
| 1206 this.classList.add('legacy-supervised'); |
| 1207 } else if (this.multiProfilesPolicyApplied) { |
| 1208 // Mark user pod as not focusable which in addition to the grayed out |
| 1209 // filter makes it look in disabled state. |
| 1210 this.classList.add('multiprofiles-policy-applied'); |
| 1211 this.setUserPodIconType('policy'); |
| 1212 |
| 1213 if (this.user.multiProfilesPolicy == 'primary-only') |
| 1214 this.querySelector('.mp-policy-primary-only-msg').hidden = false; |
| 1215 else if (this.user.multiProfilesPolicy == 'owner-primary-only') |
| 1216 this.querySelector('.mp-owner-primary-only-msg').hidden = false; |
| 1217 else |
| 1218 this.querySelector('.mp-policy-not-allowed-msg').hidden = false; |
| 1219 } else if (this.user_.isApp) { |
| 1220 this.setUserPodIconType('app'); |
| 1221 } |
| 1222 }, |
| 1223 |
| 1224 isPinReady: function() { |
| 1225 return this.pinKeyboard && this.pinKeyboard.offsetHeight > 0; |
| 1226 }, |
| 1227 |
| 1228 set showError(visible) { |
| 1229 if (this.submitButton) |
| 1230 this.submitButton.classList.toggle('error-shown', visible); |
| 1231 }, |
| 1232 |
| 1233 updatePinClass_: function(element, enable) { |
| 1234 element.classList.toggle('pin-enabled', enable); |
| 1235 element.classList.toggle('pin-disabled', !enable); |
| 1236 }, |
| 1237 |
| 1238 setPinVisibility: function(visible) { |
| 1239 if (this.isPinShown() == visible) |
| 1240 return; |
| 1241 |
| 1242 // Do not show pin if virtual keyboard is there. |
| 1243 if (visible && Oobe.getInstance().virtualKeyboardShown) |
| 1244 return; |
| 1245 |
| 1246 // Do not show pin keyboard if the pod does not have pin enabled. |
| 1247 if (visible && !this.pinEnabled) |
| 1248 return; |
| 1249 |
| 1250 var elements = this.getElementsByClassName('pin-tag'); |
| 1251 for (var i = 0; i < elements.length; ++i) |
| 1252 this.updatePinClass_(elements[i], visible); |
| 1253 this.updatePinClass_(this, visible); |
| 1254 |
| 1255 // Set the focus to the input element after showing/hiding pin keyboard. |
| 1256 this.mainInput.focus(); |
| 1257 |
| 1258 // Change the password placeholder based on pin keyboard visibility. |
| 1259 this.passwordElement.placeholder = loadTimeData.getString(visible ? |
| 1260 'pinKeyboardPlaceholderPinPassword' : 'passwordHint'); |
| 1261 |
| 1262 chrome.send('setForceDisableVirtualKeyboard', [visible]); |
| 1263 }, |
| 1264 |
| 1265 isPinShown: function() { |
| 1266 return this.classList.contains('pin-enabled'); |
| 1267 }, |
| 1268 |
| 1269 setUserPodIconType: function(userTypeClass) { |
| 1270 this.userTypeIconAreaElement.classList.add(userTypeClass); |
| 1271 this.userTypeIconAreaElement.hidden = false; |
| 1272 }, |
| 1273 |
| 1274 isFingerprintIconShown: function() { |
| 1275 return this.fingerprintIconElement && !this.fingerprintIconElement.hidden; |
| 1276 }, |
| 1277 |
| 1278 /** |
| 1279 * The user that this pod represents. |
| 1280 * @type {!Object} |
| 1281 */ |
| 1282 user_: undefined, |
| 1283 get user() { |
| 1284 return this.user_; |
| 1285 }, |
| 1286 set user(userDict) { |
| 1287 this.user_ = userDict; |
| 1288 this.update(); |
| 1289 }, |
| 1290 |
| 1291 /** |
| 1292 * Returns true if multi-profiles sign in is currently active and this |
| 1293 * user pod is restricted per policy. |
| 1294 * @type {boolean} |
| 1295 */ |
| 1296 get multiProfilesPolicyApplied() { |
| 1297 var isMultiProfilesUI = |
| 1298 (Oobe.getInstance().displayType == DISPLAY_TYPE.USER_ADDING); |
| 1299 return isMultiProfilesUI && !this.user_.isMultiProfilesAllowed; |
| 1300 }, |
| 1301 |
| 1302 /** |
| 1303 * Gets main input element. |
| 1304 * @type {(HTMLButtonElement|HTMLInputElement)} |
| 1305 */ |
| 1306 get mainInput() { |
| 1307 if (this.isAuthTypePassword) { |
| 1308 return this.passwordElement; |
| 1309 } else if (this.isAuthTypeOnlineSignIn) { |
| 1310 return this; |
| 1311 } else if (this.isAuthTypeUserClick) { |
| 1312 return this.passwordLabelElement; |
| 1313 } |
| 1314 }, |
| 1315 |
| 1316 /** |
| 1317 * Whether action box button is in active state. |
| 1318 * @type {boolean} |
| 1319 */ |
| 1320 get isActionBoxMenuActive() { |
| 1321 return this.actionBoxAreaElement.classList.contains('active'); |
| 1322 }, |
| 1323 set isActionBoxMenuActive(active) { |
| 1324 if (active == this.isActionBoxMenuActive) |
| 1325 return; |
| 1326 |
| 1327 if (active) { |
| 1328 this.actionBoxMenuRemoveElement.hidden = !this.user_.canRemove; |
| 1329 this.actionBoxRemoveUserWarningElement.hidden = true; |
| 1330 |
| 1331 // Clear focus first if another pod is focused. |
| 1332 if (!this.parentNode.isFocused(this)) { |
| 1333 this.parentNode.focusPod(undefined, true); |
| 1334 this.actionBoxAreaElement.focus(); |
| 1335 } |
| 1336 |
| 1337 // Hide user-type-bubble. |
| 1338 this.userTypeBubbleElement.classList.remove('bubble-shown'); |
| 1339 |
| 1340 this.actionBoxAreaElement.classList.add('active'); |
| 1341 |
| 1342 // Invisible focus causes ChromeVox to read user name and email. |
| 1343 this.actionBoxMenuTitleElement.tabIndex = UserPodTabOrder.POD_MENU_ITEM; |
| 1344 this.actionBoxMenuTitleElement.focus(); |
| 1345 |
| 1346 // If the user pod is on either edge of the screen, then the menu |
| 1347 // could be displayed partially ofscreen. |
| 1348 this.actionBoxMenu.classList.remove('left-edge-offset'); |
| 1349 this.actionBoxMenu.classList.remove('right-edge-offset'); |
| 1350 |
| 1351 var offsetLeft = |
| 1352 cr.ui.login.DisplayManager.getOffset(this.actionBoxMenu).left; |
| 1353 var menuWidth = this.actionBoxMenu.offsetWidth; |
| 1354 if (offsetLeft < 0) |
| 1355 this.actionBoxMenu.classList.add('left-edge-offset'); |
| 1356 else if (offsetLeft + menuWidth > window.innerWidth) |
| 1357 this.actionBoxMenu.classList.add('right-edge-offset'); |
| 1358 } else { |
| 1359 this.actionBoxAreaElement.classList.remove('active'); |
| 1360 this.actionBoxAreaElement.classList.remove('menu-moved-up'); |
| 1361 this.actionBoxMenu.classList.remove('menu-moved-up'); |
| 1362 } |
| 1363 }, |
| 1364 |
| 1365 /** |
| 1366 * Whether action box button is in hovered state. |
| 1367 * @type {boolean} |
| 1368 */ |
| 1369 get isActionBoxMenuHovered() { |
| 1370 return this.actionBoxAreaElement.classList.contains('hovered'); |
| 1371 }, |
| 1372 set isActionBoxMenuHovered(hovered) { |
| 1373 if (hovered == this.isActionBoxMenuHovered) |
| 1374 return; |
| 1375 |
| 1376 if (hovered) { |
| 1377 this.actionBoxAreaElement.classList.add('hovered'); |
| 1378 this.classList.add('hovered'); |
| 1379 } else { |
| 1380 if (this.multiProfilesPolicyApplied) |
| 1381 this.userTypeBubbleElement.classList.remove('bubble-shown'); |
| 1382 this.actionBoxAreaElement.classList.remove('hovered'); |
| 1383 this.classList.remove('hovered'); |
| 1384 } |
| 1385 }, |
| 1386 |
| 1387 /** |
| 1388 * Set the authentication type for the pod. |
| 1389 * @param {number} An auth type value defined in the AUTH_TYPE enum. |
| 1390 * @param {string} authValue The initial value used for the auth type. |
| 1391 */ |
| 1392 setAuthType: function(authType, authValue) { |
| 1393 this.authType_ = authType; |
| 1394 this.authValue_ = authValue; |
| 1395 this.setAttribute('auth-type', AUTH_TYPE_NAMES[this.authType_]); |
| 1396 this.update(); |
| 1397 this.reset(this.parentNode.isFocused(this)); |
| 1398 }, |
| 1399 |
| 1400 /** |
| 1401 * The auth type of the user pod. This value is one of the enum |
| 1402 * values in AUTH_TYPE. |
| 1403 * @type {number} |
| 1404 */ |
| 1405 get authType() { |
| 1406 return this.authType_; |
| 1407 }, |
| 1408 |
| 1409 /** |
| 1410 * The initial value used for the pod's authentication type. |
| 1411 * eg. a prepopulated password input when using password authentication. |
| 1412 */ |
| 1413 get authValue() { |
| 1414 return this.authValue_; |
| 1415 }, |
| 1416 |
| 1417 /** |
| 1418 * True if the the user pod uses a password to authenticate. |
| 1419 * @type {bool} |
| 1420 */ |
| 1421 get isAuthTypePassword() { |
| 1422 return this.authType_ == AUTH_TYPE.OFFLINE_PASSWORD || |
| 1423 this.authType_ == AUTH_TYPE.FORCE_OFFLINE_PASSWORD; |
| 1424 }, |
| 1425 |
| 1426 /** |
| 1427 * True if the the user pod uses a user click to authenticate. |
| 1428 * @type {bool} |
| 1429 */ |
| 1430 get isAuthTypeUserClick() { |
| 1431 return this.authType_ == AUTH_TYPE.USER_CLICK; |
| 1432 }, |
| 1433 |
| 1434 /** |
| 1435 * True if the the user pod uses a online sign in to authenticate. |
| 1436 * @type {bool} |
| 1437 */ |
| 1438 get isAuthTypeOnlineSignIn() { |
| 1439 return this.authType_ == AUTH_TYPE.ONLINE_SIGN_IN; |
| 1440 }, |
| 1441 |
| 1442 /** |
| 1443 * Updates the image element of the user. |
| 1444 */ |
| 1445 updateUserImage: function() { |
| 1446 UserPod.userImageSalt_[this.user.username] = new Date().getTime(); |
| 1447 this.update(); |
| 1448 }, |
| 1449 |
| 1450 /** |
| 1451 * Focuses on input element. |
| 1452 */ |
| 1453 focusInput: function() { |
| 1454 // Move tabIndex from the whole pod to the main input. |
| 1455 // Note: the |mainInput| can be the pod itself. |
| 1456 this.tabIndex = -1; |
| 1457 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| 1458 this.mainInput.focus(); |
| 1459 }, |
| 1460 |
| 1461 /** |
| 1462 * Activates the pod. |
| 1463 * @param {Event} e Event object. |
| 1464 * @return {boolean} True if activated successfully. |
| 1465 */ |
| 1466 activate: function(e) { |
| 1467 if (this.isAuthTypeOnlineSignIn) { |
| 1468 this.showSigninUI(); |
| 1469 } else if (this.isAuthTypeUserClick) { |
| 1470 Oobe.disableSigninUI(); |
| 1471 this.classList.toggle('signing-in', true); |
| 1472 chrome.send('attemptUnlock', [this.user.username]); |
| 1473 } else if (this.isAuthTypePassword) { |
| 1474 if (this.fingerprintAuthenticated_) { |
| 1475 this.fingerprintAuthenticated_ = false; |
| 1476 return true; |
| 1477 } |
| 1478 var pinValue = this.pinKeyboard ? this.pinKeyboard.value : ''; |
| 1479 var password = this.passwordElement.value || pinValue; |
| 1480 if (!password) |
| 1481 return false; |
| 1482 Oobe.disableSigninUI(); |
| 1483 chrome.send('authenticateUser', [this.user.username, password, |
| 1484 this.isPinShown()]); |
| 1485 } else { |
| 1486 console.error('Activating user pod with invalid authentication type: ' + |
| 1487 this.authType); |
| 1488 } |
| 1489 |
| 1490 return true; |
| 1491 }, |
| 1492 |
| 1493 showSupervisedUserSigninWarning: function() { |
| 1494 // Legacy supervised user token has been invalidated. |
| 1495 // Make sure that pod is focused i.e. "Sign in" button is seen. |
| 1496 this.parentNode.focusPod(this); |
| 1497 |
| 1498 var error = document.createElement('div'); |
| 1499 var messageDiv = document.createElement('div'); |
| 1500 messageDiv.className = 'error-message-bubble'; |
| 1501 messageDiv.textContent = |
| 1502 loadTimeData.getString('supervisedUserExpiredTokenWarning'); |
| 1503 error.appendChild(messageDiv); |
| 1504 |
| 1505 $('bubble').showContentForElement( |
| 1506 this.reauthWarningElement, |
| 1507 cr.ui.Bubble.Attachment.TOP, |
| 1508 error, |
| 1509 this.reauthWarningElement.offsetWidth / 2, |
| 1510 4); |
| 1511 // Move warning bubble up if it overlaps the shelf. |
| 1512 var maxHeight = |
| 1513 cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping($('bubble')); |
| 1514 if (maxHeight < $('bubble').offsetHeight) { |
| 1515 $('bubble').showContentForElement( |
| 1516 this.reauthWarningElement, |
| 1517 cr.ui.Bubble.Attachment.BOTTOM, |
| 1518 error, |
| 1519 this.reauthWarningElement.offsetWidth / 2, |
| 1520 4); |
| 1521 } |
| 1522 }, |
| 1523 |
| 1524 /** |
| 1525 * Shows signin UI for this user. |
| 1526 */ |
| 1527 showSigninUI: function() { |
| 1528 if (this.user.legacySupervisedUser && !this.user.isDesktopUser) { |
| 1529 this.showSupervisedUserSigninWarning(); |
| 1530 } else { |
| 1531 // Special case for multi-profiles sign in. We show users even if they |
| 1532 // are not allowed per policy. Restrict those users from starting GAIA. |
| 1533 if (this.multiProfilesPolicyApplied) |
| 1534 return; |
| 1535 |
| 1536 this.parentNode.showSigninUI(this.user.emailAddress); |
| 1537 } |
| 1538 }, |
| 1539 |
| 1540 /** |
| 1541 * Resets the input field and updates the tab order of pod controls. |
| 1542 * @param {boolean} takeFocus If true, input field takes focus. |
| 1543 */ |
| 1544 reset: function(takeFocus) { |
| 1545 this.passwordElement.value = ''; |
| 1546 if (this.pinKeyboard) |
| 1547 this.pinKeyboard.value = ''; |
| 1548 this.updateInput_(); |
| 1549 this.classList.toggle('signing-in', false); |
| 1550 if (takeFocus) { |
| 1551 if (!this.multiProfilesPolicyApplied) |
| 1552 this.focusInput(); // This will set a custom tab order. |
| 1553 } |
| 1554 else |
| 1555 this.resetTabOrder(); |
| 1556 }, |
| 1557 |
| 1558 /** |
| 1559 * Removes a user using the correct identifier based on user type. |
| 1560 * @param {Object} user User to be removed. |
| 1561 */ |
| 1562 removeUser: function(user) { |
| 1563 chrome.send('removeUser', |
| 1564 [user.isDesktopUser ? user.profilePath : user.username]); |
| 1565 }, |
| 1566 |
| 1567 /** |
| 1568 * Handles a click event on action area button. |
| 1569 * @param {Event} e Click event. |
| 1570 */ |
| 1571 handleActionAreaButtonClick_: function(e) { |
| 1572 if (this.parentNode.disabled) |
| 1573 return; |
| 1574 this.isActionBoxMenuActive = !this.isActionBoxMenuActive; |
| 1575 e.stopPropagation(); |
| 1576 }, |
| 1577 |
| 1578 /** |
| 1579 * Handles a keydown event on action area button. |
| 1580 * @param {Event} e KeyDown event. |
| 1581 */ |
| 1582 handleActionAreaButtonKeyDown_: function(e) { |
| 1583 if (this.disabled) |
| 1584 return; |
| 1585 switch (e.key) { |
| 1586 case 'Enter': |
| 1587 case ' ': |
| 1588 if (this.parentNode.focusedPod_ && !this.isActionBoxMenuActive) |
| 1589 this.isActionBoxMenuActive = true; |
| 1590 e.stopPropagation(); |
| 1591 break; |
| 1592 case 'ArrowUp': |
| 1593 case 'ArrowDown': |
| 1594 if (this.isActionBoxMenuActive) { |
| 1595 this.actionBoxMenuRemoveElement.tabIndex = |
| 1596 UserPodTabOrder.POD_MENU_ITEM; |
| 1597 this.actionBoxMenuRemoveElement.focus(); |
| 1598 } |
| 1599 e.stopPropagation(); |
| 1600 break; |
| 1601 // Ignore these two, so ChromeVox hotkeys don't close the menu before |
| 1602 // they can navigate through it. |
| 1603 case 'Shift': |
| 1604 case 'Meta': |
| 1605 break; |
| 1606 case 'Escape': |
| 1607 this.actionBoxAreaElement.focus(); |
| 1608 this.isActionBoxMenuActive = false; |
| 1609 e.stopPropagation(); |
| 1610 break; |
| 1611 case 'Tab': |
| 1612 if (!this.parentNode.alwaysFocusSinglePod) |
| 1613 this.parentNode.focusPod(); |
| 1614 default: |
| 1615 this.isActionBoxMenuActive = false; |
| 1616 break; |
| 1617 } |
| 1618 }, |
| 1619 |
| 1620 /** |
| 1621 * Handles a keydown event on menu title. |
| 1622 * @param {Event} e KeyDown event. |
| 1623 */ |
| 1624 handleMenuTitleElementKeyDown_: function(e) { |
| 1625 if (this.disabled) |
| 1626 return; |
| 1627 |
| 1628 if (e.key != 'Tab') { |
| 1629 this.handleActionAreaButtonKeyDown_(e); |
| 1630 return; |
| 1631 } |
| 1632 |
| 1633 if (e.shiftKey == false) { |
| 1634 if (this.actionBoxMenuRemoveElement.hidden) { |
| 1635 this.isActionBoxMenuActive = false; |
| 1636 } else { |
| 1637 this.actionBoxMenuRemoveElement.tabIndex = |
| 1638 UserPodTabOrder.POD_MENU_ITEM; |
| 1639 this.actionBoxMenuRemoveElement.focus(); |
| 1640 e.preventDefault(); |
| 1641 } |
| 1642 } else { |
| 1643 this.isActionBoxMenuActive = false; |
| 1644 this.focusInput(); |
| 1645 e.preventDefault(); |
| 1646 } |
| 1647 }, |
| 1648 |
| 1649 /** |
| 1650 * Handles a blur event on menu title. |
| 1651 * @param {Event} e Blur event. |
| 1652 */ |
| 1653 handleMenuTitleElementBlur_: function(e) { |
| 1654 if (this.disabled) |
| 1655 return; |
| 1656 this.actionBoxMenuTitleElement.tabIndex = -1; |
| 1657 }, |
| 1658 |
| 1659 /** |
| 1660 * Handles a click event on remove user command. |
| 1661 * @param {Event} e Click event. |
| 1662 */ |
| 1663 handleRemoveCommandClick_: function(e) { |
| 1664 this.showRemoveWarning_(); |
| 1665 }, |
| 1666 |
| 1667 /** |
| 1668 * Move the action box menu up if needed. |
| 1669 */ |
| 1670 moveActionMenuUpIfNeeded_: function() { |
| 1671 // Skip checking (computationally expensive) if already moved up. |
| 1672 if (this.actionBoxMenu.classList.contains('menu-moved-up')) |
| 1673 return; |
| 1674 |
| 1675 // Move up the menu if it overlaps shelf. |
| 1676 var maxHeight = cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping( |
| 1677 this.actionBoxMenu, true); |
| 1678 var actualHeight = parseInt( |
| 1679 window.getComputedStyle(this.actionBoxMenu).height); |
| 1680 if (maxHeight < actualHeight) { |
| 1681 this.actionBoxMenu.classList.add('menu-moved-up'); |
| 1682 this.actionBoxAreaElement.classList.add('menu-moved-up'); |
| 1683 } |
| 1684 }, |
| 1685 |
| 1686 /** |
| 1687 * Shows remove user warning. Used for legacy supervised users |
| 1688 * and non-device-owner on CrOS, and for all users on desktop. |
| 1689 */ |
| 1690 showRemoveWarning_: function() { |
| 1691 this.actionBoxMenuRemoveElement.hidden = true; |
| 1692 this.actionBoxRemoveUserWarningElement.hidden = false; |
| 1693 |
| 1694 if (!this.user.isDesktopUser) { |
| 1695 this.moveActionMenuUpIfNeeded_(); |
| 1696 if (!this.user.legacySupervisedUser) { |
| 1697 this.querySelector( |
| 1698 '.action-box-remove-user-warning-text').style.display = 'none'; |
| 1699 this.querySelector( |
| 1700 '.action-box-remove-user-warning-table-nonsync').style.display |
| 1701 = 'none'; |
| 1702 var message = loadTimeData.getString('removeNonOwnerUserWarningText'); |
| 1703 this.updateRemoveNonOwnerUserWarningMessage_(this.user.profilePath, |
| 1704 message); |
| 1705 } |
| 1706 } else { |
| 1707 // Show extra statistics information for desktop users |
| 1708 this.querySelector( |
| 1709 '.action-box-remove-non-owner-user-warning-text').hidden = true; |
| 1710 this.RemoveWarningDialogSetMessage_(true, false); |
| 1711 // set a global handler for the callback |
| 1712 window.updateRemoveWarningDialog = |
| 1713 this.updateRemoveWarningDialog_.bind(this); |
| 1714 chrome.send('removeUserWarningLoadStats', [this.user.profilePath]); |
| 1715 } |
| 1716 chrome.send('logRemoveUserWarningShown'); |
| 1717 }, |
| 1718 |
| 1719 /** |
| 1720 * Refresh the statistics in the remove user warning dialog. |
| 1721 * @param {string} profilePath The filepath of the URL (must be verified). |
| 1722 * @param {Object} profileStats Statistics associated with profileURL. |
| 1723 */ |
| 1724 updateRemoveWarningDialog_: function(profilePath, profileStats) { |
| 1725 if (profilePath !== this.user.profilePath) |
| 1726 return; |
| 1727 |
| 1728 var stats_elements = this.statsMapElements; |
| 1729 // Update individual statistics |
| 1730 var hasErrors = false; |
| 1731 for (var key in profileStats) { |
| 1732 if (stats_elements.hasOwnProperty(key)) { |
| 1733 if (profileStats[key].success) { |
| 1734 this.user.statistics[key] = profileStats[key]; |
| 1735 } else if (!this.user.statistics[key].success) { |
| 1736 hasErrors = true; |
| 1737 stats_elements[key].textContent = ''; |
| 1738 } |
| 1739 } |
| 1740 } |
| 1741 |
| 1742 this.RemoveWarningDialogSetMessage_(false, hasErrors); |
| 1743 }, |
| 1744 |
| 1745 /** |
| 1746 * Set the new message in the dialog. |
| 1747 * @param {boolean} Whether this is the first output, that requires setting |
| 1748 * a in-progress message. |
| 1749 * @param {boolean} Whether any actual query to the statistics have failed. |
| 1750 * Should be true only if there is an error and the corresponding statistic |
| 1751 * is also unavailable in ProfileAttributesStorage. |
| 1752 */ |
| 1753 RemoveWarningDialogSetMessage_: function(isInitial, hasErrors) { |
| 1754 var stats_elements = this.statsMapElements; |
| 1755 var total_count = 0; |
| 1756 var num_stats_loaded = 0; |
| 1757 for (var key in stats_elements) { |
| 1758 if (this.user.statistics[key].success) { |
| 1759 var count = this.user.statistics[key].count; |
| 1760 stats_elements[key].textContent = count; |
| 1761 total_count += count; |
| 1762 num_stats_loaded++; |
| 1763 } |
| 1764 } |
| 1765 |
| 1766 // this.classList is used for selecting the appropriate dialog. |
| 1767 if (total_count) |
| 1768 this.classList.remove('has-no-stats'); |
| 1769 |
| 1770 var is_synced_user = this.user.emailAddress !== ""; |
| 1771 // Write total number if all statistics are loaded. |
| 1772 if (num_stats_loaded === Object.keys(stats_elements).length) { |
| 1773 if (!total_count) { |
| 1774 this.classList.add('has-no-stats'); |
| 1775 var message = loadTimeData.getString( |
| 1776 is_synced_user ? 'removeUserWarningTextSyncNoStats' : |
| 1777 'removeUserWarningTextNonSyncNoStats'); |
| 1778 this.updateRemoveWarningDialogSetMessage_(this.user.profilePath, |
| 1779 message); |
| 1780 } else { |
| 1781 window.updateRemoveWarningDialogSetMessage = |
| 1782 this.updateRemoveWarningDialogSetMessage_.bind(this); |
| 1783 chrome.send('getRemoveWarningDialogMessage',[{ |
| 1784 profilePath: this.user.profilePath, |
| 1785 isSyncedUser: is_synced_user, |
| 1786 hasErrors: hasErrors, |
| 1787 totalCount: total_count |
| 1788 }]); |
| 1789 } |
| 1790 } else if (isInitial) { |
| 1791 if (!this.user.isProfileLoaded) { |
| 1792 message = loadTimeData.getString( |
| 1793 is_synced_user ? 'removeUserWarningTextSyncNoStats' : |
| 1794 'removeUserWarningTextNonSyncNoStats'); |
| 1795 this.updateRemoveWarningDialogSetMessage_(this.user.profilePath, |
| 1796 message); |
| 1797 } else { |
| 1798 message = loadTimeData.getString( |
| 1799 is_synced_user ? 'removeUserWarningTextSyncCalculating' : |
| 1800 'removeUserWarningTextNonSyncCalculating'); |
| 1801 substitute = loadTimeData.getString( |
| 1802 'removeUserWarningTextCalculating'); |
| 1803 this.updateRemoveWarningDialogSetMessage_(this.user.profilePath, |
| 1804 message, substitute); |
| 1805 } |
| 1806 } |
| 1807 }, |
| 1808 |
| 1809 /** |
| 1810 * Refresh the message in the remove user warning dialog. |
| 1811 * @param {string} profilePath The filepath of the URL (must be verified). |
| 1812 * @param {string} message The message to be written. |
| 1813 * @param {number|string=} count The number or string to replace $1 in |
| 1814 * |message|. Can be omitted if $1 is not present in |message|. |
| 1815 */ |
| 1816 updateRemoveWarningDialogSetMessage_: function(profilePath, message, |
| 1817 count) { |
| 1818 if (profilePath !== this.user.profilePath) |
| 1819 return; |
| 1820 // Add localized messages where $1 will be replaced with |
| 1821 // <span class="total-count"></span> and $2 will be replaced with |
| 1822 // <span class="email"></span>. |
| 1823 var element = this.querySelector('.action-box-remove-user-warning-text'); |
| 1824 element.textContent = ''; |
| 1825 |
| 1826 messageParts = message.split(/(\$[12])/); |
| 1827 var numParts = messageParts.length; |
| 1828 for (var j = 0; j < numParts; j++) { |
| 1829 if (messageParts[j] === '$1') { |
| 1830 var elementToAdd = document.createElement('span'); |
| 1831 elementToAdd.classList.add('total-count'); |
| 1832 elementToAdd.textContent = count; |
| 1833 element.appendChild(elementToAdd); |
| 1834 } else if (messageParts[j] === '$2') { |
| 1835 var elementToAdd = document.createElement('span'); |
| 1836 elementToAdd.classList.add('email'); |
| 1837 elementToAdd.textContent = this.user.emailAddress; |
| 1838 element.appendChild(elementToAdd); |
| 1839 } else { |
| 1840 element.appendChild(document.createTextNode(messageParts[j])); |
| 1841 } |
| 1842 } |
| 1843 this.moveActionMenuUpIfNeeded_(); |
| 1844 }, |
| 1845 |
| 1846 /** |
| 1847 * Update the message in the "remove non-owner user warning" dialog on CrOS. |
| 1848 * @param {string} profilePath The filepath of the URL (must be verified). |
| 1849 * @param (string) message The message to be written. |
| 1850 */ |
| 1851 updateRemoveNonOwnerUserWarningMessage_: function(profilePath, message) { |
| 1852 if (profilePath !== this.user.profilePath) |
| 1853 return; |
| 1854 // Add localized messages where $1 will be replaced with |
| 1855 // <span class="email"></span>. |
| 1856 var element = this.querySelector( |
| 1857 '.action-box-remove-non-owner-user-warning-text'); |
| 1858 element.textContent = ''; |
| 1859 |
| 1860 messageParts = message.split(/(\$[1])/); |
| 1861 var numParts = messageParts.length; |
| 1862 for (var j = 0; j < numParts; j++) { |
| 1863 if (messageParts[j] == '$1') { |
| 1864 var elementToAdd = document.createElement('span'); |
| 1865 elementToAdd.classList.add('email'); |
| 1866 elementToAdd.textContent = this.user.emailAddress; |
| 1867 element.appendChild(elementToAdd); |
| 1868 } else { |
| 1869 element.appendChild(document.createTextNode(messageParts[j])); |
| 1870 } |
| 1871 } |
| 1872 this.moveActionMenuUpIfNeeded_(); |
| 1873 }, |
| 1874 |
| 1875 /** |
| 1876 * Handles a click event on remove user confirmation button. |
| 1877 * @param {Event} e Click event. |
| 1878 */ |
| 1879 handleRemoveUserConfirmationClick_: function(e) { |
| 1880 if (this.isActionBoxMenuActive) { |
| 1881 this.isActionBoxMenuActive = false; |
| 1882 this.removeUser(this.user); |
| 1883 e.stopPropagation(); |
| 1884 } |
| 1885 }, |
| 1886 |
| 1887 /** |
| 1888 * Handles mouseover event on fingerprint icon. |
| 1889 * @param {Event} e MouseOver event. |
| 1890 */ |
| 1891 handleFingerprintIconMouseOver_: function(e) { |
| 1892 var bubbleContent = document.createElement('div'); |
| 1893 bubbleContent.textContent = |
| 1894 loadTimeData.getString('fingerprintIconMessage'); |
| 1895 this.passwordElement.placeholder = |
| 1896 loadTimeData.getString('fingerprintHint'); |
| 1897 |
| 1898 /** @const */ var BUBBLE_OFFSET = 25; |
| 1899 /** @const */ var BUBBLE_PADDING = -8; |
| 1900 var attachment = this.isPinShown() ? cr.ui.Bubble.Attachment.RIGHT : |
| 1901 cr.ui.Bubble.Attachment.BOTTOM; |
| 1902 var bubbleAnchor = this.getBubbleAnchorForFingerprintIcon_(); |
| 1903 $('bubble').showContentForElement( |
| 1904 bubbleAnchor, attachment, bubbleContent, BUBBLE_OFFSET, |
| 1905 BUBBLE_PADDING, true); |
| 1906 }, |
| 1907 |
| 1908 /** |
| 1909 * Handles mouseout event on fingerprint icon. |
| 1910 * @param {Event} e MouseOut event. |
| 1911 */ |
| 1912 handleFingerprintIconMouseOut_: function(e) { |
| 1913 var bubbleAnchor = this.getBubbleAnchorForFingerprintIcon_(); |
| 1914 $('bubble').hideForElement(bubbleAnchor); |
| 1915 this.passwordElement.placeholder = loadTimeData.getString( |
| 1916 this.isPinShown() ? 'pinKeyboardPlaceholderPinPassword' : |
| 1917 'passwordHint'); |
| 1918 }, |
| 1919 |
| 1920 /** |
| 1921 * Returns bubble anchor of the fingerprint icon. |
| 1922 * @return {!HTMLElement} Anchor element of the bubble. |
| 1923 */ |
| 1924 getBubbleAnchorForFingerprintIcon_: function() { |
| 1925 var bubbleAnchor = this; |
| 1926 if (this.isPinShown()) |
| 1927 bubbleAnchor = (this.getElementsByClassName('auth-container'))[0]; |
| 1928 return bubbleAnchor; |
| 1929 }, |
| 1930 |
| 1931 /** |
| 1932 * Handles a keydown event on remove user confirmation button. |
| 1933 * @param {Event} e KeyDown event. |
| 1934 */ |
| 1935 handleRemoveUserConfirmationKeyDown_: function(e) { |
| 1936 if (!this.isActionBoxMenuActive) |
| 1937 return; |
| 1938 |
| 1939 // Only handle pressing 'Enter' or 'Space', and let all other events |
| 1940 // bubble to the action box menu. |
| 1941 if (e.key == 'Enter' || e.key == ' ') { |
| 1942 this.isActionBoxMenuActive = false; |
| 1943 this.removeUser(this.user); |
| 1944 e.stopPropagation(); |
| 1945 // Prevent default so that we don't trigger a 'click' event. |
| 1946 e.preventDefault(); |
| 1947 } |
| 1948 }, |
| 1949 |
| 1950 /** |
| 1951 * Handles a keydown event on remove command. |
| 1952 * @param {Event} e KeyDown event. |
| 1953 */ |
| 1954 handleRemoveCommandKeyDown_: function(e) { |
| 1955 if (this.disabled) |
| 1956 return; |
| 1957 switch (e.key) { |
| 1958 case 'Enter': |
| 1959 e.preventDefault(); |
| 1960 this.showRemoveWarning_(); |
| 1961 e.stopPropagation(); |
| 1962 break; |
| 1963 case 'ArrowUp': |
| 1964 case 'ArrowDown': |
| 1965 e.stopPropagation(); |
| 1966 break; |
| 1967 // Ignore these two, so ChromeVox hotkeys don't close the menu before |
| 1968 // they can navigate through it. |
| 1969 case 'Shift': |
| 1970 case 'Meta': |
| 1971 break; |
| 1972 case 'Escape': |
| 1973 this.actionBoxAreaElement.focus(); |
| 1974 this.isActionBoxMenuActive = false; |
| 1975 e.stopPropagation(); |
| 1976 break; |
| 1977 default: |
| 1978 this.actionBoxAreaElement.focus(); |
| 1979 this.isActionBoxMenuActive = false; |
| 1980 break; |
| 1981 } |
| 1982 }, |
| 1983 |
| 1984 /** |
| 1985 * Handles a blur event on remove command. |
| 1986 * @param {Event} e Blur event. |
| 1987 */ |
| 1988 handleRemoveCommandBlur_: function(e) { |
| 1989 if (this.disabled) |
| 1990 return; |
| 1991 this.actionBoxMenuRemoveElement.tabIndex = -1; |
| 1992 }, |
| 1993 |
| 1994 /** |
| 1995 * Handles mouse down event. It sets whether the user click auth will be |
| 1996 * allowed on the next mouse click event. The auth is allowed iff the pod |
| 1997 * was focused on the mouse down event starting the click. |
| 1998 * @param {Event} e The mouse down event. |
| 1999 */ |
| 2000 handlePodMouseDown_: function(e) { |
| 2001 this.userClickAuthAllowed_ = this.parentNode.isFocused(this); |
| 2002 }, |
| 2003 |
| 2004 /** |
| 2005 * Called when the input of the password element changes. Updates the submit |
| 2006 * button color and state and hides the error popup bubble. |
| 2007 */ |
| 2008 updateInput_: function() { |
| 2009 if (this.submitButton) { |
| 2010 this.submitButton.disabled = this.passwordElement.value.length == 0; |
| 2011 if (this.isFingerprintIconShown()) { |
| 2012 this.submitButton.hidden = this.passwordElement.value.length == 0; |
| 2013 } else { |
| 2014 this.submitButton.hidden = false; |
| 2015 } |
| 2016 } |
| 2017 this.showError = false; |
| 2018 $('bubble').hide(); |
| 2019 }, |
| 2020 |
| 2021 /** |
| 2022 * Handles input event on the password element. |
| 2023 * @param {Event} e Input event. |
| 2024 */ |
| 2025 handleInputChanged_: function(e) { |
| 2026 this.updateInput_(); |
| 2027 }, |
| 2028 |
| 2029 /** |
| 2030 * Handles mouse up event on the password element. |
| 2031 * @param {Event} e Mouse up event. |
| 2032 */ |
| 2033 handleInputMouseUp_: function(e) { |
| 2034 // If the PIN keyboard is shown and the user clicks on the password |
| 2035 // element, the virtual keyboard should pop up if it is enabled, so we |
| 2036 // must disable the virtual keyboard override. |
| 2037 if (this.isPinShown()) { |
| 2038 chrome.send('setForceDisableVirtualKeyboard', [false]); |
| 2039 } |
| 2040 }, |
| 2041 |
| 2042 /** |
| 2043 * Handles click event on a user pod. |
| 2044 * @param {Event} e Click event. |
| 2045 */ |
| 2046 handleClickOnPod_: function(e) { |
| 2047 if (this.parentNode.disabled) |
| 2048 return; |
| 2049 |
| 2050 if (!this.isActionBoxMenuActive) { |
| 2051 if (this.isAuthTypeOnlineSignIn) { |
| 2052 this.showSigninUI(); |
| 2053 } else if (this.isAuthTypeUserClick && this.userClickAuthAllowed_) { |
| 2054 // Note that this.userClickAuthAllowed_ is set in mouse down event |
| 2055 // handler. |
| 2056 this.parentNode.setActivatedPod(this); |
| 2057 } else if (this.pinKeyboard && |
| 2058 e.target == this.pinKeyboard.submitButton) { |
| 2059 // Sets the pod as activated if the submit button is clicked so that |
| 2060 // it simulates what the enter button does for the password/pin. |
| 2061 this.parentNode.setActivatedPod(this); |
| 2062 } |
| 2063 |
| 2064 if (this.multiProfilesPolicyApplied) |
| 2065 this.userTypeBubbleElement.classList.add('bubble-shown'); |
| 2066 |
| 2067 // Prevent default so that we don't trigger 'focus' event and |
| 2068 // stop propagation so that the 'click' event does not bubble |
| 2069 // up and accidentally closes the bubble tooltip. |
| 2070 stopEventPropagation(e); |
| 2071 } |
| 2072 }, |
| 2073 |
| 2074 /** |
| 2075 * Handles keydown event for a user pod. |
| 2076 * @param {Event} e Key event. |
| 2077 */ |
| 2078 handlePodKeyDown_: function(e) { |
| 2079 if (!this.isAuthTypeUserClick || this.disabled) |
| 2080 return; |
| 2081 switch (e.key) { |
| 2082 case 'Enter': |
| 2083 case ' ': |
| 2084 if (this.parentNode.isFocused(this)) |
| 2085 this.parentNode.setActivatedPod(this); |
| 2086 break; |
| 2087 } |
| 2088 } |
| 2089 }; |
| 2090 |
| 2091 /** |
| 2092 * Creates a public account user pod. |
| 2093 * @constructor |
| 2094 * @extends {UserPod} |
| 2095 */ |
| 2096 var PublicAccountUserPod = cr.ui.define(function() { |
| 2097 var node = UserPod(); |
| 2098 |
| 2099 var extras = $('public-account-user-pod-extras-template').children; |
| 2100 for (var i = 0; i < extras.length; ++i) { |
| 2101 var el = extras[i].cloneNode(true); |
| 2102 node.appendChild(el); |
| 2103 } |
| 2104 |
| 2105 return node; |
| 2106 }); |
| 2107 |
| 2108 PublicAccountUserPod.prototype = { |
| 2109 __proto__: UserPod.prototype, |
| 2110 |
| 2111 /** |
| 2112 * "Enter" button in expanded side pane. |
| 2113 * @type {!HTMLButtonElement} |
| 2114 */ |
| 2115 get enterButtonElement() { |
| 2116 return this.querySelector('.enter-button'); |
| 2117 }, |
| 2118 |
| 2119 /** |
| 2120 * Boolean flag of whether the pod is showing the side pane. The flag |
| 2121 * controls whether 'expanded' class is added to the pod's class list and |
| 2122 * resets tab order because main input element changes when the 'expanded' |
| 2123 * state changes. |
| 2124 * @type {boolean} |
| 2125 */ |
| 2126 get expanded() { |
| 2127 return this.classList.contains('expanded'); |
| 2128 }, |
| 2129 |
| 2130 set expanded(expanded) { |
| 2131 if (this.expanded == expanded) |
| 2132 return; |
| 2133 |
| 2134 this.resetTabOrder(); |
| 2135 this.classList.toggle('expanded', expanded); |
| 2136 if (expanded) { |
| 2137 // Show the advanced expanded pod directly if there are at least two |
| 2138 // recommended locales. This will be the case in multilingual |
| 2139 // environments where users are likely to want to choose among locales. |
| 2140 if (this.querySelector('.language-select').multipleRecommendedLocales) |
| 2141 this.classList.add('advanced'); |
| 2142 this.usualLeft = this.left; |
| 2143 this.makeSpaceForExpandedPod_(); |
| 2144 } else if (typeof(this.usualLeft) != 'undefined') { |
| 2145 this.left = this.usualLeft; |
| 2146 } |
| 2147 |
| 2148 var self = this; |
| 2149 this.classList.add('animating'); |
| 2150 this.addEventListener('transitionend', function f(e) { |
| 2151 self.removeEventListener('transitionend', f); |
| 2152 self.classList.remove('animating'); |
| 2153 |
| 2154 // Accessibility focus indicator does not move with the focused |
| 2155 // element. Sends a 'focus' event on the currently focused element |
| 2156 // so that accessibility focus indicator updates its location. |
| 2157 if (document.activeElement) |
| 2158 document.activeElement.dispatchEvent(new Event('focus')); |
| 2159 }); |
| 2160 // Guard timer set to animation duration + 20ms. |
| 2161 ensureTransitionEndEvent(this, 200); |
| 2162 }, |
| 2163 |
| 2164 get advanced() { |
| 2165 return this.classList.contains('advanced'); |
| 2166 }, |
| 2167 |
| 2168 /** @override */ |
| 2169 get mainInput() { |
| 2170 if (this.expanded) |
| 2171 return this.enterButtonElement; |
| 2172 else |
| 2173 return this.nameElement; |
| 2174 }, |
| 2175 |
| 2176 /** @override */ |
| 2177 decorate: function() { |
| 2178 UserPod.prototype.decorate.call(this); |
| 2179 |
| 2180 this.classList.add('public-account'); |
| 2181 |
| 2182 this.nameElement.addEventListener('keydown', (function(e) { |
| 2183 if (e.key == 'Enter') { |
| 2184 this.parentNode.setActivatedPod(this, e); |
| 2185 // Stop this keydown event from bubbling up to PodRow handler. |
| 2186 e.stopPropagation(); |
| 2187 // Prevent default so that we don't trigger a 'click' event on the |
| 2188 // newly focused "Enter" button. |
| 2189 e.preventDefault(); |
| 2190 } |
| 2191 }).bind(this)); |
| 2192 |
| 2193 var learnMore = this.querySelector('.learn-more'); |
| 2194 learnMore.addEventListener('mousedown', stopEventPropagation); |
| 2195 learnMore.addEventListener('click', this.handleLearnMoreEvent); |
| 2196 learnMore.addEventListener('keydown', this.handleLearnMoreEvent); |
| 2197 |
| 2198 learnMore = this.querySelector('.expanded-pane-learn-more'); |
| 2199 learnMore.addEventListener('click', this.handleLearnMoreEvent); |
| 2200 learnMore.addEventListener('keydown', this.handleLearnMoreEvent); |
| 2201 |
| 2202 var languageSelect = this.querySelector('.language-select'); |
| 2203 languageSelect.tabIndex = UserPodTabOrder.POD_INPUT; |
| 2204 languageSelect.manuallyChanged = false; |
| 2205 languageSelect.addEventListener( |
| 2206 'change', |
| 2207 function() { |
| 2208 languageSelect.manuallyChanged = true; |
| 2209 this.getPublicSessionKeyboardLayouts_(); |
| 2210 }.bind(this)); |
| 2211 |
| 2212 var keyboardSelect = this.querySelector('.keyboard-select'); |
| 2213 keyboardSelect.tabIndex = UserPodTabOrder.POD_INPUT; |
| 2214 keyboardSelect.loadedLocale = null; |
| 2215 |
| 2216 var languageAndInput = this.querySelector('.language-and-input'); |
| 2217 languageAndInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| 2218 languageAndInput.addEventListener('click', |
| 2219 this.transitionToAdvanced_.bind(this)); |
| 2220 |
| 2221 var monitoringLearnMore = this.querySelector('.monitoring-learn-more'); |
| 2222 monitoringLearnMore.tabIndex = UserPodTabOrder.POD_INPUT; |
| 2223 monitoringLearnMore.addEventListener( |
| 2224 'click', this.onMonitoringLearnMoreClicked_.bind(this)); |
| 2225 |
| 2226 this.enterButtonElement.addEventListener('click', (function(e) { |
| 2227 this.enterButtonElement.disabled = true; |
| 2228 var locale = this.querySelector('.language-select').value; |
| 2229 var keyboardSelect = this.querySelector('.keyboard-select'); |
| 2230 // The contents of |keyboardSelect| is updated asynchronously. If its |
| 2231 // locale does not match |locale|, it has not updated yet and the |
| 2232 // currently selected keyboard layout may not be applicable to |locale|. |
| 2233 // Do not return any keyboard layout in this case and let the backend |
| 2234 // choose a suitable layout. |
| 2235 var keyboardLayout = |
| 2236 keyboardSelect.loadedLocale == locale ? keyboardSelect.value : ''; |
| 2237 chrome.send('launchPublicSession', |
| 2238 [this.user.username, locale, keyboardLayout]); |
| 2239 }).bind(this)); |
| 2240 }, |
| 2241 |
| 2242 /** @override **/ |
| 2243 initialize: function() { |
| 2244 UserPod.prototype.initialize.call(this); |
| 2245 |
| 2246 id = this.user.username + '-keyboard'; |
| 2247 this.querySelector('.keyboard-select-label').htmlFor = id; |
| 2248 this.querySelector('.keyboard-select').setAttribute('id', id); |
| 2249 |
| 2250 var id = this.user.username + '-language'; |
| 2251 this.querySelector('.language-select-label').htmlFor = id; |
| 2252 var languageSelect = this.querySelector('.language-select'); |
| 2253 languageSelect.setAttribute('id', id); |
| 2254 this.populateLanguageSelect(this.user.initialLocales, |
| 2255 this.user.initialLocale, |
| 2256 this.user.initialMultipleRecommendedLocales); |
| 2257 }, |
| 2258 |
| 2259 /** @override **/ |
| 2260 update: function() { |
| 2261 UserPod.prototype.update.call(this); |
| 2262 this.querySelector('.expanded-pane-name').textContent = |
| 2263 this.user_.displayName; |
| 2264 this.querySelector('.info').textContent = |
| 2265 loadTimeData.getStringF('publicAccountInfoFormat', |
| 2266 this.user_.enterpriseDomain); |
| 2267 }, |
| 2268 |
| 2269 /** @override */ |
| 2270 focusInput: function() { |
| 2271 // Move tabIndex from the whole pod to the main input. |
| 2272 this.tabIndex = -1; |
| 2273 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| 2274 this.mainInput.focus(); |
| 2275 }, |
| 2276 |
| 2277 /** @override */ |
| 2278 reset: function(takeFocus) { |
| 2279 if (!takeFocus) |
| 2280 this.expanded = false; |
| 2281 this.enterButtonElement.disabled = false; |
| 2282 UserPod.prototype.reset.call(this, takeFocus); |
| 2283 }, |
| 2284 |
| 2285 /** @override */ |
| 2286 activate: function(e) { |
| 2287 if (!this.expanded) { |
| 2288 this.expanded = true; |
| 2289 this.focusInput(); |
| 2290 } |
| 2291 return true; |
| 2292 }, |
| 2293 |
| 2294 /** @override */ |
| 2295 handleClickOnPod_: function(e) { |
| 2296 if (this.parentNode.disabled) |
| 2297 return; |
| 2298 |
| 2299 this.parentNode.focusPod(this); |
| 2300 this.parentNode.setActivatedPod(this, e); |
| 2301 // Prevent default so that we don't trigger 'focus' event. |
| 2302 e.preventDefault(); |
| 2303 }, |
| 2304 |
| 2305 /** |
| 2306 * Updates the display name shown on the pod. |
| 2307 * @param {string} displayName The new display name |
| 2308 */ |
| 2309 setDisplayName: function(displayName) { |
| 2310 this.user_.displayName = displayName; |
| 2311 this.update(); |
| 2312 }, |
| 2313 |
| 2314 /** |
| 2315 * Handle mouse and keyboard events for the learn more button. Triggering |
| 2316 * the button causes information about public sessions to be shown. |
| 2317 * @param {Event} event Mouse or keyboard event. |
| 2318 */ |
| 2319 handleLearnMoreEvent: function(event) { |
| 2320 switch (event.type) { |
| 2321 // Show informaton on left click. Let any other clicks propagate. |
| 2322 case 'click': |
| 2323 if (event.button != 0) |
| 2324 return; |
| 2325 break; |
| 2326 // Show informaton when <Return> or <Space> is pressed. Let any other |
| 2327 // key presses propagate. |
| 2328 case 'keydown': |
| 2329 switch (event.keyCode) { |
| 2330 case 13: // Return. |
| 2331 case 32: // Space. |
| 2332 break; |
| 2333 default: |
| 2334 return; |
| 2335 } |
| 2336 break; |
| 2337 } |
| 2338 chrome.send('launchHelpApp', [HELP_TOPIC_PUBLIC_SESSION]); |
| 2339 stopEventPropagation(event); |
| 2340 }, |
| 2341 |
| 2342 makeSpaceForExpandedPod_: function() { |
| 2343 var width = this.classList.contains('advanced') ? |
| 2344 PUBLIC_EXPANDED_ADVANCED_WIDTH : PUBLIC_EXPANDED_BASIC_WIDTH; |
| 2345 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 2346 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 2347 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| 2348 POD_ROW_PADDING; |
| 2349 if (this.left + width > $('pod-row').offsetWidth - rowPadding) |
| 2350 this.left = $('pod-row').offsetWidth - rowPadding - width; |
| 2351 }, |
| 2352 |
| 2353 /** |
| 2354 * Transition the expanded pod from the basic to the advanced view. |
| 2355 */ |
| 2356 transitionToAdvanced_: function() { |
| 2357 var pod = this; |
| 2358 var languageAndInputSection = |
| 2359 this.querySelector('.language-and-input-section'); |
| 2360 this.classList.add('transitioning-to-advanced'); |
| 2361 setTimeout(function() { |
| 2362 pod.classList.add('advanced'); |
| 2363 pod.makeSpaceForExpandedPod_(); |
| 2364 languageAndInputSection.addEventListener('transitionend', |
| 2365 function observer() { |
| 2366 languageAndInputSection.removeEventListener('transitionend', |
| 2367 observer); |
| 2368 pod.classList.remove('transitioning-to-advanced'); |
| 2369 pod.querySelector('.language-select').focus(); |
| 2370 }); |
| 2371 // Guard timer set to animation duration + 20ms. |
| 2372 ensureTransitionEndEvent(languageAndInputSection, 380); |
| 2373 }, 0); |
| 2374 }, |
| 2375 |
| 2376 /** |
| 2377 * Show a dialog when user clicks on learn more (monitoring) button. |
| 2378 */ |
| 2379 onMonitoringLearnMoreClicked_: function() { |
| 2380 if (!this.dialogContainer_) { |
| 2381 this.dialogContainer_ = document.createElement('div'); |
| 2382 this.dialogContainer_.classList.add('monitoring-dialog-container'); |
| 2383 var topContainer = document.querySelector('#scroll-container'); |
| 2384 topContainer.appendChild(this.dialogContainer_); |
| 2385 } |
| 2386 // Public Session POD in advanced view has a different size so add a dummy |
| 2387 // parent element to enable different CSS settings. |
| 2388 this.dialogContainer_.classList.toggle( |
| 2389 'advanced', this.classList.contains('advanced')) |
| 2390 var html = ''; |
| 2391 var infoItems = ['publicAccountMonitoringInfoItem1', |
| 2392 'publicAccountMonitoringInfoItem2', |
| 2393 'publicAccountMonitoringInfoItem3', |
| 2394 'publicAccountMonitoringInfoItem4']; |
| 2395 for (item of infoItems) { |
| 2396 html += '<p class="cr-dialog-item">'; |
| 2397 html += loadTimeData.getString(item); |
| 2398 html += '</p>'; |
| 2399 } |
| 2400 var title = loadTimeData.getString('publicAccountMonitoringInfo'); |
| 2401 this.dialog_ = new cr.ui.dialogs.BaseDialog(this.dialogContainer_); |
| 2402 this.dialog_.showHtml(title, html, undefined, |
| 2403 this.onMonitoringDialogClosed_.bind(this)); |
| 2404 this.parentNode.disabled = true; |
| 2405 }, |
| 2406 |
| 2407 /** |
| 2408 * Cleanup after the monitoring warning dialog is closed. |
| 2409 */ |
| 2410 onMonitoringDialogClosed_: function() { |
| 2411 this.parentNode.disabled = false; |
| 2412 this.dialog_ = undefined; |
| 2413 }, |
| 2414 |
| 2415 /** |
| 2416 * Retrieves the list of keyboard layouts available for the currently |
| 2417 * selected locale. |
| 2418 */ |
| 2419 getPublicSessionKeyboardLayouts_: function() { |
| 2420 var selectedLocale = this.querySelector('.language-select').value; |
| 2421 if (selectedLocale == |
| 2422 this.querySelector('.keyboard-select').loadedLocale) { |
| 2423 // If the list of keyboard layouts was loaded for the currently selected |
| 2424 // locale, it is already up to date. |
| 2425 return; |
| 2426 } |
| 2427 chrome.send('getPublicSessionKeyboardLayouts', |
| 2428 [this.user.username, selectedLocale]); |
| 2429 }, |
| 2430 |
| 2431 /** |
| 2432 * Populates the keyboard layout "select" element with a list of layouts. |
| 2433 * @param {string} locale The locale to which this list of keyboard layouts |
| 2434 * applies |
| 2435 * @param {!Object} list List of available keyboard layouts |
| 2436 */ |
| 2437 populateKeyboardSelect: function(locale, list) { |
| 2438 if (locale != this.querySelector('.language-select').value) { |
| 2439 // The selected locale has changed and the list of keyboard layouts is |
| 2440 // not applicable. This method will be called again when a list of |
| 2441 // keyboard layouts applicable to the selected locale is retrieved. |
| 2442 return; |
| 2443 } |
| 2444 |
| 2445 var keyboardSelect = this.querySelector('.keyboard-select'); |
| 2446 keyboardSelect.loadedLocale = locale; |
| 2447 keyboardSelect.innerHTML = ''; |
| 2448 for (var i = 0; i < list.length; ++i) { |
| 2449 var item = list[i]; |
| 2450 keyboardSelect.appendChild( |
| 2451 new Option(item.title, item.value, item.selected, item.selected)); |
| 2452 } |
| 2453 }, |
| 2454 |
| 2455 /** |
| 2456 * Populates the language "select" element with a list of locales. |
| 2457 * @param {!Object} locales The list of available locales |
| 2458 * @param {string} defaultLocale The locale to select by default |
| 2459 * @param {boolean} multipleRecommendedLocales Whether |locales| contains |
| 2460 * two or more recommended locales |
| 2461 */ |
| 2462 populateLanguageSelect: function(locales, |
| 2463 defaultLocale, |
| 2464 multipleRecommendedLocales) { |
| 2465 var languageSelect = this.querySelector('.language-select'); |
| 2466 // If the user manually selected a locale, do not change the selection. |
| 2467 // Otherwise, select the new |defaultLocale|. |
| 2468 var selected = |
| 2469 languageSelect.manuallyChanged ? languageSelect.value : defaultLocale; |
| 2470 languageSelect.innerHTML = ''; |
| 2471 var group = languageSelect; |
| 2472 for (var i = 0; i < locales.length; ++i) { |
| 2473 var item = locales[i]; |
| 2474 if (item.optionGroupName) { |
| 2475 group = document.createElement('optgroup'); |
| 2476 group.label = item.optionGroupName; |
| 2477 languageSelect.appendChild(group); |
| 2478 } else { |
| 2479 group.appendChild(new Option(item.title, |
| 2480 item.value, |
| 2481 item.value == selected, |
| 2482 item.value == selected)); |
| 2483 } |
| 2484 } |
| 2485 languageSelect.multipleRecommendedLocales = multipleRecommendedLocales; |
| 2486 |
| 2487 // Retrieve a list of keyboard layouts applicable to the locale that is |
| 2488 // now selected. |
| 2489 this.getPublicSessionKeyboardLayouts_(); |
| 2490 } |
| 2491 }; |
| 2492 |
| 2493 /** |
| 2494 * Creates a user pod to be used only in desktop chrome. |
| 2495 * @constructor |
| 2496 * @extends {UserPod} |
| 2497 */ |
| 2498 var DesktopUserPod = cr.ui.define(function() { |
| 2499 // Don't just instantiate a UserPod(), as this will call decorate() on the |
| 2500 // parent object, and add duplicate event listeners. |
| 2501 var node = $('user-pod-template').cloneNode(true); |
| 2502 node.removeAttribute('id'); |
| 2503 return node; |
| 2504 }); |
| 2505 |
| 2506 DesktopUserPod.prototype = { |
| 2507 __proto__: UserPod.prototype, |
| 2508 |
| 2509 /** @override */ |
| 2510 initialize: function() { |
| 2511 if (this.user.needsSignin) { |
| 2512 if (this.user.hasLocalCreds) { |
| 2513 this.user.initialAuthType = AUTH_TYPE.OFFLINE_PASSWORD; |
| 2514 } else { |
| 2515 this.user.initialAuthType = AUTH_TYPE.ONLINE_SIGN_IN; |
| 2516 } |
| 2517 } |
| 2518 UserPod.prototype.initialize.call(this); |
| 2519 }, |
| 2520 |
| 2521 /** @override */ |
| 2522 get mainInput() { |
| 2523 if (this.user.needsSignin) |
| 2524 return this.passwordElement; |
| 2525 else |
| 2526 return this.nameElement; |
| 2527 }, |
| 2528 |
| 2529 /** @override */ |
| 2530 update: function() { |
| 2531 this.imageElement.src = this.user.userImage; |
| 2532 this.nameElement.textContent = this.user.displayName; |
| 2533 this.reauthNameHintElement.textContent = this.user.displayName; |
| 2534 |
| 2535 var isLockedUser = this.user.needsSignin; |
| 2536 var isLegacySupervisedUser = this.user.legacySupervisedUser; |
| 2537 var isChildUser = this.user.childUser; |
| 2538 var isSyncedUser = this.user.emailAddress !== ""; |
| 2539 var isProfileLoaded = this.user.isProfileLoaded; |
| 2540 this.classList.toggle('locked', isLockedUser); |
| 2541 this.classList.toggle('legacy-supervised', isLegacySupervisedUser); |
| 2542 this.classList.toggle('child', isChildUser); |
| 2543 this.classList.toggle('synced', isSyncedUser); |
| 2544 this.classList.toggle('has-no-stats', |
| 2545 !isProfileLoaded && !this.user.statistics.length); |
| 2546 |
| 2547 if (this.isAuthTypeUserClick) |
| 2548 this.passwordLabelElement.textContent = this.authValue; |
| 2549 |
| 2550 this.passwordElement.setAttribute('aria-label', loadTimeData.getStringF( |
| 2551 'passwordFieldAccessibleName', this.user_.emailAddress)); |
| 2552 |
| 2553 UserPod.prototype.updateActionBoxArea.call(this); |
| 2554 }, |
| 2555 |
| 2556 /** @override */ |
| 2557 activate: function(e) { |
| 2558 if (!this.user.needsSignin) { |
| 2559 Oobe.launchUser(this.user.profilePath); |
| 2560 } else if (this.user.hasLocalCreds && !this.passwordElement.value) { |
| 2561 return false; |
| 2562 } else { |
| 2563 chrome.send('authenticatedLaunchUser', |
| 2564 [this.user.profilePath, |
| 2565 this.user.emailAddress, |
| 2566 this.passwordElement.value]); |
| 2567 } |
| 2568 this.passwordElement.value = ''; |
| 2569 return true; |
| 2570 }, |
| 2571 |
| 2572 /** @override */ |
| 2573 handleClickOnPod_: function(e) { |
| 2574 if (this.parentNode.disabled) |
| 2575 return; |
| 2576 |
| 2577 Oobe.clearErrors(); |
| 2578 this.parentNode.lastFocusedPod_ = this; |
| 2579 |
| 2580 // If this is a locked pod and there are local credentials, show the |
| 2581 // password field. Otherwise call activate() which will open up a browser |
| 2582 // window or show the reauth dialog, as needed. |
| 2583 if (!(this.user.needsSignin && this.user.hasLocalCreds) && |
| 2584 !this.isActionBoxMenuActive) { |
| 2585 this.activate(e); |
| 2586 } |
| 2587 |
| 2588 if (this.isAuthTypeUserClick) |
| 2589 chrome.send('attemptUnlock', [this.user.emailAddress]); |
| 2590 }, |
| 2591 }; |
| 2592 |
| 2593 /** |
| 2594 * Creates a user pod that represents kiosk app. |
| 2595 * @constructor |
| 2596 * @extends {UserPod} |
| 2597 */ |
| 2598 var KioskAppPod = cr.ui.define(function() { |
| 2599 var node = UserPod(); |
| 2600 return node; |
| 2601 }); |
| 2602 |
| 2603 KioskAppPod.prototype = { |
| 2604 __proto__: UserPod.prototype, |
| 2605 |
| 2606 /** @override */ |
| 2607 decorate: function() { |
| 2608 UserPod.prototype.decorate.call(this); |
| 2609 this.launchAppButtonElement.addEventListener('click', |
| 2610 this.activate.bind(this)); |
| 2611 }, |
| 2612 |
| 2613 /** @override */ |
| 2614 update: function() { |
| 2615 this.imageElement.src = this.user.iconUrl; |
| 2616 this.imageElement.alt = this.user.label; |
| 2617 this.imageElement.title = this.user.label; |
| 2618 this.passwordEntryContainerElement.hidden = true; |
| 2619 this.launchAppButtonContainerElement.hidden = false; |
| 2620 this.nameElement.textContent = this.user.label; |
| 2621 this.reauthNameHintElement.textContent = this.user.label; |
| 2622 |
| 2623 UserPod.prototype.updateActionBoxArea.call(this); |
| 2624 UserPod.prototype.customizeUserPodPerUserType.call(this); |
| 2625 }, |
| 2626 |
| 2627 /** @override */ |
| 2628 get mainInput() { |
| 2629 return this.launchAppButtonElement; |
| 2630 }, |
| 2631 |
| 2632 /** @override */ |
| 2633 focusInput: function() { |
| 2634 // Move tabIndex from the whole pod to the main input. |
| 2635 this.tabIndex = -1; |
| 2636 this.mainInput.tabIndex = UserPodTabOrder.POD_INPUT; |
| 2637 this.mainInput.focus(); |
| 2638 }, |
| 2639 |
| 2640 /** @override */ |
| 2641 get forceOnlineSignin() { |
| 2642 return false; |
| 2643 }, |
| 2644 |
| 2645 /** @override */ |
| 2646 activate: function(e) { |
| 2647 var diagnosticMode = e && e.ctrlKey; |
| 2648 this.launchApp_(this.user, diagnosticMode); |
| 2649 return true; |
| 2650 }, |
| 2651 |
| 2652 /** @override */ |
| 2653 handleClickOnPod_: function(e) { |
| 2654 if (this.parentNode.disabled) |
| 2655 return; |
| 2656 |
| 2657 Oobe.clearErrors(); |
| 2658 this.parentNode.lastFocusedPod_ = this; |
| 2659 this.activate(e); |
| 2660 }, |
| 2661 |
| 2662 /** |
| 2663 * Launch the app. If |diagnosticMode| is true, ask user to confirm. |
| 2664 * @param {Object} app App data. |
| 2665 * @param {boolean} diagnosticMode Whether to run the app in diagnostic |
| 2666 * mode. |
| 2667 */ |
| 2668 launchApp_: function(app, diagnosticMode) { |
| 2669 if (!diagnosticMode) { |
| 2670 chrome.send('launchKioskApp', [app.id, false]); |
| 2671 return; |
| 2672 } |
| 2673 |
| 2674 var oobe = $('oobe'); |
| 2675 if (!oobe.confirmDiagnosticMode_) { |
| 2676 oobe.confirmDiagnosticMode_ = |
| 2677 new cr.ui.dialogs.ConfirmDialog(document.body); |
| 2678 oobe.confirmDiagnosticMode_.setOkLabel( |
| 2679 loadTimeData.getString('confirmKioskAppDiagnosticModeYes')); |
| 2680 oobe.confirmDiagnosticMode_.setCancelLabel( |
| 2681 loadTimeData.getString('confirmKioskAppDiagnosticModeNo')); |
| 2682 } |
| 2683 |
| 2684 oobe.confirmDiagnosticMode_.show( |
| 2685 loadTimeData.getStringF('confirmKioskAppDiagnosticModeFormat', |
| 2686 app.label), |
| 2687 function() { |
| 2688 chrome.send('launchKioskApp', [app.id, true]); |
| 2689 }); |
| 2690 }, |
| 2691 }; |
| 2692 |
| 2693 /** |
| 2694 * Creates a new pod row element. |
| 2695 * @constructor |
| 2696 * @extends {HTMLDivElement} |
| 2697 */ |
| 2698 var PodRow = cr.ui.define('podrow'); |
| 2699 |
| 2700 PodRow.prototype = { |
| 2701 __proto__: HTMLDivElement.prototype, |
| 2702 |
| 2703 // Whether this user pod row is shown for the first time. |
| 2704 firstShown_: true, |
| 2705 |
| 2706 // True if inside focusPod(). |
| 2707 insideFocusPod_: false, |
| 2708 |
| 2709 // Focused pod. |
| 2710 focusedPod_: undefined, |
| 2711 |
| 2712 // Activated pod, i.e. the pod of current login attempt. |
| 2713 activatedPod_: undefined, |
| 2714 |
| 2715 // Pod that was most recently focused, if any. |
| 2716 lastFocusedPod_: undefined, |
| 2717 |
| 2718 // Pods whose initial images haven't been loaded yet. |
| 2719 podsWithPendingImages_: [], |
| 2720 |
| 2721 // Whether pod placement has been postponed. |
| 2722 podPlacementPostponed_: false, |
| 2723 |
| 2724 // Standard user pod height/width. |
| 2725 userPodHeight_: 0, |
| 2726 userPodWidth_: 0, |
| 2727 |
| 2728 // Array of apps that are shown in addition to other user pods. |
| 2729 apps_: [], |
| 2730 |
| 2731 // True to show app pods along with user pods. |
| 2732 shouldShowApps_: true, |
| 2733 |
| 2734 // Array of users that are shown (public/supervised/regular). |
| 2735 users_: [], |
| 2736 |
| 2737 // If we're in Touch View mode. |
| 2738 touchViewEnabled_: false, |
| 2739 |
| 2740 /** @override */ |
| 2741 decorate: function() { |
| 2742 // Event listeners that are installed for the time period during which |
| 2743 // the element is visible. |
| 2744 this.listeners_ = { |
| 2745 focus: [this.handleFocus_.bind(this), true /* useCapture */], |
| 2746 click: [this.handleClick_.bind(this), true], |
| 2747 mousemove: [this.handleMouseMove_.bind(this), false], |
| 2748 keydown: [this.handleKeyDown.bind(this), false] |
| 2749 }; |
| 2750 |
| 2751 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 2752 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 2753 var isNewDesktopUserManager = Oobe.getInstance().newDesktopUserManager; |
| 2754 this.userPodHeight_ = isDesktopUserManager ? |
| 2755 isNewDesktopUserManager ? MD_DESKTOP_POD_HEIGHT : |
| 2756 DESKTOP_POD_HEIGHT : |
| 2757 CROS_POD_HEIGHT; |
| 2758 this.userPodWidth_ = isDesktopUserManager ? |
| 2759 isNewDesktopUserManager ? MD_DESKTOP_POD_WIDTH : |
| 2760 DESKTOP_POD_WIDTH : |
| 2761 CROS_POD_WIDTH; |
| 2762 }, |
| 2763 |
| 2764 /** |
| 2765 * Returns all the pods in this pod row. |
| 2766 * @type {NodeList} |
| 2767 */ |
| 2768 get pods() { |
| 2769 return Array.prototype.slice.call(this.children); |
| 2770 }, |
| 2771 |
| 2772 /** |
| 2773 * Return true if user pod row has only single user pod in it, which should |
| 2774 * always be focused except desktop and touch view modes. |
| 2775 * @type {boolean} |
| 2776 */ |
| 2777 get alwaysFocusSinglePod() { |
| 2778 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 2779 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 2780 |
| 2781 return (isDesktopUserManager || this.touchViewEnabled_) ? |
| 2782 false : this.children.length == 1; |
| 2783 }, |
| 2784 |
| 2785 /** |
| 2786 * Returns pod with the given app id. |
| 2787 * @param {!string} app_id Application id to be matched. |
| 2788 * @return {Object} Pod with the given app id. null if pod hasn't been |
| 2789 * found. |
| 2790 */ |
| 2791 getPodWithAppId_: function(app_id) { |
| 2792 for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| 2793 if (pod.user.isApp && pod.user.id == app_id) |
| 2794 return pod; |
| 2795 } |
| 2796 return null; |
| 2797 }, |
| 2798 |
| 2799 /** |
| 2800 * Returns pod with the given username (null if there is no such pod). |
| 2801 * @param {string} username Username to be matched. |
| 2802 * @return {Object} Pod with the given username. null if pod hasn't been |
| 2803 * found. |
| 2804 */ |
| 2805 getPodWithUsername_: function(username) { |
| 2806 for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| 2807 if (pod.user.username == username) |
| 2808 return pod; |
| 2809 } |
| 2810 return null; |
| 2811 }, |
| 2812 |
| 2813 /** |
| 2814 * True if the the pod row is disabled (handles no user interaction). |
| 2815 * @type {boolean} |
| 2816 */ |
| 2817 disabled_: false, |
| 2818 get disabled() { |
| 2819 return this.disabled_; |
| 2820 }, |
| 2821 set disabled(value) { |
| 2822 this.disabled_ = value; |
| 2823 this.pods.forEach(function(pod) { |
| 2824 pod.disabled = value; |
| 2825 }); |
| 2826 }, |
| 2827 |
| 2828 /** |
| 2829 * Creates a user pod from given email. |
| 2830 * @param {!Object} user User info dictionary. |
| 2831 */ |
| 2832 createUserPod: function(user) { |
| 2833 var userPod; |
| 2834 if (user.isDesktopUser) |
| 2835 userPod = new DesktopUserPod({user: user}); |
| 2836 else if (user.publicAccount) |
| 2837 userPod = new PublicAccountUserPod({user: user}); |
| 2838 else if (user.isApp) |
| 2839 userPod = new KioskAppPod({user: user}); |
| 2840 else |
| 2841 userPod = new UserPod({user: user}); |
| 2842 |
| 2843 userPod.hidden = false; |
| 2844 return userPod; |
| 2845 }, |
| 2846 |
| 2847 /** |
| 2848 * Add an existing user pod to this pod row. |
| 2849 * @param {!Object} user User info dictionary. |
| 2850 */ |
| 2851 addUserPod: function(user) { |
| 2852 var userPod = this.createUserPod(user); |
| 2853 this.appendChild(userPod); |
| 2854 userPod.initialize(); |
| 2855 }, |
| 2856 |
| 2857 /** |
| 2858 * Performs visual changes on the user pod if there is an error. |
| 2859 * @param {boolean} visible Whether to show or hide the display. |
| 2860 */ |
| 2861 setFocusedPodErrorDisplay: function(visible) { |
| 2862 if (this.focusedPod_) |
| 2863 this.focusedPod_.showError = visible; |
| 2864 }, |
| 2865 |
| 2866 /** |
| 2867 * Shows or hides the pin keyboard for the current focused pod. |
| 2868 * @param {boolean} visible |
| 2869 */ |
| 2870 setFocusedPodPinVisibility: function(visible) { |
| 2871 if (this.focusedPod_) |
| 2872 this.focusedPod_.setPinVisibility(visible); |
| 2873 }, |
| 2874 |
| 2875 /** |
| 2876 * Runs app with a given id from the list of loaded apps. |
| 2877 * @param {!string} app_id of an app to run. |
| 2878 * @param {boolean=} opt_diagnosticMode Whether to run the app in |
| 2879 * diagnostic mode. Default is false. |
| 2880 */ |
| 2881 findAndRunAppForTesting: function(app_id, opt_diagnosticMode) { |
| 2882 var app = this.getPodWithAppId_(app_id); |
| 2883 if (app) { |
| 2884 var activationEvent = cr.doc.createEvent('MouseEvents'); |
| 2885 var ctrlKey = opt_diagnosticMode; |
| 2886 activationEvent.initMouseEvent('click', true, true, null, |
| 2887 0, 0, 0, 0, 0, ctrlKey, false, false, false, 0, null); |
| 2888 app.dispatchEvent(activationEvent); |
| 2889 } |
| 2890 }, |
| 2891 |
| 2892 /** |
| 2893 * Enables or disables the pin keyboard for the given user. A disabled pin |
| 2894 * keyboard will never be displayed. |
| 2895 * |
| 2896 * If the user's pod is focused, then enabling the pin keyboard will display |
| 2897 * it; disabling the pin keyboard will hide it. |
| 2898 * @param {!string} username |
| 2899 * @param {boolean} enabled |
| 2900 */ |
| 2901 setPinEnabled: function(username, enabled) { |
| 2902 var pod = this.getPodWithUsername_(username); |
| 2903 if (!pod) { |
| 2904 console.error('Attempt to enable/disable pin keyboard of missing pod.'); |
| 2905 return; |
| 2906 } |
| 2907 |
| 2908 // Make sure to set |pinEnabled| before toggling visiblity to avoid |
| 2909 // validation errors. |
| 2910 pod.pinEnabled = enabled; |
| 2911 |
| 2912 if (this.focusedPod_ == pod) { |
| 2913 if (enabled) { |
| 2914 ensurePinKeyboardLoaded( |
| 2915 this.setPinVisibility.bind(this, username, true)); |
| 2916 } else { |
| 2917 this.setPinVisibility(username, false); |
| 2918 } |
| 2919 } |
| 2920 }, |
| 2921 |
| 2922 /** |
| 2923 * Shows or hides the pin keyboard from the pod with the given |username|. |
| 2924 * This is only a visibility change; the pin keyboard can be reshown. |
| 2925 * |
| 2926 * Use setPinEnabled if the pin keyboard should be disabled for the given |
| 2927 * user. |
| 2928 * @param {!user} username |
| 2929 * @param {boolean} visible |
| 2930 */ |
| 2931 setPinVisibility: function(username, visible) { |
| 2932 var pod = this.getPodWithUsername_(username); |
| 2933 if (!pod) { |
| 2934 console.error('Attempt to show/hide pin keyboard of missing pod.'); |
| 2935 return; |
| 2936 } |
| 2937 if (visible && pod.pinEnabled === false) { |
| 2938 console.error('Attempt to show disabled pin keyboard'); |
| 2939 return; |
| 2940 } |
| 2941 if (visible && this.focusedPod_ != pod) { |
| 2942 console.error('Attempt to show pin keyboard on non-focused pod'); |
| 2943 return; |
| 2944 } |
| 2945 |
| 2946 pod.setPinVisibility(visible); |
| 2947 }, |
| 2948 |
| 2949 /** |
| 2950 * Removes user pod from pod row. |
| 2951 * @param {!user} username |
| 2952 */ |
| 2953 removeUserPod: function(username) { |
| 2954 var podToRemove = this.getPodWithUsername_(username); |
| 2955 if (podToRemove == null) { |
| 2956 console.warn('Attempt to remove pod that does not exist'); |
| 2957 return; |
| 2958 } |
| 2959 this.removeChild(podToRemove); |
| 2960 if (this.pods.length > 0) |
| 2961 this.placePods_(); |
| 2962 }, |
| 2963 |
| 2964 /** |
| 2965 * Returns index of given pod or -1 if not found. |
| 2966 * @param {UserPod} pod Pod to look up. |
| 2967 * @private |
| 2968 */ |
| 2969 indexOf_: function(pod) { |
| 2970 for (var i = 0; i < this.pods.length; ++i) { |
| 2971 if (pod == this.pods[i]) |
| 2972 return i; |
| 2973 } |
| 2974 return -1; |
| 2975 }, |
| 2976 |
| 2977 /** |
| 2978 * Populates pod row with given existing users and start init animation. |
| 2979 * @param {array} users Array of existing user emails. |
| 2980 */ |
| 2981 loadPods: function(users) { |
| 2982 this.users_ = users; |
| 2983 |
| 2984 this.rebuildPods(); |
| 2985 }, |
| 2986 |
| 2987 /** |
| 2988 * Scrolls focused user pod into view. |
| 2989 */ |
| 2990 scrollFocusedPodIntoView: function() { |
| 2991 var pod = this.focusedPod_; |
| 2992 if (!pod) |
| 2993 return; |
| 2994 |
| 2995 // First check whether focused pod is already fully visible. |
| 2996 var visibleArea = $('scroll-container'); |
| 2997 // Visible area may not defined at user manager screen on all platforms. |
| 2998 // Windows, Mac and Linux do not have visible area. |
| 2999 if (!visibleArea) |
| 3000 return; |
| 3001 var scrollTop = visibleArea.scrollTop; |
| 3002 var clientHeight = visibleArea.clientHeight; |
| 3003 var podTop = $('oobe').offsetTop + pod.offsetTop; |
| 3004 var padding = USER_POD_KEYBOARD_MIN_PADDING; |
| 3005 if (podTop + pod.height + padding <= scrollTop + clientHeight && |
| 3006 podTop - padding >= scrollTop) { |
| 3007 return; |
| 3008 } |
| 3009 |
| 3010 // Scroll so that user pod is as centered as possible. |
| 3011 visibleArea.scrollTop = podTop - (clientHeight - pod.offsetHeight) / 2; |
| 3012 }, |
| 3013 |
| 3014 /** |
| 3015 * Rebuilds pod row using users_ and apps_ that were previously set or |
| 3016 * updated. |
| 3017 */ |
| 3018 rebuildPods: function() { |
| 3019 var emptyPodRow = this.pods.length == 0; |
| 3020 |
| 3021 // Clear existing pods. |
| 3022 this.innerHTML = ''; |
| 3023 this.focusedPod_ = undefined; |
| 3024 this.activatedPod_ = undefined; |
| 3025 this.lastFocusedPod_ = undefined; |
| 3026 |
| 3027 // Switch off animation |
| 3028 Oobe.getInstance().toggleClass('flying-pods', false); |
| 3029 |
| 3030 // Populate the pod row. |
| 3031 for (var i = 0; i < this.users_.length; ++i) |
| 3032 this.addUserPod(this.users_[i]); |
| 3033 |
| 3034 for (var i = 0, pod; pod = this.pods[i]; ++i) |
| 3035 this.podsWithPendingImages_.push(pod); |
| 3036 |
| 3037 // TODO(nkostylev): Edge case handling when kiosk apps are not fitting. |
| 3038 if (this.shouldShowApps_) { |
| 3039 for (var i = 0; i < this.apps_.length; ++i) |
| 3040 this.addUserPod(this.apps_[i]); |
| 3041 } |
| 3042 |
| 3043 // Make sure we eventually show the pod row, even if some image is stuck. |
| 3044 setTimeout(function() { |
| 3045 $('pod-row').classList.remove('images-loading'); |
| 3046 }, POD_ROW_IMAGES_LOAD_TIMEOUT_MS); |
| 3047 |
| 3048 var isAccountPicker = $('login-header-bar').signinUIState == |
| 3049 SIGNIN_UI_STATE.ACCOUNT_PICKER; |
| 3050 |
| 3051 // Immediately recalculate pods layout only when current UI is account |
| 3052 // picker. Otherwise postpone it. |
| 3053 if (isAccountPicker) { |
| 3054 this.placePods_(); |
| 3055 this.maybePreselectPod(); |
| 3056 |
| 3057 // Without timeout changes in pods positions will be animated even |
| 3058 // though it happened when 'flying-pods' class was disabled. |
| 3059 setTimeout(function() { |
| 3060 Oobe.getInstance().toggleClass('flying-pods', true); |
| 3061 }, 0); |
| 3062 } else { |
| 3063 this.podPlacementPostponed_ = true; |
| 3064 |
| 3065 // Update [Cancel] button state. |
| 3066 if ($('login-header-bar').signinUIState == |
| 3067 SIGNIN_UI_STATE.GAIA_SIGNIN && |
| 3068 emptyPodRow && |
| 3069 this.pods.length > 0) { |
| 3070 login.GaiaSigninScreen.updateControlsState(); |
| 3071 } |
| 3072 } |
| 3073 }, |
| 3074 |
| 3075 /** |
| 3076 * Adds given apps to the pod row. |
| 3077 * @param {array} apps Array of apps. |
| 3078 */ |
| 3079 setApps: function(apps) { |
| 3080 this.apps_ = apps; |
| 3081 this.rebuildPods(); |
| 3082 chrome.send('kioskAppsLoaded'); |
| 3083 |
| 3084 // Check whether there's a pending kiosk app error. |
| 3085 window.setTimeout(function() { |
| 3086 chrome.send('checkKioskAppLaunchError'); |
| 3087 }, 500); |
| 3088 }, |
| 3089 |
| 3090 /** |
| 3091 * Sets whether should show app pods. |
| 3092 * @param {boolean} shouldShowApps Whether app pods should be shown. |
| 3093 */ |
| 3094 setShouldShowApps: function(shouldShowApps) { |
| 3095 if (this.shouldShowApps_ == shouldShowApps) |
| 3096 return; |
| 3097 |
| 3098 this.shouldShowApps_ = shouldShowApps; |
| 3099 this.rebuildPods(); |
| 3100 }, |
| 3101 |
| 3102 /** |
| 3103 * Shows a custom icon on a user pod besides the input field. |
| 3104 * @param {string} username Username of pod to add button |
| 3105 * @param {!{id: !string, |
| 3106 * hardlockOnClick: boolean, |
| 3107 * isTrialRun: boolean, |
| 3108 * ariaLabel: string | undefined, |
| 3109 * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon |
| 3110 * The icon parameters. |
| 3111 */ |
| 3112 showUserPodCustomIcon: function(username, icon) { |
| 3113 var pod = this.getPodWithUsername_(username); |
| 3114 if (pod == null) { |
| 3115 console.error('Unable to show user pod button: user pod not found.'); |
| 3116 return; |
| 3117 } |
| 3118 |
| 3119 if (!icon.id && !icon.tooltip) |
| 3120 return; |
| 3121 |
| 3122 if (icon.id) |
| 3123 pod.customIconElement.setIcon(icon.id); |
| 3124 |
| 3125 if (icon.isTrialRun) { |
| 3126 pod.customIconElement.setInteractive( |
| 3127 this.onDidClickLockIconDuringTrialRun_.bind(this, username)); |
| 3128 } else if (icon.hardlockOnClick) { |
| 3129 pod.customIconElement.setInteractive( |
| 3130 this.hardlockUserPod_.bind(this, username)); |
| 3131 } else { |
| 3132 pod.customIconElement.setInteractive(null); |
| 3133 } |
| 3134 |
| 3135 var ariaLabel = icon.ariaLabel || (icon.tooltip && icon.tooltip.text); |
| 3136 if (ariaLabel) |
| 3137 pod.customIconElement.setAriaLabel(ariaLabel); |
| 3138 else |
| 3139 console.warn('No ARIA label for user pod custom icon.'); |
| 3140 |
| 3141 pod.customIconElement.show(); |
| 3142 |
| 3143 // This has to be called after |show| in case the tooltip should be shown |
| 3144 // immediatelly. |
| 3145 pod.customIconElement.setTooltip( |
| 3146 icon.tooltip || {text: '', autoshow: false}); |
| 3147 |
| 3148 // Hide fingerprint icon when custom icon is shown. |
| 3149 this.setUserPodFingerprintIcon(username, FINGERPRINT_STATES.HIDDEN); |
| 3150 }, |
| 3151 |
| 3152 /** |
| 3153 * Hard-locks user pod for the user. If user pod is hard-locked, it can be |
| 3154 * only unlocked using password, and the authentication type cannot be |
| 3155 * changed. |
| 3156 * @param {!string} username The user's username. |
| 3157 * @private |
| 3158 */ |
| 3159 hardlockUserPod_: function(username) { |
| 3160 chrome.send('hardlockPod', [username]); |
| 3161 }, |
| 3162 |
| 3163 /** |
| 3164 * Records a metric indicating that the user clicked on the lock icon during |
| 3165 * the trial run for Easy Unlock. |
| 3166 * @param {!string} username The user's username. |
| 3167 * @private |
| 3168 */ |
| 3169 onDidClickLockIconDuringTrialRun_: function(username) { |
| 3170 chrome.send('recordClickOnLockIcon', [username]); |
| 3171 }, |
| 3172 |
| 3173 /** |
| 3174 * Hides the custom icon in the user pod added by showUserPodCustomIcon(). |
| 3175 * @param {string} username Username of pod to remove button |
| 3176 */ |
| 3177 hideUserPodCustomIcon: function(username) { |
| 3178 var pod = this.getPodWithUsername_(username); |
| 3179 if (pod == null) { |
| 3180 console.error('Unable to hide user pod button: user pod not found.'); |
| 3181 return; |
| 3182 } |
| 3183 |
| 3184 // TODO(tengs): Allow option for a fading transition. |
| 3185 pod.customIconElement.hide(); |
| 3186 |
| 3187 // Show fingerprint icon if applicable. |
| 3188 this.setUserPodFingerprintIcon(username, FINGERPRINT_STATES.DEFAULT); |
| 3189 }, |
| 3190 |
| 3191 /** |
| 3192 * Set a fingerprint icon in the user pod of |username|. |
| 3193 * @param {string} username Username of the selected user |
| 3194 * @param {number} state Fingerprint unlock state |
| 3195 */ |
| 3196 setUserPodFingerprintIcon: function(username, state) { |
| 3197 var pod = this.getPodWithUsername_(username); |
| 3198 if (pod == null) { |
| 3199 console.error( |
| 3200 'Unable to set user pod fingerprint icon: user pod not found.'); |
| 3201 return; |
| 3202 } |
| 3203 pod.fingerprintAuthenticated_ = false; |
| 3204 if (!pod.fingerprintIconElement) |
| 3205 return; |
| 3206 if (!pod.user.allowFingerprint || state == FINGERPRINT_STATES.HIDDEN || |
| 3207 !pod.customIconElement.hidden) { |
| 3208 pod.fingerprintIconElement.hidden = true; |
| 3209 pod.submitButton.hidden = false; |
| 3210 return; |
| 3211 } |
| 3212 |
| 3213 FINGERPRINT_STATES_MAPPING.forEach(function(icon) { |
| 3214 pod.fingerprintIconElement.classList.toggle( |
| 3215 icon.class, state == icon.state); |
| 3216 }); |
| 3217 pod.fingerprintIconElement.hidden = false; |
| 3218 pod.submitButton.hidden = pod.passwordElement.value.length == 0; |
| 3219 this.updatePasswordField_(pod, state); |
| 3220 if (state == FINGERPRINT_STATES.DEFAULT) |
| 3221 return; |
| 3222 |
| 3223 pod.fingerprintAuthenticated_ = true; |
| 3224 this.setActivatedPod(pod); |
| 3225 if (state == FINGERPRINT_STATES.FAILED) { |
| 3226 /** @const */ var RESET_ICON_TIMEOUT_MS = 500; |
| 3227 setTimeout( |
| 3228 this.resetIconAndPasswordField_.bind(this, pod), |
| 3229 RESET_ICON_TIMEOUT_MS); |
| 3230 } |
| 3231 }, |
| 3232 |
| 3233 /** |
| 3234 * Reset the fingerprint icon and password field. |
| 3235 * @param {UserPod} pod Pod to reset. |
| 3236 */ |
| 3237 resetIconAndPasswordField_: function(pod) { |
| 3238 if (!pod.fingerprintIconElement) |
| 3239 return; |
| 3240 this.setUserPodFingerprintIcon( |
| 3241 pod.user.username, FINGERPRINT_STATES.DEFAULT); |
| 3242 }, |
| 3243 |
| 3244 /** |
| 3245 * Remove the fingerprint icon in the user pod. |
| 3246 * @param {string} username Username of the selected user |
| 3247 */ |
| 3248 removeUserPodFingerprintIcon: function(username) { |
| 3249 var pod = this.getPodWithUsername_(username); |
| 3250 if (pod == null) { |
| 3251 console.error('No user pod found (when removing fingerprint icon).'); |
| 3252 return; |
| 3253 } |
| 3254 this.resetIconAndPasswordField_(pod); |
| 3255 if (pod.fingerprintIconElement) { |
| 3256 pod.fingerprintIconElement.parentNode.removeChild( |
| 3257 pod.fingerprintIconElement); |
| 3258 } |
| 3259 pod.submitButton.hidden = false; |
| 3260 }, |
| 3261 |
| 3262 /** |
| 3263 * Updates the password field in the user pod. |
| 3264 * @param {UserPod} pod Pod to update. |
| 3265 * @param {number} state Fingerprint unlock state |
| 3266 */ |
| 3267 updatePasswordField_: function(pod, state) { |
| 3268 FINGERPRINT_STATES_MAPPING.forEach(function(item) { |
| 3269 pod.passwordElement.classList.toggle(item.class, state == item.state); |
| 3270 }); |
| 3271 var placeholderStr = loadTimeData.getString( |
| 3272 pod.isPinShown() ? 'pinKeyboardPlaceholderPinPassword' : |
| 3273 'passwordHint'); |
| 3274 if (state == FINGERPRINT_STATES.SIGNIN) { |
| 3275 placeholderStr = loadTimeData.getString('fingerprintSigningin'); |
| 3276 } else if (state == FINGERPRINT_STATES.FAILED) { |
| 3277 placeholderStr = loadTimeData.getString('fingerprintSigninFailed'); |
| 3278 } |
| 3279 pod.passwordElement.placeholder = placeholderStr; |
| 3280 }, |
| 3281 |
| 3282 /** |
| 3283 * Sets the authentication type used to authenticate the user. |
| 3284 * @param {string} username Username of selected user |
| 3285 * @param {number} authType Authentication type, must be one of the |
| 3286 * values listed in AUTH_TYPE enum. |
| 3287 * @param {string} value The initial value to use for authentication. |
| 3288 */ |
| 3289 setAuthType: function(username, authType, value) { |
| 3290 var pod = this.getPodWithUsername_(username); |
| 3291 if (pod == null) { |
| 3292 console.error('Unable to set auth type: user pod not found.'); |
| 3293 return; |
| 3294 } |
| 3295 pod.setAuthType(authType, value); |
| 3296 }, |
| 3297 |
| 3298 /** |
| 3299 * Sets the state of touch view mode. |
| 3300 * @param {boolean} isTouchViewEnabled true if the mode is on. |
| 3301 */ |
| 3302 setTouchViewState: function(isTouchViewEnabled) { |
| 3303 this.touchViewEnabled_ = isTouchViewEnabled; |
| 3304 this.pods.forEach(function(pod, index) { |
| 3305 pod.actionBoxAreaElement.classList.toggle('forced', isTouchViewEnabled); |
| 3306 }); |
| 3307 }, |
| 3308 |
| 3309 /** |
| 3310 * Updates the display name shown on a public session pod. |
| 3311 * @param {string} userID The user ID of the public session |
| 3312 * @param {string} displayName The new display name |
| 3313 */ |
| 3314 setPublicSessionDisplayName: function(userID, displayName) { |
| 3315 var pod = this.getPodWithUsername_(userID); |
| 3316 if (pod != null) |
| 3317 pod.setDisplayName(displayName); |
| 3318 }, |
| 3319 |
| 3320 /** |
| 3321 * Updates the list of locales available for a public session. |
| 3322 * @param {string} userID The user ID of the public session |
| 3323 * @param {!Object} locales The list of available locales |
| 3324 * @param {string} defaultLocale The locale to select by default |
| 3325 * @param {boolean} multipleRecommendedLocales Whether |locales| contains |
| 3326 * two or more recommended locales |
| 3327 */ |
| 3328 setPublicSessionLocales: function(userID, |
| 3329 locales, |
| 3330 defaultLocale, |
| 3331 multipleRecommendedLocales) { |
| 3332 var pod = this.getPodWithUsername_(userID); |
| 3333 if (pod != null) { |
| 3334 pod.populateLanguageSelect(locales, |
| 3335 defaultLocale, |
| 3336 multipleRecommendedLocales); |
| 3337 } |
| 3338 }, |
| 3339 |
| 3340 /** |
| 3341 * Updates the list of available keyboard layouts for a public session pod. |
| 3342 * @param {string} userID The user ID of the public session |
| 3343 * @param {string} locale The locale to which this list of keyboard layouts |
| 3344 * applies |
| 3345 * @param {!Object} list List of available keyboard layouts |
| 3346 */ |
| 3347 setPublicSessionKeyboardLayouts: function(userID, locale, list) { |
| 3348 var pod = this.getPodWithUsername_(userID); |
| 3349 if (pod != null) |
| 3350 pod.populateKeyboardSelect(locale, list); |
| 3351 }, |
| 3352 |
| 3353 /** |
| 3354 * Called when window was resized. |
| 3355 */ |
| 3356 onWindowResize: function() { |
| 3357 var layout = this.calculateLayout_(); |
| 3358 if (layout.columns != this.columns || layout.rows != this.rows) |
| 3359 this.placePods_(); |
| 3360 |
| 3361 // Wrap this in a set timeout so the function is called after the pod is |
| 3362 // finished transitioning so that we work with the final pod dimensions. |
| 3363 // If there is no focused pod that may be transitioning when this function |
| 3364 // is called, we can call scrollFocusedPodIntoView() right away. |
| 3365 var timeOut = 0; |
| 3366 if (this.focusedPod_) { |
| 3367 var style = getComputedStyle(this.focusedPod_); |
| 3368 timeOut = parseFloat(style.transitionDuration) * 1000; |
| 3369 } |
| 3370 |
| 3371 setTimeout(function() { |
| 3372 this.scrollFocusedPodIntoView(); |
| 3373 }.bind(this), timeOut); |
| 3374 }, |
| 3375 |
| 3376 /** |
| 3377 * Returns width of podrow having |columns| number of columns. |
| 3378 * @private |
| 3379 */ |
| 3380 columnsToWidth_: function(columns) { |
| 3381 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 3382 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 3383 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] : |
| 3384 MARGIN_BY_COLUMNS[columns]; |
| 3385 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| 3386 POD_ROW_PADDING; |
| 3387 return 2 * rowPadding + columns * this.userPodWidth_ + |
| 3388 (columns - 1) * margin; |
| 3389 }, |
| 3390 |
| 3391 /** |
| 3392 * Returns height of podrow having |rows| number of rows. |
| 3393 * @private |
| 3394 */ |
| 3395 rowsToHeight_: function(rows) { |
| 3396 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 3397 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 3398 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| 3399 POD_ROW_PADDING; |
| 3400 return 2 * rowPadding + rows * this.userPodHeight_; |
| 3401 }, |
| 3402 |
| 3403 /** |
| 3404 * Calculates number of columns and rows that podrow should have in order to |
| 3405 * hold as much its pods as possible for current screen size. Also it tries |
| 3406 * to choose layout that looks good. |
| 3407 * @return {{columns: number, rows: number}} |
| 3408 */ |
| 3409 calculateLayout_: function() { |
| 3410 var preferredColumns = this.pods.length < COLUMNS.length ? |
| 3411 COLUMNS[this.pods.length] : COLUMNS[COLUMNS.length - 1]; |
| 3412 var maxWidth = Oobe.getInstance().clientAreaSize.width; |
| 3413 var columns = preferredColumns; |
| 3414 while (maxWidth < this.columnsToWidth_(columns) && columns > 1) |
| 3415 --columns; |
| 3416 var rows = Math.floor((this.pods.length - 1) / columns) + 1; |
| 3417 if (getComputedStyle( |
| 3418 $('signin-banner'), null).getPropertyValue('display') != 'none') { |
| 3419 rows = Math.min(rows, MAX_NUMBER_OF_ROWS_UNDER_SIGNIN_BANNER); |
| 3420 } |
| 3421 if (!Oobe.getInstance().newDesktopUserManager) { |
| 3422 var maxHeigth = Oobe.getInstance().clientAreaSize.height; |
| 3423 while (maxHeigth < this.rowsToHeight_(rows) && rows > 1) |
| 3424 --rows; |
| 3425 } |
| 3426 // One more iteration if it's not enough cells to place all pods. |
| 3427 while (maxWidth >= this.columnsToWidth_(columns + 1) && |
| 3428 columns * rows < this.pods.length && |
| 3429 columns < MAX_NUMBER_OF_COLUMNS) { |
| 3430 ++columns; |
| 3431 } |
| 3432 return {columns: columns, rows: rows}; |
| 3433 }, |
| 3434 |
| 3435 /** |
| 3436 * Places pods onto their positions onto pod grid. |
| 3437 * @private |
| 3438 */ |
| 3439 placePods_: function() { |
| 3440 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 3441 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 3442 if (isDesktopUserManager && !Oobe.getInstance().userPodsPageVisible) |
| 3443 return; |
| 3444 |
| 3445 var layout = this.calculateLayout_(); |
| 3446 var columns = this.columns = layout.columns; |
| 3447 var rows = this.rows = layout.rows; |
| 3448 var maxPodsNumber = columns * rows; |
| 3449 var margin = isDesktopUserManager ? DESKTOP_MARGIN_BY_COLUMNS[columns] : |
| 3450 MARGIN_BY_COLUMNS[columns]; |
| 3451 this.parentNode.setPreferredSize( |
| 3452 this.columnsToWidth_(columns), this.rowsToHeight_(rows)); |
| 3453 var height = this.userPodHeight_; |
| 3454 var width = this.userPodWidth_; |
| 3455 var pinPodLocation = { column: columns + 1, row: rows + 1 }; |
| 3456 if (this.focusedPod_ && this.focusedPod_.isPinShown()) |
| 3457 pinPodLocation = this.findPodLocation_(this.focusedPod_, columns, rows); |
| 3458 |
| 3459 this.pods.forEach(function(pod, index) { |
| 3460 if (index >= maxPodsNumber) { |
| 3461 pod.hidden = true; |
| 3462 return; |
| 3463 } |
| 3464 pod.hidden = false; |
| 3465 if (pod.offsetHeight != height && |
| 3466 pod.offsetHeight != CROS_PIN_POD_HEIGHT) { |
| 3467 console.error('Pod offsetHeight (' + pod.offsetHeight + |
| 3468 ') and POD_HEIGHT (' + height + ') are not equal.'); |
| 3469 } |
| 3470 if (pod.offsetWidth != width) { |
| 3471 console.error('Pod offsetWidth (' + pod.offsetWidth + |
| 3472 ') and POD_WIDTH (' + width + ') are not equal.'); |
| 3473 } |
| 3474 var column = index % columns; |
| 3475 var row = Math.floor(index / columns); |
| 3476 |
| 3477 var rowPadding = isDesktopUserManager ? DESKTOP_ROW_PADDING : |
| 3478 POD_ROW_PADDING; |
| 3479 pod.left = rowPadding + column * (width + margin); |
| 3480 |
| 3481 // On desktop, we want the rows to always be equally spaced. |
| 3482 pod.top = isDesktopUserManager ? row * (height + rowPadding) : |
| 3483 row * height + rowPadding; |
| 3484 }); |
| 3485 Oobe.getInstance().updateScreenSize(this.parentNode); |
| 3486 }, |
| 3487 |
| 3488 /** |
| 3489 * Number of columns. |
| 3490 * @type {?number} |
| 3491 */ |
| 3492 set columns(columns) { |
| 3493 // Cannot use 'columns' here. |
| 3494 this.setAttribute('ncolumns', columns); |
| 3495 }, |
| 3496 get columns() { |
| 3497 return parseInt(this.getAttribute('ncolumns')); |
| 3498 }, |
| 3499 |
| 3500 /** |
| 3501 * Number of rows. |
| 3502 * @type {?number} |
| 3503 */ |
| 3504 set rows(rows) { |
| 3505 // Cannot use 'rows' here. |
| 3506 this.setAttribute('nrows', rows); |
| 3507 }, |
| 3508 get rows() { |
| 3509 return parseInt(this.getAttribute('nrows')); |
| 3510 }, |
| 3511 |
| 3512 /** |
| 3513 * Whether the pod is currently focused. |
| 3514 * @param {UserPod} pod Pod to check for focus. |
| 3515 * @return {boolean} Pod focus status. |
| 3516 */ |
| 3517 isFocused: function(pod) { |
| 3518 return this.focusedPod_ == pod; |
| 3519 }, |
| 3520 |
| 3521 /** |
| 3522 * Focuses a given user pod or clear focus when given null. |
| 3523 * @param {UserPod=} podToFocus User pod to focus (undefined clears focus). |
| 3524 * @param {boolean=} opt_force If true, forces focus update even when |
| 3525 * podToFocus is already focused. |
| 3526 * @param {boolean=} opt_skipInputFocus If true, don't focus on the input |
| 3527 * box of user pod. |
| 3528 */ |
| 3529 focusPod: function(podToFocus, opt_force, opt_skipInputFocus) { |
| 3530 if (this.isFocused(podToFocus) && !opt_force) { |
| 3531 // Calling focusPod w/o podToFocus means reset. |
| 3532 if (!podToFocus) |
| 3533 Oobe.clearErrors(); |
| 3534 return; |
| 3535 } |
| 3536 |
| 3537 // Make sure there's only one focusPod operation happening at a time. |
| 3538 if (this.insideFocusPod_) { |
| 3539 return; |
| 3540 } |
| 3541 this.insideFocusPod_ = true; |
| 3542 |
| 3543 for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| 3544 if (!this.alwaysFocusSinglePod) { |
| 3545 pod.isActionBoxMenuActive = false; |
| 3546 } |
| 3547 if (pod != podToFocus) { |
| 3548 pod.isActionBoxMenuHovered = false; |
| 3549 pod.classList.remove('focused'); |
| 3550 pod.setPinVisibility(false); |
| 3551 this.setUserPodFingerprintIcon( |
| 3552 pod.user.username, FINGERPRINT_STATES.HIDDEN); |
| 3553 // On Desktop, the faded style is not set correctly, so we should |
| 3554 // manually fade out non-focused pods if there is a focused pod. |
| 3555 if (pod.user.isDesktopUser && podToFocus) |
| 3556 pod.classList.add('faded'); |
| 3557 else |
| 3558 pod.classList.remove('faded'); |
| 3559 pod.reset(false); |
| 3560 } |
| 3561 } |
| 3562 |
| 3563 // Clear any error messages for previous pod. |
| 3564 if (!this.isFocused(podToFocus)) |
| 3565 Oobe.clearErrors(); |
| 3566 |
| 3567 this.focusedPod_ = podToFocus; |
| 3568 if (podToFocus) { |
| 3569 // Only show the keyboard if it is fully loaded. |
| 3570 if (podToFocus.isPinReady()) |
| 3571 podToFocus.setPinVisibility(true); |
| 3572 podToFocus.classList.remove('faded'); |
| 3573 podToFocus.classList.add('focused'); |
| 3574 if (!podToFocus.multiProfilesPolicyApplied) { |
| 3575 podToFocus.classList.toggle('signing-in', false); |
| 3576 if (!opt_skipInputFocus) |
| 3577 podToFocus.focusInput(); |
| 3578 } else { |
| 3579 podToFocus.userTypeBubbleElement.classList.add('bubble-shown'); |
| 3580 // Note it is not necessary to skip this focus request when |
| 3581 // |opt_skipInputFocus| is true. When |multiProfilesPolicyApplied| |
| 3582 // is false, it doesn't focus on the password input box by default. |
| 3583 podToFocus.focus(); |
| 3584 } |
| 3585 |
| 3586 // focusPod() automatically loads wallpaper |
| 3587 if (!podToFocus.user.isApp) |
| 3588 chrome.send('focusPod', [podToFocus.user.username]); |
| 3589 this.firstShown_ = false; |
| 3590 this.lastFocusedPod_ = podToFocus; |
| 3591 this.scrollFocusedPodIntoView(); |
| 3592 this.setUserPodFingerprintIcon( |
| 3593 podToFocus.user.username, FINGERPRINT_STATES.DEFAULT); |
| 3594 } else { |
| 3595 chrome.send('noPodFocused'); |
| 3596 } |
| 3597 this.insideFocusPod_ = false; |
| 3598 }, |
| 3599 |
| 3600 /** |
| 3601 * Resets wallpaper to the last active user's wallpaper, if any. |
| 3602 */ |
| 3603 loadLastWallpaper: function() { |
| 3604 if (this.lastFocusedPod_ && !this.lastFocusedPod_.user.isApp) |
| 3605 chrome.send('loadWallpaper', [this.lastFocusedPod_.user.username]); |
| 3606 }, |
| 3607 |
| 3608 /** |
| 3609 * Returns the currently activated pod. |
| 3610 * @type {UserPod} |
| 3611 */ |
| 3612 get activatedPod() { |
| 3613 return this.activatedPod_; |
| 3614 }, |
| 3615 |
| 3616 /** |
| 3617 * Sets currently activated pod. |
| 3618 * @param {UserPod} pod Pod to check for focus. |
| 3619 * @param {Event} e Event object. |
| 3620 */ |
| 3621 setActivatedPod: function(pod, e) { |
| 3622 if (this.disabled) { |
| 3623 console.error('Cannot activate pod while sign-in UI is disabled.'); |
| 3624 return; |
| 3625 } |
| 3626 if (pod && pod.activate(e)) |
| 3627 this.activatedPod_ = pod; |
| 3628 }, |
| 3629 |
| 3630 /** |
| 3631 * The pod of the signed-in user, if any; null otherwise. |
| 3632 * @type {?UserPod} |
| 3633 */ |
| 3634 get lockedPod() { |
| 3635 for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| 3636 if (pod.user.signedIn) |
| 3637 return pod; |
| 3638 } |
| 3639 return null; |
| 3640 }, |
| 3641 |
| 3642 /** |
| 3643 * The pod that is preselected on user pod row show. |
| 3644 * @type {?UserPod} |
| 3645 */ |
| 3646 get preselectedPod() { |
| 3647 var isDesktopUserManager = Oobe.getInstance().displayType == |
| 3648 DISPLAY_TYPE.DESKTOP_USER_MANAGER; |
| 3649 if (isDesktopUserManager) { |
| 3650 // On desktop, don't pre-select a pod if it's the only one. |
| 3651 if (this.pods.length == 1) |
| 3652 return null; |
| 3653 |
| 3654 // The desktop User Manager can send an URI encoded profile path in the |
| 3655 // url hash, that indicates a pod that should be initially focused. |
| 3656 var focusedProfilePath = |
| 3657 decodeURIComponent(window.location.hash.substr(1)); |
| 3658 for (var i = 0, pod; pod = this.pods[i]; ++i) { |
| 3659 if (focusedProfilePath === pod.user.profilePath) |
| 3660 return pod; |
| 3661 } |
| 3662 return null; |
| 3663 } |
| 3664 |
| 3665 var lockedPod = this.lockedPod; |
| 3666 if (lockedPod) |
| 3667 return lockedPod; |
| 3668 for (i = 0; pod = this.pods[i]; ++i) { |
| 3669 if (!pod.multiProfilesPolicyApplied) |
| 3670 return pod; |
| 3671 } |
| 3672 return this.pods[0]; |
| 3673 }, |
| 3674 |
| 3675 /** |
| 3676 * Resets input UI. |
| 3677 * @param {boolean} takeFocus True to take focus. |
| 3678 */ |
| 3679 reset: function(takeFocus) { |
| 3680 this.disabled = false; |
| 3681 if (this.activatedPod_) |
| 3682 this.activatedPod_.reset(takeFocus); |
| 3683 }, |
| 3684 |
| 3685 /** |
| 3686 * Restores input focus to current selected pod, if there is any. |
| 3687 */ |
| 3688 refocusCurrentPod: function() { |
| 3689 if (this.focusedPod_ && !this.focusedPod_.multiProfilesPolicyApplied) { |
| 3690 this.focusedPod_.focusInput(); |
| 3691 } |
| 3692 }, |
| 3693 |
| 3694 /** |
| 3695 * Clears focused pod password field. |
| 3696 */ |
| 3697 clearFocusedPod: function() { |
| 3698 if (!this.disabled && this.focusedPod_) |
| 3699 this.focusedPod_.reset(true); |
| 3700 }, |
| 3701 |
| 3702 /** |
| 3703 * Shows signin UI. |
| 3704 * @param {string} email Email for signin UI. |
| 3705 */ |
| 3706 showSigninUI: function(email) { |
| 3707 // Clear any error messages that might still be around. |
| 3708 Oobe.clearErrors(); |
| 3709 this.disabled = true; |
| 3710 this.lastFocusedPod_ = this.getPodWithUsername_(email); |
| 3711 Oobe.showSigninUI(email); |
| 3712 }, |
| 3713 |
| 3714 /** |
| 3715 * Updates current image of a user. |
| 3716 * @param {string} username User for which to update the image. |
| 3717 */ |
| 3718 updateUserImage: function(username) { |
| 3719 var pod = this.getPodWithUsername_(username); |
| 3720 if (pod) |
| 3721 pod.updateUserImage(); |
| 3722 }, |
| 3723 |
| 3724 /** |
| 3725 * Handler of click event. |
| 3726 * @param {Event} e Click Event object. |
| 3727 * @private |
| 3728 */ |
| 3729 handleClick_: function(e) { |
| 3730 if (this.disabled) |
| 3731 return; |
| 3732 |
| 3733 // Clear all menus if the click is outside pod menu and its |
| 3734 // button area. |
| 3735 if (!findAncestorByClass(e.target, 'action-box-menu') && |
| 3736 !findAncestorByClass(e.target, 'action-box-area')) { |
| 3737 for (var i = 0, pod; pod = this.pods[i]; ++i) |
| 3738 pod.isActionBoxMenuActive = false; |
| 3739 } |
| 3740 |
| 3741 // Clears focus if not clicked on a pod and if there's more than one pod. |
| 3742 var pod = findAncestorByClass(e.target, 'pod'); |
| 3743 if ((!pod || pod.parentNode != this) && !this.alwaysFocusSinglePod) { |
| 3744 this.focusPod(); |
| 3745 } |
| 3746 |
| 3747 if (pod) |
| 3748 pod.isActionBoxMenuHovered = true; |
| 3749 |
| 3750 // Return focus back to single pod. |
| 3751 if (this.alwaysFocusSinglePod && !pod) { |
| 3752 if ($('login-header-bar').contains(e.target)) |
| 3753 return; |
| 3754 this.focusPod(this.focusedPod_, true /* force */); |
| 3755 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown'); |
| 3756 this.focusedPod_.isActionBoxMenuHovered = false; |
| 3757 } |
| 3758 }, |
| 3759 |
| 3760 /** |
| 3761 * Handler of mouse move event. |
| 3762 * @param {Event} e Click Event object. |
| 3763 * @private |
| 3764 */ |
| 3765 handleMouseMove_: function(e) { |
| 3766 if (this.disabled) |
| 3767 return; |
| 3768 if (e.movementX == 0 && e.movementY == 0) |
| 3769 return; |
| 3770 |
| 3771 // Defocus (thus hide) action box, if it is focused on a user pod |
| 3772 // and the pointer is not hovering over it. |
| 3773 var pod = findAncestorByClass(e.target, 'pod'); |
| 3774 if (document.activeElement && |
| 3775 document.activeElement.parentNode != pod && |
| 3776 document.activeElement.classList.contains('action-box-area')) { |
| 3777 document.activeElement.parentNode.focus(); |
| 3778 } |
| 3779 |
| 3780 if (pod) |
| 3781 pod.isActionBoxMenuHovered = true; |
| 3782 |
| 3783 // Hide action boxes on other user pods. |
| 3784 for (var i = 0, p; p = this.pods[i]; ++i) |
| 3785 if (p != pod && !p.isActionBoxMenuActive) |
| 3786 p.isActionBoxMenuHovered = false; |
| 3787 }, |
| 3788 |
| 3789 /** |
| 3790 * Handles focus event. |
| 3791 * @param {Event} e Focus Event object. |
| 3792 * @private |
| 3793 */ |
| 3794 handleFocus_: function(e) { |
| 3795 if (this.disabled) |
| 3796 return; |
| 3797 if (e.target.parentNode == this) { |
| 3798 // Focus on a pod |
| 3799 if (e.target.classList.contains('focused')) { |
| 3800 if (!e.target.multiProfilesPolicyApplied) |
| 3801 e.target.focusInput(); |
| 3802 else |
| 3803 e.target.userTypeBubbleElement.classList.add('bubble-shown'); |
| 3804 } else |
| 3805 this.focusPod(e.target); |
| 3806 return; |
| 3807 } |
| 3808 |
| 3809 var pod = findAncestorByClass(e.target, 'pod'); |
| 3810 if (pod && pod.parentNode == this) { |
| 3811 // Focus on a control of a pod but not on the action area button. |
| 3812 if (!pod.classList.contains('focused')) { |
| 3813 if (e.target.classList.contains('action-box-area') || |
| 3814 e.target.classList.contains('remove-warning-button')) { |
| 3815 // focusPod usually moves focus on the password input box which |
| 3816 // triggers virtual keyboard to show up. But the focus may move to a |
| 3817 // non text input element shortly by e.target.focus. Hence, a |
| 3818 // virtual keyboard flicking might be observed. We need to manually |
| 3819 // prevent focus on password input box to avoid virtual keyboard |
| 3820 // flicking in this case. See crbug.com/396016 for details. |
| 3821 this.focusPod(pod, false, true /* opt_skipInputFocus */); |
| 3822 } else { |
| 3823 this.focusPod(pod); |
| 3824 } |
| 3825 pod.userTypeBubbleElement.classList.remove('bubble-shown'); |
| 3826 e.target.focus(); |
| 3827 } |
| 3828 return; |
| 3829 } |
| 3830 |
| 3831 // Clears pod focus when we reach here. It means new focus is neither |
| 3832 // on a pod nor on a button/input for a pod. |
| 3833 // Do not "defocus" user pod when it is a single pod. |
| 3834 // That means that 'focused' class will not be removed and |
| 3835 // input field/button will always be visible. |
| 3836 if (!this.alwaysFocusSinglePod) |
| 3837 this.focusPod(); |
| 3838 else { |
| 3839 // Hide user-type-bubble in case this is one pod and we lost focus of |
| 3840 // it. |
| 3841 this.focusedPod_.userTypeBubbleElement.classList.remove('bubble-shown'); |
| 3842 } |
| 3843 }, |
| 3844 |
| 3845 /** |
| 3846 * Handler of keydown event. |
| 3847 * @param {Event} e KeyDown Event object. |
| 3848 */ |
| 3849 handleKeyDown: function(e) { |
| 3850 if (this.disabled) |
| 3851 return; |
| 3852 var editing = e.target.tagName == 'INPUT' && e.target.value; |
| 3853 switch (e.key) { |
| 3854 case 'ArrowLeft': |
| 3855 if (!editing) { |
| 3856 if (this.focusedPod_ && this.focusedPod_.previousElementSibling) |
| 3857 this.focusPod(this.focusedPod_.previousElementSibling); |
| 3858 else |
| 3859 this.focusPod(this.lastElementChild); |
| 3860 |
| 3861 e.stopPropagation(); |
| 3862 } |
| 3863 break; |
| 3864 case 'ArrowRight': |
| 3865 if (!editing) { |
| 3866 if (this.focusedPod_ && this.focusedPod_.nextElementSibling) |
| 3867 this.focusPod(this.focusedPod_.nextElementSibling); |
| 3868 else |
| 3869 this.focusPod(this.firstElementChild); |
| 3870 |
| 3871 e.stopPropagation(); |
| 3872 } |
| 3873 break; |
| 3874 case 'Enter': |
| 3875 if (this.focusedPod_) { |
| 3876 var targetTag = e.target.tagName; |
| 3877 if (e.target == this.focusedPod_.passwordElement || |
| 3878 (this.focusedPod_.pinKeyboard && |
| 3879 e.target == this.focusedPod_.pinKeyboard.inputElement) || |
| 3880 (targetTag != 'INPUT' && |
| 3881 targetTag != 'BUTTON' && |
| 3882 targetTag != 'A')) { |
| 3883 this.setActivatedPod(this.focusedPod_, e); |
| 3884 e.stopPropagation(); |
| 3885 } |
| 3886 } |
| 3887 break; |
| 3888 case 'Escape': |
| 3889 if (!this.alwaysFocusSinglePod) |
| 3890 this.focusPod(); |
| 3891 break; |
| 3892 } |
| 3893 }, |
| 3894 |
| 3895 /** |
| 3896 * Called right after the pod row is shown. |
| 3897 */ |
| 3898 handleAfterShow: function() { |
| 3899 var focusedPod = this.focusedPod_; |
| 3900 |
| 3901 // Without timeout changes in pods positions will be animated even though |
| 3902 // it happened when 'flying-pods' class was disabled. |
| 3903 setTimeout(function() { |
| 3904 Oobe.getInstance().toggleClass('flying-pods', true); |
| 3905 if (focusedPod) |
| 3906 ensureTransitionEndEvent(focusedPod); |
| 3907 }, 0); |
| 3908 |
| 3909 // Force input focus for user pod on show and once transition ends. |
| 3910 if (focusedPod) { |
| 3911 var screen = this.parentNode; |
| 3912 var self = this; |
| 3913 focusedPod.addEventListener('transitionend', function f(e) { |
| 3914 focusedPod.removeEventListener('transitionend', f); |
| 3915 focusedPod.reset(true); |
| 3916 // Notify screen that it is ready. |
| 3917 screen.onShow(); |
| 3918 }); |
| 3919 } |
| 3920 }, |
| 3921 |
| 3922 /** |
| 3923 * Called right before the pod row is shown. |
| 3924 */ |
| 3925 handleBeforeShow: function() { |
| 3926 Oobe.getInstance().toggleClass('flying-pods', false); |
| 3927 for (var event in this.listeners_) { |
| 3928 this.ownerDocument.addEventListener( |
| 3929 event, this.listeners_[event][0], this.listeners_[event][1]); |
| 3930 } |
| 3931 $('login-header-bar').buttonsTabIndex = UserPodTabOrder.HEADER_BAR; |
| 3932 |
| 3933 if (this.podPlacementPostponed_) { |
| 3934 this.podPlacementPostponed_ = false; |
| 3935 this.placePods_(); |
| 3936 this.maybePreselectPod(); |
| 3937 } |
| 3938 }, |
| 3939 |
| 3940 /** |
| 3941 * Called when the element is hidden. |
| 3942 */ |
| 3943 handleHide: function() { |
| 3944 for (var event in this.listeners_) { |
| 3945 this.ownerDocument.removeEventListener( |
| 3946 event, this.listeners_[event][0], this.listeners_[event][1]); |
| 3947 } |
| 3948 $('login-header-bar').buttonsTabIndex = 0; |
| 3949 }, |
| 3950 |
| 3951 /** |
| 3952 * Called when a pod's user image finishes loading. |
| 3953 */ |
| 3954 handlePodImageLoad: function(pod) { |
| 3955 var index = this.podsWithPendingImages_.indexOf(pod); |
| 3956 if (index == -1) { |
| 3957 return; |
| 3958 } |
| 3959 |
| 3960 this.podsWithPendingImages_.splice(index, 1); |
| 3961 if (this.podsWithPendingImages_.length == 0) { |
| 3962 this.classList.remove('images-loading'); |
| 3963 } |
| 3964 }, |
| 3965 |
| 3966 /** |
| 3967 * Preselects pod, if needed. |
| 3968 */ |
| 3969 maybePreselectPod: function() { |
| 3970 var pod = this.preselectedPod; |
| 3971 this.focusPod(pod); |
| 3972 |
| 3973 // Hide user-type-bubble in case all user pods are disabled and we focus |
| 3974 // first pod. |
| 3975 if (pod && pod.multiProfilesPolicyApplied) { |
| 3976 pod.userTypeBubbleElement.classList.remove('bubble-shown'); |
| 3977 } |
| 3978 } |
| 3979 }; |
| 3980 |
| 3981 return { |
| 3982 PodRow: PodRow |
| 3983 }; |
| 3984 }); |
OLD | NEW |