blob: a3c34f9fefb84b96cb0e4401245d444060fcf75b [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/bind_helpers.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "content/browser/browsing_data/storage_partition_http_cache_data_remover.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 "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/origin.h"
using base::UserMetricsAction;
namespace content {
namespace {
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 DoesOriginMatchMaskAndURLs(
int origin_type_mask,
const base::Callback<bool(const GURL&)>& predicate,
const BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher&
embedder_matcher,
const GURL& origin,
storage::SpecialStoragePolicy* policy) {
if (!predicate.is_null() && !predicate.Run(origin))
return false;
const std::vector<std::string>& schemes = url::GetWebStorageSchemes();
bool is_web_scheme =
base::ContainsValue(schemes, origin.GetOrigin().scheme());
// If a websafe origin is unprotected, it matches iff UNPROTECTED_WEB.
if ((!policy || !policy->IsStorageProtected(origin.GetOrigin())) &&
is_web_scheme &&
(origin_type_mask & BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB)) {
return true;
}
origin_type_mask &= ~BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB;
// Hosted applications (protected and websafe origins) iff PROTECTED_WEB.
if (policy && policy->IsStorageProtected(origin.GetOrigin()) &&
is_web_scheme &&
(origin_type_mask & BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB)) {
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_(-1),
origin_type_mask_(-1),
is_removing_(false),
storage_partition_for_testing_(nullptr),
weak_ptr_factory_(this) {
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 unsucessfuly) processed, so they can unregister themselves.
// TODO(bauerb): If it becomes a problem that browsing data might not actually
// be fully cleared when an observer is notified, add a success flag.
while (!task_queue_.empty()) {
if (observer_list_.HasObserver(task_queue_.front().observer))
task_queue_.front().observer->OnBrowsingDataRemoverDone();
task_queue_.pop();
}
}
void BrowsingDataRemoverImpl::SetRemoving(bool is_removing) {
DCHECK_NE(is_removing_, is_removing);
is_removing_ = is_removing;
}
void BrowsingDataRemoverImpl::SetEmbedderDelegate(
BrowsingDataRemoverDelegate* embedder_delegate) {
embedder_delegate_ = embedder_delegate;
}
bool BrowsingDataRemoverImpl::DoesOriginMatchMask(
int origin_type_mask,
const GURL& origin,
storage::SpecialStoragePolicy* policy) const {
BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher embedder_matcher;
if (embedder_delegate_)
embedder_matcher = embedder_delegate_->GetOriginTypeMatcher();
return DoesOriginMatchMaskAndURLs(
origin_type_mask, base::Callback<bool(const GURL&)>(),
std::move(embedder_matcher), origin, policy);
}
void BrowsingDataRemoverImpl::Remove(const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int 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,
int remove_mask,
int origin_type_mask,
Observer* observer) {
DCHECK(observer);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder>(), observer);
}
void BrowsingDataRemoverImpl::RemoveWithFilter(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int origin_type_mask,
std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
DCHECK(filter_builder);
RemoveInternal(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), nullptr);
}
void BrowsingDataRemoverImpl::RemoveWithFilterAndReply(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int 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,
int remove_mask,
int 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 |IsEmptyBlacklist()|.
if (!filter_builder) {
filter_builder =
BrowsingDataFilterBuilder::Create(BrowsingDataFilterBuilder::BLACKLIST);
DCHECK(filter_builder->IsEmptyBlacklist());
}
task_queue_.emplace(delete_begin, delete_end, remove_mask, origin_type_mask,
std::move(filter_builder), observer);
// 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();
RemoveImpl(removal_task.delete_begin, removal_task.delete_end,
removal_task.remove_mask, *removal_task.filter_builder,
removal_task.origin_type_mask);
}
void BrowsingDataRemoverImpl::RemoveImpl(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
const BrowsingDataFilterBuilder& filter_builder,
int 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.IsEmptyBlacklist()|. Add a comment explaining why this
// is acceptable.
base::ScopedClosureRunner synchronous_clear_operations(
CreatePendingTaskCompletionClosure());
// 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);
delete_begin_ = delete_begin;
delete_end_ = delete_end;
remove_mask_ = remove_mask;
origin_type_mask_ = origin_type_mask;
// 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;
}
UMA_HISTOGRAM_ENUMERATION(
"History.ClearBrowsingData.UserDeletedCookieOrCache", choice,
MAX_CHOICE_VALUE);
//////////////////////////////////////////////////////////////////////////////
// INITIALIZATION
base::RepeatingCallback<bool(const GURL& url)> filter =
filter_builder.BuildGeneralFilter();
// Some backends support a filter that |is_null()| to make complete deletion
// more efficient.
base::RepeatingCallback<bool(const GURL&)> nullable_filter =
filter_builder.IsEmptyBlacklist()
? base::RepeatingCallback<bool(const GURL&)>()
: 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(filter, delete_begin_,
delete_end_);
}
//////////////////////////////////////////////////////////////////////////////
// DATA_TYPE_CHANNEL_IDS
// Channel IDs are not separated for protected and unprotected web
// origins. We check the origin_type_mask_ to prevent unintended deletion.
if (remove_mask & DATA_TYPE_CHANNEL_IDS &&
!(remove_mask & DATA_TYPE_AVOID_CLOSING_CONNECTIONS) &&
origin_type_mask_ & ORIGIN_TYPE_UNPROTECTED_WEB) {
base::RecordAction(UserMetricsAction("ClearBrowsingData_ChannelIDs"));
network::mojom::ClearDataFilterPtr service_filter =
filter_builder.BuildNetworkServiceFilter();
DCHECK(!service_filter || service_filter->origins.empty())
<< "Origin-based deletion is not suitable for channel IDs.";
BrowserContext::GetDefaultStoragePartition(browser_context_)
->GetNetworkContext()
->ClearChannelIds(delete_begin, delete_end, std::move(service_filter),
CreatePendingTaskCompletionClosureForMojo());
}
//////////////////////////////////////////////////////////////////////////////
// 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 (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;
}
// 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;
}
StoragePartition* storage_partition;
if (storage_partition_for_testing_) {
storage_partition = storage_partition_for_testing_;
} else {
storage_partition =
BrowserContext::GetDefaultStoragePartition(browser_context_);
}
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.IsEmptyBlacklist() &&
(storage_partition_remove_mask &
StoragePartition::REMOVE_DATA_MASK_COOKIES)) {
deletion_filter = filter_builder.BuildCookieDeletionFilter();
} else {
deletion_filter = network::mojom::CookieDeletionFilter::New();
}
BrowsingDataRemoverDelegate::EmbedderOriginTypeMatcher embedder_matcher;
if (embedder_delegate_)
embedder_matcher = embedder_delegate_->GetOriginTypeMatcher();
bool perform_cleanup =
delete_begin_.is_null() && delete_end_.is_max() &&
filter_builder.GetMode() == BrowsingDataFilterBuilder::BLACKLIST;
storage_partition->ClearData(
storage_partition_remove_mask, quota_storage_remove_mask,
base::BindRepeating(&DoesOriginMatchMaskAndURLs, origin_type_mask_,
filter, std::move(embedder_matcher)),
std::move(deletion_filter), perform_cleanup, delete_begin_, delete_end_,
CreatePendingTaskCompletionClosure());
}
//////////////////////////////////////////////////////////////////////////////
// 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.
if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
// 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(),
CreatePendingTaskCompletionClosureForMojo());
} else {
storage_partition->ClearHttpAndMediaCaches(
delete_begin, delete_end, nullable_filter,
CreatePendingTaskCompletionClosureForMojo());
}
storage_partition->ClearCodeCaches(
CreatePendingTaskCompletionClosureForMojo());
// When clearing cache, wipe accumulated network related data
// (TransportSecurityState and HttpServerPropertiesManager data).
network_context->ClearNetworkingHistorySince(
delete_begin, CreatePendingTaskCompletionClosureForMojo());
// Tell the shader disk cache to clear.
base::RecordAction(UserMetricsAction("ClearBrowsingData_ShaderCache"));
storage_partition_remove_mask |=
StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
}
#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(),
CreatePendingTaskCompletionClosureForMojo());
network_context->ClearNetworkErrorLogging(
filter_builder.BuildNetworkServiceFilter(),
CreatePendingTaskCompletionClosureForMojo());
}
#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,
CreatePendingTaskCompletionClosureForMojo());
}
//////////////////////////////////////////////////////////////////////////////
// Embedder data.
if (embedder_delegate_) {
embedder_delegate_->RemoveEmbedderData(
delete_begin_, delete_end_, remove_mask, filter_builder,
origin_type_mask, CreatePendingTaskCompletionClosure());
}
}
void BrowsingDataRemoverImpl::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void BrowsingDataRemoverImpl::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void BrowsingDataRemoverImpl::SetWouldCompleteCallbackForTesting(
const base::Callback<void(const base::Closure& continue_to_completion)>&
callback) {
would_complete_callback_ = callback;
}
void BrowsingDataRemoverImpl::OverrideStoragePartitionForTesting(
StoragePartition* storage_partition) {
storage_partition_for_testing_ = storage_partition;
}
const base::Time& BrowsingDataRemoverImpl::GetLastUsedBeginTime() {
return delete_begin_;
}
const base::Time& BrowsingDataRemoverImpl::GetLastUsedEndTime() {
return delete_end_;
}
int BrowsingDataRemoverImpl::GetLastUsedRemovalMask() {
return remove_mask_;
}
int BrowsingDataRemoverImpl::GetLastUsedOriginTypeMask() {
return origin_type_mask_;
}
BrowsingDataRemoverImpl::RemovalTask::RemovalTask(
const base::Time& delete_begin,
const base::Time& delete_end,
int remove_mask,
int 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)),
observer(observer) {}
BrowsingDataRemoverImpl::RemovalTask::RemovalTask(
RemovalTask&& other) noexcept = default;
BrowsingDataRemoverImpl::RemovalTask::~RemovalTask() {}
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();
if (task.observer != nullptr && observer_list_.HasObserver(task.observer)) {
task.observer->OnBrowsingDataRemoverDone();
}
if (task.filter_builder->GetMode() == BrowsingDataFilterBuilder::BLACKLIST) {
base::TimeDelta delta = base::Time::Now() - task.task_started;
// Full and partial deletions are often implemented differently, so
// we track them in seperate metrics.
if (task.delete_begin.is_null() && task.delete_end.is_max() &&
task.filter_builder->IsEmptyBlacklist()) {
UMA_HISTOGRAM_MEDIUM_TIMES(
"History.ClearBrowsingData.Duration.FullDeletion", delta);
} else {
UMA_HISTOGRAM_MEDIUM_TIMES(
"History.ClearBrowsingData.Duration.PartialDeletion", delta);
}
}
task_queue_.pop();
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.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&BrowsingDataRemoverImpl::RunNextTask, GetWeakPtr()));
}
void BrowsingDataRemoverImpl::OnTaskComplete() {
// 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_GT(num_pending_tasks_, 0);
num_pending_tasks_--;
if (num_pending_tasks_ > 0)
return;
if (!would_complete_callback_.is_null()) {
would_complete_callback_.Run(
base::Bind(&BrowsingDataRemoverImpl::Notify, GetWeakPtr()));
return;
}
Notify();
}
base::OnceClosure
BrowsingDataRemoverImpl::CreatePendingTaskCompletionClosure() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
num_pending_tasks_++;
return base::BindOnce(&BrowsingDataRemoverImpl::OnTaskComplete, GetWeakPtr());
}
base::OnceClosure
BrowsingDataRemoverImpl::CreatePendingTaskCompletionClosureForMojo() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return RunsOrPostOnCurrentTaskRunner(mojo::WrapCallbackWithDropHandler(
CreatePendingTaskCompletionClosure(),
base::BindOnce(&BrowsingDataRemoverImpl::OnTaskComplete, GetWeakPtr())));
}
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