blob: 8294e976c94b7a025594cc564510448b9e867efa [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'settings-people-page' is the settings page containing sign-in settings.
*/
Polymer({
is: 'settings-people-page',
behaviors: [
settings.RouteObserverBehavior, I18nBehavior, WebUIListenerBehavior,
// <if expr="chromeos">
CrPngBehavior, LockStateBehavior,
// </if>
],
properties: {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
/** @private Filter applied to passwords and password exceptions. */
passwordFilter_: String,
// <if expr="not chromeos">
/**
* This flag is used to conditionally show a set of new sign-in UIs to the
* profiles that have been migrated to be consistent with the web sign-ins.
* TODO(scottchen): In the future when all profiles are completely migrated,
* this should be removed, and UIs hidden behind it should become default.
* @private
*/
diceEnabled_: {
type: Boolean,
value: function() {
return loadTimeData.getBoolean('diceEnabled');
},
},
// </if>
/**
* This flag is used to conditionally show a set of sync UIs to the
* profiles that have been migrated to have a unified consent flow.
* TODO(scottchen): In the future when all profiles are completely migrated,
* this should be removed, and UIs hidden behind it should become default.
* @private
*/
unifiedConsentEnabled_: {
type: Boolean,
value: function() {
return loadTimeData.getBoolean('unifiedConsentEnabled');
},
},
// TODO(jdoerrie): https://crbug.com/854562.
// Remove once Autofill Home is launched.
autofillHomeEnabled: Boolean,
/**
* The current sync status, supplied by SyncBrowserProxy.
* @type {?settings.SyncStatus}
*/
syncStatus: Object,
/**
* Dictionary defining page visibility.
* @type {!GuestModePageVisibility}
*/
pageVisibility: Object,
/**
* The currently selected profile icon URL. May be a data URL.
* @private
*/
profileIconUrl_: String,
/**
* The current profile name.
* @private
*/
profileName_: String,
/**
* The profile deletion warning. The message indicates the number of
* profile stats that will be deleted if a non-zero count for the profile
* stats is returned from the browser.
* @private
*/
deleteProfileWarning_: String,
/**
* True if the profile deletion warning is visible.
* @private
*/
deleteProfileWarningVisible_: Boolean,
/**
* True if the checkbox to delete the profile has been checked.
* @private
*/
deleteProfile_: Boolean,
// <if expr="not chromeos">
/** @private */
showImportDataDialog_: {
type: Boolean,
value: false,
},
// </if>
/** @private */
showDisconnectDialog_: Boolean,
// <if expr="chromeos">
/**
* True if fingerprint settings should be displayed on this machine.
* @private
*/
fingerprintUnlockEnabled_: {
type: Boolean,
value: function() {
return loadTimeData.getBoolean('fingerprintUnlockEnabled');
},
readOnly: true,
},
// </if>
/** @private {!Map<string, string>} */
focusConfig_: {
type: Object,
value: function() {
const map = new Map();
if (settings.routes.SYNC) {
const syncId = loadTimeData.getBoolean('unifiedConsentEnabled') ?
'#sync-setup' :
'#sync-status';
map.set(settings.routes.SYNC.path, `${syncId} .subpage-arrow button`);
}
if (settings.routes.MANAGE_PASSWORDS) {
map.set(
settings.routes.MANAGE_PASSWORDS.path, '#passwordManagerButton');
}
if (settings.routes.AUTOFILL) {
map.set(settings.routes.AUTOFILL.path, '#addressesManagerButton');
}
if (settings.routes.PAYMENTS) {
map.set(settings.routes.PAYMENTS.path, '#paymentManagerButton');
}
// <if expr="not chromeos">
if (settings.routes.MANAGE_PROFILE) {
map.set(
settings.routes.MANAGE_PROFILE.path,
'#picture-subpage-trigger .subpage-arrow button');
}
// </if>
// <if expr="chromeos">
if (settings.routes.CHANGE_PICTURE) {
map.set(
settings.routes.CHANGE_PICTURE.path,
'#picture-subpage-trigger .subpage-arrow button');
}
if (settings.routes.LOCK_SCREEN) {
map.set(
settings.routes.LOCK_SCREEN.path,
'#lock-screen-subpage-trigger .subpage-arrow button');
}
if (settings.routes.ACCOUNTS) {
map.set(
settings.routes.ACCOUNTS.path,
'#manage-other-people-subpage-trigger .subpage-arrow button');
}
// </if>
return map;
},
},
/**
* True if current user is child user.
*/
isChild_: Boolean,
},
/** @private {?settings.SyncBrowserProxy} */
syncBrowserProxy_: null,
/** @override */
created: function() {
// <if expr="chromeos">
chrome.usersPrivate.getCurrentUser(user => {
this.isChild_ = user.isChild;
});
// </if>
},
/** @override */
attached: function() {
const profileInfoProxy = settings.ProfileInfoBrowserProxyImpl.getInstance();
profileInfoProxy.getProfileInfo().then(this.handleProfileInfo_.bind(this));
this.addWebUIListener(
'profile-info-changed', this.handleProfileInfo_.bind(this));
this.addWebUIListener(
'profile-stats-count-ready', this.handleProfileStatsCount_.bind(this));
this.syncBrowserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
this.syncBrowserProxy_.getSyncStatus().then(
this.handleSyncStatus_.bind(this));
this.addWebUIListener(
'sync-status-changed', this.handleSyncStatus_.bind(this));
// <if expr="not chromeos">
this.addWebUIListener('sync-settings-saved', () => {
/** @type {!CrToastElement} */ (this.$.toast).show();
});
// </if>
},
/** @protected */
currentRouteChanged: function() {
this.showImportDataDialog_ =
settings.getCurrentRoute() == settings.routes.IMPORT_DATA;
if (settings.getCurrentRoute() == settings.routes.SIGN_OUT) {
// <if expr="not chromeos">
settings.ProfileInfoBrowserProxyImpl.getInstance().getProfileStatsCount();
// </if>
// If the sync status has not been fetched yet, optimistically display
// the disconnect dialog. There is another check when the sync status is
// fetched. The dialog will be closed then the user is not signed in.
if (this.syncStatus && !this.syncStatus.signedIn) {
settings.navigateToPreviousRoute();
} else {
this.showDisconnectDialog_ = true;
this.async(() => {
this.$$('#disconnectDialog').showModal();
});
}
} else if (this.showDisconnectDialog_) {
this.$$('#disconnectDialog').close();
}
},
// <if expr="chromeos">
/** @private */
getPasswordState_: function(hasPin, enableScreenLock) {
if (!enableScreenLock)
return this.i18n('lockScreenNone');
if (hasPin)
return this.i18n('lockScreenPinOrPassword');
return this.i18n('lockScreenPasswordOnly');
},
// </if>
/**
* Handler for when the profile's icon and name is updated.
* @private
* @param {!settings.ProfileInfo} info
*/
handleProfileInfo_: function(info) {
this.profileName_ = info.name;
/**
* Extract first frame from image by creating a single frame PNG using
* url as input if base64 encoded and potentially animated.
*/
// <if expr="chromeos">
if (info.iconUrl.startsWith('data:image/png;base64')) {
this.profileIconUrl_ =
CrPngBehavior.convertImageSequenceToPng([info.iconUrl]);
return;
}
// </if>
this.profileIconUrl_ = info.iconUrl;
},
/**
* Handler for when the profile stats count is pushed from the browser.
* @param {number} count
* @private
*/
handleProfileStatsCount_: function(count) {
const username = this.syncStatus.signedInUsername || '';
this.deleteProfileWarning_ = (count > 0) ?
(count == 1) ?
loadTimeData.getStringF(
'deleteProfileWarningWithCountsSingular', username) :
loadTimeData.getStringF(
'deleteProfileWarningWithCountsPlural', count, username) :
loadTimeData.getStringF('deleteProfileWarningWithoutCounts', username);
},
/**
* Handler for when the sync state is pushed from the browser.
* @param {?settings.SyncStatus} syncStatus
* @private
*/
handleSyncStatus_: function(syncStatus) {
// Sign-in impressions should be recorded only if the sign-in promo is
// shown. They should be recorder only once, the first time
// |this.syncStatus| is set.
const shouldRecordSigninImpression =
!this.syncStatus && syncStatus && this.showSignin_(syncStatus);
if (!syncStatus.signedIn && this.showDisconnectDialog_)
this.$$('#disconnectDialog').close();
this.syncStatus = syncStatus;
if (shouldRecordSigninImpression && !this.shouldShowSyncAccountControl_()) {
// SyncAccountControl records the impressions user actions.
chrome.metricsPrivate.recordUserAction('Signin_Impression_FromSettings');
}
},
/** @private */
onProfileTap_: function() {
// <if expr="chromeos">
settings.navigateTo(settings.routes.CHANGE_PICTURE);
// </if>
// <if expr="not chromeos">
settings.navigateTo(settings.routes.MANAGE_PROFILE);
// </if>
},
/** @private */
onSigninTap_: function() {
this.syncBrowserProxy_.startSignIn();
},
/**
* Shows the manage passwords sub page.
* @param {!Event} event
* @private
*/
onPasswordsTap_: function(event) {
settings.navigateTo(settings.routes.MANAGE_PASSWORDS);
},
/**
* Shows the manage autofill addresses sub page.
* @param {!Event} event
* @private
*/
onAutofillTap_: function(event) {
settings.navigateTo(settings.routes.AUTOFILL);
},
/**
* Shows the manage payment information sub page.
* @param {!Event} event
* @private
*/
onPaymentsTap_: function(event) {
settings.navigateTo(settings.routes.PAYMENTS);
},
/** @private */
onDisconnectClosed_: function() {
this.showDisconnectDialog_ = false;
// <if expr="not chromeos">
if (!this.diceEnabled_) {
// If DICE-enabled, this button won't exist here.
cr.ui.focusWithoutInk(assert(this.$$('#disconnectButton')));
}
// </if>
// <if expr="chromeos">
cr.ui.focusWithoutInk(assert(this.$$('#disconnectButton')));
// </if>
if (settings.getCurrentRoute() == settings.routes.SIGN_OUT)
settings.navigateToPreviousRoute();
this.fire('signout-dialog-closed');
},
/** @private */
onDisconnectTap_: function() {
settings.navigateTo(settings.routes.SIGN_OUT);
},
/** @private */
onDisconnectCancel_: function() {
this.$$('#disconnectDialog').close();
},
/** @private */
onDisconnectConfirm_: function() {
const deleteProfile = !!this.syncStatus.domain || this.deleteProfile_;
// Trigger the sign out event after the navigateToPreviousRoute().
// So that the navigation to the setting page could be finished before the
// sign out if navigateToPreviousRoute() returns synchronously even the
// browser is closed after the sign out. Otherwise, the navigation will be
// finished during session restore if the browser is closed before the async
// callback executed.
listenOnce(this, 'signout-dialog-closed', () => {
this.syncBrowserProxy_.signOut(deleteProfile);
});
this.$$('#disconnectDialog').close();
},
/** @private */
onSyncTap_: function() {
// When unified-consent is enabled, users can go to sync subpage regardless
// of sync status.
// TODO(scottchen): figure out how to deal with sync error states in the
// subpage (https://crbug.com/824546).
if (this.unifiedConsentEnabled_) {
settings.navigateTo(settings.routes.SYNC);
return;
}
assert(this.syncStatus.signedIn);
assert(this.syncStatus.syncSystemEnabled);
if (!this.isSyncStatusActionable_(this.syncStatus))
return;
switch (this.syncStatus.statusAction) {
case settings.StatusAction.REAUTHENTICATE:
this.syncBrowserProxy_.startSignIn();
break;
case settings.StatusAction.SIGNOUT_AND_SIGNIN:
// <if expr="chromeos">
this.syncBrowserProxy_.attemptUserExit();
// </if>
// <if expr="not chromeos">
if (this.syncStatus.domain)
settings.navigateTo(settings.routes.SIGN_OUT);
else {
// Silently sign the user out without deleting their profile and
// prompt them to sign back in.
this.syncBrowserProxy_.signOut(false);
this.syncBrowserProxy_.startSignIn();
}
// </if>
break;
case settings.StatusAction.UPGRADE_CLIENT:
settings.navigateTo(settings.routes.ABOUT);
break;
case settings.StatusAction.ENTER_PASSPHRASE:
case settings.StatusAction.CONFIRM_SYNC_SETTINGS:
case settings.StatusAction.NO_ACTION:
default:
settings.navigateTo(settings.routes.SYNC);
}
},
// <if expr="chromeos">
/**
* @param {!Event} e
* @private
*/
onConfigureLockTap_: function(e) {
// Navigating to the lock screen will always open the password prompt
// dialog, so prevent the end of the tap event to focus what is underneath
// it, which takes focus from the dialog.
e.preventDefault();
settings.navigateTo(settings.routes.LOCK_SCREEN);
},
// </if>
/** @private */
onManageOtherPeople_: function() {
// <if expr="not chromeos">
this.syncBrowserProxy_.manageOtherPeople();
// </if>
// <if expr="chromeos">
settings.navigateTo(settings.routes.ACCOUNTS);
// </if>
},
// <if expr="not chromeos">
/**
* @private
* @param {string} domain
* @return {string}
*/
getDomainHtml_: function(domain) {
const innerSpan = '<span id="managed-by-domain-name">' + domain + '</span>';
return loadTimeData.getStringF('domainManagedProfile', innerSpan);
},
/** @private */
onImportDataTap_: function() {
settings.navigateTo(settings.routes.IMPORT_DATA);
},
/** @private */
onImportDataDialogClosed_: function() {
settings.navigateToPreviousRoute();
cr.ui.focusWithoutInk(assert(this.$.importDataDialogTrigger));
},
/**
* @return {boolean}
* @private
*/
shouldShowSyncAccountControl_: function() {
if (this.syncStatus == undefined)
return false;
return this.diceEnabled_ && !!this.syncStatus.syncSystemEnabled &&
!!this.syncStatus.signinAllowed;
},
// </if>
/**
* @private
* @param {string} domain
* @return {string}
*/
getDisconnectExplanationHtml_: function(domain) {
// <if expr="not chromeos">
if (domain) {
return loadTimeData.getStringF(
'syncDisconnectManagedProfileExplanation',
'<span id="managed-by-domain-name">' + domain + '</span>');
}
// </if>
return loadTimeData.getString('syncDisconnectExplanation');
},
/**
* @private
* @param {?settings.SyncStatus} syncStatus
* @return {boolean}
*/
isAdvancedSyncSettingsVisible_: function(syncStatus) {
return !!syncStatus && !!syncStatus.signedIn &&
!!syncStatus.syncSystemEnabled && !this.unifiedConsentEnabled_;
},
/**
* @private
* @param {?settings.SyncStatus} syncStatus
* @return {boolean} Whether an action can be taken with the sync status. sync
* status is actionable if sync is not managed and if there is a sync
* error, there is an action associated with it.
*/
isSyncStatusActionable_: function(syncStatus) {
return !!syncStatus && !syncStatus.managed &&
(!syncStatus.hasError ||
syncStatus.statusAction != settings.StatusAction.NO_ACTION);
},
/**
* @private
* @param {?settings.SyncStatus} syncStatus
* @return {string}
*/
getSyncIcon_: function(syncStatus) {
if (!syncStatus)
return '';
let syncIcon = 'cr:sync';
if (syncStatus.hasError)
syncIcon = 'settings:sync-problem';
// Override the icon to the disabled icon if sync is managed.
if (syncStatus.managed ||
syncStatus.statusAction == settings.StatusAction.REAUTHENTICATE)
syncIcon = 'settings:sync-disabled';
return syncIcon;
},
/**
* @private
* @param {?settings.SyncStatus} syncStatus
* @return {string} The class name for the sync status row.
*/
getSyncStatusClass_: function(syncStatus) {
if (syncStatus && syncStatus.hasError) {
// Most of the time re-authenticate states are caused by intentional user
// action, so they will be displayed differently as other errors.
return syncStatus.statusAction == settings.StatusAction.REAUTHENTICATE ?
'auth-error' :
'sync-error';
}
return '';
},
/**
* @param {string} iconUrl
* @return {string} A CSS image-set for multiple scale factors.
* @private
*/
getIconImageSet_: function(iconUrl) {
return cr.icon.getImage(iconUrl);
},
/**
* @param {!settings.SyncStatus} syncStatus
* @return {boolean} Whether to show the "Sign in to Chrome" button.
* @private
*/
showSignin_: function(syncStatus) {
return !!syncStatus.signinAllowed && !syncStatus.signedIn;
},
/**
* Looks up the translation id, which depends on PIN login support.
* @param {boolean} hasPinLogin
* @private
*/
selectLockScreenTitleString(hasPinLogin) {
if (hasPinLogin)
return this.i18n('lockScreenTitleLoginLock');
return this.i18n('lockScreenTitleLock');
},
});