blob: fa8ba3affef3cab32767fb425245e13849619a03 [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.
(function() {
/**
* Names of the radio buttons which allow the user to choose their encryption
* mechanism.
* @enum {string}
*/
const RadioButtonNames = {
ENCRYPT_WITH_GOOGLE: 'encrypt-with-google',
ENCRYPT_WITH_PASSPHRASE: 'encrypt-with-passphrase',
};
/**
* @fileoverview
* 'settings-sync-page' is the settings page containing sync settings.
*/
Polymer({
is: 'settings-sync-page',
behaviors: [
WebUIListenerBehavior,
settings.RouteObserverBehavior,
],
properties: {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
/** @private */
pages_: {
type: Object,
value: settings.PageStatus,
readOnly: true,
},
/**
* The current page status. Defaults to |CONFIGURE| such that the searching
* algorithm can search useful content when the page is not visible to the
* user.
* @private {?settings.PageStatus}
*/
pageStatus_: {
type: String,
value: settings.PageStatus.CONFIGURE,
},
/**
* Dictionary defining page visibility.
* @type {!PrivacyPageVisibility}
*/
pageVisibility: Object,
/**
* The current sync preferences, supplied by SyncBrowserProxy.
* @type {settings.SyncPrefs|undefined}
*/
syncPrefs: {
type: Object,
},
/** @type {settings.SyncStatus} */
syncStatus: {
type: Object,
},
/**
* Whether the "create passphrase" inputs should be shown. These inputs
* give the user the opportunity to use a custom passphrase instead of
* authenticating with their Google credentials.
* @private
*/
creatingNewPassphrase_: {
type: Boolean,
value: false,
},
/**
* The passphrase input field value.
* @private
*/
passphrase_: {
type: String,
value: '',
},
/**
* The passphrase confirmation input field value.
* @private
*/
confirmation_: {
type: String,
value: '',
},
/**
* The existing passphrase input field value.
* @private
*/
existingPassphrase_: {
type: String,
value: '',
},
/** @private */
signedIn_: {
type: Boolean,
value: true,
computed: 'computeSignedIn_(syncStatus.signedIn)',
},
/** @private */
syncDisabledByAdmin_: {
type: Boolean,
value: false,
computed: 'computeSyncDisabledByAdmin_(syncStatus.managed)',
},
/** @private */
syncSectionDisabled_: {
type: Boolean,
value: false,
computed: 'computeSyncSectionDisabled_(' +
'unifiedConsentEnabled, syncStatus.signedIn, syncStatus.disabled, ' +
'syncStatus.hasError, syncStatus.statusAction)',
},
// <if expr="not chromeos">
diceEnabled: Boolean,
// </if>
unifiedConsentEnabled: Boolean,
},
/** @private {?settings.SyncBrowserProxy} */
browserProxy_: null,
/**
* The unload callback is needed because the sign-in flow needs to know
* if the user has closed the tab with the sync settings. This property is
* non-null if the user is currently navigated on the sync settings route.
*
* TODO(scottchen): We had to change from unload to beforeunload due to
* crbug.com/501292. Change back to unload once it's fixed.
*
* @private {?Function}
*/
beforeunloadCallback_: null,
/**
* Whether the initial layout for collapsible sections has been computed. It
* is computed only once, the first time the sync status is updated.
* @private {boolean}
*/
collapsibleSectionsInitialized_: false,
/**
* Whether the user decided to abort sync.
* @private {boolean}
*/
didAbort_: false,
/** @override */
created: function() {
this.browserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
},
/** @override */
attached: function() {
this.addWebUIListener(
'page-status-changed', this.handlePageStatusChanged_.bind(this));
this.addWebUIListener(
'sync-prefs-changed', this.handleSyncPrefsChanged_.bind(this));
if (settings.getCurrentRoute() == settings.routes.SYNC) {
this.onNavigateToPage_();
}
},
/**
* Can be called from subpages to notify this page (=main sync page) that sync
* setup was cancelled.
*/
cancelSyncSetup: function() {
this.didAbort_ = true;
settings.navigateTo(settings.routes.BASIC);
},
/** @override */
detached: function() {
if (settings.routes.SYNC.contains(settings.getCurrentRoute())) {
this.onNavigateAwayFromPage_();
}
if (this.beforeunloadCallback_) {
window.removeEventListener('beforeunload', this.beforeunloadCallback_);
this.beforeunloadCallback_ = null;
}
},
/**
* @return {boolean}
* @private
*/
computeSignedIn_: function() {
return !!this.syncStatus.signedIn;
},
/**
* @return {boolean}
* @private
*/
computeSyncSectionDisabled_: function() {
return !!this.unifiedConsentEnabled && this.syncStatus !== undefined &&
(!this.syncStatus.signedIn || !!this.syncStatus.disabled ||
(!!this.syncStatus.hasError &&
this.syncStatus.statusAction !==
settings.StatusAction.ENTER_PASSPHRASE));
},
/**
* @return {boolean}
* @private
*/
computeSyncDisabledByAdmin_: function() {
return this.syncStatus != undefined && !!this.syncStatus.managed;
},
/** @protected */
currentRouteChanged: function() {
if (settings.getCurrentRoute() == settings.routes.SYNC) {
this.onNavigateToPage_();
} else if (!settings.routes.SYNC.contains(settings.getCurrentRoute())) {
this.onNavigateAwayFromPage_();
}
},
/**
* @param {!settings.PageStatus} expectedPageStatus
* @return {boolean}
* @private
*/
isStatus_: function(expectedPageStatus) {
return expectedPageStatus == this.pageStatus_;
},
/** @private */
onNavigateToPage_: function() {
assert(settings.getCurrentRoute() == settings.routes.SYNC);
if (this.beforeunloadCallback_) {
return;
}
this.collapsibleSectionsInitialized_ = false;
// Display loading page until the settings have been retrieved.
this.pageStatus_ = settings.PageStatus.SPINNER;
this.browserProxy_.didNavigateToSyncPage();
this.beforeunloadCallback_ = this.onNavigateAwayFromPage_.bind(this);
window.addEventListener('beforeunload', this.beforeunloadCallback_);
},
/** @private */
onNavigateAwayFromPage_: function() {
if (!this.beforeunloadCallback_) {
return;
}
// Reset the status to CONFIGURE such that the searching algorithm can
// search useful content when the page is not visible to the user.
this.pageStatus_ = settings.PageStatus.CONFIGURE;
this.browserProxy_.didNavigateAwayFromSyncPage(this.didAbort_);
this.didAbort_ = false;
window.removeEventListener('beforeunload', this.beforeunloadCallback_);
this.beforeunloadCallback_ = null;
},
/**
* Handler for when the sync preferences are updated.
* @private
*/
handleSyncPrefsChanged_: function(syncPrefs) {
this.syncPrefs = syncPrefs;
this.pageStatus_ = settings.PageStatus.CONFIGURE;
// Hide the new passphrase box if the sync data has been encrypted.
if (this.syncPrefs.encryptAllData) {
this.creatingNewPassphrase_ = false;
}
// Focus the password input box if password is needed to start sync.
if (this.syncPrefs.passphraseRequired) {
// Wait for the dom-if templates to render and subpage to become visible.
listenOnce(document, 'show-container', () => {
const input = /** @type {!CrInputElement} */ (
this.$$('#existingPassphraseInput'));
if (!input.matches(':focus-within')) {
input.focus();
}
});
}
},
/** @private */
onActivityControlsTap_: function() {
this.browserProxy_.openActivityControlsUrl();
},
/**
* @param {string} passphrase The passphrase input field value
* @param {string} confirmation The passphrase confirmation input field value.
* @return {boolean} Whether the passphrase save button should be enabled.
* @private
*/
isSaveNewPassphraseEnabled_: function(passphrase, confirmation) {
return passphrase !== '' && confirmation !== '';
},
/**
* Sends the newly created custom sync passphrase to the browser.
* @private
* @param {!Event} e
*/
onSaveNewPassphraseTap_: function(e) {
assert(this.creatingNewPassphrase_);
// Ignore events on irrelevant elements or with irrelevant keys.
if (e.target.tagName != 'PAPER-BUTTON' && e.target.tagName != 'CR-INPUT') {
return;
}
if (e.type == 'keypress' && e.key != 'Enter') {
return;
}
// If a new password has been entered but it is invalid, do not send the
// sync state to the API.
if (!this.validateCreatedPassphrases_()) {
return;
}
this.syncPrefs.encryptAllData = true;
this.syncPrefs.setNewPassphrase = true;
this.syncPrefs.passphrase = this.passphrase_;
this.browserProxy_.setSyncEncryption(this.syncPrefs)
.then(this.handlePageStatusChanged_.bind(this));
},
/**
* Sends the user-entered existing password to re-enable sync.
* @private
* @param {!Event} e
*/
onSubmitExistingPassphraseTap_: function(e) {
if (e.type == 'keypress' && e.key != 'Enter') {
return;
}
assert(!this.creatingNewPassphrase_);
this.syncPrefs.setNewPassphrase = false;
this.syncPrefs.passphrase = this.existingPassphrase_;
this.existingPassphrase_ = '';
this.browserProxy_.setSyncEncryption(this.syncPrefs)
.then(this.handlePageStatusChanged_.bind(this));
},
/**
* Called when the page status updates.
* @param {!settings.PageStatus} pageStatus
* @private
*/
handlePageStatusChanged_: function(pageStatus) {
switch (pageStatus) {
case settings.PageStatus.SPINNER:
case settings.PageStatus.TIMEOUT:
case settings.PageStatus.CONFIGURE:
this.pageStatus_ = pageStatus;
return;
case settings.PageStatus.DONE:
if (settings.getCurrentRoute() == settings.routes.SYNC) {
settings.navigateTo(settings.routes.PEOPLE);
}
return;
case settings.PageStatus.PASSPHRASE_FAILED:
if (this.pageStatus_ == this.pages_.CONFIGURE && this.syncPrefs &&
this.syncPrefs.passphraseRequired) {
this.$$('#existingPassphraseInput').invalid = true;
}
return;
}
assertNotReached();
},
/**
* Called when the encryption
* @param {!Event} event
* @private
*/
onEncryptionRadioSelectionChanged_: function(event) {
this.creatingNewPassphrase_ =
event.detail.value == RadioButtonNames.ENCRYPT_WITH_PASSPHRASE;
},
/**
* Computed binding returning the selected encryption radio button.
* @private
*/
selectedEncryptionRadio_: function() {
return this.syncPrefs.encryptAllData || this.creatingNewPassphrase_ ?
RadioButtonNames.ENCRYPT_WITH_PASSPHRASE :
RadioButtonNames.ENCRYPT_WITH_GOOGLE;
},
/**
* Computed binding returning text of the prompt for entering the passphrase.
* @private
*/
enterPassphrasePrompt_: function() {
if (this.syncPrefs && this.syncPrefs.passphraseTypeIsCustom) {
return this.syncPrefs.enterPassphraseBody;
}
return this.syncPrefs.enterGooglePassphraseBody;
},
/**
* Checks the supplied passphrases to ensure that they are not empty and that
* they match each other. Additionally, displays error UI if they are invalid.
* @return {boolean} Whether the check was successful (i.e., that the
* passphrases were valid).
* @private
*/
validateCreatedPassphrases_: function() {
const emptyPassphrase = !this.passphrase_;
const mismatchedPassphrase = this.passphrase_ != this.confirmation_;
this.$$('#passphraseInput').invalid = emptyPassphrase;
this.$$('#passphraseConfirmationInput').invalid =
!emptyPassphrase && mismatchedPassphrase;
return !emptyPassphrase && !mismatchedPassphrase;
},
/**
* @param {!Event} event
* @private
*/
onLearnMoreTap_: function(event) {
if (event.target.tagName == 'A') {
// Stop the propagation of events, so that clicking on links inside
// checkboxes or radio buttons won't change the value.
event.stopPropagation();
}
},
/**
* When unified-consent enabled, the non-toggle items on the bottom of sync
* section should be wrapped with 'list-frame' in order to be indented
* correctly.
* @return {string}
* @private
*/
getListFrameClass_: function() {
return this.unifiedConsentEnabled ? 'list-frame' : '';
},
/**
* When unified-consent enabled, the non-toggle items on the bottom of sync
* section will be wrapped with 'list-frame', and should have the 'list-item'
* instead of 'settings-box' in order to be indented correctly.
* @return {string}
* @private
*/
getListItemClass_: function() {
return this.unifiedConsentEnabled ? 'list-item' : 'settings-box';
},
/**
* When there is a sync passphrase, some items have an additional line for the
* passphrase reset hint, making them three lines rather than two.
* @return {string}
* @private
*/
getPassphraseHintLines_: function() {
return this.syncPrefs.encryptAllData ? 'three-line' : 'two-line';
},
// <if expr="not chromeos">
/**
* @return {boolean}
* @private
*/
shouldShowSyncAccountControl_: function() {
return !!this.unifiedConsentEnabled && this.syncStatus !== undefined &&
!!this.syncStatus.syncSystemEnabled && !!this.syncStatus.signinAllowed;
},
// </if>
/**
* @return {boolean}
* @private
*/
shouldShowExistingPassphraseBelowAccount_: function() {
return !!this.unifiedConsentEnabled && this.syncPrefs !== undefined &&
!!this.syncPrefs.passphraseRequired;
},
/**
* @return {boolean}
* @private
*/
shouldShowExistingPassphraseInSyncSection_: function() {
return !this.unifiedConsentEnabled && this.syncPrefs !== undefined &&
!!this.syncPrefs.passphraseRequired;
},
/** @private */
onSyncAdvancedTap_: function() {
settings.navigateTo(settings.routes.SYNC_ADVANCED);
},
/**
* @return {boolean}
* @private
*/
shouldShowDriveSuggest_: function() {
return loadTimeData.getBoolean('driveSuggestAvailable') &&
!this.unifiedConsentEnabled;
},
});
})();