Commit ab321585 authored by Demetrios Papadopoulos's avatar Demetrios Papadopoulos Committed by Commit Bot

Settings: Remove patterns that break semi-automatic Polymer3 migration.

This is in preparation of running auto conversion tools for c/b/r/settings.

Bug: 1026426
Change-Id: I1a1fdbd1f97910c2611f54f8f0ccb87192b4ad52
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2001379
Auto-Submit: Demetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: default avatarRebekah Potter <rbpotter@chromium.org>
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Commit-Queue: Demetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#731661}
parent cdd1d6e3
...@@ -6,200 +6,199 @@ ...@@ -6,200 +6,199 @@
* @fileoverview 'password-edit-dialog' is the dialog that allows showing a * @fileoverview 'password-edit-dialog' is the dialog that allows showing a
* saved password. * saved password.
*/ */
(function() { cr.define('settings.address', function() {
'use strict'; Polymer({
is: 'settings-address-edit-dialog',
Polymer({ behaviors: [
is: 'settings-address-edit-dialog', I18nBehavior,
],
behaviors: [ properties: {
I18nBehavior, /** @type {chrome.autofillPrivate.AddressEntry} */
], address: Object,
properties: { /** @private */
/** @type {chrome.autofillPrivate.AddressEntry} */ title_: String,
address: Object,
/** @private */ /** @private {!Array<!chrome.autofillPrivate.CountryEntry>} */
title_: String, countries_: Array,
/** @private {!Array<!chrome.autofillPrivate.CountryEntry>} */ /**
countries_: Array, * Updates the address wrapper.
* @private {string|undefined}
*/
countryCode_: {
type: String,
observer: 'onUpdateCountryCode_',
},
/** /** @private {!Array<!Array<!settings.address.AddressComponentUI>>} */
* Updates the address wrapper. addressWrapper_: Object,
* @private {string|undefined}
*/
countryCode_: {
type: String,
observer: 'onUpdateCountryCode_',
},
/** @private {!Array<!Array<!settings.address.AddressComponentUI>>} */ /** @private */
addressWrapper_: Object, phoneNumber_: String,
/** @private */ /** @private */
phoneNumber_: String, email_: String,
/** @private */ /** @private */
email_: String, canSave_: Boolean,
},
/** @private */ /** @override */
canSave_: Boolean, attached() {
}, this.countryInfo =
settings.address.CountryDetailManagerImpl.getInstance();
/** @override */ this.countryInfo.getCountryList().then(countryList => {
attached() { this.countries_ = countryList;
this.countryInfo = settings.address.CountryDetailManagerImpl.getInstance();
this.countryInfo.getCountryList().then(countryList => { this.title_ = this.i18n(
this.countries_ = countryList; this.address.guid ? 'editAddressTitle' : 'addAddressTitle');
this.title_ = // |phoneNumbers| and |emailAddresses| are a single item array.
this.i18n(this.address.guid ? 'editAddressTitle' : 'addAddressTitle'); // See crbug.com/497934 for details.
this.phoneNumber_ =
// |phoneNumbers| and |emailAddresses| are a single item array. this.address.phoneNumbers ? this.address.phoneNumbers[0] : '';
// See crbug.com/497934 for details. this.email_ =
this.phoneNumber_ = this.address.emailAddresses ? this.address.emailAddresses[0] : '';
this.address.phoneNumbers ? this.address.phoneNumbers[0] : '';
this.email_ = this.async(() => {
this.address.emailAddresses ? this.address.emailAddresses[0] : ''; if (this.countryCode_ == this.address.countryCode) {
this.updateAddressWrapper_();
this.async(() => { } else {
if (this.countryCode_ == this.address.countryCode) { this.countryCode_ = this.address.countryCode;
this.updateAddressWrapper_(); }
} else { });
this.countryCode_ = this.address.countryCode;
}
}); });
});
// Open is called on the dialog after the address wrapper has been updated. // Open is called on the dialog after the address wrapper has been
}, // updated.
},
/** /**
* Returns a class to denote how long this entry is. * Returns a class to denote how long this entry is.
* @param {settings.address.AddressComponentUI} setting * @param {settings.address.AddressComponentUI} setting
* @return {string} * @return {string}
*/ */
long_(setting) { long_(setting) {
return setting.component.isLongField ? 'long' : ''; return setting.component.isLongField ? 'long' : '';
}, },
/** /**
* Updates the wrapper that represents this address in the country's format. * Updates the wrapper that represents this address in the country's format.
* @private * @private
*/ */
updateAddressWrapper_() { updateAddressWrapper_() {
// Default to the last country used if no country code is provided. // Default to the last country used if no country code is provided.
const countryCode = this.countryCode_ || this.countries_[0].countryCode; const countryCode = this.countryCode_ || this.countries_[0].countryCode;
this.countryInfo.getAddressFormat(countryCode).then(format => { this.countryInfo.getAddressFormat(countryCode).then(format => {
this.addressWrapper_ = format.components.map( this.addressWrapper_ = format.components.map(
component => component.row.map( component => component.row.map(
c => new settings.address.AddressComponentUI(this.address, c))); c => new settings.address.AddressComponentUI(this.address, c)));
// Flush dom before resize and savability updates. // Flush dom before resize and savability updates.
Polymer.dom.flush(); Polymer.dom.flush();
this.updateCanSave_(); this.updateCanSave_();
this.fire('on-update-address-wrapper'); // For easier testing. this.fire('on-update-address-wrapper'); // For easier testing.
const dialog = /** @type {HTMLDialogElement} */ (this.$.dialog); const dialog = /** @type {HTMLDialogElement} */ (this.$.dialog);
if (!dialog.open) { if (!dialog.open) {
dialog.showModal(); dialog.showModal();
} }
}); });
}, },
updateCanSave_() { updateCanSave_() {
const inputs = this.$.dialog.querySelectorAll('.address-column, select'); const inputs = this.$.dialog.querySelectorAll('.address-column, select');
for (let i = 0; i < inputs.length; ++i) { for (let i = 0; i < inputs.length; ++i) {
if (inputs[i].value) { if (inputs[i].value) {
this.canSave_ = true; this.canSave_ = true;
this.fire('on-update-can-save'); // For easier testing. this.fire('on-update-can-save'); // For easier testing.
return; return;
}
} }
}
this.canSave_ = false; this.canSave_ = false;
this.fire('on-update-can-save'); // For easier testing. this.fire('on-update-can-save'); // For easier testing.
}, },
/** /**
* @param {!chrome.autofillPrivate.CountryEntry} country * @param {!chrome.autofillPrivate.CountryEntry} country
* @return {string} * @return {string}
* @private * @private
*/ */
getCode_(country) { getCode_(country) {
return country.countryCode || 'SPACER'; return country.countryCode || 'SPACER';
}, },
/** /**
* @param {!chrome.autofillPrivate.CountryEntry} country * @param {!chrome.autofillPrivate.CountryEntry} country
* @return {string} * @return {string}
* @private * @private
*/ */
getName_(country) { getName_(country) {
return country.name || '------'; return country.name || '------';
}, },
/** /**
* @param {!chrome.autofillPrivate.CountryEntry} country * @param {!chrome.autofillPrivate.CountryEntry} country
* @return {boolean} * @return {boolean}
* @private * @private
*/ */
isDivision_(country) { isDivision_(country) {
return !country.countryCode; return !country.countryCode;
}, },
/** @private */ /** @private */
onCancelTap_() { onCancelTap_() {
this.$.dialog.cancel(); this.$.dialog.cancel();
}, },
/** /**
* Handler for tapping the save button. * Handler for tapping the save button.
* @private * @private
*/ */
onSaveButtonTap_() { onSaveButtonTap_() {
// The Enter key can call this function even if the button is disabled. // The Enter key can call this function even if the button is disabled.
if (!this.canSave_) { if (!this.canSave_) {
return; return;
} }
// Set a default country if none is set. // Set a default country if none is set.
if (!this.address.countryCode) { if (!this.address.countryCode) {
this.address.countryCode = this.countries_[0].countryCode; this.address.countryCode = this.countries_[0].countryCode;
} }
this.address.phoneNumbers = this.phoneNumber_ ? [this.phoneNumber_] : []; this.address.phoneNumbers = this.phoneNumber_ ? [this.phoneNumber_] : [];
this.address.emailAddresses = this.email_ ? [this.email_] : []; this.address.emailAddresses = this.email_ ? [this.email_] : [];
this.fire('save-address', this.address); this.fire('save-address', this.address);
this.$.dialog.close(); this.$.dialog.close();
}, },
/** /**
* Syncs the country code back to the address and rebuilds the address wrapper * Syncs the country code back to the address and rebuilds the address
* for the new location. * wrapper for the new location.
* @param {string|undefined} countryCode * @param {string|undefined} countryCode
* @private * @private
*/ */
onUpdateCountryCode_(countryCode) { onUpdateCountryCode_(countryCode) {
this.address.countryCode = countryCode; this.address.countryCode = countryCode;
this.updateAddressWrapper_(); this.updateAddressWrapper_();
}, },
/** @private */ /** @private */
onCountryChange_() { onCountryChange_() {
const countrySelect = /** @type {!HTMLSelectElement} */ (this.$$('select')); const countrySelect =
this.countryCode_ = countrySelect.value; /** @type {!HTMLSelectElement} */ (this.$$('select'));
}, this.countryCode_ = countrySelect.value;
}); },
})(); });
cr.define('settings.address', function() {
/** /**
* Creates a wrapper against a single data member for an address. * Creates a wrapper against a single data member for an address.
*/ */
......
...@@ -20,136 +20,136 @@ cr.define('settings', function() { ...@@ -20,136 +20,136 @@ cr.define('settings', function() {
*/ */
const CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW = 4; const CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW = 4;
return {ChromeCleanupRemovalListItem, CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW}; /**
}); * @fileoverview
* 'items-to-remove-list' represents a list of items to
/** * be removed or changed to be shown on the Chrome Cleanup page.
* @fileoverview * TODO(crbug.com/776538): Update the strings to say that some items are only
* 'items-to-remove-list' represents a list of items to * changed and not removed.
* be removed or changed to be shown on the Chrome Cleanup page. *
* TODO(crbug.com/776538): Update the strings to say that some items are only * Example:
* changed and not removed. *
* * <!-- Items list initially shows |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|
* Example: * items. If there are more than
* * |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items on the list, then a "show
* <!-- Items list initially shows |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| * more" link is shown; tapping on it expands the list. -->
* items. If there are more than |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| * <items-to-remove-list
* items on the list, then a "show more" link is shown; tapping on it * title="Files and programs:"
* expands the list. --> * items-to-show="[[filesToShow]]">
* <items-to-remove-list * </items-to-remove-list>
* title="Files and programs:" */
* items-to-show="[[filesToShow]]"> Polymer({
* </items-to-remove-list> is: 'items-to-remove-list',
*/
Polymer({ properties: {
is: 'items-to-remove-list', title: {
type: String,
properties: { value: '',
title: { },
type: String,
value: '', /** @type {!Array<settings.ChromeCleanupRemovalListItem>} */
itemsToShow: {
type: Array,
observer: 'updateVisibleState_',
},
/**
* If true, all items from |itemsToShow| will be presented on the card,
* and the "show more" link will be omitted.
*/
expanded_: {
type: Boolean,
value: false,
},
/**
* The first |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items of |itemsToShow|
* if the list is longer than |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|.
* @private {?Array<settings.ChromeCleanupRemovalListItem>}
*/
initialItems_: Array,
/**
* The remaining items to be presented that are not included in
* |initialItems_|. Items in this list are only shown to the user if
* |expanded_| is true.
* @private {?Array<settings.ChromeCleanupRemovalListItem>}
*/
remainingItems_: Array,
/**
* The text for the "show more" link available if not all files are
* visible in the card.
* @private
*/
moreItemsLinkText_: {
type: String,
value: '',
},
}, },
/** @type {!Array<settings.ChromeCleanupRemovalListItem>} */ /** @private */
itemsToShow: { expandList_() {
type: Array, this.expanded_ = true;
observer: 'updateVisibleState_', this.moreItemsLinkText_ = '';
}, },
/** /**
* If true, all items from |itemsToShow| will be presented on the card, * Decides which elements will be visible in the card and if the "show more"
* and the "show more" link will be omitted. * link will be rendered.
*
* 1. If size(itemsToShow) < CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW, then all
* items will be visible.
* 2. Otherwise, exactly |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1| will be
* visible and the "show more" link will be rendered. The list presented
* to the user will contain exactly |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|
* elements, and the last one will be the "show more" link.
*
* @param {!Array<settings.ChromeCleanupRemovalListItem>} itemsToShow
*/ */
expanded_: { updateVisibleState_(itemsToShow) {
type: Boolean, // Start expanded if there are less than
value: false, // |settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items to show.
this.expanded_ =
itemsToShow.length <= settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW;
if (this.expanded_) {
this.initialItems_ = itemsToShow;
this.remainingItems_ = [];
this.moreItemsLinkText_ = '';
return;
}
this.initialItems_ = itemsToShow.slice(
0, settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1);
this.remainingItems_ =
itemsToShow.slice(settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1);
const browserProxy = settings.ChromeCleanupProxyImpl.getInstance();
browserProxy.getMoreItemsPluralString(this.remainingItems_.length)
.then(linkText => {
this.moreItemsLinkText_ = linkText;
});
}, },
/** /**
* The first |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items of |itemsToShow| * Returns the class for the <li> elements that correspond to the items
* if the list is longer than |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|. * hidden in the default view.
* @private {?Array<settings.ChromeCleanupRemovalListItem>} * @param {boolean} expanded
*/
initialItems_: Array,
/**
* The remaining items to be presented that are not included in
* |initialItems_|. Items in this list are only shown to the user if
* |expanded_| is true.
* @private {?Array<settings.ChromeCleanupRemovalListItem>}
*/ */
remainingItems_: Array, remainingItemsClass_(expanded) {
return expanded ? 'visible-item' : 'hidden-item';
},
/** /**
* The text for the "show more" link available if not all files are visible * @param {settings.ChromeCleanupRemovalListItem} item
* in the card. * @return {boolean} Whether a highlight suffix exists.
* @private * @private
*/ */
moreItemsLinkText_: { hasHighlightSuffix_(item) {
type: String, return item.highlightSuffix !== null;
value: '',
}, },
}, });
/** @private */
expandList_() {
this.expanded_ = true;
this.moreItemsLinkText_ = '';
},
/** return {ChromeCleanupRemovalListItem, CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW};
* Decides which elements will be visible in the card and if the "show more"
* link will be rendered.
*
* 1. If size(itemsToShow) < CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW, then all
* items will be visible.
* 2. Otherwise, exactly |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1| will be
* visible and the "show more" link will be rendered. The list presented to
* the user will contain exactly |CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW|
* elements, and the last one will be the "show more" link.
*
* @param {!Array<settings.ChromeCleanupRemovalListItem>} itemsToShow
*/
updateVisibleState_(itemsToShow) {
// Start expanded if there are less than
// |settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW| items to show.
this.expanded_ =
itemsToShow.length <= settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW;
if (this.expanded_) {
this.initialItems_ = itemsToShow;
this.remainingItems_ = [];
this.moreItemsLinkText_ = '';
return;
}
this.initialItems_ =
itemsToShow.slice(0, settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1);
this.remainingItems_ =
itemsToShow.slice(settings.CHROME_CLEANUP_DEFAULT_ITEMS_TO_SHOW - 1);
const browserProxy = settings.ChromeCleanupProxyImpl.getInstance();
browserProxy.getMoreItemsPluralString(this.remainingItems_.length)
.then(linkText => {
this.moreItemsLinkText_ = linkText;
});
},
/**
* Returns the class for the <li> elements that correspond to the items hidden
* in the default view.
* @param {boolean} expanded
*/
remainingItemsClass_(expanded) {
return expanded ? 'visible-item' : 'hidden-item';
},
/**
* @param {settings.ChromeCleanupRemovalListItem} item
* @return {boolean} Whether a highlight suffix exists.
* @private
*/
hasHighlightSuffix_(item) {
return item.highlightSuffix !== null;
},
}); });
...@@ -35,6 +35,5 @@ ...@@ -35,6 +35,5 @@
<cr-button on-click="onDisableTap_">$i18n{disable}</cr-button> <cr-button on-click="onDisableTap_">$i18n{disable}</cr-button>
</template> </template>
</template> </template>
<script src="extension_controlled_indicator.js"></script>
</dom-module> </dom-module>
<script src="extension_controlled_indicator.js"></script>
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
/**
* @fileoverview
* 'settings-privacy-page' is the settings page containing privacy and
* security settings.
*/
cr.define('settings', function() { cr.define('settings', function() {
/** /**
* @typedef {{ * @typedef {{
...@@ -11,485 +17,481 @@ cr.define('settings', function() { ...@@ -11,485 +17,481 @@ cr.define('settings', function() {
*/ */
let BlockAutoplayStatus; let BlockAutoplayStatus;
return {BlockAutoplayStatus}; /**
}); * Must be kept in sync with the C++ enum of the same name.
* @enum {number}
*/
const NetworkPredictionOptions = {
ALWAYS: 0,
WIFI_ONLY: 1,
NEVER: 2,
DEFAULT: 1,
};
Polymer({
is: 'settings-privacy-page',
behaviors: [
PrefsBehavior,
settings.RouteObserverBehavior,
I18nBehavior,
WebUIListenerBehavior,
],
properties: {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
/** /**
* @fileoverview * The current sync status, supplied by SyncBrowserProxy.
* 'settings-privacy-page' is the settings page containing privacy and * @type {?settings.SyncStatus}
* security settings. */
*/ syncStatus: Object,
(function() {
/**
* Dictionary defining page visibility.
* @type {!PrivacyPageVisibility}
*/
pageVisibility: Object,
/** @private */
passwordsLeakDetectionEnabled_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('passwordsLeakDetectionEnabled');
},
},
/** /** @private {chrome.settingsPrivate.PrefObject} */
* Must be kept in sync with the C++ enum of the same name. safeBrowsingReportingPref_: {
* @enum {number} type: Object,
*/ value() {
const NetworkPredictionOptions = { return /** @type {chrome.settingsPrivate.PrefObject} */ ({
ALWAYS: 0, key: '',
WIFI_ONLY: 1, type: chrome.settingsPrivate.PrefType.BOOLEAN,
NEVER: 2, value: false,
DEFAULT: 1, });
}; },
},
Polymer({
is: 'settings-privacy-page',
behaviors: [
PrefsBehavior,
settings.RouteObserverBehavior,
I18nBehavior,
WebUIListenerBehavior,
],
properties: {
/**
* Preferences state.
*/
prefs: {
type: Object,
notify: true,
},
/** /** @private */
* The current sync status, supplied by SyncBrowserProxy. isGuest_: {
* @type {?settings.SyncStatus} type: Boolean,
*/ value() {
syncStatus: Object, return loadTimeData.getBoolean('isGuest');
}
},
/** /** @private */
* Dictionary defining page visibility. showClearBrowsingDataDialog_: Boolean,
* @type {!PrivacyPageVisibility}
*/
pageVisibility: Object,
/** @private */ /** @private */
passwordsLeakDetectionEnabled_: { showDoNotTrackDialog_: {
type: Boolean, type: Boolean,
value() { value: false,
return loadTimeData.getBoolean('passwordsLeakDetectionEnabled');
}, },
},
/** @private {chrome.settingsPrivate.PrefObject} */ /**
safeBrowsingReportingPref_: { * Used for HTML bindings. This is defined as a property rather than
type: Object, * within the ready callback, because the value needs to be available
value() { * before local DOM initialization - otherwise, the toggle has unexpected
return /** @type {chrome.settingsPrivate.PrefObject} */ ({ * behavior.
key: '', * @private
type: chrome.settingsPrivate.PrefType.BOOLEAN, */
value: false, networkPredictionUncheckedValue_: {
}); type: Number,
value: NetworkPredictionOptions.NEVER,
}, },
},
/** @private */ /** @private */
isGuest_: { enableSafeBrowsingSubresourceFilter_: {
type: Boolean, type: Boolean,
value() { value() {
return loadTimeData.getBoolean('isGuest'); return loadTimeData.getBoolean('enableSafeBrowsingSubresourceFilter');
} }
},
/** @private */
privacySettingsRedesignEnabled_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('privacySettingsRedesignEnabled');
},
},
/**
* Whether the more settings list is opened.
* @private
*/
moreOpened_: {
type: Boolean,
value: false,
},
/** @private */
enableBlockAutoplayContentSetting_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('enableBlockAutoplayContentSetting');
}
},
/** @private {settings.BlockAutoplayStatus} */
blockAutoplayStatus_: {
type: Object,
value() {
return /** @type {settings.BlockAutoplayStatus} */ ({});
}
},
/** @private */
enablePaymentHandlerContentSetting_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('enablePaymentHandlerContentSetting');
}
},
/** @private */
enableExperimentalWebPlatformFeatures_: {
type: Boolean,
value() {
return loadTimeData.getBoolean(
'enableExperimentalWebPlatformFeatures');
},
},
/** @private */
enableSecurityKeysSubpage_: {
type: Boolean,
readOnly: true,
value() {
return loadTimeData.getBoolean('enableSecurityKeysSubpage');
}
},
/** @private */
enableInsecureContentContentSetting_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('enableInsecureContentContentSetting');
}
},
/** @private */
enableNativeFileSystemWriteContentSetting_: {
type: Boolean,
value() {
return loadTimeData.getBoolean(
'enableNativeFileSystemWriteContentSetting');
}
},
/** @private */
enableQuietNotificationPromptsSetting_: {
type: Boolean,
value: () =>
loadTimeData.getBoolean('enableQuietNotificationPromptsSetting'),
},
/** @private {!Map<string, string>} */
focusConfig_: {
type: Object,
value() {
const map = new Map();
// <if expr="use_nss_certs">
if (settings.routes.CERTIFICATES) {
map.set(settings.routes.CERTIFICATES.path, '#manageCertificates');
}
// </if>
if (settings.routes.SITE_SETTINGS) {
map.set(
settings.routes.SITE_SETTINGS.path,
'#site-settings-subpage-trigger');
}
if (settings.routes.SITE_SETTINGS_SITE_DATA) {
map.set(
settings.routes.SITE_SETTINGS_SITE_DATA.path,
'#site-data-trigger');
}
if (settings.routes.SECURITY_KEYS) {
map.set(
settings.routes.SECURITY_KEYS.path,
'#security-keys-subpage-trigger');
}
return map;
},
},
/** @private */
searchFilter_: String,
/** @private */
siteDataFilter_: String,
}, },
/** @private */ observers: [
showClearBrowsingDataDialog_: Boolean, 'onSafeBrowsingReportingPrefChange_(prefs.safebrowsing.*)',
],
/** @private */ /** @override */
showDoNotTrackDialog_: { ready() {
type: Boolean, this.ContentSettingsTypes = settings.ContentSettingsTypes;
value: false, this.ChooserType = settings.ChooserType;
this.browserProxy_ = settings.PrivacyPageBrowserProxyImpl.getInstance();
this.onBlockAutoplayStatusChanged_({
pref: /** @type {chrome.settingsPrivate.PrefObject} */ ({value: false}),
enabled: false
});
this.addWebUIListener(
'onBlockAutoplayStatusChanged',
this.onBlockAutoplayStatusChanged_.bind(this));
settings.SyncBrowserProxyImpl.getInstance().getSyncStatus().then(
this.handleSyncStatus_.bind(this));
this.addWebUIListener(
'sync-status-changed', this.handleSyncStatus_.bind(this));
}, },
/** /**
* Used for HTML bindings. This is defined as a property rather than within * @return {boolean}
* the ready callback, because the value needs to be available before
* local DOM initialization - otherwise, the toggle has unexpected behavior.
* @private * @private
*/ */
networkPredictionUncheckedValue_: { getDisabledExtendedSafeBrowsing_() {
type: Number, return !this.getPref('safebrowsing.enabled').value;
value: NetworkPredictionOptions.NEVER,
}, },
/** @private */ /** @private */
enableSafeBrowsingSubresourceFilter_: { onSafeBrowsingReportingToggleChange_() {
type: Boolean, this.setPrefValue(
value() { 'safebrowsing.scout_reporting_enabled',
return loadTimeData.getBoolean('enableSafeBrowsingSubresourceFilter'); this.$$('#safeBrowsingReportingToggle').checked);
}
}, },
/** @private */ /** @private */
privacySettingsRedesignEnabled_: { onSafeBrowsingReportingPrefChange_() {
type: Boolean, if (this.prefs == undefined) {
value() { return;
return loadTimeData.getBoolean('privacySettingsRedesignEnabled'); }
}, const safeBrowsingScoutPref =
this.getPref('safebrowsing.scout_reporting_enabled');
const prefValue = !!this.getPref('safebrowsing.enabled').value &&
!!safeBrowsingScoutPref.value;
this.safeBrowsingReportingPref_ = {
key: '',
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: prefValue,
enforcement: safeBrowsingScoutPref.enforcement,
controlledBy: safeBrowsingScoutPref.controlledBy,
};
}, },
/** /**
* Whether the more settings list is opened. * Handler for when the sync state is pushed from the browser.
* @param {?settings.SyncStatus} syncStatus
* @private * @private
*/ */
moreOpened_: { handleSyncStatus_(syncStatus) {
type: Boolean, this.syncStatus = syncStatus;
value: false,
}, },
/** @private */ /** @protected */
enableBlockAutoplayContentSetting_: { currentRouteChanged() {
type: Boolean, this.showClearBrowsingDataDialog_ =
value() { settings.getCurrentRoute() == settings.routes.CLEAR_BROWSER_DATA;
return loadTimeData.getBoolean('enableBlockAutoplayContentSetting');
}
}, },
/** @private {settings.BlockAutoplayStatus} */ /**
blockAutoplayStatus_: { * @param {!Event} event
type: Object, * @private
value() { */
return /** @type {settings.BlockAutoplayStatus} */ ({}); onDoNotTrackDomChange_(event) {
if (this.showDoNotTrackDialog_) {
this.maybeShowDoNotTrackDialog_();
} }
}, },
/** @private */ /**
enablePaymentHandlerContentSetting_: { * Called when the block autoplay status changes.
type: Boolean, * @param {settings.BlockAutoplayStatus} autoplayStatus
value() { * @private
return loadTimeData.getBoolean('enablePaymentHandlerContentSetting'); */
} onBlockAutoplayStatusChanged_(autoplayStatus) {
this.blockAutoplayStatus_ = autoplayStatus;
}, },
/** @private */ /**
enableExperimentalWebPlatformFeatures_: { * Updates the block autoplay pref when the toggle is changed.
type: Boolean, * @param {!Event} event
value() { * @private
return loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures'); */
}, onBlockAutoplayToggleChange_(event) {
const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
this.browserProxy_.setBlockAutoplayEnabled(target.checked);
}, },
/** @private */ /**
enableSecurityKeysSubpage_: { * Records changes made to the "can a website check if you have saved
type: Boolean, * payment methods" setting for logging, the logic of actually changing the
readOnly: true, * setting is taken care of by the webUI pref.
value() { * @private
return loadTimeData.getBoolean('enableSecurityKeysSubpage'); */
} onCanMakePaymentChange_() {
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_PAYMENT_METHOD);
}, },
/** @private */ /**
enableInsecureContentContentSetting_: { * Handles the change event for the do-not-track toggle. Shows a
type: Boolean, * confirmation dialog when enabling the setting.
value() { * @param {!Event} event
return loadTimeData.getBoolean('enableInsecureContentContentSetting'); * @private
*/
onDoNotTrackChange_(event) {
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_DO_NOT_TRACK);
const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
if (!target.checked) {
// Always allow disabling the pref.
target.sendPrefChange();
return;
} }
this.showDoNotTrackDialog_ = true;
// If the dialog has already been stamped, show it. Otherwise it will be
// shown in onDomChange_.
this.maybeShowDoNotTrackDialog_();
}, },
/** @private */ /** @private */
enableNativeFileSystemWriteContentSetting_: { maybeShowDoNotTrackDialog_() {
type: Boolean, const dialog = this.$$('#confirmDoNotTrackDialog');
value() { if (dialog && !dialog.open) {
return loadTimeData.getBoolean( dialog.showModal();
'enableNativeFileSystemWriteContentSetting');
} }
}, },
/** @private */ /** @private */
enableQuietNotificationPromptsSetting_: { closeDoNotTrackDialog_() {
type: Boolean, this.$$('#confirmDoNotTrackDialog').close();
value: () => this.showDoNotTrackDialog_ = false;
loadTimeData.getBoolean('enableQuietNotificationPromptsSetting'),
}, },
/** @private {!Map<string, string>} */ /** @private */
focusConfig_: { onDoNotTrackDialogClosed_() {
type: Object, cr.ui.focusWithoutInk(this.$.doNotTrack);
value() { },
const map = new Map();
// <if expr="use_nss_certs">
if (settings.routes.CERTIFICATES) {
map.set(settings.routes.CERTIFICATES.path, '#manageCertificates');
}
// </if>
if (settings.routes.SITE_SETTINGS) {
map.set(
settings.routes.SITE_SETTINGS.path,
'#site-settings-subpage-trigger');
}
if (settings.routes.SITE_SETTINGS_SITE_DATA) {
map.set(
settings.routes.SITE_SETTINGS_SITE_DATA.path,
'#site-data-trigger');
}
if (settings.routes.SECURITY_KEYS) { /**
map.set( * Handles the shared proxy confirmation dialog 'Confirm' button.
settings.routes.SECURITY_KEYS.path, * @private
'#security-keys-subpage-trigger'); */
} onDoNotTrackDialogConfirm_() {
return map; /** @type {!SettingsToggleButtonElement} */ (this.$.doNotTrack)
}, .sendPrefChange();
this.closeDoNotTrackDialog_();
}, },
/** @private */ /**
searchFilter_: String, * Handles the shared proxy confirmation dialog 'Cancel' button or a cancel
* event.
* @private
*/
onDoNotTrackDialogCancel_() {
/** @type {!SettingsToggleButtonElement} */ (this.$.doNotTrack)
.resetToPrefValue();
this.closeDoNotTrackDialog_();
},
/** @private */ /** @private */
siteDataFilter_: String, onManageCertificatesTap_() {
}, // <if expr="use_nss_certs">
settings.navigateTo(settings.routes.CERTIFICATES);
observers: [ // </if>
'onSafeBrowsingReportingPrefChange_(prefs.safebrowsing.*)', // <if expr="is_win or is_macosx">
], this.browserProxy_.showManageSSLCertificates();
// </if>
/** @override */ this.browserProxy_.recordSettingsPageHistogram(
ready() { settings.SettingsPageInteractions.PRIVACY_MANAGE_CERTIFICATES);
this.ContentSettingsTypes = settings.ContentSettingsTypes; },
this.ChooserType = settings.ChooserType;
this.browserProxy_ = settings.PrivacyPageBrowserProxyImpl.getInstance();
this.onBlockAutoplayStatusChanged_({
pref: /** @type {chrome.settingsPrivate.PrefObject} */ ({value: false}),
enabled: false
});
this.addWebUIListener(
'onBlockAutoplayStatusChanged',
this.onBlockAutoplayStatusChanged_.bind(this));
settings.SyncBrowserProxyImpl.getInstance().getSyncStatus().then(
this.handleSyncStatus_.bind(this));
this.addWebUIListener(
'sync-status-changed', this.handleSyncStatus_.bind(this));
},
/**
* @return {boolean}
* @private
*/
getDisabledExtendedSafeBrowsing_() {
return !this.getPref('safebrowsing.enabled').value;
},
/** @private */
onSafeBrowsingReportingToggleChange_() {
this.setPrefValue(
'safebrowsing.scout_reporting_enabled',
this.$$('#safeBrowsingReportingToggle').checked);
},
/** @private */
onSafeBrowsingReportingPrefChange_() {
if (this.prefs == undefined) {
return;
}
const safeBrowsingScoutPref =
this.getPref('safebrowsing.scout_reporting_enabled');
const prefValue = !!this.getPref('safebrowsing.enabled').value &&
!!safeBrowsingScoutPref.value;
this.safeBrowsingReportingPref_ = {
key: '',
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: prefValue,
enforcement: safeBrowsingScoutPref.enforcement,
controlledBy: safeBrowsingScoutPref.controlledBy,
};
},
/**
* Handler for when the sync state is pushed from the browser.
* @param {?settings.SyncStatus} syncStatus
* @private
*/
handleSyncStatus_(syncStatus) {
this.syncStatus = syncStatus;
},
/** @protected */ /**
currentRouteChanged() { * Records changes made to the network prediction setting for logging, the
this.showClearBrowsingDataDialog_ = * logic of actually changing the setting is taken care of by the webUI
settings.getCurrentRoute() == settings.routes.CLEAR_BROWSER_DATA; * pref.
}, * @private
*/
onNetworkPredictionChange_() {
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_NETWORK_PREDICTION);
},
/** /**
* @param {!Event} event * This is a workaround to connect the remove all button to the subpage.
* @private * @private
*/ */
onDoNotTrackDomChange_(event) { onRemoveAllCookiesFromSite_() {
if (this.showDoNotTrackDialog_) { const node = /** @type {?SiteDataDetailsSubpageElement} */ (
this.maybeShowDoNotTrackDialog_(); this.$$('site-data-details-subpage'));
} if (node) {
}, node.removeAll();
}
},
/** /** @private */
* Called when the block autoplay status changes. onSiteDataTap_() {
* @param {settings.BlockAutoplayStatus} autoplayStatus settings.navigateTo(settings.routes.SITE_SETTINGS_SITE_DATA);
* @private },
*/
onBlockAutoplayStatusChanged_(autoplayStatus) {
this.blockAutoplayStatus_ = autoplayStatus;
},
/** /** @private */
* Updates the block autoplay pref when the toggle is changed. onSiteSettingsTap_() {
* @param {!Event} event settings.navigateTo(settings.routes.SITE_SETTINGS);
* @private this.browserProxy_.recordSettingsPageHistogram(
*/ settings.SettingsPageInteractions.PRIVACY_SITE_SETTINGS);
onBlockAutoplayToggleChange_(event) { },
const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
this.browserProxy_.setBlockAutoplayEnabled(target.checked);
},
/** /** @private */
* Records changes made to the "can a website check if you have saved payment onClearBrowsingDataTap_() {
* methods" setting for logging, the logic of actually changing the setting settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA);
* is taken care of by the webUI pref. this.browserProxy_.recordSettingsPageHistogram(
* @private settings.SettingsPageInteractions.PRIVACY_CLEAR_BROWSING_DATA);
*/ },
onCanMakePaymentChange_() {
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_PAYMENT_METHOD);
},
/** /** @private */
* Handles the change event for the do-not-track toggle. Shows a onDialogClosed_() {
* confirmation dialog when enabling the setting. settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA.parent);
* @param {!Event} event cr.ui.focusWithoutInk(assert(this.$.clearBrowsingData));
* @private },
*/
onDoNotTrackChange_(event) {
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_DO_NOT_TRACK);
const target = /** @type {!SettingsToggleButtonElement} */ (event.target);
if (!target.checked) {
// Always allow disabling the pref.
target.sendPrefChange();
return;
}
this.showDoNotTrackDialog_ = true;
// If the dialog has already been stamped, show it. Otherwise it will be
// shown in onDomChange_.
this.maybeShowDoNotTrackDialog_();
},
/** @private */
maybeShowDoNotTrackDialog_() {
const dialog = this.$$('#confirmDoNotTrackDialog');
if (dialog && !dialog.open) {
dialog.showModal();
}
},
/** @private */
closeDoNotTrackDialog_() {
this.$$('#confirmDoNotTrackDialog').close();
this.showDoNotTrackDialog_ = false;
},
/** @private */
onDoNotTrackDialogClosed_() {
cr.ui.focusWithoutInk(this.$.doNotTrack);
},
/** /** @private */
* Handles the shared proxy confirmation dialog 'Confirm' button. onSecurityKeysTap_() {
* @private settings.navigateTo(settings.routes.SECURITY_KEYS);
*/ this.browserProxy_.recordSettingsPageHistogram(
onDoNotTrackDialogConfirm_() { settings.SettingsPageInteractions.PRIVACY_SECURITY_KEYS);
/** @type {!SettingsToggleButtonElement} */ (this.$.doNotTrack) },
.sendPrefChange();
this.closeDoNotTrackDialog_();
},
/** /** @private */
* Handles the shared proxy confirmation dialog 'Cancel' button or a cancel getProtectedContentLabel_(value) {
* event. return value ? this.i18n('siteSettingsProtectedContentEnable') :
* @private this.i18n('siteSettingsBlocked');
*/ },
onDoNotTrackDialogCancel_() {
/** @type {!SettingsToggleButtonElement} */ (this.$.doNotTrack)
.resetToPrefValue();
this.closeDoNotTrackDialog_();
},
/** @private */
onManageCertificatesTap_() {
// <if expr="use_nss_certs">
settings.navigateTo(settings.routes.CERTIFICATES);
// </if>
// <if expr="is_win or is_macosx">
this.browserProxy_.showManageSSLCertificates();
// </if>
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_MANAGE_CERTIFICATES);
},
/** /** @private */
* Records changes made to the network prediction setting for logging, the getProtectedContentIdentifiersLabel_(value) {
* logic of actually changing the setting is taken care of by the webUI pref. return value ?
* @private this.i18n('siteSettingsProtectedContentEnableIdentifiers') :
*/ this.i18n('siteSettingsBlocked');
onNetworkPredictionChange_() { },
this.browserProxy_.recordSettingsPageHistogram( });
settings.SettingsPageInteractions.PRIVACY_NETWORK_PREDICTION);
},
/** return {BlockAutoplayStatus};
* This is a workaround to connect the remove all button to the subpage.
* @private
*/
onRemoveAllCookiesFromSite_() {
const node = /** @type {?SiteDataDetailsSubpageElement} */ (
this.$$('site-data-details-subpage'));
if (node) {
node.removeAll();
}
},
/** @private */
onSiteDataTap_() {
settings.navigateTo(settings.routes.SITE_SETTINGS_SITE_DATA);
},
/** @private */
onSiteSettingsTap_() {
settings.navigateTo(settings.routes.SITE_SETTINGS);
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_SITE_SETTINGS);
},
/** @private */
onClearBrowsingDataTap_() {
settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA);
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_CLEAR_BROWSING_DATA);
},
/** @private */
onDialogClosed_() {
settings.navigateTo(settings.routes.CLEAR_BROWSER_DATA.parent);
cr.ui.focusWithoutInk(assert(this.$.clearBrowsingData));
},
/** @private */
onSecurityKeysTap_() {
settings.navigateTo(settings.routes.SECURITY_KEYS);
this.browserProxy_.recordSettingsPageHistogram(
settings.SettingsPageInteractions.PRIVACY_SECURITY_KEYS);
},
/** @private */
getProtectedContentLabel_(value) {
return value ? this.i18n('siteSettingsProtectedContentEnable') :
this.i18n('siteSettingsBlocked');
},
/** @private */
getProtectedContentIdentifiersLabel_(value) {
return value ? this.i18n('siteSettingsProtectedContentEnableIdentifiers') :
this.i18n('siteSettingsBlocked');
},
}); });
})();
...@@ -19,354 +19,350 @@ cr.define('settings', function() { ...@@ -19,354 +19,350 @@ cr.define('settings', function() {
ERROR: 'error', ERROR: 'error',
}; };
return { Polymer({
BioEnrollDialogPage: BioEnrollDialogPage, is: 'settings-security-keys-bio-enroll-dialog',
};
}); behaviors: [
I18nBehavior,
WebUIListenerBehavior,
],
properties: {
/** @private */
cancelButtonDisabled_: Boolean,
/** @private */
cancelButtonVisible_: Boolean,
/** @private */
confirmButtonDisabled_: Boolean,
/** @private */
confirmButtonVisible_: Boolean,
/** @private */
deleteInProgress_: Boolean,
/**
* The ID of the element currently shown in the dialog.
* @private {!settings.BioEnrollDialogPage}
*/
dialogPage_: {
type: String,
value: BioEnrollDialogPage.INITIAL,
observer: 'dialogPageChanged_',
},
/** @private */
doneButtonVisible_: Boolean,
/**
* The list of enrollments displayed.
* @private {!Array<!settings.Enrollment>}
*/
enrollments_: Array,
/** @private */
progressArcLabel_: String,
/** @private */
recentEnrollmentName_: String,
},
(function() { /** @private {?settings.SecurityKeysBioEnrollProxyImpl} */
'use strict'; browserProxy_: null,
/** @private {number} */
maxSamples_: -1,
/** @private {string} */
recentEnrollmentId_: '',
/** @override */
attached() {
Polymer.RenderStatus.afterNextRender(this, function() {
Polymer.IronA11yAnnouncer.requestAvailability();
});
this.$.dialog.showModal();
this.addWebUIListener(
'security-keys-bio-enroll-error', this.onError_.bind(this));
this.addWebUIListener(
'security-keys-bio-enroll-status', this.onEnrolling_.bind(this));
this.browserProxy_ =
settings.SecurityKeysBioEnrollProxyImpl.getInstance();
this.browserProxy_.startBioEnroll().then(() => {
this.dialogPage_ = BioEnrollDialogPage.PIN_PROMPT;
});
},
const BioEnrollDialogPage = settings.BioEnrollDialogPage; /**
* @private
* @param {string} error
*/
onError_(error) {
this.errorMsg_ = error;
this.dialogPage_ = BioEnrollDialogPage.ERROR;
},
Polymer({ /** @private */
is: 'settings-security-keys-bio-enroll-dialog', submitPIN_() {
// Disable the confirm button to prevent concurrent submissions.
this.confirmButtonDisabled_ = true;
this.$.pin.trySubmit(pin => this.browserProxy_.providePIN(pin))
.then(
() => {
// Leave confirm button disabled while enumerating fingerprints.
// It will be re-enabled by dialogPageChanged_() where
// appropriate.
this.showEnrollmentsPage_();
},
() => {
// Wrong PIN.
this.confirmButtonDisabled_ = false;
});
},
behaviors: [ /**
I18nBehavior, * @private
WebUIListenerBehavior, * @param {!Array<!settings.Enrollment>} enrollments
], */
onEnrollments_(enrollments) {
this.enrollments_ = enrollments;
this.$.enrollmentList.fire('iron-resize');
this.dialogPage_ = BioEnrollDialogPage.ENROLLMENTS;
},
properties: {
/** @private */ /** @private */
cancelButtonDisabled_: Boolean, dialogPageChanged_() {
switch (this.dialogPage_) {
case BioEnrollDialogPage.INITIAL:
this.cancelButtonVisible_ = true;
this.cancelButtonDisabled_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = false;
break;
case BioEnrollDialogPage.PIN_PROMPT:
this.cancelButtonVisible_ = true;
this.cancelButtonDisabled_ = false;
this.confirmButtonVisible_ = true;
this.confirmButtonDisabled_ = false;
this.doneButtonVisible_ = false;
this.$.pin.focus();
break;
case BioEnrollDialogPage.ENROLLMENTS:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = true;
break;
case BioEnrollDialogPage.ENROLL:
this.cancelButtonVisible_ = true;
this.cancelButtonDisabled_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = false;
break;
case BioEnrollDialogPage.CHOOSE_NAME:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = true;
this.confirmButtonDisabled_ = !this.recentEnrollmentName_.length;
this.doneButtonVisible_ = false;
this.$.enrollmentName.focus();
break;
case BioEnrollDialogPage.ERROR:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = true;
break;
default:
assertNotReached();
}
this.fire('bio-enroll-dialog-ready-for-testing');
},
/** @private */ /** @private */
cancelButtonVisible_: Boolean, addButtonClick_() {
assert(this.dialogPage_ == BioEnrollDialogPage.ENROLLMENTS);
this.maxSamples_ = -1; // Reset maxSamples_ before enrolling starts.
this.$.arc.reset();
this.progressArcLabel_ =
this.i18n('securityKeysBioEnrollmentEnrollingLabel');
this.recentEnrollmentId_ = '';
this.recentEnrollmentName_ = '';
this.dialogPage_ = BioEnrollDialogPage.ENROLL;
this.browserProxy_.startEnrolling().then(response => {
this.onEnrolling_(response);
});
},
/**
* @private
* @param {!settings.EnrollmentStatus} response
*/
onEnrolling_(response) {
if (response.code == settings.Ctap2Status.ERR_KEEPALIVE_CANCEL) {
this.showEnrollmentsPage_();
return;
}
if (this.maxSamples_ == -1 && response.status != null) {
if (response.status == 0) {
// If the first sample is valid, remaining is one less than max
// samples required.
this.maxSamples_ = response.remaining + 1;
} else {
// If the first sample failed for any reason (timed out, key full,
// etc), the remaining number of samples is the max samples required.
this.maxSamples_ = response.remaining;
}
}
// If 0 samples remain, the enrollment has finished in some state.
// Currently not checking response['code'] for an error.
this.$.arc.setProgress(
100 - (100 * (response.remaining + 1) / this.maxSamples_),
100 - (100 * response.remaining / this.maxSamples_),
response.remaining == 0);
if (response.remaining == 0) {
assert(response.enrollment);
this.recentEnrollmentId_ = response.enrollment.id;
this.recentEnrollmentName_ = response.enrollment.name;
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = true;
this.confirmButtonDisabled_ = false;
this.progressArcLabel_ =
this.i18n('securityKeysBioEnrollmentEnrollingCompleteLabel');
this.$.confirmButton.focus();
// Make screen-readers announce enrollment completion.
this.fire('iron-announce', {text: this.progressArcLabel_});
}
this.fire('bio-enroll-dialog-ready-for-testing');
},
/** @private */ /** @private */
confirmButtonDisabled_: Boolean, confirmButtonClick_() {
switch (this.dialogPage_) {
case BioEnrollDialogPage.PIN_PROMPT:
this.submitPIN_();
break;
case BioEnrollDialogPage.ENROLL:
assert(!!this.recentEnrollmentId_.length);
this.dialogPage_ = BioEnrollDialogPage.CHOOSE_NAME;
break;
case BioEnrollDialogPage.CHOOSE_NAME:
this.renameNewEnrollment_();
break;
default:
assertNotReached();
}
},
/** @private */ /** @private */
confirmButtonVisible_: Boolean, renameNewEnrollment_() {
assert(this.dialogPage_ == BioEnrollDialogPage.CHOOSE_NAME);
// Disable the confirm button to prevent concurrent submissions. It will
// be re-enabled by dialogPageChanged_() where appropriate.
this.confirmButtonDisabled_ = true;
this.browserProxy_
.renameEnrollment(
this.recentEnrollmentId_, this.recentEnrollmentName_)
.then(enrollments => {
this.onEnrollments_(enrollments);
});
},
/** @private */ /** @private */
deleteInProgress_: Boolean, showEnrollmentsPage_() {
this.browserProxy_.enumerateEnrollments().then(enrollments => {
this.onEnrollments_(enrollments);
});
},
/** /** @private */
* The ID of the element currently shown in the dialog. cancel_() {
* @private {!settings.BioEnrollDialogPage} if (this.dialogPage_ == BioEnrollDialogPage.ENROLL) {
*/ // Cancel an ongoing enrollment. Will cause the pending
dialogPage_: { // enumerateEnrollments() promise to be resolved and proceed to the
type: String, // enrollments page.
value: BioEnrollDialogPage.INITIAL, this.cancelButtonDisabled_ = true;
observer: 'dialogPageChanged_', this.browserProxy_.cancelEnrollment();
} else {
// On any other screen, simply close the dialog.
this.done_();
}
}, },
/** @private */ /** @private */
doneButtonVisible_: Boolean, done_() {
this.$.dialog.close();
},
/** @private */
onDialogClosed_() {
this.browserProxy_.close();
},
/** /**
* The list of enrollments displayed. * @private
* @private {!Array<!settings.Enrollment>} * @param {!Event} e
*/ */
enrollments_: Array, onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering
// the listener within settings-animated-pages.
e.stopPropagation();
},
/** @private */ /**
progressArcLabel_: String, * @private
* @param {!DomRepeatEvent} event
*/
deleteEnrollment_(event) {
if (this.deleteInProgress_) {
return;
}
this.deleteInProgress_ = true;
const enrollment = this.enrollments_[event.model.index];
this.browserProxy_.deleteEnrollment(enrollment.id).then(enrollments => {
this.deleteInProgress_ = false;
this.onEnrollments_(enrollments);
});
},
/** @private */ /** @private */
recentEnrollmentName_: String, onEnrollmentNameInput_() {
}, this.confirmButtonDisabled_ = !this.recentEnrollmentName_.length;
},
/** @private {?settings.SecurityKeysBioEnrollProxyImpl} */
browserProxy_: null, /**
* @private
/** @private {number} */ * @param {!settings.BioEnrollDialogPage} dialogPage
maxSamples_: -1, * @return {string} The title string for the current dialog page.
*/
/** @private {string} */ dialogTitle_(dialogPage) {
recentEnrollmentId_: '', if (dialogPage == BioEnrollDialogPage.ENROLL ||
dialogPage == BioEnrollDialogPage.CHOOSE_NAME) {
/** @override */ return this.i18n('securityKeysBioEnrollmentAddTitle');
attached() {
Polymer.RenderStatus.afterNextRender(this, function() {
Polymer.IronA11yAnnouncer.requestAvailability();
});
this.$.dialog.showModal();
this.addWebUIListener(
'security-keys-bio-enroll-error', this.onError_.bind(this));
this.addWebUIListener(
'security-keys-bio-enroll-status', this.onEnrolling_.bind(this));
this.browserProxy_ = settings.SecurityKeysBioEnrollProxyImpl.getInstance();
this.browserProxy_.startBioEnroll().then(() => {
this.dialogPage_ = BioEnrollDialogPage.PIN_PROMPT;
});
},
/**
* @private
* @param {string} error
*/
onError_(error) {
this.errorMsg_ = error;
this.dialogPage_ = BioEnrollDialogPage.ERROR;
},
/** @private */
submitPIN_() {
// Disable the confirm button to prevent concurrent submissions.
this.confirmButtonDisabled_ = true;
this.$.pin.trySubmit(pin => this.browserProxy_.providePIN(pin))
.then(
() => {
// Leave confirm button disabled while enumerating fingerprints.
// It will be re-enabled by dialogPageChanged_() where
// appropriate.
this.showEnrollmentsPage_();
},
() => {
// Wrong PIN.
this.confirmButtonDisabled_ = false;
});
},
/**
* @private
* @param {!Array<!settings.Enrollment>} enrollments
*/
onEnrollments_(enrollments) {
this.enrollments_ = enrollments;
this.$.enrollmentList.fire('iron-resize');
this.dialogPage_ = BioEnrollDialogPage.ENROLLMENTS;
},
/** @private */
dialogPageChanged_() {
switch (this.dialogPage_) {
case BioEnrollDialogPage.INITIAL:
this.cancelButtonVisible_ = true;
this.cancelButtonDisabled_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = false;
break;
case BioEnrollDialogPage.PIN_PROMPT:
this.cancelButtonVisible_ = true;
this.cancelButtonDisabled_ = false;
this.confirmButtonVisible_ = true;
this.confirmButtonDisabled_ = false;
this.doneButtonVisible_ = false;
this.$.pin.focus();
break;
case BioEnrollDialogPage.ENROLLMENTS:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = true;
break;
case BioEnrollDialogPage.ENROLL:
this.cancelButtonVisible_ = true;
this.cancelButtonDisabled_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = false;
break;
case BioEnrollDialogPage.CHOOSE_NAME:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = true;
this.confirmButtonDisabled_ = !this.recentEnrollmentName_.length;
this.doneButtonVisible_ = false;
this.$.enrollmentName.focus();
break;
case BioEnrollDialogPage.ERROR:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = false;
this.doneButtonVisible_ = true;
break;
default:
assertNotReached();
}
this.fire('bio-enroll-dialog-ready-for-testing');
},
/** @private */
addButtonClick_() {
assert(this.dialogPage_ == BioEnrollDialogPage.ENROLLMENTS);
this.maxSamples_ = -1; // Reset maxSamples_ before enrolling starts.
this.$.arc.reset();
this.progressArcLabel_ =
this.i18n('securityKeysBioEnrollmentEnrollingLabel');
this.recentEnrollmentId_ = '';
this.recentEnrollmentName_ = '';
this.dialogPage_ = BioEnrollDialogPage.ENROLL;
this.browserProxy_.startEnrolling().then(response => {
this.onEnrolling_(response);
});
},
/**
* @private
* @param {!settings.EnrollmentStatus} response
*/
onEnrolling_(response) {
if (response.code == settings.Ctap2Status.ERR_KEEPALIVE_CANCEL) {
this.showEnrollmentsPage_();
return;
}
if (this.maxSamples_ == -1 && response.status != null) {
if (response.status == 0) {
// If the first sample is valid, remaining is one less than max samples
// required.
this.maxSamples_ = response.remaining + 1;
} else {
// If the first sample failed for any reason (timed out, key full, etc),
// the remaining number of samples is the max samples required.
this.maxSamples_ = response.remaining;
} }
} return this.i18n('securityKeysBioEnrollmentDialogTitle');
// If 0 samples remain, the enrollment has finished in some state. },
// Currently not checking response['code'] for an error.
this.$.arc.setProgress( /**
100 - (100 * (response.remaining + 1) / this.maxSamples_), * @private
100 - (100 * response.remaining / this.maxSamples_), * @param {?Array} enrollments
response.remaining == 0); * @return {string} The header label for the enrollments page.
if (response.remaining == 0) { */
assert(response.enrollment); enrollmentsHeader_(enrollments) {
this.recentEnrollmentId_ = response.enrollment.id; return this.i18n(
this.recentEnrollmentName_ = response.enrollment.name; enrollments && enrollments.length ?
this.cancelButtonVisible_ = false; 'securityKeysBioEnrollmentEnrollmentsLabel' :
this.confirmButtonVisible_ = true; 'securityKeysBioEnrollmentNoEnrollmentsLabel');
this.confirmButtonDisabled_ = false; },
this.progressArcLabel_ = });
this.i18n('securityKeysBioEnrollmentEnrollingCompleteLabel');
this.$.confirmButton.focus(); return {
// Make screen-readers announce enrollment completion. BioEnrollDialogPage: BioEnrollDialogPage,
this.fire('iron-announce', {text: this.progressArcLabel_}); };
}
this.fire('bio-enroll-dialog-ready-for-testing');
},
/** @private */
confirmButtonClick_() {
switch (this.dialogPage_) {
case BioEnrollDialogPage.PIN_PROMPT:
this.submitPIN_();
break;
case BioEnrollDialogPage.ENROLL:
assert(!!this.recentEnrollmentId_.length);
this.dialogPage_ = BioEnrollDialogPage.CHOOSE_NAME;
break;
case BioEnrollDialogPage.CHOOSE_NAME:
this.renameNewEnrollment_();
break;
default:
assertNotReached();
}
},
/** @private */
renameNewEnrollment_() {
assert(this.dialogPage_ == BioEnrollDialogPage.CHOOSE_NAME);
// Disable the confirm button to prevent concurrent submissions. It will be
// re-enabled by dialogPageChanged_() where appropriate.
this.confirmButtonDisabled_ = true;
this.browserProxy_
.renameEnrollment(this.recentEnrollmentId_, this.recentEnrollmentName_)
.then(enrollments => {
this.onEnrollments_(enrollments);
});
},
/** @private */
showEnrollmentsPage_() {
this.browserProxy_.enumerateEnrollments().then(enrollments => {
this.onEnrollments_(enrollments);
});
},
/** @private */
cancel_() {
if (this.dialogPage_ == BioEnrollDialogPage.ENROLL) {
// Cancel an ongoing enrollment. Will cause the pending
// enumerateEnrollments() promise to be resolved and proceed to the
// enrollments page.
this.cancelButtonDisabled_ = true;
this.browserProxy_.cancelEnrollment();
} else {
// On any other screen, simply close the dialog.
this.done_();
}
},
/** @private */
done_() {
this.$.dialog.close();
},
/** @private */
onDialogClosed_() {
this.browserProxy_.close();
},
/**
* @private
* @param {!Event} e
*/
onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering the
// listener within settings-animated-pages.
e.stopPropagation();
},
/**
* @private
* @param {!DomRepeatEvent} event
*/
deleteEnrollment_(event) {
if (this.deleteInProgress_) {
return;
}
this.deleteInProgress_ = true;
const enrollment = this.enrollments_[event.model.index];
this.browserProxy_.deleteEnrollment(enrollment.id).then(enrollments => {
this.deleteInProgress_ = false;
this.onEnrollments_(enrollments);
});
},
/** @private */
onEnrollmentNameInput_() {
this.confirmButtonDisabled_ = !this.recentEnrollmentName_.length;
},
/**
* @private
* @param {!settings.BioEnrollDialogPage} dialogPage
* @return {string} The title string for the current dialog page.
*/
dialogTitle_(dialogPage) {
if (dialogPage == BioEnrollDialogPage.ENROLL ||
dialogPage == BioEnrollDialogPage.CHOOSE_NAME) {
return this.i18n('securityKeysBioEnrollmentAddTitle');
}
return this.i18n('securityKeysBioEnrollmentDialogTitle');
},
/**
* @private
* @param {?Array} enrollments
* @return {string} The header label for the enrollments page.
*/
enrollmentsHeader_(enrollments) {
return this.i18n(
enrollments && enrollments.length ?
'securityKeysBioEnrollmentEnrollmentsLabel' :
'securityKeysBioEnrollmentNoEnrollmentsLabel');
},
}); });
})();
...@@ -16,255 +16,251 @@ cr.define('settings', function() { ...@@ -16,255 +16,251 @@ cr.define('settings', function() {
ERROR: 'error', ERROR: 'error',
}; };
return { Polymer({
CredentialManagementDialogPage: CredentialManagementDialogPage, is: 'settings-security-keys-credential-management-dialog',
};
}); behaviors: [
I18nBehavior,
(function() { WebUIListenerBehavior,
'use strict'; ],
const CredentialManagementDialogPage = settings.CredentialManagementDialogPage; properties: {
/**
Polymer({ * The ID of the element currently shown in the dialog.
is: 'settings-security-keys-credential-management-dialog', * @private {!settings.CredentialManagementDialogPage}
*/
dialogPage_: {
type: String,
value: CredentialManagementDialogPage.INITIAL,
observer: 'dialogPageChanged_',
},
/**
* The list of credentials displayed in the dialog.
* @private {!Array<!settings.Credential>}
*/
credentials_: Array,
/**
* The message displayed on the "error" dialog page.
* @private
*/
errorMsg_: String,
/** @private */
cancelButtonVisible_: Boolean,
/** @private */
confirmButtonVisible_: Boolean,
/** @private */
confirmButtonDisabled_: Boolean,
/** @private */
confirmButtonLabel_: String,
/** @private */
closeButtonVisible_: Boolean,
/** @private */
deleteInProgress_: Boolean,
},
behaviors: [ /** @private {?settings.SecurityKeysCredentialBrowserProxy} */
I18nBehavior, browserProxy_: null,
WebUIListenerBehavior,
], /** @private {?Set<string>} */
checkedCredentialIds_: null,
/** @override */
attached() {
this.$.dialog.showModal();
this.addWebUIListener(
'security-keys-credential-management-finished',
this.onError_.bind(this));
this.checkedCredentialIds_ = new Set();
this.browserProxy_ =
settings.SecurityKeysCredentialBrowserProxyImpl.getInstance();
this.browserProxy_.startCredentialManagement().then(() => {
this.dialogPage_ = CredentialManagementDialogPage.PIN_PROMPT;
});
},
properties: {
/** /**
* The ID of the element currently shown in the dialog. * @private
* @private {!settings.CredentialManagementDialogPage} * @param {string} error
*/ */
dialogPage_: { onError_(error) {
type: String, this.errorMsg_ = error;
value: CredentialManagementDialogPage.INITIAL, this.dialogPage_ = CredentialManagementDialogPage.ERROR;
observer: 'dialogPageChanged_',
}, },
/** /** @private */
* The list of credentials displayed in the dialog. submitPIN_() {
* @private {!Array<!settings.Credential>} // Disable the confirm button to prevent concurrent submissions.
*/ this.confirmButtonDisabled_ = true;
credentials_: Array,
this.$.pin.trySubmit(pin => this.browserProxy_.providePIN(pin))
.then(
() => {
// Leave confirm button disabled while enumerating credentials.
this.browserProxy_.enumerateCredentials().then(
this.onCredentials_.bind(this));
},
() => {
// Wrong PIN.
this.confirmButtonDisabled_ = false;
});
},
/** /**
* The message displayed on the "error" dialog page.
* @private * @private
* @param {!Array<!settings.Credential>} credentials
*/ */
errorMsg_: String, onCredentials_(credentials) {
if (!credentials.length) {
this.onError_(
this.i18n('securityKeysCredentialManagementNoCredentials'));
return;
}
this.credentials_ = credentials;
this.$.credentialList.fire('iron-resize');
this.dialogPage_ = CredentialManagementDialogPage.CREDENTIALS;
},
/** @private */ /** @private */
cancelButtonVisible_: Boolean, dialogPageChanged_() {
switch (this.dialogPage_) {
case CredentialManagementDialogPage.INITIAL:
this.cancelButtonVisible_ = true;
this.confirmButtonVisible_ = false;
this.closeButtonVisible_ = false;
break;
case CredentialManagementDialogPage.PIN_PROMPT:
this.cancelButtonVisible_ = true;
this.confirmButtonLabel_ = this.i18n('continue');
this.confirmButtonDisabled_ = false;
this.confirmButtonVisible_ = true;
this.closeButtonVisible_ = false;
this.$.pin.focus();
break;
case CredentialManagementDialogPage.CREDENTIALS:
this.cancelButtonVisible_ = true;
this.confirmButtonLabel_ = this.i18n('delete');
this.confirmButtonDisabled_ = true;
this.confirmButtonVisible_ = true;
this.closeButtonVisible_ = false;
break;
case CredentialManagementDialogPage.ERROR:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = false;
this.closeButtonVisible_ = true;
break;
default:
assertNotReached();
}
this.fire('credential-management-dialog-ready-for-testing');
},
/** @private */ /** @private */
confirmButtonVisible_: Boolean, confirmButtonClick_() {
switch (this.dialogPage_) {
case CredentialManagementDialogPage.PIN_PROMPT:
this.submitPIN_();
break;
case CredentialManagementDialogPage.CREDENTIALS:
this.deleteSelectedCredentials_();
break;
default:
assertNotReached();
}
},
/** @private */ /** @private */
confirmButtonDisabled_: Boolean, close_() {
this.$.dialog.close();
},
/** @private */ /**
confirmButtonLabel_: String, * Stringifies the user entity of a Credential for display in the dialog.
* @private
* @param {!settings.Credential} credential
* @return {string}
*/
formatUser_(credential) {
if (this.isEmpty_(credential.userDisplayName)) {
return credential.userName;
}
return `${credential.userDisplayName} (${credential.userName})`;
},
/** @private */ /** @private */
closeButtonVisible_: Boolean, onDialogClosed_() {
this.browserProxy_.close();
},
/**
* @private
* @param {?string} str
* @return {boolean} Whether this credential has been selected for removal.
*/
isEmpty_(str) {
return !str || str.length == 0;
},
/**
* @param {!Event} e
* @private
*/
onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering
// the listener within settings-animated-pages.
e.stopPropagation();
},
/**
* Handler for checking or unchecking a credential.
* @param {!Event} e
* @private
*/
checkedCredentialsChanged_(e) {
const credentialId = e.target.dataset.id;
if (e.target.checked) {
this.checkedCredentialIds_.add(credentialId);
} else {
this.checkedCredentialIds_.delete(credentialId);
}
this.confirmButtonDisabled_ = this.checkedCredentialIds_.size == 0;
},
/**
* @private
* @param {string} credentialId
* @return {boolean} true if the checkbox for |credentialId| is checked
*/
credentialIsChecked_(credentialId) {
return this.checkedCredentialIds_.has(credentialId);
},
/** @private */ /** @private */
deleteInProgress_: Boolean, deleteSelectedCredentials_() {
}, assert(this.dialogPage_ == CredentialManagementDialogPage.CREDENTIALS);
assert(this.credentials_ && this.credentials_.length > 0);
/** @private {?settings.SecurityKeysCredentialBrowserProxy} */ assert(this.checkedCredentialIds_.size > 0);
browserProxy_: null,
this.confirmButtonDisabled_ = true;
/** @private {?Set<string>} */ this.deleteInProgress_ = true;
checkedCredentialIds_: null, this.browserProxy_
.deleteCredentials(Array.from(this.checkedCredentialIds_))
/** @override */ .then((err) => {
attached() { this.confirmButtonDisabled_ = false;
this.$.dialog.showModal(); this.deleteInProgress_ = false;
this.addWebUIListener( this.onError_(err);
'security-keys-credential-management-finished', });
this.onError_.bind(this)); },
this.checkedCredentialIds_ = new Set(); });
this.browserProxy_ =
settings.SecurityKeysCredentialBrowserProxyImpl.getInstance(); return {
this.browserProxy_.startCredentialManagement().then(() => { CredentialManagementDialogPage: CredentialManagementDialogPage,
this.dialogPage_ = CredentialManagementDialogPage.PIN_PROMPT; };
});
},
/**
* @private
* @param {string} error
*/
onError_(error) {
this.errorMsg_ = error;
this.dialogPage_ = CredentialManagementDialogPage.ERROR;
},
/** @private */
submitPIN_() {
// Disable the confirm button to prevent concurrent submissions.
this.confirmButtonDisabled_ = true;
this.$.pin.trySubmit(pin => this.browserProxy_.providePIN(pin))
.then(
() => {
// Leave confirm button disabled while enumerating credentials.
this.browserProxy_.enumerateCredentials().then(
this.onCredentials_.bind(this));
},
() => {
// Wrong PIN.
this.confirmButtonDisabled_ = false;
});
},
/**
* @private
* @param {!Array<!settings.Credential>} credentials
*/
onCredentials_(credentials) {
if (!credentials.length) {
this.onError_(this.i18n('securityKeysCredentialManagementNoCredentials'));
return;
}
this.credentials_ = credentials;
this.$.credentialList.fire('iron-resize');
this.dialogPage_ = CredentialManagementDialogPage.CREDENTIALS;
},
/** @private */
dialogPageChanged_() {
switch (this.dialogPage_) {
case CredentialManagementDialogPage.INITIAL:
this.cancelButtonVisible_ = true;
this.confirmButtonVisible_ = false;
this.closeButtonVisible_ = false;
break;
case CredentialManagementDialogPage.PIN_PROMPT:
this.cancelButtonVisible_ = true;
this.confirmButtonLabel_ = this.i18n('continue');
this.confirmButtonDisabled_ = false;
this.confirmButtonVisible_ = true;
this.closeButtonVisible_ = false;
this.$.pin.focus();
break;
case CredentialManagementDialogPage.CREDENTIALS:
this.cancelButtonVisible_ = true;
this.confirmButtonLabel_ = this.i18n('delete');
this.confirmButtonDisabled_ = true;
this.confirmButtonVisible_ = true;
this.closeButtonVisible_ = false;
break;
case CredentialManagementDialogPage.ERROR:
this.cancelButtonVisible_ = false;
this.confirmButtonVisible_ = false;
this.closeButtonVisible_ = true;
break;
default:
assertNotReached();
}
this.fire('credential-management-dialog-ready-for-testing');
},
/** @private */
confirmButtonClick_() {
switch (this.dialogPage_) {
case CredentialManagementDialogPage.PIN_PROMPT:
this.submitPIN_();
break;
case CredentialManagementDialogPage.CREDENTIALS:
this.deleteSelectedCredentials_();
break;
default:
assertNotReached();
}
},
/** @private */
close_() {
this.$.dialog.close();
},
/**
* Stringifies the user entity of a Credential for display in the dialog.
* @private
* @param {!settings.Credential} credential
* @return {string}
*/
formatUser_(credential) {
if (this.isEmpty_(credential.userDisplayName)) {
return credential.userName;
}
return `${credential.userDisplayName} (${credential.userName})`;
},
/** @private */
onDialogClosed_() {
this.browserProxy_.close();
},
/**
* @private
* @param {?string} str
* @return {boolean} Whether this credential has been selected for removal.
*/
isEmpty_(str) {
return !str || str.length == 0;
},
/**
* @param {!Event} e
* @private
*/
onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering the
// listener within settings-animated-pages.
e.stopPropagation();
},
/**
* Handler for checking or unchecking a credential.
* @param {!Event} e
* @private
*/
checkedCredentialsChanged_(e) {
const credentialId = e.target.dataset.id;
if (e.target.checked) {
this.checkedCredentialIds_.add(credentialId);
} else {
this.checkedCredentialIds_.delete(credentialId);
}
this.confirmButtonDisabled_ = this.checkedCredentialIds_.size == 0;
},
/**
* @private
* @param {string} credentialId
* @return {boolean} true if the checkbox for |credentialId| is checked
*/
credentialIsChecked_(credentialId) {
return this.checkedCredentialIds_.has(credentialId);
},
/** @private */
deleteSelectedCredentials_() {
assert(this.dialogPage_ == CredentialManagementDialogPage.CREDENTIALS);
assert(this.credentials_ && this.credentials_.length > 0);
assert(this.checkedCredentialIds_.size > 0);
this.confirmButtonDisabled_ = true;
this.deleteInProgress_ = true;
this.browserProxy_.deleteCredentials(Array.from(this.checkedCredentialIds_))
.then((err) => {
this.confirmButtonDisabled_ = false;
this.deleteInProgress_ = false;
this.onError_(err);
});
},
}); });
})();
...@@ -16,192 +16,193 @@ cr.define('settings', function() { ...@@ -16,192 +16,193 @@ cr.define('settings', function() {
*/ */
let PINFieldSubmitFunc; let PINFieldSubmitFunc;
return {
PINFieldSubmitFunc: PINFieldSubmitFunc,
};
});
Polymer({ Polymer({
is: 'settings-security-keys-pin-field', is: 'settings-security-keys-pin-field',
behaviors: [ behaviors: [
I18nBehavior, I18nBehavior,
], ],
properties: { properties: {
/* @private */ /* @private */
error_: { error_: {
type: String, type: String,
observer: 'errorChanged_', observer: 'errorChanged_',
}, },
/* @private */ /* @private */
value_: String, value_: String,
/* @private */ /* @private */
inputVisible_: { inputVisible_: {
type: Boolean, type: Boolean,
value: false, value: false,
},
}, },
},
/** @override */ /** @override */
attached() { attached() {
Polymer.RenderStatus.afterNextRender(this, function() { Polymer.RenderStatus.afterNextRender(this, function() {
Polymer.IronA11yAnnouncer.requestAvailability(); Polymer.IronA11yAnnouncer.requestAvailability();
}); });
}, },
/** Focuses the PIN input field. */ /** Focuses the PIN input field. */
focus() { focus() {
this.$.pin.focus(); this.$.pin.focus();
}, },
/** /**
* Validates the PIN and sets the validation error if it is not valid. * Validates the PIN and sets the validation error if it is not valid.
* @return {boolean} True iff the PIN is valid. * @return {boolean} True iff the PIN is valid.
* @private * @private
*/ */
validate_() { validate_() {
const error = this.isValidPIN_(this.value_); const error = this.isValidPIN_(this.value_);
if (error != '') { if (error != '') {
this.error_ = error; this.error_ = error;
return false; return false;
} }
return true; return true;
}, },
/** /**
* Attempts submission of the PIN by invoking |submitFunc|. Updates the UI to * Attempts submission of the PIN by invoking |submitFunc|. Updates the UI
* show an error if the PIN was incorrect. * to show an error if the PIN was incorrect.
* @param {!settings.PINFieldSubmitFunc} submitFunc * @param {!settings.PINFieldSubmitFunc} submitFunc
* @return {!Promise} resolves if the PIN was correct, else rejects * @return {!Promise} resolves if the PIN was correct, else rejects
*/ */
trySubmit(submitFunc) { trySubmit(submitFunc) {
if (!this.validate_()) { if (!this.validate_()) {
this.focus();
return Promise.reject();
}
return submitFunc(this.value_).then(retries => {
if (retries != null) {
this.showIncorrectPINError_(retries);
this.focus(); this.focus();
return Promise.reject(); return Promise.reject();
} }
}); return submitFunc(this.value_).then(retries => {
}, if (retries != null) {
this.showIncorrectPINError_(retries);
this.focus();
return Promise.reject();
}
});
},
/** /**
* Sets the validation error to indicate the PIN was incorrect. * Sets the validation error to indicate the PIN was incorrect.
* @param {number} retries The number of retries remaining. * @param {number} retries The number of retries remaining.
* @private * @private
*/ */
showIncorrectPINError_(retries) { showIncorrectPINError_(retries) {
// Warn the user if the number of retries is getting low. // Warn the user if the number of retries is getting low.
let error; let error;
if (1 < retries && retries <= 3) { if (1 < retries && retries <= 3) {
error = error =
this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString()); this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString());
} else if (retries == 1) { } else if (retries == 1) {
error = this.i18n('securityKeysPINIncorrectRetriesSin'); error = this.i18n('securityKeysPINIncorrectRetriesSin');
} else { } else {
error = this.i18n('securityKeysPINIncorrect'); error = this.i18n('securityKeysPINIncorrect');
} }
this.error_ = error; this.error_ = error;
}, },
/** @private */
onPINInput_() {
// Typing in the PIN box after an error makes the error message
// disappear.
this.error_ = '';
},
/** /** @private */
* Polymer helper function to detect when an error string is empty. onPINInput_() {
* @param {string} s Arbitrary string // Typing in the PIN box after an error makes the error message
* @return {boolean} True iff |s| is non-empty. // disappear.
* @private this.error_ = '';
*/ },
isNonEmpty_(s) {
return s != '';
},
/** /**
* @return {string} The PIN-input element type. * Polymer helper function to detect when an error string is empty.
* @private * @param {string} s Arbitrary string
*/ * @return {boolean} True iff |s| is non-empty.
inputType_() { * @private
return this.inputVisible_ ? 'text' : 'password'; */
}, isNonEmpty_(s) {
return s != '';
},
/** /**
* @return {string} The class (and thus icon) to be displayed. * @return {string} The PIN-input element type.
* @private * @private
*/ */
showButtonClass_() { inputType_() {
return 'icon-visibility' + (this.inputVisible_ ? '-off' : ''); return this.inputVisible_ ? 'text' : 'password';
}, },
/** /**
* @return {string} The tooltip for the icon. * @return {string} The class (and thus icon) to be displayed.
* @private * @private
*/ */
showButtonTitle_() { showButtonClass_() {
return this.i18n( return 'icon-visibility' + (this.inputVisible_ ? '-off' : '');
this.inputVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs'); },
},
/** /**
* onClick handler for the show/hide icon. * @return {string} The tooltip for the icon.
* @private * @private
*/ */
showButtonClick_() { showButtonTitle_() {
this.inputVisible_ = !this.inputVisible_; return this.i18n(
}, this.inputVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs');
},
/** /**
* @param {string} pin A candidate PIN. * onClick handler for the show/hide icon.
* @return {string} An error string or else '' to indicate validity. * @private
* @private */
*/ showButtonClick_() {
isValidPIN_(pin) { this.inputVisible_ = !this.inputVisible_;
// The UTF-8 encoding of the PIN must be between 4 },
// and 63 bytes, and the final byte cannot be zero.
const utf8Encoded = new TextEncoder().encode(pin); /**
if (utf8Encoded.length < 4) { * @param {string} pin A candidate PIN.
return this.i18n('securityKeysPINTooShort'); * @return {string} An error string or else '' to indicate validity.
} * @private
if (utf8Encoded.length > 63 || */
// If the PIN somehow has a NUL at the end then it's invalid, but this isValidPIN_(pin) {
// is so obscure that we don't try to message it. Rather we just say // The UTF-8 encoding of the PIN must be between 4
// that it's too long because trimming the final character is the best // and 63 bytes, and the final byte cannot be zero.
// response by the user. const utf8Encoded = new TextEncoder().encode(pin);
utf8Encoded[utf8Encoded.length - 1] == 0) { if (utf8Encoded.length < 4) {
return this.i18n('securityKeysPINTooLong'); return this.i18n('securityKeysPINTooShort');
} }
if (utf8Encoded.length > 63 ||
// A PIN must contain at least four code-points. Javascript strings are // If the PIN somehow has a NUL at the end then it's invalid, but this
// UCS-2 and the |length| property counts UCS-2 elements, not code-points. // is so obscure that we don't try to message it. Rather we just say
// (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.) // that it's too long because trimming the final character is the best
// Therefore, iterate over the string (which does yield codepoints) and // response by the user.
// check that four or more were seen. utf8Encoded[utf8Encoded.length - 1] == 0) {
let length = 0; return this.i18n('securityKeysPINTooLong');
for (const codepoint of pin) { }
length++;
} // A PIN must contain at least four code-points. Javascript strings are
// UCS-2 and the |length| property counts UCS-2 elements, not code-points.
if (length < 4) { // (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.)
return this.i18n('securityKeysPINTooShort'); // Therefore, iterate over the string (which does yield codepoints) and
} // check that four or more were seen.
let length = 0;
return ''; for (const codepoint of pin) {
}, length++;
}
/** @private */
errorChanged_() { if (length < 4) {
// Make screen readers announce changes to the PIN validation error return this.i18n('securityKeysPINTooShort');
// label. }
this.fire('iron-announce', {text: this.error_});
}, return '';
},
/** @private */
errorChanged_() {
// Make screen readers announce changes to the PIN validation error
// label.
this.fire('iron-announce', {text: this.error_});
},
});
return {
PINFieldSubmitFunc: PINFieldSubmitFunc,
};
}); });
...@@ -19,147 +19,141 @@ cr.define('settings', function() { ...@@ -19,147 +19,141 @@ cr.define('settings', function() {
}; };
return { Polymer({
ResetDialogPage: ResetDialogPage, is: 'settings-security-keys-reset-dialog',
};
}); behaviors: [I18nBehavior],
(function() { properties: {
'use strict'; /**
* A CTAP error code for when the specific error was not recognised.
* @private
*/
errorCode_: Number,
/**
* True iff the process has completed, successfully or otherwise.
* @private
*/
complete_: {
type: Boolean,
value: false,
},
/**
* The id of an element on the page that is currently shown.
* @private {!settings.ResetDialogPage}
*/
shown_: {
type: String,
value: ResetDialogPage.INITIAL,
},
/**
* @private
*/
title_: String,
},
const ResetDialogPage = settings.ResetDialogPage; /** @private {?settings.SecurityKeysResetBrowserProxy} */
browserProxy_: null,
/** @override */
attached() {
this.title_ = this.i18n('securityKeysResetTitle');
this.browserProxy_ =
settings.SecurityKeysResetBrowserProxyImpl.getInstance();
this.$.dialog.showModal();
this.browserProxy_.reset().then(code => {
// code is a CTAP error code. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
if (code == 1 /* INVALID_COMMAND */) {
this.shown_ = ResetDialogPage.NO_RESET;
this.finish_();
} else if (code != 0 /* unknown error */) {
this.errorCode_ = code;
this.shown_ = ResetDialogPage.RESET_FAILED;
this.finish_();
} else {
this.title_ = this.i18n('securityKeysResetConfirmTitle');
this.shown_ = ResetDialogPage.RESET_CONFIRM;
this.browserProxy_.completeReset().then(code => {
this.title_ = this.i18n('securityKeysResetTitle');
if (code == 0 /* SUCCESS */) {
this.shown_ = ResetDialogPage.RESET_SUCCESS;
} else if (code == 48 /* NOT_ALLOWED */) {
this.shown_ = ResetDialogPage.RESET_NOT_ALLOWED;
} else /* unknown error */ {
this.errorCode_ = code;
this.shown_ = ResetDialogPage.RESET_FAILED;
}
this.finish_();
});
}
});
},
Polymer({ /** @private */
is: 'settings-security-keys-reset-dialog', closeDialog_() {
this.$.dialog.close();
this.finish_();
},
behaviors: [I18nBehavior], /** @private */
finish_() {
if (this.complete_) {
return;
}
this.complete_ = true;
this.browserProxy_.close();
},
properties: {
/** /**
* A CTAP error code for when the specific error was not recognised. * @param {!Event} e
* @private * @private
*/ */
errorCode_: Number, onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering
// the listener within settings-animated-pages.
e.stopPropagation();
},
/** /**
* True iff the process has completed, successfully or otherwise. @param {number} code CTAP error code.
* @private @return {string} Contents of the error string that may be displayed
to the user. Used automatically by Polymer.
@private
*/ */
complete_: { resetFailed_(code) {
type: Boolean, if (code === null) {
value: false, return '';
}
return this.i18n('securityKeysResetError', code.toString());
}, },
/** /**
* The id of an element on the page that is currently shown. * @param {boolean} complete Whether the dialog process is complete.
* @private {!settings.ResetDialogPage} * @return {string} The label of the dialog button. Used automatically by
* Polymer.
* @private
*/ */
shown_: { closeText_(complete) {
type: String, return this.i18n(complete ? 'ok' : 'cancel');
value: ResetDialogPage.INITIAL,
}, },
/** /**
* @param {boolean} complete Whether the dialog process is complete.
* @return {string} The class of the dialog button. Used automatically by
* Polymer.
* @private * @private
*/ */
title_: String, maybeActionButton_(complete) {
}, return complete ? 'action-button' : 'cancel-button';
},
/** @private {?settings.SecurityKeysResetBrowserProxy} */ });
browserProxy_: null,
return {
/** @override */ ResetDialogPage: ResetDialogPage,
attached() { };
this.title_ = this.i18n('securityKeysResetTitle');
this.browserProxy_ =
settings.SecurityKeysResetBrowserProxyImpl.getInstance();
this.$.dialog.showModal();
this.browserProxy_.reset().then(code => {
// code is a CTAP error code. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
if (code == 1 /* INVALID_COMMAND */) {
this.shown_ = ResetDialogPage.NO_RESET;
this.finish_();
} else if (code != 0 /* unknown error */) {
this.errorCode_ = code;
this.shown_ = ResetDialogPage.RESET_FAILED;
this.finish_();
} else {
this.title_ = this.i18n('securityKeysResetConfirmTitle');
this.shown_ = ResetDialogPage.RESET_CONFIRM;
this.browserProxy_.completeReset().then(code => {
this.title_ = this.i18n('securityKeysResetTitle');
if (code == 0 /* SUCCESS */) {
this.shown_ = ResetDialogPage.RESET_SUCCESS;
} else if (code == 48 /* NOT_ALLOWED */) {
this.shown_ = ResetDialogPage.RESET_NOT_ALLOWED;
} else /* unknown error */ {
this.errorCode_ = code;
this.shown_ = ResetDialogPage.RESET_FAILED;
}
this.finish_();
});
}
});
},
/** @private */
closeDialog_() {
this.$.dialog.close();
this.finish_();
},
/** @private */
finish_() {
if (this.complete_) {
return;
}
this.complete_ = true;
this.browserProxy_.close();
},
/**
* @param {!Event} e
* @private
*/
onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering the
// listener within settings-animated-pages.
e.stopPropagation();
},
/**
@param {number} code CTAP error code.
@return {string} Contents of the error string that may be displayed
to the user. Used automatically by Polymer.
@private
*/
resetFailed_(code) {
if (code === null) {
return '';
}
return this.i18n('securityKeysResetError', code.toString());
},
/**
* @param {boolean} complete Whether the dialog process is complete.
* @return {string} The label of the dialog button. Used automatically by
* Polymer.
* @private
*/
closeText_(complete) {
return this.i18n(complete ? 'ok' : 'cancel');
},
/**
* @param {boolean} complete Whether the dialog process is complete.
* @return {string} The class of the dialog button. Used automatically by
* Polymer.
* @private
*/
maybeActionButton_(complete) {
return complete ? 'action-button' : 'cancel-button';
},
}); });
})();
...@@ -19,467 +19,465 @@ cr.define('settings', function() { ...@@ -19,467 +19,465 @@ cr.define('settings', function() {
SUCCESS: 'success', SUCCESS: 'success',
}; };
return { Polymer({
SetPINDialogPage: SetPINDialogPage, is: 'settings-security-keys-set-pin-dialog',
};
}); behaviors: [I18nBehavior],
(function() { properties: {
'use strict'; /**
* Whether the value of the current PIN textbox is a valid PIN or not.
* @private
*/
currentPINValid_: Boolean,
/** @private */
newPINValid_: Boolean,
/** @private */
confirmPINValid_: Boolean,
/**
* Whether the dialog is in a state where the Set PIN button should be
* enabled. Read by Polymer.
* @private
*/
setPINButtonValid_: {
type: Boolean,
value: false,
},
/**
* The value of the new PIN textbox. Read/write by Polymer.
* @private
*/
newPIN_: {
type: String,
value: '',
},
/** @private */
confirmPIN_: {
type: String,
value: '',
},
/** @private */
currentPIN_: {
type: String,
value: '',
},
/**
* The number of PIN attempts remaining.
* @private
*/
retries_: Number,
/**
* A CTAP error code when we don't recognise the specific error. Read by
* Polymer.
* @private
*/
errorCode_: Number,
/**
* Whether an entry for the current PIN should be displayed. (If no PIN
* has been set then it won't be shown.)
* @private
*/
showCurrentEntry_: {
type: Boolean,
value: false,
},
/**
* Error string to display under the current PIN entry, or empty.
* @private
*/
currentPINError_: {
type: String,
value: '',
},
/**
* Error string to display under the new PIN entry, or empty.
* @private
*/
newPINError_: {
type: String,
value: '',
},
/**
* Error string to display under the confirmation PIN entry, or empty.
* @private
*/
confirmPINError_: {
type: String,
value: '',
},
/**
* Whether the dialog process has completed, successfully or otherwise.
* @private
*/
complete_: {
type: Boolean,
value: false,
},
/**
* The id of an element on the page that is currently shown.
* @private {!settings.SetPINDialogPage}
*/
shown_: {
type: String,
value: SetPINDialogPage.INITIAL,
},
/**
* Whether the contents of the PIN entries are visible, or are displayed
* like passwords.
* @private
*/
pinsVisible_: {
type: Boolean,
value: false,
},
/** @private */
title_: String,
},
const SetPINDialogPage = settings.SetPINDialogPage; /** @private {?settings.SecurityKeysPINBrowserProxy} */
browserProxy_: null,
/** @override */
attached() {
this.title_ = this.i18n('securityKeysSetPINInitialTitle');
this.browserProxy_ =
settings.SecurityKeysPINBrowserProxyImpl.getInstance();
this.$.dialog.showModal();
Polymer.RenderStatus.afterNextRender(this, function() {
Polymer.IronA11yAnnouncer.requestAvailability();
});
this.browserProxy_.startSetPIN().then(([success, errorCode]) => {
if (success) {
// Operation is complete. errorCode is a CTAP error code. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
if (errorCode == 1 /* INVALID_COMMAND */) {
this.shown_ = SetPINDialogPage.NO_PIN_SUPPORT;
this.finish_();
} else if (errorCode == 52 /* temporarily locked */) {
this.shown_ = SetPINDialogPage.REINSERT;
this.finish_();
} else if (errorCode == 50 /* locked */) {
this.shown_ = SetPINDialogPage.LOCKED;
this.finish_();
} else {
this.errorCode_ = errorCode;
this.shown_ = SetPINDialogPage.ERROR;
this.finish_();
}
} else if (errorCode == 0) {
// A device can also signal that it is locked by returning zero
// retries.
this.shown_ = SetPINDialogPage.LOCKED;
this.finish_();
} else {
// Need to prompt for a pin. Initially set the text boxes to valid so
// that they don't all appear red without the user typing anything.
this.currentPINValid_ = true;
this.newPINValid_ = true;
this.confirmPINValid_ = true;
this.setPINButtonValid_ = true;
this.retries_ = errorCode;
// retries_ may be null to indicate that there is currently no PIN
// set.
let focusTarget;
if (this.retries_ === null) {
this.showCurrentEntry_ = false;
focusTarget = this.$.newPIN;
this.title_ = this.i18n('securityKeysSetPINCreateTitle');
} else {
this.showCurrentEntry_ = true;
focusTarget = this.$.currentPIN;
this.title_ = this.i18n('securityKeysSetPINChangeTitle');
}
this.shown_ = SetPINDialogPage.PIN_PROMPT;
// Focus cannot be set directly from within a backend callback.
window.setTimeout(function() {
focusTarget.focus();
}, 0);
this.fire('ui-ready'); // for test synchronization.
}
});
},
Polymer({ /** @private */
is: 'settings-security-keys-set-pin-dialog', closeDialog_() {
this.$.dialog.close();
this.finish_();
},
behaviors: [I18nBehavior], /** @private */
finish_() {
if (this.complete_) {
return;
}
this.complete_ = true;
// Setting |complete_| to true hides the |pinSubmitNew| button while it
// has focus, which in turn causes the browser to move focus to the <body>
// element, which in turn prevents subsequent "Enter" keystrokes to be
// handled by cr-dialog itself. Re-focusing manually fixes this.
this.$.dialog.focus();
this.browserProxy_.close();
},
properties: {
/** /**
* Whether the value of the current PIN textbox is a valid PIN or not. * @param {!Event} e
* @private * @private
*/ */
currentPINValid_: Boolean, onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering
// the listener within settings-animated-pages.
e.stopPropagation();
},
/** @private */
onCurrentPINInput_() {
// Typing in the current PIN box after an error makes the error message
// disappear.
this.currentPINError_ = '';
},
/** @private */ /** @private */
newPINValid_: Boolean, onNewPINInput_() {
// Typing in the new PIN box after an error makes the error message
// disappear.
this.newPINError_ = '';
},
/** @private */ /** @private */
confirmPINValid_: Boolean, onConfirmPINInput_() {
// Typing in the confirm PIN box after an error makes the error message
// disappear.
this.confirmPINError_ = '';
},
/** /**
* Whether the dialog is in a state where the Set PIN button should be @param {string} pin A candidate PIN.
* enabled. Read by Polymer. @return {string} An error string or else '' to indicate validity.
* @private @private
*/ */
setPINButtonValid_: { isValidPIN_(pin) {
type: Boolean, // The UTF-8 encoding of the PIN must be between 4 and 63 bytes, and the
value: false, // final byte cannot be zero.
const utf8Encoded = new TextEncoder().encode(pin);
if (utf8Encoded.length < 4) {
return this.i18n('securityKeysPINTooShort');
}
if (utf8Encoded.length > 63 ||
// If the PIN somehow has a NUL at the end then it's invalid, but this
// is so obscure that we don't try to message it. Rather we just say
// that it's too long because trimming the final character is the best
// response by the user.
utf8Encoded[utf8Encoded.length - 1] == 0) {
return this.i18n('securityKeysPINTooLong');
}
// A PIN must contain at least four code-points. Javascript strings are
// UCS-2 and the |length| property counts UCS-2 elements, not code-points.
// (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.)
// Therefore, iterate over the string (which does yield codepoints) and
// check that four or more were seen.
let length = 0;
for (const codepoint of pin) {
length++;
}
if (length < 4) {
return this.i18n('securityKeysPINTooShort');
}
return '';
}, },
/** /**
* The value of the new PIN textbox. Read/write by Polymer. * @param {number} retries The number of PIN attempts remaining.
* @return {string} The message to show under the text box.
* @private * @private
*/ */
newPIN_: { mismatchError_(retries) {
type: String, // Warn the user if the number of retries is getting low.
value: '', if (1 < retries && retries <= 3) {
}, return this.i18n(
'securityKeysPINIncorrectRetriesPl', retries.toString());
/** @private */ }
confirmPIN_: { if (retries == 1) {
type: String, return this.i18n('securityKeysPINIncorrectRetriesSin');
value: '', }
return this.i18n('securityKeysPINIncorrect');
}, },
/** @private */ /**
currentPIN_: { * Called to set focus from inside a callback.
type: String, * @private
value: '', */
focusOn_(focusTarget) {
// Focus cannot be set directly from within a backend callback. Also,
// directly focusing |currentPIN| doesn't always seem to work(!). Thus
// focus something else first, which is a hack that seems to solve the
// problem.
let preFocusTarget = this.$.newPIN;
if (preFocusTarget == focusTarget) {
preFocusTarget = this.$.currentPIN;
}
window.setTimeout(function() {
preFocusTarget.focus();
focusTarget.focus();
}, 0);
}, },
/** /**
* The number of PIN attempts remaining. * Called by Polymer when the Set PIN button is activated.
* @private * @private
*/ */
retries_: Number, pinSubmitNew_() {
if (this.showCurrentEntry_) {
this.currentPINError_ = this.isValidPIN_(this.currentPIN_);
if (this.currentPINError_ != '') {
this.focusOn_(this.$.currentPIN);
this.fire('iron-announce', {text: this.currentPINError_});
this.fire('ui-ready'); // for test synchronization.
return;
}
}
this.newPINError_ = this.isValidPIN_(this.newPIN_);
if (this.newPINError_ != '') {
this.focusOn_(this.$.newPIN);
this.fire('iron-announce', {text: this.newPINError_});
this.fire('ui-ready'); // for test synchronization.
return;
}
if (this.newPIN_ != this.confirmPIN_) {
this.confirmPINError_ = this.i18n('securityKeysPINMismatch');
this.focusOn_(this.$.confirmPIN);
this.fire('iron-announce', {text: this.confirmPINError_});
this.fire('ui-ready'); // for test synchronization.
return;
}
this.setPINButtonValid_ = false;
this.browserProxy_.setPIN(this.currentPIN_, this.newPIN_).then(result => {
// This call always completes the process so result[0] is always 1.
// result[1] is a CTAP2 error code. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
if (result[1] == 0 /* SUCCESS */) {
this.shown_ = SetPINDialogPage.SUCCESS;
this.finish_();
} else if (result[1] == 52 /* temporarily locked */) {
this.shown_ = SetPINDialogPage.REINSERT;
this.finish_();
} else if (result[1] == 50 /* locked */) {
this.shown_ = SetPINDialogPage.LOCKED;
this.finish_();
} else if (result[1] == 49 /* PIN_INVALID */) {
this.currentPINValid_ = false;
this.retries_--;
this.currentPINError_ = this.mismatchError_(this.retries_);
this.setPINButtonValid_ = true;
this.focusOn_(this.$.currentPIN);
this.fire('iron-announce', {text: this.currentPINError_});
this.fire('ui-ready'); // for test synchronization.
} else {
// Unknown error.
this.errorCode_ = result[1];
this.shown_ = SetPINDialogPage.ERROR;
this.finish_();
}
});
},
/** /**
* A CTAP error code when we don't recognise the specific error. Read by * onClick handler for the show/hide icon.
* Polymer.
* @private * @private
*/ */
errorCode_: Number, showPINsClick_() {
this.pinsVisible_ = !this.pinsVisible_;
},
/** /**
* Whether an entry for the current PIN should be displayed. (If no PIN * Polymer helper function to detect when an error string is empty.
* has been set then it won't be shown.) * @param {string} s Arbitrary string
* @return {boolean} True iff |s| is non-empty.
* @private * @private
*/ */
showCurrentEntry_: { isNonEmpty_(s) {
type: Boolean, return s != '';
value: false,
}, },
/** /**
* Error string to display under the current PIN entry, or empty. * Called by Polymer when |errorCode_| changes to set the error string.
* @private * @private
*/ */
currentPINError_: { pinFailed_() {
type: String, if (this.errorCode_ === null) {
value: '', return '';
}
return this.i18n('securityKeysPINError', this.errorCode_.toString());
}, },
/** /**
* Error string to display under the new PIN entry, or empty. * @return {string} The class of the Ok / Cancel button.
* @private * @private
*/ */
newPINError_: { maybeActionButton_() {
type: String, return this.complete_ ? 'action-button' : 'cancel-button';
value: '',
}, },
/** /**
* Error string to display under the confirmation PIN entry, or empty. * @return {string} The label of the Ok / Cancel button.
* @private * @private
*/ */
confirmPINError_: { closeText_() {
type: String, return this.i18n(this.complete_ ? 'ok' : 'cancel');
value: '',
}, },
/** /**
* Whether the dialog process has completed, successfully or otherwise. * @return {string} The class (and thus icon) to be displayed.
* @private * @private
*/ */
complete_: { showPINsClass_() {
type: Boolean, return 'icon-visibility' + (this.pinsVisible_ ? '-off' : '');
value: false,
}, },
/** /**
* The id of an element on the page that is currently shown. * @return {string} The tooltip for the icon.
* @private {!settings.SetPINDialogPage} * @private
*/ */
shown_: { showPINsTitle_() {
type: String, return this.i18n(
value: SetPINDialogPage.INITIAL, this.pinsVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs');
}, },
/** /**
* Whether the contents of the PIN entries are visible, or are displayed * @return {string} The PIN-input element type.
* like passwords.
* @private * @private
*/ */
pinsVisible_: { inputType_() {
type: Boolean, return this.pinsVisible_ ? 'text' : 'password';
value: false,
}, },
});
/** @private */ return {
title_: String, SetPINDialogPage: SetPINDialogPage,
}, };
/** @private {?settings.SecurityKeysPINBrowserProxy} */
browserProxy_: null,
/** @override */
attached() {
this.title_ = this.i18n('securityKeysSetPINInitialTitle');
this.browserProxy_ = settings.SecurityKeysPINBrowserProxyImpl.getInstance();
this.$.dialog.showModal();
Polymer.RenderStatus.afterNextRender(this, function() {
Polymer.IronA11yAnnouncer.requestAvailability();
});
this.browserProxy_.startSetPIN().then(([success, errorCode]) => {
if (success) {
// Operation is complete. errorCode is a CTAP error code. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
if (errorCode == 1 /* INVALID_COMMAND */) {
this.shown_ = SetPINDialogPage.NO_PIN_SUPPORT;
this.finish_();
} else if (errorCode == 52 /* temporarily locked */) {
this.shown_ = SetPINDialogPage.REINSERT;
this.finish_();
} else if (errorCode == 50 /* locked */) {
this.shown_ = SetPINDialogPage.LOCKED;
this.finish_();
} else {
this.errorCode_ = errorCode;
this.shown_ = SetPINDialogPage.ERROR;
this.finish_();
}
} else if (errorCode == 0) {
// A device can also signal that it is locked by returning zero retries.
this.shown_ = SetPINDialogPage.LOCKED;
this.finish_();
} else {
// Need to prompt for a pin. Initially set the text boxes to valid so
// that they don't all appear red without the user typing anything.
this.currentPINValid_ = true;
this.newPINValid_ = true;
this.confirmPINValid_ = true;
this.setPINButtonValid_ = true;
this.retries_ = errorCode;
// retries_ may be null to indicate that there is currently no PIN set.
let focusTarget;
if (this.retries_ === null) {
this.showCurrentEntry_ = false;
focusTarget = this.$.newPIN;
this.title_ = this.i18n('securityKeysSetPINCreateTitle');
} else {
this.showCurrentEntry_ = true;
focusTarget = this.$.currentPIN;
this.title_ = this.i18n('securityKeysSetPINChangeTitle');
}
this.shown_ = SetPINDialogPage.PIN_PROMPT;
// Focus cannot be set directly from within a backend callback.
window.setTimeout(function() {
focusTarget.focus();
}, 0);
this.fire('ui-ready'); // for test synchronization.
}
});
},
/** @private */
closeDialog_() {
this.$.dialog.close();
this.finish_();
},
/** @private */
finish_() {
if (this.complete_) {
return;
}
this.complete_ = true;
// Setting |complete_| to true hides the |pinSubmitNew| button while it
// has focus, which in turn causes the browser to move focus to the <body>
// element, which in turn prevents subsequent "Enter" keystrokes to be
// handled by cr-dialog itself. Re-focusing manually fixes this.
this.$.dialog.focus();
this.browserProxy_.close();
},
/**
* @param {!Event} e
* @private
*/
onIronSelect_(e) {
// Prevent this event from bubbling since it is unnecessarily triggering the
// listener within settings-animated-pages.
e.stopPropagation();
},
/** @private */
onCurrentPINInput_() {
// Typing in the current PIN box after an error makes the error message
// disappear.
this.currentPINError_ = '';
},
/** @private */
onNewPINInput_() {
// Typing in the new PIN box after an error makes the error message
// disappear.
this.newPINError_ = '';
},
/** @private */
onConfirmPINInput_() {
// Typing in the confirm PIN box after an error makes the error message
// disappear.
this.confirmPINError_ = '';
},
/**
@param {string} pin A candidate PIN.
@return {string} An error string or else '' to indicate validity.
@private
*/
isValidPIN_(pin) {
// The UTF-8 encoding of the PIN must be between 4 and 63 bytes, and the
// final byte cannot be zero.
const utf8Encoded = new TextEncoder().encode(pin);
if (utf8Encoded.length < 4) {
return this.i18n('securityKeysPINTooShort');
}
if (utf8Encoded.length > 63 ||
// If the PIN somehow has a NUL at the end then it's invalid, but this
// is so obscure that we don't try to message it. Rather we just say
// that it's too long because trimming the final character is the best
// response by the user.
utf8Encoded[utf8Encoded.length - 1] == 0) {
return this.i18n('securityKeysPINTooLong');
}
// A PIN must contain at least four code-points. Javascript strings are
// UCS-2 and the |length| property counts UCS-2 elements, not code-points.
// (For example, '\u{1f6b4}'.length == 2, but it's a single code-point.)
// Therefore, iterate over the string (which does yield codepoints) and
// check that four or more were seen.
let length = 0;
for (const codepoint of pin) {
length++;
}
if (length < 4) {
return this.i18n('securityKeysPINTooShort');
}
return '';
},
/**
* @param {number} retries The number of PIN attempts remaining.
* @return {string} The message to show under the text box.
* @private
*/
mismatchError_(retries) {
// Warn the user if the number of retries is getting low.
if (1 < retries && retries <= 3) {
return this.i18n('securityKeysPINIncorrectRetriesPl', retries.toString());
}
if (retries == 1) {
return this.i18n('securityKeysPINIncorrectRetriesSin');
}
return this.i18n('securityKeysPINIncorrect');
},
/**
* Called to set focus from inside a callback.
* @private
*/
focusOn_(focusTarget) {
// Focus cannot be set directly from within a backend callback. Also,
// directly focusing |currentPIN| doesn't always seem to work(!). Thus
// focus something else first, which is a hack that seems to solve the
// problem.
let preFocusTarget = this.$.newPIN;
if (preFocusTarget == focusTarget) {
preFocusTarget = this.$.currentPIN;
}
window.setTimeout(function() {
preFocusTarget.focus();
focusTarget.focus();
}, 0);
},
/**
* Called by Polymer when the Set PIN button is activated.
* @private
*/
pinSubmitNew_() {
if (this.showCurrentEntry_) {
this.currentPINError_ = this.isValidPIN_(this.currentPIN_);
if (this.currentPINError_ != '') {
this.focusOn_(this.$.currentPIN);
this.fire('iron-announce', {text: this.currentPINError_});
this.fire('ui-ready'); // for test synchronization.
return;
}
}
this.newPINError_ = this.isValidPIN_(this.newPIN_);
if (this.newPINError_ != '') {
this.focusOn_(this.$.newPIN);
this.fire('iron-announce', {text: this.newPINError_});
this.fire('ui-ready'); // for test synchronization.
return;
}
if (this.newPIN_ != this.confirmPIN_) {
this.confirmPINError_ = this.i18n('securityKeysPINMismatch');
this.focusOn_(this.$.confirmPIN);
this.fire('iron-announce', {text: this.confirmPINError_});
this.fire('ui-ready'); // for test synchronization.
return;
}
this.setPINButtonValid_ = false;
this.browserProxy_.setPIN(this.currentPIN_, this.newPIN_).then(result => {
// This call always completes the process so result[0] is always 1.
// result[1] is a CTAP2 error code. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#error-responses
if (result[1] == 0 /* SUCCESS */) {
this.shown_ = SetPINDialogPage.SUCCESS;
this.finish_();
} else if (result[1] == 52 /* temporarily locked */) {
this.shown_ = SetPINDialogPage.REINSERT;
this.finish_();
} else if (result[1] == 50 /* locked */) {
this.shown_ = SetPINDialogPage.LOCKED;
this.finish_();
} else if (result[1] == 49 /* PIN_INVALID */) {
this.currentPINValid_ = false;
this.retries_--;
this.currentPINError_ = this.mismatchError_(this.retries_);
this.setPINButtonValid_ = true;
this.focusOn_(this.$.currentPIN);
this.fire('iron-announce', {text: this.currentPINError_});
this.fire('ui-ready'); // for test synchronization.
} else {
// Unknown error.
this.errorCode_ = result[1];
this.shown_ = SetPINDialogPage.ERROR;
this.finish_();
}
});
},
/**
* onClick handler for the show/hide icon.
* @private
*/
showPINsClick_() {
this.pinsVisible_ = !this.pinsVisible_;
},
/**
* Polymer helper function to detect when an error string is empty.
* @param {string} s Arbitrary string
* @return {boolean} True iff |s| is non-empty.
* @private
*/
isNonEmpty_(s) {
return s != '';
},
/**
* Called by Polymer when |errorCode_| changes to set the error string.
* @private
*/
pinFailed_() {
if (this.errorCode_ === null) {
return '';
}
return this.i18n('securityKeysPINError', this.errorCode_.toString());
},
/**
* @return {string} The class of the Ok / Cancel button.
* @private
*/
maybeActionButton_() {
return this.complete_ ? 'action-button' : 'cancel-button';
},
/**
* @return {string} The label of the Ok / Cancel button.
* @private
*/
closeText_() {
return this.i18n(this.complete_ ? 'ok' : 'cancel');
},
/**
* @return {string} The class (and thus icon) to be displayed.
* @private
*/
showPINsClass_() {
return 'icon-visibility' + (this.pinsVisible_ ? '-off' : '');
},
/**
* @return {string} The tooltip for the icon.
* @private
*/
showPINsTitle_() {
return this.i18n(
this.pinsVisible_ ? 'securityKeysHidePINs' : 'securityKeysShowPINs');
},
/**
* @return {string} The PIN-input element type.
* @private
*/
inputType_() {
return this.pinsVisible_ ? 'text' : 'password';
},
}); });
})();
...@@ -2,55 +2,51 @@ ...@@ -2,55 +2,51 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
(function() { cr.define('settings.WebsiteUsagePrivateApi', function() {
'use strict'; Polymer({
is: 'website-usage-private-api',
properties: {
/**
* The amount of data used by the given website.
*/
websiteDataUsage: {
type: String,
notify: true,
},
/**
* The number of cookies used by the given website.
*/
websiteCookieUsage: {
type: String,
notify: true,
},
},
Polymer({ /** @override */
is: 'website-usage-private-api', attached() {
settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance = this;
},
properties: { /** @param {string} host */
/** fetchUsageTotal(host) {
* The amount of data used by the given website. settings.WebsiteUsagePrivateApi.fetchUsageTotal(host);
*/
websiteDataUsage: {
type: String,
notify: true,
}, },
/** /**
* The number of cookies used by the given website. * @param {string} origin
*/ */
websiteCookieUsage: { clearUsage(origin) {
type: String, settings.WebsiteUsagePrivateApi.clearUsage(origin);
notify: true,
}, },
},
/** @override */
attached() {
settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance = this;
},
/** @param {string} host */ /** @param {string} origin */
fetchUsageTotal(host) { notifyUsageDeleted(origin) {
settings.WebsiteUsagePrivateApi.fetchUsageTotal(host); this.fire('usage-deleted', {origin: origin});
}, },
});
/**
* @param {string} origin
*/
clearUsage(origin) {
settings.WebsiteUsagePrivateApi.clearUsage(origin);
},
/** @param {string} origin */
notifyUsageDeleted(origin) {
this.fire('usage-deleted', {origin: origin});
},
});
})();
cr.define('settings.WebsiteUsagePrivateApi', function() {
/** /**
* @type {Object} An instance of the polymer object defined above. * @type {Object} An instance of the polymer object defined above.
* All data will be set here. * All data will be set here.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment