blob: c115d3a551664ba6e8c5cf80e272eedbd59a1c84 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'site-data' handles showing the local storage summary list for all sites.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
import 'chrome://resources/cr_elements/cr_search_field/cr_search_field.js';
import 'chrome://resources/cr_elements/icons.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
import '../settings_shared_css.js';
import './site_data_entry.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
import {ListPropertyUpdateBehavior} from 'chrome://resources/js/list_property_update_behavior.m.js';
import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {GlobalScrollTargetBehavior, GlobalScrollTargetBehaviorImpl} from '../global_scroll_target_behavior.js';
import {loadTimeData} from '../i18n_setup.js';
import {MetricsBrowserProxyImpl, PrivacyElementInteractions} from '../metrics_browser_proxy.js';
import {routes} from '../route.js';
import {Route, RouteObserverBehavior, Router} from '../router.js';
import {LocalDataBrowserProxy, LocalDataBrowserProxyImpl, LocalDataItem} from './local_data_browser_proxy.js';
import {SiteSettingsBehavior} from './site_settings_behavior.js';
/**
* @typedef {{
* id: string,
* start: number,
* count: number,
* }}
*/
let CookieRemovePacket;
Polymer({
is: 'site-data',
_template: html`{__html_template__}`,
behaviors: [
I18nBehavior,
ListPropertyUpdateBehavior,
GlobalScrollTargetBehavior,
WebUIListenerBehavior,
],
properties: {
/**
* The current filter applied to the cookie data list.
*/
filter: {
observer: 'onFilterChanged_',
notify: true,
type: String,
},
/** @type {!Map<string, (string|Function)>} */
focusConfig: {
type: Object,
observer: 'focusConfigChanged_',
},
isLoading_: Boolean,
/** @type {!Array<!LocalDataItem>} */
sites: {
type: Array,
value() {
return [];
},
},
/**
* GlobalScrollTargetBehavior
* @override
*/
subpageRoute: {
type: Object,
value: routes.SITE_SETTINGS_SITE_DATA,
},
/** @private */
lastFocused_: Object,
/** @private */
listBlurred_: Boolean,
},
/** @private {LocalDataBrowserProxy} */
browserProxy_: null,
/**
* When navigating to site data details sub-page, |lastSelected_| holds the
* site name as well as the index of the selected site. This is used when
* navigating back to site data in order to focus on the correct site.
* @private {!{item: !LocalDataItem, index: number}|null}
*/
lastSelected_: null,
/** @override */
created() {
this.browserProxy_ = LocalDataBrowserProxyImpl.getInstance();
},
/** @override */
ready() {
this.addWebUIListener(
'on-tree-item-removed', this.updateSiteList_.bind(this));
},
/**
* Reload cookies when the site data page is visited.
*
* RouteObserverBehavior
* @param {!Route} currentRoute
* @param {!Route} previousRoute
* @protected
*/
currentRouteChanged(currentRoute, previousRoute) {
GlobalScrollTargetBehaviorImpl.currentRouteChanged.call(this, currentRoute);
// Reload cookies on navigation to the site data page from a different
// page. Avoid reloading on repeated navigations to the same page, as these
// are likely search queries.
if (currentRoute === routes.SITE_SETTINGS_SITE_DATA &&
currentRoute !== previousRoute) {
this.isLoading_ = true;
// Needed to fix iron-list rendering issue. The list will not render
// correctly until a scroll occurs.
// See https://crbug.com/853906.
const ironList = /** @type {!IronListElement} */ (this.$$('iron-list'));
ironList.scrollToIndex(0);
this.browserProxy_.reloadCookies().then(this.updateSiteList_.bind(this));
}
},
/**
* @param {!Map<string, (string|Function)>} newConfig
* @param {?Map<string, (string|Function)>} oldConfig
* @private
*/
focusConfigChanged_(newConfig, oldConfig) {
// focusConfig is set only once on the parent, so this observer should only
// fire once.
assert(!oldConfig);
// Populate the |focusConfig| map of the parent <settings-animated-pages>
// element, with additional entries that correspond to subpage trigger
// elements residing in this element's Shadow DOM.
if (routes.SITE_SETTINGS_DATA_DETAILS) {
const onNavigatedTo = () => this.async(() => {
if (this.lastSelected_ === null || this.sites.length === 0) {
return;
}
const lastSelectedSite = this.lastSelected_.item.site;
const lastSelectedIndex = this.lastSelected_.index;
this.lastSelected_ = null;
const indexFromId =
this.sites.findIndex(site => site.site === lastSelectedSite);
// If the site is no longer in |sites|, use the index as a fallback.
// Since the sites are sorted, an alternative could be to select the
// site that comes next in sort order.
const indexFallback = lastSelectedIndex < this.sites.length ?
lastSelectedIndex :
this.sites.length - 1;
const index = indexFromId > -1 ? indexFromId : indexFallback;
this.focusOnSiteSelectButton_(index);
});
this.focusConfig.set(
routes.SITE_SETTINGS_DATA_DETAILS.path, onNavigatedTo);
}
},
/**
* @param {number} index
* @private
*/
focusOnSiteSelectButton_(index) {
const ironList =
/** @type {!IronListElement} */ (this.$$('iron-list'));
ironList.focusItem(index);
const siteToSelect = this.sites[index].site.replace(/[.]/g, '\\.');
const button = this.$$(`#siteItem_${siteToSelect}`).$$('.subpage-arrow');
focusWithoutInk(assert(button));
},
/**
* @param {string} current
* @param {string|undefined} previous
* @private
*/
onFilterChanged_(current, previous) {
// Ignore filter changes which do not occur on the site data page. The
// site settings data details subpage expects the tree model to remain in
// the same state.
if (previous === undefined ||
Router.getInstance().getCurrentRoute() !==
routes.SITE_SETTINGS_SITE_DATA) {
return;
}
this.updateSiteList_();
},
/**
* Gather all the site data.
* @private
*/
updateSiteList_() {
this.isLoading_ = true;
this.browserProxy_.getDisplayList(this.filter).then(localDataItems => {
this.updateList('sites', item => item.site, localDataItems);
this.isLoading_ = false;
this.fire('site-data-list-complete');
});
},
/**
* Returns the string to use for the Remove label.
* @param {string} filter The current filter string.
* @return {string}
* @private
*/
computeRemoveLabel_(filter) {
if (filter.length === 0) {
return loadTimeData.getString('siteSettingsCookieRemoveAll');
}
return loadTimeData.getString('siteSettingsCookieRemoveAllShown');
},
/** @private */
onCloseDialog_() {
this.$.confirmDeleteDialog.close();
},
/** @private */
onCloseThirdPartyDialog_() {
this.$.confirmDeleteThirdPartyDialog.close();
},
/** @private */
onConfirmDeleteDialogClosed_() {
focusWithoutInk(assert(this.$.removeShowingSites));
},
/** @private */
onConfirmDeleteThirdPartyDialogClosed_() {
focusWithoutInk(assert(this.$.removeAllThirdPartyCookies));
},
/**
* Shows a dialog to confirm the deletion of multiple sites.
* @param {!Event} e
* @private
*/
onRemoveShowingSitesTap_(e) {
e.preventDefault();
this.$.confirmDeleteDialog.showModal();
},
/**
* Shows a dialog to confirm the deletion of cookies available
* in third-party contexts and associated site data.
* @private
*/
onRemoveThirdPartyCookiesTap_(e) {
e.preventDefault();
this.$.confirmDeleteThirdPartyDialog.showModal();
},
/**
* Called when deletion for all showing sites has been confirmed.
* @private
*/
onConfirmDelete_() {
this.$.confirmDeleteDialog.close();
if (this.filter.length === 0) {
MetricsBrowserProxyImpl.getInstance().recordSettingsPageHistogram(
PrivacyElementInteractions.SITE_DATA_REMOVE_ALL);
this.browserProxy_.removeAll().then(() => {
this.sites = [];
});
} else {
MetricsBrowserProxyImpl.getInstance().recordSettingsPageHistogram(
PrivacyElementInteractions.SITE_DATA_REMOVE_FILTERED);
this.browserProxy_.removeShownItems();
// We just deleted all items found by the filter, let's reset the filter.
this.fire('clear-subpage-search');
}
},
/**
* Called when deletion of all third-party cookies and site data has been
* confirmed.
* @private
*/
onConfirmThirdPartyDelete_() {
this.$.confirmDeleteThirdPartyDialog.close();
this.browserProxy_.removeAllThirdPartyCookies().then(() => {
this.updateSiteList_();
});
},
/**
* @param {!{model: !{item: !LocalDataItem, index: number}}} event
* @private
*/
onSiteClick_(event) {
// If any delete button is selected, the focus will be in a bad state when
// returning to this page. To avoid this, the site select button is given
// focus. See https://crbug.com/872197.
this.focusOnSiteSelectButton_(event.model.index);
Router.getInstance().navigateTo(
routes.SITE_SETTINGS_DATA_DETAILS,
new URLSearchParams('site=' + event.model.item.site));
this.lastSelected_ = event.model;
},
/**
* @private
* @return {boolean}
*/
showRemoveThirdPartyCookies_() {
return loadTimeData.getBoolean('enableRemovingAllThirdPartyCookies') &&
this.sites.length > 0 && this.filter.length === 0;
},
});