// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'all-sites' is the polymer element for showing the list of all sites under
* Site Settings.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.js';
import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
import 'chrome://resources/cr_elements/md_select.css.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
import '../settings_shared.css.js';
import './all_sites_icons.html.js';
import './clear_storage_dialog_shared.css.js';
import './site_entry.js';
import type {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import type {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import type {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {GlobalScrollTargetMixin} from '../global_scroll_target_mixin.js';
import {loadTimeData} from '../i18n_setup.js';
import type {MetricsBrowserProxy} from '../metrics_browser_proxy.js';
import {DeleteBrowsingDataAction, MetricsBrowserProxyImpl} from '../metrics_browser_proxy.js';
import {routes} from '../route.js';
import type {Route} from '../router.js';
import {RouteObserverMixin, Router} from '../router.js';
import {getTemplate} from './all_sites.html.js';
import {AllSitesAction2, AllSitesDialog, ContentSetting, SortMethod} from './constants.js';
import {SiteSettingsMixin} from './site_settings_mixin.js';
import type {OriginInfo, SiteGroup} from './site_settings_prefs_browser_proxy.js';
interface ActionMenuModel {
actionScope: string;
index: number;
item: SiteGroup;
origin: string;
isPartitioned: boolean;
path: string;
target: HTMLElement;
}
type OpenMenuEvent = CustomEvent<ActionMenuModel>;
type RemoveSiteEvent = CustomEvent<ActionMenuModel>;
interface SelectedItem {
item: SiteGroup;
index: number;
}
declare global {
interface HTMLElementEventMap {
'open-menu': OpenMenuEvent;
'remove-site': RemoveSiteEvent;
'site-entry-selected': CustomEvent<SelectedItem>;
}
}
export interface AllSitesElement {
$: {
allSitesList: IronListElement,
clearAllButton: HTMLElement,
clearLabel: HTMLElement,
confirmClearAllData: CrLazyRenderElement<CrDialogElement>,
confirmRemoveSite: CrLazyRenderElement<CrDialogElement>,
listContainer: HTMLElement,
menu: CrLazyRenderElement<CrActionMenuElement>,
sortMethod: HTMLSelectElement,
};
}
const AllSitesElementBase = GlobalScrollTargetMixin(RouteObserverMixin(
WebUiListenerMixin(I18nMixin(SiteSettingsMixin(PolymerElement)))));
const RWS_RELATED_SEARCH_PREFIX: string = 'related:';
export class AllSitesElement extends AllSitesElementBase {
static get is() {
return 'all-sites';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
// TODO(crbug.com/40112954): Refactor siteGroupMap to use an Object
// instead of a Map so that it's observable by Polymer more naturally. As
// it stands, one cannot use computed properties based off the value of
// siteGroupMap nor can one use observable functions to listen to changes
// to siteGroupMap.
/**
* Map containing sites to display in the widget, grouped into their
* group names.
*/
siteGroupMap: {
type: Object,
value() {
return new Map();
},
},
/**
* Filtered site group list.
*/
filteredList_: {
type: Array,
},
/**
* Needed by GlobalScrollTargetMixin.
*/
subpageRoute: {
type: Object,
value: routes.SITE_SETTINGS_ALL,
readOnly: true,
},
/**
* The search query entered into the All Sites search textbox. Used to
* filter the All Sites list.
*/
filter: {
type: String,
value: '',
observer: 'forceListUpdate_',
},
/**
* All possible sort methods.
*/
sortMethods_: {
type: Object,
value: SortMethod,
readOnly: true,
},
/**
* Stores the last selected item in the All Sites list.
*/
selectedItem_: Object,
/**
* Used to track the last-focused element across rows for the
* FocusRowMixin.
*/
lastFocused_: Object,
/**
* Used to track whether the list of row items has been blurred for the
* FocusRowMixin.
*/
listBlurred_: Boolean,
actionMenuModel_: Object,
/**
* Used to determine if user is attempting to clear all site data
* rather than a single site or origin's data.
*/
clearAllData_: Boolean,
/**
* The selected sort method.
*/
sortMethod_: String,
/**
* The total usage of all sites for this profile.
*/
totalUsage_: {
type: String,
value: '0 B',
},
};
}
siteGroupMap: Map<string, SiteGroup>;
private filteredList_: SiteGroup[];
subpageRoute: Route;
filter: string;
private selectedItem_: SelectedItem|null;
private listBlurred_: boolean;
private actionMenuModel_: ActionMenuModel|null;
private clearAllData_: boolean;
private sortMethod_?: SortMethod;
private totalUsage_: string;
private metricsBrowserProxy: MetricsBrowserProxy =
MetricsBrowserProxyImpl.getInstance();
override ready() {
super.ready();
this.addWebUiListener(
'onStorageListFetched', this.onStorageListFetched.bind(this));
this.addEventListener(
'site-entry-selected', (e: CustomEvent<SelectedItem>) => {
this.selectedItem_ = e.detail;
});
this.addEventListener('open-menu', this.onOpenMenu_.bind(this));
this.addEventListener('remove-site', this.onRemoveSite_.bind(this));
const sortParam = Router.getInstance().getQueryParameters().get('sort');
if (sortParam !== null &&
Object.values(SortMethod).includes(sortParam as SortMethod)) {
this.$.sortMethod.value = sortParam;
}
this.sortMethod_ = this.$.sortMethod.value as (SortMethod | undefined);
}
override connectedCallback() {
super.connectedCallback();
// Set scrollOffset so the iron-list scrolling accounts for the space the
// title takes.
afterNextRender(this, () => {
this.$.allSitesList.scrollOffset = this.$.allSitesList.offsetTop;
});
}
/**
* Reload the site list when the all sites page is visited.
*
* RouteObserverBehavior
*/
override currentRouteChanged(currentRoute: Route, oldRoute?: Route) {
super.currentRouteChanged(currentRoute);
if (currentRoute === routes.SITE_SETTINGS_ALL &&
currentRoute !== oldRoute) {
this.populateList_();
}
}
/**
* Retrieves a list of all known sites with site details.
*/
private populateList_() {
this.browserProxy.getAllSites().then((response) => {
// Create a new map to make an observable change.
const newMap = new Map(this.siteGroupMap);
response.forEach(siteGroup => {
newMap.set(siteGroup.groupingKey, siteGroup);
});
this.siteGroupMap = newMap;
this.forceListUpdate_();
});
}
/**
* Integrate sites using storage into the existing sites map, as there
* may be overlap between the existing sites.
* @param list The list of sites using storage.
*/
onStorageListFetched(list: SiteGroup[]) {
// Create a new map to make an observable change.
const newMap = new Map(this.siteGroupMap);
list.forEach(storageSiteGroup => {
newMap.set(storageSiteGroup.groupingKey, storageSiteGroup);
});
this.siteGroupMap = newMap;
this.forceListUpdate_();
this.focusOnLastSelectedEntry_();
}
/**
* Update the total usage by all sites for this profile after updates
* to the list
*/
private updateTotalUsage_() {
let usageSum = 0;
for (const siteGroup of this.filteredList_) {
siteGroup.origins.forEach(origin => {
usageSum += origin.usage;
});
}
this.browserProxy.getFormattedBytes(usageSum).then(totalUsage => {
this.totalUsage_ = totalUsage;
});
}
/**
* Filters the all sites list with the given search query text.
* @param siteGroupMap The map of sites to filter.
* @param searchQuery The filter text.
*/
private filterPopulatedList_(
siteGroupMap: Map<string, SiteGroup>, searchQuery: string): SiteGroup[] {
const result = [];
for (const [_groupingKey, siteGroup] of siteGroupMap) {
if (this.isRwsFiltered_()) {
const rwsOwnerFilter =
this.filter.substring(this.filter.indexOf(':') + 1);
// Checking `siteGroup.rwsOwner` to ensure that we're not matching with
// site entries that are not a member of a related website set.
if (siteGroup.rwsOwner && siteGroup.rwsOwner === rwsOwnerFilter) {
result.push(siteGroup);
}
} else {
if (siteGroup.origins.find(
originInfo => originInfo.origin.includes(searchQuery))) {
result.push(siteGroup);
}
}
}
return this.sortSiteGroupList_(result);
}
/**
* Sorts the given SiteGroup list with the currently selected sort method.
* @param siteGroupList The list of sites to sort.
*/
private sortSiteGroupList_(siteGroupList: SiteGroup[]): SiteGroup[] {
const sortMethod = this.$.sortMethod.value;
if (!sortMethod) {
return siteGroupList;
}
if (sortMethod === SortMethod.MOST_VISITED) {
siteGroupList.sort(this.mostVisitedComparator_);
} else if (sortMethod === SortMethod.STORAGE) {
siteGroupList.sort(this.storageComparator_);
} else if (sortMethod === SortMethod.NAME) {
siteGroupList.sort(this.nameComparator_);
}
return siteGroupList;
}
/**
* Comparator used to sort SiteGroups by the amount of engagement the user has
* with the origins listed inside it. Note only the maximum engagement is used
* for each SiteGroup (as opposed to the sum) in order to prevent domains with
* higher numbers of origins from always floating to the top of the list.
*/
private mostVisitedComparator_(siteGroup1: SiteGroup, siteGroup2: SiteGroup):
number {
const getMaxEngagement = (max: number, originInfo: OriginInfo) => {
return (max > originInfo.engagement) ? max : originInfo.engagement;
};
const score1 = siteGroup1.origins.reduce(getMaxEngagement, 0);
const score2 = siteGroup2.origins.reduce(getMaxEngagement, 0);
return score2 - score1;
}
/**
* Comparator used to sort SiteGroups by the amount of storage they use. Note
* this sorts in descending order.
*/
private storageComparator_(siteGroup1: SiteGroup, siteGroup2: SiteGroup):
number {
const getOverallUsage = (siteGroup: SiteGroup) => {
let usage = 0;
siteGroup.origins.forEach(originInfo => {
usage += originInfo.usage;
});
return usage;
};
const siteGroup1Size = getOverallUsage(siteGroup1);
const siteGroup2Size = getOverallUsage(siteGroup2);
// Use the number of cookies as a tie breaker.
return siteGroup2Size - siteGroup1Size ||
siteGroup2.numCookies - siteGroup1.numCookies;
}
/**
* Comparator used to sort SiteGroups by their eTLD+1 name (domain).
*/
private nameComparator_(siteGroup1: SiteGroup, siteGroup2: SiteGroup):
number {
return siteGroup1.displayName.localeCompare(siteGroup2.displayName);
}
/**
* Called when the user chooses a different sort method to the default.
*/
private onSortMethodChanged_() {
this.sortMethod_ = this.$.sortMethod.value as SortMethod;
this.filteredList_ = this.sortSiteGroupList_(this.filteredList_);
// Force the iron-list to rerender its items, as the order has changed.
this.$.allSitesList.fire('iron-resize');
}
/**
* Forces the all sites list to update its list of items, taking into account
* the search query and the sort method, then re-renders it.
*/
private forceListUpdate_() {
this.filteredList_ =
this.filterPopulatedList_(this.siteGroupMap, this.filter);
this.updateTotalUsage_();
this.$.allSitesList.fire('iron-resize');
}
forceListUpdateForTesting() {
this.forceListUpdate_();
}
/**
* @return Whether the |siteGroupMap| is empty.
*/
private siteGroupMapEmpty_(): boolean {
return !this.siteGroupMap.size;
}
/**
* @return Whether the |filteredList_| is empty due to searching.
*/
private noSearchResultFound_(): boolean {
return !this.filteredList_.length && !this.siteGroupMapEmpty_();
}
/**
* Focus on previously selected entry.
*/
private focusOnLastSelectedEntry_() {
if (!this.selectedItem_ || this.siteGroupMap.size === 0) {
return;
}
// Focus the site-entry to ensure the iron-list renders it, otherwise
// the query selector will not be able to find it. Note the index is
// used here instead of the item, in case the item was already removed.
const index =
Math.max(0, Math.min(this.selectedItem_.index, this.siteGroupMap.size));
this.$.allSitesList.focusItem(index);
this.selectedItem_ = null;
}
/**
* Open the overflow menu and ensure that the item is visible in the scroll
* pane when its menu is opened (it is possible to open off-screen items using
* keyboard shortcuts).
*/
private onOpenMenu_(e: OpenMenuEvent) {
const index = e.detail.index;
const list = this.$.allSitesList;
if (index < list.firstVisibleIndex || index > list.lastVisibleIndex) {
list.scrollToIndex(index);
}
const target = e.detail.target;
this.actionMenuModel_ = e.detail;
this.$.menu.get().showAt(target);
}
private shouldShowClearAllButton_(): boolean {
return this.filteredList_.length > 0;
}
private shouldShowRwsLearnMore_(): boolean {
return this.isRwsFiltered_() && this.filteredList_ &&
this.filteredList_.length > 0;
}
private onShowRelatedSites_() {
this.browserProxy.recordAction(AllSitesAction2.FILTER_BY_FPS_OWNER);
this.$.menu.get().close();
const siteGroup = this.filteredList_[this.actionMenuModel_!.index];
const searchParams = new URLSearchParams(
'searchSubpage=' +
encodeURIComponent(RWS_RELATED_SEARCH_PREFIX + siteGroup.rwsOwner!));
const currentRoute = Router.getInstance().getCurrentRoute();
Router.getInstance().navigateTo(currentRoute, searchParams);
}
private onRemoveSite_(e: RemoveSiteEvent) {
this.actionMenuModel_ = e.detail;
this.$.confirmRemoveSite.get().showModal();
}
private onRemove_() {
this.$.confirmRemoveSite.get().showModal();
}
// Creates a placeholder origin used to hold cookies scoped at the eTLD+1
// level.
private generatePlaceholderOrigin_(
numCookies: number, origin: string, etldPlus1?: string): OriginInfo {
return {
origin: etldPlus1 ? `http://${etldPlus1}/` : origin,
engagement: 0,
usage: 0,
numCookies: numCookies,
hasPermissionSettings: false,
isInstalled: false,
isPartitioned: false,
};
}
private onConfirmRemoveSite_(e: Event) {
const {index, actionScope, origin, isPartitioned} = this.actionMenuModel_!;
const siteGroupToUpdate = this.filteredList_[index];
const updatedSiteGroup: SiteGroup = {
groupingKey: siteGroupToUpdate.groupingKey,
displayName: siteGroupToUpdate.displayName,
hasInstalledPWA: siteGroupToUpdate.hasInstalledPWA,
numCookies: siteGroupToUpdate.numCookies,
rwsOwner: siteGroupToUpdate.rwsOwner,
rwsNumMembers: siteGroupToUpdate.rwsNumMembers,
origins: [],
};
this.metricsBrowserProxy.recordDeleteBrowsingDataAction(
DeleteBrowsingDataAction.SITES_SETTINGS_PAGE);
if (actionScope === 'origin') {
if (isPartitioned) {
this.browserProxy.recordAction(
AllSitesAction2.REMOVE_ORIGIN_PARTITIONED);
this.browserProxy.clearPartitionedOriginDataAndCookies(
this.toUrl(origin)!.href, siteGroupToUpdate.groupingKey);
} else {
this.browserProxy.recordAction(AllSitesAction2.REMOVE_ORIGIN);
this.browserProxy.clearUnpartitionedOriginDataAndCookies(
this.toUrl(origin)!.href);
this.resetPermissionsForOrigin_(origin);
}
updatedSiteGroup.origins = siteGroupToUpdate.origins.filter(
o => (o.isPartitioned !== isPartitioned || o.origin !== origin));
updatedSiteGroup.hasInstalledPWA =
updatedSiteGroup.origins.some(o => o.isInstalled);
updatedSiteGroup.numCookies -=
siteGroupToUpdate.origins
.find(
o => o.isPartitioned === isPartitioned &&
o.origin === origin)!.numCookies;
if (updatedSiteGroup.origins.length === 0 &&
updatedSiteGroup.numCookies > 0) {
const originPlaceHolder = this.generatePlaceholderOrigin_(
updatedSiteGroup.numCookies, origin, updatedSiteGroup.etldPlus1);
updatedSiteGroup.origins.push(originPlaceHolder);
}
} else {
this.browserProxy.recordAction(AllSitesAction2.REMOVE_SITE_GROUP);
this.browserProxy.clearSiteGroupDataAndCookies(
siteGroupToUpdate.groupingKey);
siteGroupToUpdate.origins.forEach(originEntry => {
this.resetPermissionsForOrigin_(originEntry.origin);
});
if (updatedSiteGroup.rwsOwner) {
this.decrementRwsNumMembers_(updatedSiteGroup.rwsOwner);
}
}
this.updateSiteGroup_(index, updatedSiteGroup);
this.$.allSitesList.fire('iron-resize');
this.updateTotalUsage_();
this.onCloseDialog_(e);
}
/**
* Checks if a filter is applied.
* @return True if a filter is applied.
*/
private isFiltered_(): boolean {
return this.filter !== '';
}
/**
* Checks if a related website set search filter is applied.
* @return True if filter starts with `RWS_RELATED_SEARCH_PREFIX`.
*/
private isRwsFiltered_(): boolean {
return this.filter.startsWith(RWS_RELATED_SEARCH_PREFIX);
}
private getRwsLearnMoreLabel_() {
const rwsOwner = this.filter.substring(this.filter.indexOf(':') + 1);
return loadTimeData.getStringF(
'siteSettingsRelatedWebsiteSetsLearnMore', rwsOwner);
}
/**
* Selects the appropriate string to display for clear button based on whether
* a filter is applied.
* @return The appropriate |clearAllButton| string based on whether a filter
* is applied.
*/
private getClearDataButtonString_(): string {
const buttonStringId = this.isFiltered_() ?
'siteSettingsDeleteDisplayedStorageLabel' :
'siteSettingsDeleteAllStorageLabel';
return this.i18n(buttonStringId);
}
/**
* Selects the appropriate string to display for total usage based on whether
* a filter is applied.
* @return The appropriate |clearLabel| string based on whether a filter
* is applied.
*/
private getClearStorageDescription_(): string {
const descriptionId = this.isFiltered_() ?
'siteSettingsClearDisplayedStorageDescription' :
'siteSettingsClearAllStorageDescription';
return loadTimeData.substituteString(
this.i18n(descriptionId), this.totalUsage_);
}
/**
* Confirms the clearing of all storage data for all sites.
*/
private onConfirmClearAllData_(e: Event) {
e.preventDefault();
this.clearAllData_ = true;
const anyAppsInstalled = this.filteredList_.some(g => g.hasInstalledPWA);
const scopes = [AllSitesDialog.CLEAR_DATA, 'All'];
const installed = anyAppsInstalled ? 'Installed' : '';
this.recordUserAction_([...scopes, installed, 'DialogOpened']);
this.$.confirmClearAllData.get().showModal();
}
private onCloseDialog_(e: Event) {
chrome.metricsPrivate.recordUserAction('AllSites_DialogClosed');
(e.target as HTMLElement).closest('cr-dialog')!.close();
this.actionMenuModel_ = null;
this.$.menu.get().close();
}
private getRemoveSiteTitle_(): string {
if (this.actionMenuModel_ === null) {
return '';
}
const originScoped = this.actionMenuModel_.actionScope === 'origin';
const singleOriginSite =
!originScoped && this.actionMenuModel_.item.origins.length === 1;
if (this.actionMenuModel_.isPartitioned) {
assert(originScoped);
return loadTimeData.substituteString(this.i18n(
'siteSettingsRemoveSiteOriginPartitionedDialogTitle',
this.originRepresentation(this.actionMenuModel_.origin),
this.actionMenuModel_.item.displayName));
}
const numInstalledApps =
this.actionMenuModel_.item.origins
.filter(
o =>
!originScoped || this.actionMenuModel_!.origin === o.origin)
.filter(o => o.isInstalled)
.length;
let messageId;
if (originScoped || singleOriginSite) {
if (numInstalledApps === 1) {
messageId = 'siteSettingsRemoveSiteOriginAppDialogTitle';
} else {
assert(numInstalledApps === 0);
messageId = 'siteSettingsRemoveSiteOriginDialogTitle';
}
} else {
if (numInstalledApps > 1) {
messageId = 'siteSettingsRemoveSiteGroupAppPluralDialogTitle';
} else if (numInstalledApps === 1) {
messageId = 'siteSettingsRemoveSiteGroupAppDialogTitle';
} else {
messageId = 'siteSettingsRemoveSiteGroupDialogTitle';
}
}
let displayOrigin;
if (originScoped) {
displayOrigin = this.actionMenuModel_.origin;
} else if (singleOriginSite) {
displayOrigin = this.actionMenuModel_.item.origins[0].origin;
} else {
displayOrigin = this.actionMenuModel_.item.displayName;
}
return loadTimeData.substituteString(
this.i18n(messageId), this.originRepresentation(displayOrigin));
}
private getRemoveSiteLogoutBulletPoint_() {
if (this.actionMenuModel_ === null) {
return '';
}
const originScoped = this.actionMenuModel_.actionScope === 'origin';
const singleOriginSite =
!originScoped && this.actionMenuModel_.item.origins.length === 1;
return originScoped || singleOriginSite ?
this.i18n('siteSettingsRemoveSiteOriginLogout') :
this.i18n('siteSettingsRemoveSiteGroupLogout');
}
private showPermissionsBulletPoint_(): boolean {
if (this.actionMenuModel_ === null) {
return false;
}
// If the selected item if a site group, search all child origins for
// permissions. If it is not, only look at the relevant origin.
return this.actionMenuModel_.item.origins
.filter(
o => this.actionMenuModel_!.actionScope !== 'origin' ||
this.actionMenuModel_!.origin === o.origin)
.some(o => o.hasPermissionSettings);
}
/**
* Selects the appropriate title to display for clear storage confirmation
* dialog based on whether a filter is applied.
* @return The appropriate title for clear storage confirmation dialog.
*/
private getClearAllStorageDialogTitle_(): string {
const titleId = this.isFiltered_() ?
'siteSettingsDeleteDisplayedStorageDialogTitle' :
'siteSettingsDeleteAllStorageDialogTitle';
return loadTimeData.substituteString(this.i18n(titleId), this.totalUsage_);
}
/**
* Get the appropriate label for the clear data confirmation dialog, depending
* on whether any apps are installed and/or filter is applied.
* @return The appropriate description for clear data confirmation dialog.
*/
private getClearAllStorageDialogDescription_(): string {
const anyAppsInstalled = this.filteredList_.some(g => g.hasInstalledPWA);
let messageId;
if (anyAppsInstalled) {
messageId = this.isFiltered_() ?
'siteSettingsDeleteDisplayedStorageConfirmationInstalled' :
'siteSettingsDeleteAllStorageConfirmationInstalled';
} else {
messageId = this.isFiltered_() ?
'siteSettingsDeleteDisplayedStorageConfirmation' :
'siteSettingsDeleteAllStorageConfirmation';
}
return loadTimeData.substituteString(
this.i18n(messageId), this.totalUsage_);
}
/**
* Selects the appropriate string to display for the sign-out string in
* confirmation popup based on whether a filter is applied.
* @return The appropriate sign out confirmation string based on whether a
* filter is applied.
*/
private getClearAllStorageDialogSignOutLabel_(): string {
const signOutLabelId = this.isFiltered_() ?
'siteSettingsClearDisplayedStorageSignOut' :
'siteSettingsClearAllStorageSignOut';
return this.i18n(signOutLabelId);
}
private recordUserAction_(scopes: string[]) {
chrome.metricsPrivate.recordUserAction(
['AllSites', ...scopes].filter(Boolean).join('_'));
}
/**
* Decrements the number of rws members for a given owner eTLD+1 by 1.
* @param rwsOwner The related website set owner.
*/
private decrementRwsNumMembers_(rwsOwner: string) {
this.filteredList_.forEach((siteGroup, index) => {
if (siteGroup.rwsOwner === rwsOwner) {
this.set(
'filteredList_.' + index + '.rwsNumMembers',
siteGroup.rwsNumMembers! - 1);
}
});
}
/**
* Resets all permission settings for a single origin.
*/
private resetPermissionsForOrigin_(origin: string) {
this.browserProxy.setOriginPermissions(
origin, null, ContentSetting.DEFAULT);
}
/**
* Helper to remove data and cookies for a group.
* @param index The index of the target siteGroup in filteredList_ that should
* be cleared.
*/
private clearDataForSiteGroupIndex_(index: number) {
const siteGroupToUpdate = this.filteredList_[index];
const updatedSiteGroup: SiteGroup = {
groupingKey: siteGroupToUpdate.groupingKey,
displayName: siteGroupToUpdate.displayName,
hasInstalledPWA: siteGroupToUpdate.hasInstalledPWA,
numCookies: 0,
rwsOwner: siteGroupToUpdate.rwsOwner,
rwsNumMembers: siteGroupToUpdate.rwsNumMembers,
origins: [],
};
this.browserProxy.clearSiteGroupDataAndCookies(
siteGroupToUpdate.groupingKey);
for (let i = 0; i < siteGroupToUpdate.origins.length; ++i) {
const updatedOrigin = Object.assign({}, siteGroupToUpdate.origins[i]);
if (updatedOrigin.hasPermissionSettings) {
updatedOrigin.numCookies = 0;
updatedOrigin.usage = 0;
updatedSiteGroup.origins.push(updatedOrigin);
}
}
this.updateSiteGroup_(index, updatedSiteGroup);
}
/**
* Updates the UI after permissions have been reset or data/cookies
* have been cleared
* @param index The index of the target siteGroup in filteredList_ that should
* be updated.
* @param updatedSiteGroup The SiteGroup object that represents the new state.
*/
private updateSiteGroup_(index: number, updatedSiteGroup: SiteGroup) {
if (updatedSiteGroup.origins.length > 0) {
this.set('filteredList_.' + index, updatedSiteGroup);
this.siteGroupMap.set(updatedSiteGroup.groupingKey, updatedSiteGroup);
} else {
this.splice('filteredList_', index, 1);
this.siteGroupMap.delete(updatedSiteGroup.groupingKey);
}
}
/**
* Clear data and cookies for all sites.
*/
private onClearAllData_(e: Event) {
this.browserProxy.recordAction(AllSitesAction2.CLEAR_ALL_DATA);
const scopes = [AllSitesDialog.CLEAR_DATA, 'All'];
const anyAppsInstalled = this.filteredList_.some(g => g.hasInstalledPWA);
const installed = anyAppsInstalled ? 'Installed' : '';
this.recordUserAction_([...scopes, installed, 'Confirm']);
this.metricsBrowserProxy.recordDeleteBrowsingDataAction(
DeleteBrowsingDataAction.SITES_SETTINGS_PAGE);
if (this.isRwsFiltered_()) {
this.browserProxy.recordAction(AllSitesAction2.DELETE_FOR_ENTIRE_FPS);
}
for (let index = this.filteredList_.length - 1; index >= 0; index--) {
this.clearDataForSiteGroupIndex_(index);
}
// Needed to update the filteredList_ for the "No sites found" text to
// appear.
this.forceListUpdate_();
this.totalUsage_ = '0 B';
this.onCloseDialog_(e);
}
}
declare global {
interface HTMLElementTagNameMap {
'all-sites': AllSitesElement;
}
}
customElements.define(AllSitesElement.is, AllSitesElement);