// 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 "content/browser/browsing_data/storage_partition_http_cache_data_remover.h"
#include "content/public/browser/browser_context.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());
  const RemovalTask& removal_task = task_queue_.front();

  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::Callback<bool(const GURL& url)> filter =
      filter_builder.BuildGeneralFilter();

  //////////////////////////////////////////////////////////////////////////////
  // 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->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();

    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), 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.

    if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
      // The clearing of the HTTP cache happens in the network service process
      // when enabled.
      network_context->ClearHttpCache(
          delete_begin, delete_end, filter_builder.BuildNetworkServiceFilter(),
          CreatePendingTaskCompletionClosureForMojo());
    }

    // In the network service case, the call below will only clear the media
    // cache.
    // TODO(crbug.com/813882): implement retry on network service.
    storage_partition->ClearHttpAndMediaCaches(
        delete_begin, delete_end,
        filter_builder.IsEmptyBlacklist() ? base::Callback<bool(const GURL&)>()
                                          : std::move(filter),
        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());

  if (task_queue_.front().observer != nullptr &&
      observer_list_.HasObserver(task_queue_.front().observer)) {
    task_queue_.front().observer->OnBrowsingDataRemoverDone();
  }

  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.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      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
