blob: d430a07e63643176767feb47f0f3f65df9d8c2ce [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/browsing_data/browsing_data_remover_impl.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/observer_list.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/browsing_data/browsing_data_filter_builder_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/browsing_data_remover_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/clear_data_filter.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_util.h"
using base::UserMetricsAction;
namespace content {
namespace {
// Timeout after which the History.ClearBrowsingData.Duration.SlowTasks180s
// histogram is recorded.
const base::TimeDelta kSlowTaskTimeout = base::Seconds(180);
base::OnceClosure RunsOrPostOnCurrentTaskRunner(base::OnceClosure closure) {
return base::BindOnce(
[](base::OnceClosure closure,
scoped_refptr<base::TaskRunner> task_runner) {
if (base::SingleThreadTaskRunner::GetCurrentDefault() == task_runner) {
std::move(closure).Run();
return;
}
task_runner->PostTask(FROM_HERE, std::move(closure));
},
std::move(closure), base::SingleThreadTaskRunner::GetCurrentDefault());
}
// Returns whether `storage_key` matches `origin_type_mask` given the special
// storage `policy`. If `origin_type_mask` contains embedder-specific
// datatypes, `embedder_matcher` must not be null; the decision for those
// datatypes will be delegated to it.
bool DoesStorageKeyMatchMask(
uint64_t origin_type_mask,
const BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher&
embedder_matcher,
const blink::StorageKey& storage_key,
storage::SpecialStoragePolicy* policy) {
const std::vector<std::string>& schemes = url::GetWebStorageSchemes();
bool is_web_scheme = base::Contains(schemes, storage_key.origin().scheme());
// If a websafe origin is unprotected, it matches iff UNPROTECTED_WEB.
if ((origin_type_mask & BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB) &&
is_web_scheme &&
(!policy || !policy->IsStorageProtected(storage_key.origin().GetURL()))) {
return true;
}
origin_type_mask &= ~BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB;
// Hosted applications (protected and websafe origins) iff PROTECTED_WEB.
if ((origin_type_mask & BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB) &&
is_web_scheme && policy &&
policy->IsStorageProtected(storage_key.origin().GetURL())) {
return true;
}
origin_type_mask &= ~BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB;
DCHECK(embedder_matcher || !origin_type_mask)
<< "The mask contains embedder-defined origin types, but there is no "
<< "embedder delegate matcher to process them.";
if (!embedder_matcher.is_null())
return embedder_matcher.Run(origin_type_mask, storage_key.origin(), policy);
return false;
}
} // namespace
BrowsingDataRemoverImpl::BrowsingDataRemoverImpl(
BrowserContext* browser_context)
: browser_context_(browser_context),
remove_mask_(0xffffffffffffffffull),
origin_type_mask_(0xffffffffffffffffull),
is_removing_(false),
storage_partition_for_testing_(nullptr) {
DCHECK(browser_context_);
}
BrowsingDataRemoverImpl::~BrowsingDataRemoverImpl() {
if (!task_queue_.empty()) {
VLOG(1) << "BrowsingDataRemoverImpl shuts down with " << task_queue_.size()
<< " pending tasks";
}
UMA_HISTOGRAM_EXACT_LINEAR("History.ClearBrowsingData.TaskQueueAtShutdown",
task_queue_.size(), 10);
// If we are still removing data, notify observers that their task has been
// (albeit unsuccessfully) processed, so they can unregister themselves.
while (!task_queue_.empty()) {
const RemovalTask& task = task_queue_.front();
for (Observer* observer : task.observers) {
if (observer_list_.HasObserver(observer)) {
observer->OnBrowsingDataRemoverDone(
/*failed_data_types=*/task.remove_mask);
}
}
task_queue_.pop_front();
}
}
void BrowsingDataRemoverImpl::SetRemoving(bool is_removing) {
DCHECK_NE(is_removing_, is_removing);
is_removing_ = is_removing;
if (embedder_delegate_) {
if (is_removing_) {
embedder_delegate_->OnStartRemoving();
} else {
embedder_delegate_->OnDoneRemoving();
}
}
}
void BrowsingDataRemoverImpl::SetEmbedderDelegate(
BrowsingDataRemoverDelegate* embedder_delegate) {
embedder_delegate_ = embedder_delegate;
}
bool BrowsingDataRemoverImpl::DoesOriginMatchMaskForTesting(
uint64_t origin_type_mask,
const url::Origin& origin,
storage::SpecialStoragePolicy* policy) {
BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher embedder_matcher;
if (embedder_delegate_)
embedder_matcher = embedder_delegate_->GetOriginTypeMatcher();
return DoesStorageKeyMatchMask(origin_type_mask, std::move(embedder_matcher),
blink::StorageKey::CreateFirstParty(origin),
policy);
}
void BrowsingDataRemoverImpl::Remove(const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
uint64_t origin_type_mask) {
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder>(), nullptr);
}
void BrowsingDataRemoverImpl::RemoveWithFilter(
const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
uint64_t origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), nullptr);
}
void BrowsingDataRemoverImpl::RemoveAndReply(const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
uint64_t origin_type_mask,
Observer* observer) {
DCHECK(observer);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder>(), observer);
}
void BrowsingDataRemoverImpl::RemoveWithFilterAndReply(
const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
uint64_t origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder,
Observer* observer) {
DCHECK(filter_builder);
DCHECK(observer);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), observer);
}
void BrowsingDataRemoverImpl::RemoveStorageBucketsAndReply(
const blink::StorageKey& storage_key,
const std::set<std::string>& storage_buckets,
base::OnceClosure callback) {
DCHECK(callback);
GetStoragePartition()->ClearDataForBuckets(
storage_key, storage_buckets,
base::BindPostTaskToCurrentDefault(
base::BindOnce(&BrowsingDataRemoverImpl::DidRemoveStorageBuckets,
GetWeakPtr(), std::move(callback))));
}
void BrowsingDataRemoverImpl::DidRemoveStorageBuckets(
base::OnceClosure callback) {
DCHECK(callback);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback).Run();
}
void BrowsingDataRemoverImpl::RemoveInternal(
const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
uint64_t origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder,
Observer* observer) {
DCHECK(!observer || observer_list_.HasObserver(observer))
<< "Every observer must register itself (by calling AddObserver()) "
<< "before observing a removal task.";
// Remove() and RemoveAndReply() pass a null pointer to indicate no filter.
// No filter is equivalent to one that |MatchesAllOriginsAndDomains()|.
if (!filter_builder) {
filter_builder = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve);
DCHECK(filter_builder->MatchesAllOriginsAndDomains());
}
RemovalTask task(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), observer);
// If there is an identical deletion task that is not already running,
// we don't have to perform the deletion twice.
for (size_t i = 1; i < task_queue_.size(); i++) {
if (task_queue_[i].IsSameDeletion(task)) {
if (observer)
task_queue_[i].observers.push_back(observer);
return;
}
}
task_queue_.push_back(std::move(task));
// If this is the only scheduled task, execute it immediately. Otherwise,
// it will be automatically executed when all tasks scheduled before it
// finish.
if (task_queue_.size() == 1) {
SetRemoving(true);
RunNextTask();
}
}
void BrowsingDataRemoverImpl::RunNextTask() {
DCHECK(!task_queue_.empty());
RemovalTask& removal_task = task_queue_.front();
removal_task.task_started = base::TimeTicks::Now();
// To detect tasks that are causing slow deletions, record running sub tasks
// after a delay.
slow_pending_tasks_closure_.Reset(base::BindOnce(
&BrowsingDataRemoverImpl::RecordUnfinishedSubTasks, GetWeakPtr()));
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE, slow_pending_tasks_closure_.callback(), kSlowTaskTimeout);
RemoveImpl(removal_task.delete_begin, removal_task.delete_end,
removal_task.remove_mask, removal_task.filter_builder.get(),
removal_task.origin_type_mask);
}
void BrowsingDataRemoverImpl::RemoveImpl(
const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
BrowsingDataFilterBuilder* filter_builder,
uint64_t origin_type_mask) {
// =============== README before adding more storage backends ===============
//
// If you're adding a data storage backend that is included among
// RemoveDataMask::FILTERABLE_DATATYPES, you must do one of the following:
// 1. Support one of the filters generated by |filter_builder|.
// 2. Add a comment explaining why is it acceptable in your case to delete all
// data without filtering URLs / origins / domains.
// 3. Do not support partial deletion, i.e. only delete your data if
// |filter_builder.MatchesAllOriginsAndDomains()|. Add a comment explaining
// why this is acceptable.
base::ScopedClosureRunner synchronous_clear_operations(
CreateTaskCompletionClosure(TracingDataType::kSynchronous));
TRACE_EVENT0("browsing_data", "BrowsingDataRemoverImpl::RemoveImpl");
// Asynchronous removal tasks might end up finishing after an arbitrary
// delay - this can postpone when OnTaskComplete runs. Therefore we need to
// check if destruction of our `browser_context_` might have started in the
// meantime. See also https://crbug.com/1216406.
if (browser_context_->ShutdownStarted()) {
// Conservatively mark *all* data types as failures.
failed_data_types_ |= remove_mask_;
return;
}
// crbug.com/140910: Many places were calling this with base::Time() as
// delete_end, even though they should've used base::Time::Max().
DCHECK_NE(base::Time(), delete_end);
DCHECK(domains_for_deferred_cookie_deletion_.empty());
delete_begin_ = delete_begin;
delete_end_ = delete_end;
remove_mask_ = remove_mask;
origin_type_mask_ = origin_type_mask;
failed_data_types_ = 0;
// Record the combined deletion of cookies and cache.
CookieOrCacheDeletionChoice choice = NEITHER_COOKIES_NOR_CACHE;
if (remove_mask & DATA_TYPE_COOKIES &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB) {
choice =
remove_mask & DATA_TYPE_CACHE ? BOTH_COOKIES_AND_CACHE : ONLY_COOKIES;
} else if (remove_mask & DATA_TYPE_CACHE) {
choice = ONLY_CACHE;
}
base::UmaHistogramEnumeration(
"History.ClearBrowsingData.UserDeletedCookieOrCache", choice,
MAX_CHOICE_VALUE);
//////////////////////////////////////////////////////////////////////////////
// INITIALIZATION
base::RepeatingCallback<bool(const GURL&)> url_filter =
filter_builder->BuildUrlFilter();
// Some backends support a filter that |is_null()| to make complete deletion
// more efficient.
base::RepeatingCallback<bool(const GURL&)> nullable_url_filter =
filter_builder->MatchesAllOriginsAndDomains()
? base::RepeatingCallback<bool(const GURL&)>()
: url_filter;
//////////////////////////////////////////////////////////////////////////////
// DATA_TYPE_DOWNLOADS
if ((remove_mask & DATA_TYPE_DOWNLOADS) &&
(!embedder_delegate_ || embedder_delegate_->MayRemoveDownloadHistory())) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Downloads"));
DownloadManager* download_manager = browser_context_->GetDownloadManager();
download_manager->RemoveDownloadsByURLAndTime(url_filter, delete_begin_,
delete_end_);
}
//////////////////////////////////////////////////////////////////////////////
// STORAGE PARTITION DATA
uint32_t storage_partition_remove_mask = 0;
// We ignore the DATA_TYPE_COOKIES request if UNPROTECTED_WEB is not set,
// so that callers who request DATA_TYPE_SITE_DATA with another origin type
// don't accidentally remove the cookies that are associated with the
// UNPROTECTED_WEB origin. This is necessary because cookies are not separated
// between UNPROTECTED_WEB and other origin types.
if (remove_mask & DATA_TYPE_COOKIES &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB) {
storage_partition_remove_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
// Interest groups should be cleared with cookies for its origin trial as
// the current FLEDGE implementation has the same privacy characteristics.
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS;
if (embedder_delegate_) {
domains_for_deferred_cookie_deletion_ =
embedder_delegate_->GetDomainsForDeferredCookieDeletion(remove_mask);
}
}
if (remove_mask & DATA_TYPE_LOCAL_STORAGE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
}
if (remove_mask & DATA_TYPE_INDEXED_DB) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
}
if (remove_mask & DATA_TYPE_WEB_SQL) {
storage_partition_remove_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
}
if (remove_mask & DATA_TYPE_SERVICE_WORKERS) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS;
}
if (remove_mask & DATA_TYPE_CACHE_STORAGE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE;
}
if (remove_mask & DATA_TYPE_FILE_SYSTEMS) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
}
if (remove_mask & DATA_TYPE_BACKGROUND_FETCH) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_BACKGROUND_FETCH;
}
if (remove_mask & DATA_TYPE_CACHE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUP_PERMISSIONS_CACHE;
// Tell the shader disk cache to clear.
base::RecordAction(UserMetricsAction("ClearBrowsingData_ShaderCache"));
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
}
if (remove_mask & DATA_TYPE_MEDIA_LICENSES) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_MEDIA_LICENSES;
}
if (remove_mask & DATA_TYPE_ATTRIBUTION_REPORTING_SITE_CREATED) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_ATTRIBUTION_REPORTING_SITE_CREATED;
}
if (remove_mask & DATA_TYPE_ATTRIBUTION_REPORTING_INTERNAL) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_ATTRIBUTION_REPORTING_INTERNAL;
}
if (remove_mask & DATA_TYPE_AGGREGATION_SERVICE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_AGGREGATION_SERVICE;
}
if (remove_mask & DATA_TYPE_PRIVATE_AGGREGATION_INTERNAL) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_PRIVATE_AGGREGATION_INTERNAL;
}
if (remove_mask & DATA_TYPE_INTEREST_GROUPS) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS;
}
if (remove_mask & DATA_TYPE_INTEREST_GROUPS_INTERNAL) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_INTEREST_GROUPS_INTERNAL;
}
if (remove_mask & DATA_TYPE_SHARED_STORAGE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SHARED_STORAGE;
}
StoragePartition* storage_partition = GetStoragePartition();
if (storage_partition_remove_mask) {
// If cookies are supposed to be conditionally deleted from the storage
// partition, create the deletion info object.
network::mojom::CookieDeletionFilterPtr deletion_filter;
if (!filter_builder->MatchesAllOriginsAndDomains() &&
(storage_partition_remove_mask &
StoragePartition::REMOVE_DATA_MASK_COOKIES)) {
deletion_filter = filter_builder->BuildCookieDeletionFilter();
} else {
deletion_filter = network::mojom::CookieDeletionFilter::New();
}
if (!domains_for_deferred_cookie_deletion_.empty()) {
// The data types that require deferred deletion are currently not
// filterable. If they become filterable we need to check if the
// selected domains should actually be deleted.
DCHECK(!deletion_filter->excluding_domains.has_value());
deletion_filter->excluding_domains =
domains_for_deferred_cookie_deletion_;
}
BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher embedder_matcher;
if (embedder_delegate_)
embedder_matcher = embedder_delegate_->GetOriginTypeMatcher();
// Rewrite leveldb instances to clean up data from disk if almost all data
// is deleted. Do not perform the cleanup for partial deletions or when only
// hosted app data is removed as this would be very slow.
bool perform_storage_cleanup =
delete_begin_.is_null() && delete_end_.is_max() &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB &&
filter_builder->GetMode() == BrowsingDataFilterBuilder::Mode::kPreserve;
storage_partition->ClearData(
storage_partition_remove_mask,
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, filter_builder,
base::BindRepeating(&DoesStorageKeyMatchMask, origin_type_mask_,
std::move(embedder_matcher)),
std::move(deletion_filter), perform_storage_cleanup, delete_begin_,
delete_end_,
CreateTaskCompletionClosure(TracingDataType::kStoragePartition));
}
//////////////////////////////////////////////////////////////////////////////
// CACHE
if (remove_mask & DATA_TYPE_CACHE) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_Cache"));
network::mojom::NetworkContext* network_context =
storage_partition->GetNetworkContext();
RenderProcessHostImpl::ClearAllResourceCaches();
// TODO(crbug.com/813882): implement retry on network service.
// The clearing of the HTTP cache happens in the network service process
// when enabled. Note that we've deprecated the concept of a media cache,
// and are now using a single cache for both purposes.
network_context->ClearHttpCache(
delete_begin, delete_end, filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(TracingDataType::kHttpCache));
if (base::FeatureList::IsEnabled(
features::kCodeCacheDeletionWithoutFilter)) {
// Experimentally perform preservelist deletions without filter and skip
// origin specific deletions. See crbug.com/1040039#26.
if (filter_builder->GetMode() ==
BrowsingDataFilterBuilder::Mode::kPreserve) {
storage_partition->ClearCodeCaches(
delete_begin, delete_end, /*filter=*/base::NullCallback(),
CreateTaskCompletionClosureForMojo(TracingDataType::kCodeCaches));
}
} else {
storage_partition->ClearCodeCaches(
delete_begin, delete_end, nullable_url_filter,
CreateTaskCompletionClosureForMojo(TracingDataType::kCodeCaches));
}
// TODO(crbug.com/1985971) : Implement filtering for NetworkHistory.
if (filter_builder->GetMode() ==
BrowsingDataFilterBuilder::Mode::kPreserve) {
// When clearing cache, wipe accumulated network related data
// (TransportSecurityState and HttpServerPropertiesManager data).
network_context->ClearNetworkingHistoryBetween(
delete_begin, delete_end,
CreateTaskCompletionClosureForMojo(TracingDataType::kNetworkHistory));
}
// Clears the PrefetchedSignedExchangeCache of all RenderFrameHostImpls.
RenderFrameHostImpl::ClearAllPrefetchedSignedExchangeCache();
// Clears the CORS PreFlight cache. We don't support delete_begin,
// delete_end time range, as the preflight cache max age is capped to 2hrs.
network_context->ClearCorsPreflightCache(
filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(TracingDataType::kPreflightCache));
}
//////////////////////////////////////////////////////////////////////////////
// Prototype Trust Token API (https://github.com/wicg/trust-token-api).
// We don't support clearing data for specific time ranges because much Trust
// Tokens state (e.g. issuers associated with each top-level origin) has no
// notion of associated creation time. Consequently, like for reporting and
// network error logging below, a data removal request for certain
// sites/origins that has the Trust Tokens type in scope will clear all Trust
// Tokens data associated with the requested sites/origins.
if (remove_mask & DATA_TYPE_TRUST_TOKENS) {
network::mojom::NetworkContext* network_context =
storage_partition->GetNetworkContext();
network_context->ClearTrustTokenData(
filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(TracingDataType::kTrustTokens));
}
//////////////////////////////////////////////////////////////////////////////
// Reporting cache.
// TODO(https://crbug.com/1291489): Add unit test to cover this.
if (remove_mask & DATA_TYPE_COOKIES) {
network::mojom::NetworkContext* network_context =
browser_context_->GetDefaultStoragePartition()->GetNetworkContext();
network_context->ClearReportingCacheClients(
filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(TracingDataType::kReportingCache));
network_context->ClearNetworkErrorLogging(
filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(
TracingDataType::kNetworkErrorLogging));
}
//////////////////////////////////////////////////////////////////////////////
// Auth cache.
if ((remove_mask & DATA_TYPE_COOKIES) &&
!(remove_mask & DATA_TYPE_AVOID_CLOSING_CONNECTIONS)) {
browser_context_->GetDefaultStoragePartition()
->GetNetworkContext()
->ClearHttpAuthCache(
delete_begin_.is_null() ? base::Time::Min() : delete_begin_,
delete_end_.is_null() ? base::Time::Max() : delete_end_,
CreateTaskCompletionClosureForMojo(TracingDataType::kAuthCache));
}
//////////////////////////////////////////////////////////////////////////////
// Embedder data.
if (embedder_delegate_) {
embedder_delegate_->RemoveEmbedderData(
delete_begin_, delete_end_, remove_mask, filter_builder,
origin_type_mask,
base::BindOnce(
&BrowsingDataRemoverImpl::OnDelegateDone, GetWeakPtr(),
CreateTaskCompletionClosure(TracingDataType::kEmbedderData)));
}
}
void BrowsingDataRemoverImpl::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void BrowsingDataRemoverImpl::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void BrowsingDataRemoverImpl::SetWouldCompleteCallbackForTesting(
const base::RepeatingCallback<
void(base::OnceClosure continue_to_completion)>& callback) {
would_complete_callback_ = callback;
}
void BrowsingDataRemoverImpl::OverrideStoragePartitionForTesting(
StoragePartition* storage_partition) {
storage_partition_for_testing_ = storage_partition;
}
const base::Time& BrowsingDataRemoverImpl::GetLastUsedBeginTimeForTesting() {
return delete_begin_;
}
uint64_t BrowsingDataRemoverImpl::GetLastUsedRemovalMaskForTesting() {
return remove_mask_;
}
uint64_t BrowsingDataRemoverImpl::GetLastUsedOriginTypeMaskForTesting() {
return origin_type_mask_;
}
BrowsingDataRemoverImpl::RemovalTask::RemovalTask(
const base::Time& delete_begin,
const base::Time& delete_end,
uint64_t remove_mask,
uint64_t origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder,
Observer* observer)
: delete_begin(delete_begin),
delete_end(delete_end),
remove_mask(remove_mask),
origin_type_mask(origin_type_mask),
filter_builder(std::move(filter_builder)) {
if (observer)
observers.push_back(observer);
}
BrowsingDataRemoverImpl::RemovalTask::RemovalTask(
RemovalTask&& other) noexcept = default;
BrowsingDataRemoverImpl::RemovalTask::~RemovalTask() = default;
bool BrowsingDataRemoverImpl::RemovalTask::IsSameDeletion(
const RemovalTask& other) {
return delete_begin == other.delete_begin && delete_end == other.delete_end &&
remove_mask == other.remove_mask &&
origin_type_mask == other.origin_type_mask &&
*filter_builder == *other.filter_builder;
}
StoragePartition* BrowsingDataRemoverImpl::GetStoragePartition() {
DCHECK(!browser_context_->ShutdownStarted());
return storage_partition_for_testing_
? storage_partition_for_testing_.get()
: browser_context_->GetDefaultStoragePartition();
}
void BrowsingDataRemoverImpl::OnDelegateDone(
base::OnceClosure completion_closure,
uint64_t failed_data_types) {
failed_data_types_ |= failed_data_types;
std::move(completion_closure).Run();
}
void BrowsingDataRemoverImpl::Notify() {
// Some tests call |RemoveImpl| directly, without using the task scheduler.
// TODO(msramek): Improve those tests so we don't have to do this. Tests
// relying on |RemoveImpl| do so because they need to pass in
// BrowsingDataFilterBuilder while still keeping ownership of it. Making
// BrowsingDataFilterBuilder copyable would solve this.
if (!is_removing_) {
DCHECK(task_queue_.empty());
return;
}
// Inform the observer of the current task unless it has unregistered
// itself in the meantime.
DCHECK(!task_queue_.empty());
const RemovalTask& task = task_queue_.front();
for (Observer* observer : task.observers) {
if (observer_list_.HasObserver(observer)) {
observer->OnBrowsingDataRemoverDone(failed_data_types_);
}
}
base::TimeDelta delta = base::TimeTicks::Now() - task.task_started;
if (task.filter_builder->GetMode() ==
BrowsingDataFilterBuilder::Mode::kPreserve) {
// Full, and time based and filtered deletions are often implemented
// differently, so we track them in separate metrics.
if (!task.filter_builder->MatchesAllOriginsAndDomains()) {
base::UmaHistogramMediumTimes(
"History.ClearBrowsingData.Duration.FilteredDeletion", delta);
} else if (task.delete_begin.is_null() && task.delete_end.is_max()) {
base::UmaHistogramMediumTimes(
"History.ClearBrowsingData.Duration.FullDeletion", delta);
} else {
base::UmaHistogramMediumTimes(
"History.ClearBrowsingData.Duration.TimeRangeDeletion", delta);
}
} else {
base::UmaHistogramMediumTimes(
"History.ClearBrowsingData.Duration.OriginDeletion", delta);
}
task_queue_.pop_front();
if (task_queue_.empty()) {
// All removal tasks have finished. Inform the observers that we're idle.
SetRemoving(false);
return;
}
// Yield to the UI thread before executing the next removal task.
// TODO(msramek): Consider also adding a backoff if too many tasks
// are scheduled.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowsingDataRemoverImpl::RunNextTask, GetWeakPtr()));
}
void BrowsingDataRemoverImpl::OnTaskComplete(TracingDataType data_type,
base::TimeTicks started) {
// TODO(brettw) http://crbug.com/305259: This should also observe session
// clearing (what about other things such as passwords, etc.?) and wait for
// them to complete before continuing.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
size_t num_erased = pending_sub_tasks_.erase(data_type);
DCHECK_EQ(num_erased, 1U);
TRACE_EVENT_NESTABLE_ASYNC_END1(
"browsing_data", "BrowsingDataRemoverImpl",
TRACE_ID_WITH_SCOPE("BrowsingDataRemoverImpl",
static_cast<int>(data_type)),
"data_type", static_cast<int>(data_type));
base::UmaHistogramMediumTimes(
base::StrCat({"History.ClearBrowsingData.Duration.Task.",
GetHistogramSuffix(data_type)}),
base::TimeTicks::Now() - started);
if (!pending_sub_tasks_.empty())
return;
// If any cookie deletions have been deferred do them now since all other
// tasks are completed.
if (!domains_for_deferred_cookie_deletion_.empty()) {
DCHECK(remove_mask_ & DATA_TYPE_COOKIES);
auto deletion_filter = network::mojom::CookieDeletionFilter::New();
deletion_filter->including_domains =
std::move(domains_for_deferred_cookie_deletion_);
// Moving a vector is defined to empty this vector.
DCHECK(domains_for_deferred_cookie_deletion_.empty());
// Asynchronous removal tasks might end up finishing after an arbitrary
// delay - this can postpone when OnTaskComplete runs. Therefore we need to
// check if destruction of our `browser_context_` might have started in the
// meantime. See also https://crbug.com/1216406.
if (browser_context_->ShutdownStarted()) {
// The tasks related to `domains_for_deferred_cookie_deletion_` and
// `deletion_filter` are implicitly dropped if we can't clear the data
// because the StoragePartition's destructor has already started running.
failed_data_types_ |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
} else {
GetStoragePartition()->ClearData(
StoragePartition::REMOVE_DATA_MASK_COOKIES,
/*quota_storage_remove_mask=*/0,
/*filter_builder=*/nullptr,
/*storage_key_policy_matcher=*/base::NullCallback(),
std::move(deletion_filter),
/*perform_storage_cleanup=*/false, delete_begin_, delete_end_,
CreateTaskCompletionClosure(TracingDataType::kDeferredCookies));
return;
}
}
slow_pending_tasks_closure_.Cancel();
if (!would_complete_callback_.is_null()) {
would_complete_callback_.Run(
base::BindOnce(&BrowsingDataRemoverImpl::Notify, GetWeakPtr()));
return;
}
Notify();
}
const char* BrowsingDataRemoverImpl::GetHistogramSuffix(TracingDataType task) {
switch (task) {
case TracingDataType::kSynchronous:
return "Synchronous";
case TracingDataType::kEmbedderData:
return "EmbedderData";
case TracingDataType::kStoragePartition:
return "StoragePartition";
case TracingDataType::kHttpCache:
return "HttpCache";
case TracingDataType::kHttpAndMediaCaches:
return "HttpAndMediaCaches";
case TracingDataType::kReportingCache:
return "ReportingCache";
case TracingDataType::kChannelIds:
return "ChannelIds";
case TracingDataType::kNetworkHistory:
return "NetworkHistory";
case TracingDataType::kAuthCache:
return "AuthCache";
case TracingDataType::kCodeCaches:
return "CodeCaches";
case TracingDataType::kNetworkErrorLogging:
return "NetworkErrorLogging";
case TracingDataType::kTrustTokens:
return "TrustTokens";
case TracingDataType::kConversions:
return "Conversions";
case TracingDataType::kDeferredCookies:
return "DeferredCookies";
case TracingDataType::kSharedStorage:
return "SharedStorage";
case TracingDataType::kPreflightCache:
return "PreflightCache";
}
}
base::OnceClosure BrowsingDataRemoverImpl::CreateTaskCompletionClosure(
TracingDataType data_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto result = pending_sub_tasks_.insert(data_type);
DCHECK(result.second) << "Task already started: "
<< static_cast<int>(data_type);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"browsing_data", "BrowsingDataRemoverImpl",
TRACE_ID_WITH_SCOPE("BrowsingDataRemoverImpl",
static_cast<int>(data_type)),
"data_type", static_cast<int>(data_type));
return base::BindOnce(&BrowsingDataRemoverImpl::OnTaskComplete, GetWeakPtr(),
data_type, base::TimeTicks::Now());
}
base::OnceClosure BrowsingDataRemoverImpl::CreateTaskCompletionClosureForMojo(
TracingDataType data_type) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return RunsOrPostOnCurrentTaskRunner(mojo::WrapCallbackWithDropHandler(
CreateTaskCompletionClosure(data_type),
base::BindOnce(&BrowsingDataRemoverImpl::OnTaskComplete, GetWeakPtr(),
data_type, base::TimeTicks::Now())));
}
void BrowsingDataRemoverImpl::RecordUnfinishedSubTasks() {
DCHECK(!pending_sub_tasks_.empty());
for (TracingDataType task : pending_sub_tasks_) {
UMA_HISTOGRAM_ENUMERATION(
"History.ClearBrowsingData.Duration.SlowTasks180s", task);
}
}
base::WeakPtr<BrowsingDataRemoverImpl> BrowsingDataRemoverImpl::GetWeakPtr() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::WeakPtr<BrowsingDataRemoverImpl> weak_ptr =
weak_ptr_factory_.GetWeakPtr();
// Immediately bind the weak pointer to the UI thread. This makes it easier
// to discover potential misuse on the IO thread.
weak_ptr.get();
return weak_ptr;
}
} // namespace content