blob: 40cfa71ff9417e4feb8ae371a163c36355bcb4d8 [file] [log] [blame]
// Copyright (c) 2012 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.
#include "content/browser/browsing_data/browsing_data_remover_impl.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/feature_list.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/stl_util.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/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 "ppapi/buildflags/buildflags.h"
#include "services/network/public/cpp/features.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::TimeDelta::FromSeconds(180);
base::OnceClosure RunsOrPostOnCurrentTaskRunner(base::OnceClosure closure) {
return base::BindOnce(
[](base::OnceClosure closure,
scoped_refptr<base::TaskRunner> task_runner) {
if (base::ThreadTaskRunnerHandle::Get() == task_runner) {
std::move(closure).Run();
return;
}
task_runner->PostTask(FROM_HERE, std::move(closure));
},
std::move(closure), base::ThreadTaskRunnerHandle::Get());
}
// Returns whether |origin| matches |origin_type_mask| given the special
// storage |policy|; and if |predicate| is not null, then also whether
// it matches |predicate|. 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 DoesOriginMatchMaskAndPredicate(
uint64_t origin_type_mask,
base::OnceCallback<bool(const url::Origin&)> predicate,
const BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher&
embedder_matcher,
const url::Origin& origin,
storage::SpecialStoragePolicy* policy) {
if (predicate && !std::move(predicate).Run(origin))
return false;
const std::vector<std::string>& schemes = url::GetWebStorageSchemes();
bool is_web_scheme = base::Contains(schemes, 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(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(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, 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 DoesOriginMatchMaskAndPredicate(origin_type_mask, base::NullCallback(),
std::move(embedder_matcher), 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::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::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::Time::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");
// 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();
base::RepeatingCallback<bool(const url::Origin&)> origin_filter =
static_cast<BrowsingDataFilterBuilderImpl*>(filter_builder)
->BuildOriginFilter();
// 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 =
BrowserContext::GetDownloadManager(browser_context_);
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;
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_APP_CACHE) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_APPCACHE;
}
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) {
// Tell the shader disk cache to clear.
base::RecordAction(UserMetricsAction("ClearBrowsingData_ShaderCache"));
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
}
// Content Decryption Modules used by Encrypted Media store licenses in a
// private filesystem. These are different than content licenses used by
// Flash (which are deleted father down in this method).
if (remove_mask & DATA_TYPE_MEDIA_LICENSES) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_PLUGIN_PRIVATE_DATA;
}
if (remove_mask & DATA_TYPE_CONVERSIONS) {
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_CONVERSIONS;
}
StoragePartition* storage_partition = GetStoragePartition();
if (storage_partition_remove_mask) {
uint32_t quota_storage_remove_mask =
~StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT;
if (delete_begin_ == base::Time() ||
((origin_type_mask_ & ~ORIGIN_TYPE_UNPROTECTED_WEB) != 0)) {
// If we're deleting since the beginning of time, or we're removing
// protected origins, then remove persistent quota data.
quota_storage_remove_mask |=
StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT;
}
// 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, quota_storage_remove_mask,
base::BindRepeating(&DoesOriginMatchMaskAndPredicate, origin_type_mask_,
origin_filter, 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();
// TODO(msramek): Clear the cache of all renderers.
// 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();
}
//////////////////////////////////////////////////////////////////////////////
// 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));
}
#if BUILDFLAG(ENABLE_REPORTING)
//////////////////////////////////////////////////////////////////////////////
// Reporting cache.
if (remove_mask & DATA_TYPE_COOKIES) {
network::mojom::NetworkContext* network_context =
BrowserContext::GetDefaultStoragePartition(browser_context_)
->GetNetworkContext();
network_context->ClearReportingCacheClients(
filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(TracingDataType::kReportingCache));
network_context->ClearNetworkErrorLogging(
filter_builder->BuildNetworkServiceFilter(),
CreateTaskCompletionClosureForMojo(
TracingDataType::kNetworkErrorLogging));
}
#endif // BUILDFLAG(ENABLE_REPORTING)
//////////////////////////////////////////////////////////////////////////////
// Auth cache.
if ((remove_mask & DATA_TYPE_COOKIES) &&
!(remove_mask & DATA_TYPE_AVOID_CLOSING_CONNECTIONS)) {
BrowserContext::GetDefaultStoragePartition(browser_context_)
->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() {
return storage_partition_for_testing_
? storage_partition_for_testing_
: BrowserContext::GetDefaultStoragePartition(browser_context_);
}
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::Time::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) {
// 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_ASYNC_END1("browsing_data", "BrowsingDataRemoverImpl",
static_cast<int>(data_type), "data_type",
static_cast<int>(data_type));
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());
GetStoragePartition()->ClearData(
StoragePartition::REMOVE_DATA_MASK_COOKIES,
/*quota_storage_remove_mask=*/0,
/*origin_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();
}
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_ASYNC_BEGIN1("browsing_data", "BrowsingDataRemoverImpl",
static_cast<int>(data_type), "data_type",
static_cast<int>(data_type));
return base::BindOnce(&BrowsingDataRemoverImpl::OnTaskComplete, GetWeakPtr(),
data_type);
}
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)));
}
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