Index: ui/login/account_picker/user_pod_row.js |
diff --git a/ui/login/account_picker/user_pod_row.js b/ui/login/account_picker/user_pod_row.js |
index c52ccfa36948317df4a453d3197ba3dc7e0dd191..13a1c64d1ef3afee5b957db7f01ca5408c119855 100644 |
--- a/ui/login/account_picker/user_pod_row.js |
+++ b/ui/login/account_picker/user_pod_row.js |
@@ -125,6 +125,32 @@ cr.define('login', function() { |
5: 'forceOfflinePassword' |
}; |
+ /** |
+ * Supported fingerprint unlock states. |
+ * @enum {number} |
+ * @const |
+ */ |
+ var FINGERPRINT_STATES = { |
+ HIDDEN: 0, |
+ DEFAULT: 1, |
+ SIGNIN: 2, |
+ FAILED: 3, |
+ }; |
+ |
+ /** |
+ * The fingerprint states to classes mapping. |
+ * {@code state} properties indicate current fingerprint unlock state. |
+ * {@code class} properties are CSS classes used to set the icons' background |
+ * and password placeholder color. |
+ * @const {Array<{type: !number, class: !string}>} |
+ */ |
+ var FINGERPRINT_STATES_MAPPING = [ |
+ {state: FINGERPRINT_STATES.HIDDEN, class: 'hidden'}, |
+ {state: FINGERPRINT_STATES.DEFAULT, class: 'default'}, |
+ {state: FINGERPRINT_STATES.SIGNIN, class: 'signin'}, |
+ {state: FINGERPRINT_STATES.FAILED, class: 'failed'} |
+ ]; |
+ |
// Focus and tab order are organized as follows: |
// |
// (1) all user pods have tab index 1 so they are traversed first; |
@@ -711,6 +737,13 @@ cr.define('login', function() { |
*/ |
userClickAuthAllowed_: false, |
+ /** |
+ * Whether the user has recently authenticated with fingerprint. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ fingerprintAuthenticated_: false, |
+ |
/** @override */ |
decorate: function() { |
this.tabIndex = UserPodTabOrder.POD_INPUT; |
@@ -750,6 +783,15 @@ cr.define('login', function() { |
this.actionBoxRemoveUserWarningButtonElement.addEventListener('keydown', |
this.handleRemoveUserConfirmationKeyDown_.bind(this)); |
+ if (this.fingerprintIconElement) { |
+ this.fingerprintIconElement.addEventListener( |
+ 'mouseover', this.handleFingerprintIconMouseOver_.bind(this)); |
+ this.fingerprintIconElement.addEventListener( |
+ 'mouseout', this.handleFingerprintIconMouseOut_.bind(this)); |
+ this.fingerprintIconElement.addEventListener( |
+ 'mousedown', stopEventPropagation); |
+ } |
+ |
var customIcon = this.customIconElement; |
customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon); |
}, |
@@ -1096,6 +1138,14 @@ cr.define('login', function() { |
}, |
/** |
+ * Gets the fingerprint icon area. |
+ * @type {!HTMLDivElement} |
+ */ |
+ get fingerprintIconElement() { |
+ return this.querySelector('.fingerprint-icon-container'); |
+ }, |
+ |
+ /** |
* Updates the user pod element. |
*/ |
update: function() { |
@@ -1216,6 +1266,10 @@ cr.define('login', function() { |
this.userTypeIconAreaElement.hidden = false; |
}, |
+ isFingerprintIconShown: function() { |
+ return this.fingerprintIconElement && !this.fingerprintIconElement.hidden; |
+ }, |
+ |
/** |
* The user that this pod represents. |
* @type {!Object} |
@@ -1412,6 +1466,10 @@ cr.define('login', function() { |
this.classList.toggle('signing-in', true); |
chrome.send('attemptUnlock', [this.user.username]); |
} else if (this.isAuthTypePassword) { |
+ if (this.fingerprintAuthenticated_) { |
+ this.fingerprintAuthenticated_ = false; |
+ return true; |
+ } |
var pinValue = this.pinKeyboard ? this.pinKeyboard.value : ''; |
var password = this.passwordElement.value || pinValue; |
if (!password) |
@@ -1822,6 +1880,50 @@ cr.define('login', function() { |
}, |
/** |
+ * Handles mouseover event on fingerprint icon. |
+ * @param {Event} e MouseOver event. |
+ */ |
+ handleFingerprintIconMouseOver_: function(e) { |
+ var bubbleContent = document.createElement('div'); |
+ bubbleContent.textContent = |
+ loadTimeData.getString('fingerprintIconMessage'); |
+ this.passwordElement.placeholder = |
+ loadTimeData.getString('fingerprintHint'); |
+ |
+ /** @const */ var BUBBLE_OFFSET = 25; |
+ /** @const */ var BUBBLE_PADDING = -8; |
+ var attachment = this.isPinShown() ? cr.ui.Bubble.Attachment.RIGHT : |
+ cr.ui.Bubble.Attachment.BOTTOM; |
+ var bubbleAnchor = this.getBubbleAnchorForFingerprintIcon_(); |
+ $('bubble').showContentForElement( |
+ bubbleAnchor, attachment, bubbleContent, BUBBLE_OFFSET, |
+ BUBBLE_PADDING, true); |
+ }, |
+ |
+ /** |
+ * Handles mouseout event on fingerprint icon. |
+ * @param {Event} e MouseOut event. |
+ */ |
+ handleFingerprintIconMouseOut_: function(e) { |
+ var bubbleAnchor = this.getBubbleAnchorForFingerprintIcon_(); |
+ $('bubble').hideForElement(bubbleAnchor); |
+ this.passwordElement.placeholder = loadTimeData.getString( |
+ this.isPinShown() ? 'pinKeyboardPlaceholderPinPassword' : |
+ 'passwordHint'); |
+ }, |
+ |
+ /** |
+ * Returns bubble anchor of the fingerprint icon. |
+ * @return {!HTMLElement} Anchor element of the bubble. |
+ */ |
+ getBubbleAnchorForFingerprintIcon_: function() { |
+ var bubbleAnchor = this; |
+ if (this.isPinShown()) |
+ bubbleAnchor = (this.getElementsByClassName('auth-container'))[0]; |
+ return bubbleAnchor; |
+ }, |
+ |
+ /** |
* Handles a keydown event on remove user confirmation button. |
* @param {Event} e KeyDown event. |
*/ |
@@ -1899,8 +2001,14 @@ cr.define('login', function() { |
* button color and state and hides the error popup bubble. |
*/ |
updateInput_: function() { |
- if (this.submitButton) |
- this.submitButton.disabled = this.passwordElement.value.length <= 0; |
+ if (this.submitButton) { |
+ this.submitButton.disabled = this.passwordElement.value.length == 0; |
+ if (this.isFingerprintIconShown()) { |
+ this.submitButton.hidden = this.passwordElement.value.length == 0; |
+ } else { |
+ this.submitButton.hidden = false; |
+ } |
+ } |
this.showError = false; |
$('bubble').hide(); |
}, |
@@ -3001,6 +3109,9 @@ cr.define('login', function() { |
// immediatelly. |
pod.customIconElement.setTooltip( |
icon.tooltip || {text: '', autoshow: false}); |
+ |
+ // Hide fingerprint icon when custom icon is shown. |
+ this.setUserPodFingerprintIcon(username, FINGERPRINT_STATES.HIDDEN); |
}, |
/** |
@@ -3037,6 +3148,100 @@ cr.define('login', function() { |
// TODO(tengs): Allow option for a fading transition. |
pod.customIconElement.hide(); |
+ |
+ // Show fingerprint icon if applicable. |
+ this.setUserPodFingerprintIcon(username, FINGERPRINT_STATES.DEFAULT); |
+ }, |
+ |
+ /** |
+ * Set a fingerprint icon in the user pod of |username|. |
+ * @param {string} username Username of the selected user |
+ * @param {number} state Fingerprint unlock state |
+ */ |
+ setUserPodFingerprintIcon: function(username, state) { |
+ var pod = this.getPodWithUsername_(username); |
+ if (pod == null) { |
+ console.error( |
+ 'Unable to set user pod fingerprint icon: user pod not found.'); |
+ return; |
+ } |
+ pod.fingerprintAuthenticated_ = false; |
+ if (!pod.fingerprintIconElement) |
+ return; |
+ if (!pod.user.allowFingerprint || state == FINGERPRINT_STATES.HIDDEN || |
+ !pod.customIconElement.hidden) { |
+ pod.fingerprintIconElement.hidden = true; |
+ pod.submitButton.hidden = false; |
+ return; |
+ } |
+ |
+ FINGERPRINT_STATES_MAPPING.forEach(function(icon) { |
+ pod.fingerprintIconElement.classList.toggle( |
+ icon.class, state == icon.state); |
+ }); |
+ pod.fingerprintIconElement.hidden = false; |
+ pod.submitButton.hidden = pod.passwordElement.value.length == 0; |
+ this.updatePasswordField_(pod, state); |
+ if (state == FINGERPRINT_STATES.DEFAULT) |
+ return; |
+ |
+ pod.fingerprintAuthenticated_ = true; |
+ this.setActivatedPod(pod); |
+ if (state == FINGERPRINT_STATES.FAILED) { |
+ /** @const */ var RESET_ICON_TIMEOUT_MS = 500; |
+ setTimeout( |
+ this.resetIconAndPasswordField_.bind(this, pod), |
+ RESET_ICON_TIMEOUT_MS); |
+ } |
+ }, |
+ |
+ /** |
+ * Reset the fingerprint icon and password field. |
+ * @param {UserPod} pod Pod to reset. |
+ */ |
+ resetIconAndPasswordField_: function(pod) { |
+ if (!pod.fingerprintIconElement) |
+ return; |
+ this.setUserPodFingerprintIcon( |
+ pod.user.username, FINGERPRINT_STATES.DEFAULT); |
+ }, |
+ |
+ /** |
+ * Remove the fingerprint icon in the user pod. |
+ * @param {string} username Username of the selected user |
+ */ |
+ removeUserPodFingerprintIcon: function(username) { |
+ var pod = this.getPodWithUsername_(username); |
+ if (pod == null) { |
+ console.error('No user pod found (when removing fingerprint icon).'); |
+ return; |
+ } |
+ this.resetIconAndPasswordField_(pod); |
+ if (pod.fingerprintIconElement) { |
+ pod.fingerprintIconElement.parentNode.removeChild( |
+ pod.fingerprintIconElement); |
+ } |
+ pod.submitButton.hidden = false; |
+ }, |
+ |
+ /** |
+ * Updates the password field in the user pod. |
+ * @param {UserPod} pod Pod to update. |
+ * @param {number} state Fingerprint unlock state |
+ */ |
+ updatePasswordField_: function(pod, state) { |
+ FINGERPRINT_STATES_MAPPING.forEach(function(item) { |
+ pod.passwordElement.classList.toggle(item.class, state == item.state); |
+ }); |
+ var placeholderStr = loadTimeData.getString( |
+ pod.isPinShown() ? 'pinKeyboardPlaceholderPinPassword' : |
+ 'passwordHint'); |
+ if (state == FINGERPRINT_STATES.SIGNIN) { |
+ placeholderStr = loadTimeData.getString('fingerprintSigningin'); |
+ } else if (state == FINGERPRINT_STATES.FAILED) { |
+ placeholderStr = loadTimeData.getString('fingerprintSigninFailed'); |
+ } |
+ pod.passwordElement.placeholder = placeholderStr; |
}, |
/** |
@@ -3308,6 +3513,8 @@ cr.define('login', function() { |
pod.isActionBoxMenuHovered = false; |
pod.classList.remove('focused'); |
pod.setPinVisibility(false); |
+ this.setUserPodFingerprintIcon( |
+ pod.user.username, FINGERPRINT_STATES.HIDDEN); |
// On Desktop, the faded style is not set correctly, so we should |
// manually fade out non-focused pods if there is a focused pod. |
if (pod.user.isDesktopUser && podToFocus) |
@@ -3347,6 +3554,8 @@ cr.define('login', function() { |
this.firstShown_ = false; |
this.lastFocusedPod_ = podToFocus; |
this.scrollFocusedPodIntoView(); |
+ this.setUserPodFingerprintIcon( |
+ podToFocus.user.username, FINGERPRINT_STATES.DEFAULT); |
} else { |
chrome.send('noPodFocused'); |
} |