
This reverts commitf8849ddbca
. Reason for revert: Fixed bad merge with crrev.com/c/6174062 Original change's description: > Revert "[DIPS] Rename DIPS prefix to Btm." > > This reverts commit4d4b33ce5c
. > > Reason for revert: Tree closure for compile failure > > Original change's description: > > [DIPS] Rename DIPS prefix to Btm. > > > > This makes it match the external name (Bounce Tracking Mitigations) and > > comply with the C++ style guide, which says "prefer to capitalize > > abbreviations as single words". > > > > A followup CL will rename filename prefixes from dips_ to btm_ (and the > > directories from dips/ to btm/). > > > > Bug: 388538934 > > Change-Id: I43d3f1c24632919ac8985aea5b7f2d945d2a9628 > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6172859 > > Owners-Override: Avi Drissman <avi@chromium.org> > > Commit-Queue: Ryan Tarpine <rtarpine@chromium.org> > > Reviewed-by: Avi Drissman <avi@chromium.org> > > Cr-Commit-Position: refs/heads/main@{#1406975} > > Bug: 388538934 > Change-Id: Ic86e19c9c3a23d7dcb014bab5bc6bc13419a0ae0 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6173773 > Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> > Auto-Submit: Joey Arhar <jarhar@chromium.org> > Owners-Override: Joey Arhar <jarhar@google.com> > Commit-Queue: Joey Arhar <jarhar@chromium.org> > Cr-Commit-Position: refs/heads/main@{#1406979} Bug: 388538934 Change-Id: I9a269534fb7bd8b35dc721ed7e2b851d248133eb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6180157 Owners-Override: Avi Drissman <avi@chromium.org> Reviewed-by: Avi Drissman <avi@chromium.org> Commit-Queue: Ryan Tarpine <rtarpine@chromium.org> Cr-Commit-Position: refs/heads/main@{#1407417}
484 lines
17 KiB
C++
484 lines
17 KiB
C++
// Copyright 2021 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/browser_context_impl.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "base/check_is_test.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/location.h"
|
|
#include "base/memory/ref_counted.h"
|
|
#include "base/task/sequenced_task_runner.h"
|
|
#include "base/trace_event/trace_event.h"
|
|
#include "build/build_config.h"
|
|
#include "components/content_settings/core/common/features.h"
|
|
#include "content/browser/background_sync/background_sync_scheduler.h"
|
|
#include "content/browser/browsing_data/browsing_data_remover_impl.h"
|
|
#include "content/browser/dips/dips_service_impl.h"
|
|
#include "content/browser/download/download_manager_impl.h"
|
|
#include "content/browser/in_memory_federated_permission_context.h"
|
|
#include "content/browser/permissions/permission_controller_impl.h"
|
|
#include "content/browser/preloading/prefetch/prefetch_service.h"
|
|
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
|
|
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_manager.h"
|
|
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_config.h"
|
|
#include "content/browser/renderer_host/render_frame_host_impl.h"
|
|
#include "content/browser/speech/tts_controller_impl.h"
|
|
#include "content/browser/storage_partition_impl.h"
|
|
#include "content/browser/storage_partition_impl_map.h"
|
|
#include "content/public/browser/browser_context.h"
|
|
#include "content/public/browser/browser_thread.h"
|
|
#include "content/public/browser/content_browser_client.h"
|
|
#include "content/public/browser/render_process_host.h"
|
|
#include "content/public/browser/shared_worker_service.h"
|
|
#include "content/public/common/content_client.h"
|
|
#include "media/capabilities/webrtc_video_stats_db_impl.h"
|
|
#include "media/learning/common/media_learning_tasks.h"
|
|
#include "media/learning/impl/learning_session_impl.h"
|
|
#include "media/mojo/services/video_decode_perf_history.h"
|
|
#include "media/mojo/services/webrtc_video_perf_history.h"
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
#include "storage/browser/file_system/external_mount_points.h"
|
|
#endif
|
|
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
void NotifyContextWillBeDestroyed(StoragePartition* partition) {
|
|
static_cast<StoragePartitionImpl*>(partition)
|
|
->OnBrowserContextWillBeDestroyed();
|
|
}
|
|
|
|
void RegisterMediaLearningTask(
|
|
media::learning::LearningSessionImpl* learning_session,
|
|
const media::learning::LearningTask& task) {
|
|
// The RegisterTask method cannot be directly used in base::Bind, because it
|
|
// provides a default argument value for the 2nd parameter
|
|
// (`feature_provider`).
|
|
learning_session->RegisterTask(task);
|
|
}
|
|
|
|
// Kill switch that controls whether to cancel navigations as part of
|
|
// BrowserContext shutdown. See https://crbug.com/40274462.
|
|
BASE_FEATURE(kCancelNavigationsDuringBrowserContextShutdown,
|
|
"CancelNavigationsDuringBrowserContextShutdown",
|
|
base::FEATURE_ENABLED_BY_DEFAULT);
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
BrowserContextImpl* BrowserContextImpl::From(BrowserContext* self) {
|
|
return self->impl();
|
|
}
|
|
|
|
void BrowserContextImpl::MaybeCleanupDips() {
|
|
base::ScopedClosureRunner quit_runner(dips_cleanup_loop_.QuitClosure());
|
|
// Don't attempt to delete the database if the DIPS feature is enabled; we
|
|
// need it.
|
|
if (base::FeatureList::IsEnabled(features::kBtm)) {
|
|
return;
|
|
}
|
|
|
|
// Don't attempt to delete the database if this browser context should never
|
|
// have DIPS enabled. (This is important for embedders like ChromeOS, which
|
|
// have internal non-user-facing browser contexts. We don't want to touch
|
|
// them.)
|
|
if (!GetContentClient()->browser()->ShouldEnableDips(self_)) {
|
|
return;
|
|
}
|
|
|
|
// Don't attempt to delete the database if this browser context doesn't write
|
|
// to disk. (This is important for embedders like Chrome, which can make OTR
|
|
// browser contexts share the same data directory as a non-OTR context.)
|
|
if (self_->IsOffTheRecord()) {
|
|
return;
|
|
}
|
|
|
|
BtmStorage::DeleteDatabaseFiles(GetBtmFilePath(self_), quit_runner.Release());
|
|
}
|
|
|
|
void BrowserContextImpl::WaitForDipsCleanupForTesting() {
|
|
dips_cleanup_loop_.Run();
|
|
}
|
|
|
|
BrowserContextImpl::BrowserContextImpl(BrowserContext* self) : self_(self) {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
background_sync_scheduler_ = base::MakeRefCounted<BackgroundSyncScheduler>();
|
|
|
|
// Run MaybeCleanupDips() very soon. We can't call it right now because it
|
|
// calls a virtual function (BrowserContext::IsOffTheRecord()), which causes
|
|
// undefined behavior since we're called by the BrowserContext constructor
|
|
// and the method is not implemented by that class.
|
|
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindOnce(&BrowserContextImpl::MaybeCleanupDips,
|
|
weak_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
BrowserContextImpl::~BrowserContextImpl() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
DCHECK(!storage_partition_map_)
|
|
<< "StoragePartitionMap is not shut down properly";
|
|
|
|
if (!will_be_destroyed_soon_) {
|
|
NOTREACHED();
|
|
}
|
|
|
|
// Verify that there are no outstanding RenderProcessHosts that reference
|
|
// this context. Trigger a crash report if there are still references so
|
|
// we can detect/diagnose potential UAFs.
|
|
std::string rph_crash_key_value;
|
|
ChildProcessSecurityPolicyImpl* policy =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
for (RenderProcessHost::iterator host_iterator =
|
|
RenderProcessHost::AllHostsIterator();
|
|
!host_iterator.IsAtEnd(); host_iterator.Advance()) {
|
|
RenderProcessHost* host = host_iterator.GetCurrentValue();
|
|
if (host->GetBrowserContext() == self_) {
|
|
rph_crash_key_value +=
|
|
"{ " + host->GetInfoForBrowserContextDestructionCrashReporting() +
|
|
" }";
|
|
}
|
|
}
|
|
if (!rph_crash_key_value.empty()) {
|
|
SCOPED_CRASH_KEY_STRING256("BrowserContext", "dangling_rph",
|
|
rph_crash_key_value);
|
|
DUMP_WILL_BE_NOTREACHED()
|
|
<< "rph_with_bc_reference : " << rph_crash_key_value;
|
|
}
|
|
|
|
// Clean up any isolated origins and other security state associated with this
|
|
// BrowserContext.
|
|
policy->RemoveStateForBrowserContext(*self_);
|
|
|
|
if (download_manager_)
|
|
download_manager_->Shutdown();
|
|
|
|
TtsControllerImpl::GetInstance()->OnBrowserContextDestroyed(self_);
|
|
|
|
if (BrowserThread::IsThreadInitialized(BrowserThread::IO)) {
|
|
GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
|
|
std::move(resource_context_));
|
|
}
|
|
|
|
TRACE_EVENT_NESTABLE_ASYNC_END1(
|
|
"shutdown", "BrowserContextImpl::NotifyWillBeDestroyed() called.", this,
|
|
"browser_context_impl", static_cast<void*>(this));
|
|
}
|
|
|
|
bool BrowserContextImpl::ShutdownStarted() {
|
|
return will_be_destroyed_soon_;
|
|
}
|
|
|
|
void BrowserContextImpl::NotifyWillBeDestroyed() {
|
|
TRACE_EVENT1("shutdown", "BrowserContextImpl::NotifyWillBeDestroyed",
|
|
"browser_context_impl", static_cast<void*>(this));
|
|
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
|
|
"shutdown", "BrowserContextImpl::NotifyWillBeDestroyed() called.", this,
|
|
"browser_context_impl", static_cast<void*>(this));
|
|
// Make sure NotifyWillBeDestroyed is idempotent. This helps facilitate the
|
|
// pattern where NotifyWillBeDestroyed is called from *both*
|
|
// ShellBrowserContext and its derived classes (e.g. WebTestBrowserContext).
|
|
if (will_be_destroyed_soon_)
|
|
return;
|
|
will_be_destroyed_soon_ = true;
|
|
|
|
self_->ForEachLoadedStoragePartition(&NotifyContextWillBeDestroyed);
|
|
|
|
// Cancel navigations that are happening in the BrowserContext that's going
|
|
// away.
|
|
if (base::FeatureList::IsEnabled(
|
|
kCancelNavigationsDuringBrowserContextShutdown)) {
|
|
RenderFrameHostImpl::CancelAllNavigationsForBrowserContextShutdown(self_);
|
|
}
|
|
|
|
// Also forcibly release keep alive refcounts on RenderProcessHosts, to ensure
|
|
// they destruct before the BrowserContext does.
|
|
for (RenderProcessHost::iterator host_iterator =
|
|
RenderProcessHost::AllHostsIterator();
|
|
!host_iterator.IsAtEnd(); host_iterator.Advance()) {
|
|
RenderProcessHost* host = host_iterator.GetCurrentValue();
|
|
if (host->GetBrowserContext() == self_) {
|
|
// This will also clean up spare RPH references.
|
|
host->DisableRefCounts();
|
|
}
|
|
}
|
|
}
|
|
|
|
StoragePartitionImplMap* BrowserContextImpl::GetOrCreateStoragePartitionMap() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
if (!storage_partition_map_)
|
|
storage_partition_map_ = std::make_unique<StoragePartitionImplMap>(self_);
|
|
|
|
return storage_partition_map_.get();
|
|
}
|
|
|
|
BrowsingDataRemoverImpl* BrowserContextImpl::GetBrowsingDataRemover() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
if (!browsing_data_remover_) {
|
|
browsing_data_remover_ = std::make_unique<BrowsingDataRemoverImpl>(self_);
|
|
browsing_data_remover_->SetEmbedderDelegate(
|
|
self_->GetBrowsingDataRemoverDelegate());
|
|
}
|
|
|
|
return browsing_data_remover_.get();
|
|
}
|
|
|
|
media::learning::LearningSession* BrowserContextImpl::GetLearningSession() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
if (!learning_session_) {
|
|
learning_session_ = std::make_unique<media::learning::LearningSessionImpl>(
|
|
base::SequencedTaskRunner::GetCurrentDefault());
|
|
|
|
// Using base::Unretained is safe below, because the callback here will not
|
|
// be called or retained after the Register method below returns.
|
|
media::learning::MediaLearningTasks::Register(base::BindRepeating(
|
|
&RegisterMediaLearningTask, base::Unretained(learning_session_.get())));
|
|
}
|
|
|
|
return learning_session_.get();
|
|
}
|
|
|
|
media::VideoDecodePerfHistory* BrowserContextImpl::GetVideoDecodePerfHistory() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
if (!video_decode_perf_history_)
|
|
video_decode_perf_history_ = self_->CreateVideoDecodePerfHistory();
|
|
|
|
return video_decode_perf_history_.get();
|
|
}
|
|
|
|
std::unique_ptr<media::WebrtcVideoPerfHistory>
|
|
BrowserContextImpl::CreateWebrtcVideoPerfHistory() {
|
|
// TODO(crbug.com/40172952): Implement in memory path in
|
|
// off_the_record_profile_impl.cc and web_engine_browser_context.cc
|
|
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
auto* db_provider =
|
|
self_->GetDefaultStoragePartition()->GetProtoDatabaseProvider();
|
|
|
|
std::unique_ptr<media::WebrtcVideoStatsDB> stats_db =
|
|
media::WebrtcVideoStatsDBImpl::Create(
|
|
self_->GetPath().Append(FILE_PATH_LITERAL("WebrtcVideoStats")),
|
|
db_provider);
|
|
|
|
return std::make_unique<media::WebrtcVideoPerfHistory>(std::move(stats_db));
|
|
}
|
|
|
|
media::WebrtcVideoPerfHistory* BrowserContextImpl::GetWebrtcVideoPerfHistory() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
if (!webrtc_video_perf_history_)
|
|
webrtc_video_perf_history_ = CreateWebrtcVideoPerfHistory();
|
|
|
|
return webrtc_video_perf_history_.get();
|
|
}
|
|
|
|
void BrowserContextImpl::ShutdownStoragePartitions() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
// The BackgroundSyncScheduler keeps raw pointers to partitions; clear it
|
|
// first.
|
|
DCHECK(background_sync_scheduler_->HasOneRef());
|
|
background_sync_scheduler_.reset();
|
|
|
|
storage_partition_map_.reset();
|
|
|
|
// Delete the BtmService, causing its SQLite database file to be closed. This
|
|
// is necessary for TestBrowserContext to be able to delete its temporary
|
|
// directory.
|
|
dips_service_.reset();
|
|
}
|
|
|
|
DownloadManager* BrowserContextImpl::GetDownloadManager() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
// Lazily populate `download_manager_`. This is important to
|
|
// 1) Avoid constructing DownloadManagerImpl when a test might have provided
|
|
// an alternative object via SetDownloadManagerForTesting.
|
|
// 2) Avoiding calling into DownloadManagerImpl's constructor with a partially
|
|
// constructed BrowserContext.
|
|
if (!download_manager_) {
|
|
download_manager_ = std::make_unique<DownloadManagerImpl>(self_);
|
|
|
|
// Note that GetDownloadManagerDelegate might call into GetDownloadManager,
|
|
// leading to re-entrancy concerns. We avoid re-entrancy by making sure
|
|
// `download_manager_` is set earlier, above.
|
|
download_manager_->SetDelegate(self_->GetDownloadManagerDelegate());
|
|
}
|
|
|
|
return download_manager_.get();
|
|
}
|
|
|
|
void BrowserContextImpl::SetDownloadManagerForTesting(
|
|
std::unique_ptr<DownloadManager> download_manager) {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
if (download_manager_)
|
|
download_manager_->Shutdown();
|
|
download_manager_ = std::move(download_manager);
|
|
}
|
|
|
|
PermissionController* BrowserContextImpl::GetPermissionController() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
if (!permission_controller_)
|
|
permission_controller_ = std::make_unique<PermissionControllerImpl>(self_);
|
|
|
|
return permission_controller_.get();
|
|
}
|
|
|
|
void BrowserContextImpl::SetPermissionControllerForTesting(
|
|
std::unique_ptr<PermissionController> permission_controller) {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
permission_controller_ = std::move(permission_controller);
|
|
}
|
|
|
|
storage::ExternalMountPoints* BrowserContextImpl::GetMountPoints() {
|
|
// Ensure that these methods are called on the UI thread, except for
|
|
// unittests where a UI thread might not have been created.
|
|
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
|
|
!BrowserThread::IsThreadInitialized(BrowserThread::UI));
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
if (!external_mount_points_)
|
|
external_mount_points_ = storage::ExternalMountPoints::CreateRefCounted();
|
|
return external_mount_points_.get();
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
PrefetchService* BrowserContextImpl::GetPrefetchService() {
|
|
if (!prefetch_service_) {
|
|
prefetch_service_ = std::make_unique<PrefetchService>(self_);
|
|
}
|
|
|
|
return prefetch_service_.get();
|
|
}
|
|
|
|
InMemoryFederatedPermissionContext*
|
|
BrowserContextImpl::GetFederatedPermissionContext() {
|
|
if (!federated_permission_context_) {
|
|
federated_permission_context_ =
|
|
std::make_unique<InMemoryFederatedPermissionContext>();
|
|
}
|
|
return federated_permission_context_.get();
|
|
}
|
|
|
|
void BrowserContextImpl::ResetFederatedPermissionContext() {
|
|
federated_permission_context_.reset();
|
|
}
|
|
|
|
void BrowserContextImpl::SetPrefetchServiceForTesting(
|
|
std::unique_ptr<PrefetchService> prefetch_service) {
|
|
prefetch_service_ = std::move(prefetch_service);
|
|
}
|
|
|
|
NavigationEntryScreenshotManager*
|
|
BrowserContextImpl::GetNavigationEntryScreenshotManager() {
|
|
if (!nav_entry_screenshot_manager_ &&
|
|
NavigationTransitionConfig::AreBackForwardTransitionsEnabled()) {
|
|
nav_entry_screenshot_manager_ =
|
|
std::make_unique<NavigationEntryScreenshotManager>();
|
|
}
|
|
return nav_entry_screenshot_manager_.get();
|
|
}
|
|
|
|
void BrowserContextImpl::WriteIntoTrace(
|
|
perfetto::TracedProto<TraceProto> proto) const {
|
|
proto->set_id(UniqueId());
|
|
}
|
|
|
|
namespace {
|
|
bool ShouldEnableDips(BrowserContext* browser_context) {
|
|
if (!base::FeatureList::IsEnabled(features::kBtm)) {
|
|
return false;
|
|
}
|
|
|
|
if (!GetContentClient()->browser()->ShouldEnableDips(browser_context)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
BtmServiceImpl* BrowserContextImpl::GetDipsService() {
|
|
if (!dips_service_) {
|
|
if (!ShouldEnableDips(self_)) {
|
|
return nullptr;
|
|
}
|
|
dips_service_ = std::make_unique<BtmServiceImpl>(
|
|
base::PassKey<BrowserContextImpl>(), self_);
|
|
GetContentClient()->browser()->OnDipsServiceCreated(self_,
|
|
dips_service_.get());
|
|
}
|
|
|
|
return dips_service_.get();
|
|
}
|
|
|
|
namespace {
|
|
void CreatePopupHeuristicGrants(base::WeakPtr<BrowserContext> browser_context,
|
|
base::OnceCallback<void(bool)> callback,
|
|
std::vector<PopupWithTime> recent_popups) {
|
|
if (!browser_context) {
|
|
std::move(callback).Run(false);
|
|
return;
|
|
}
|
|
|
|
for (const PopupWithTime& popup : recent_popups) {
|
|
base::TimeDelta grant_duration =
|
|
content_settings::features::kTpcdBackfillPopupHeuristicsGrants.Get() -
|
|
(base::Time::Now() - popup.last_popup_time);
|
|
if (!grant_duration.is_positive()) {
|
|
continue;
|
|
}
|
|
|
|
// `popup_site` and `opener_site` were read from the DIPS database,
|
|
// and were originally computed by calling GetSiteForBtm().
|
|
// GrantCookieAccessDueToHeuristic() takes SchemefulSites, so we create some
|
|
// here, but since we pass ignore_schemes=true the scheme doesn't matter
|
|
// (and port never matters for SchemefulSites), so we hardcode http and 80.
|
|
net::SchemefulSite popup_site(
|
|
url::Origin::CreateFromNormalizedTuple("http", popup.popup_site, 80));
|
|
net::SchemefulSite opener_site(
|
|
url::Origin::CreateFromNormalizedTuple("http", popup.opener_site, 80));
|
|
|
|
GetContentClient()->browser()->GrantCookieAccessDueToHeuristic(
|
|
browser_context.get(), opener_site, popup_site, grant_duration,
|
|
/*ignore_schemes=*/true);
|
|
}
|
|
std::move(callback).Run(true);
|
|
}
|
|
} // namespace
|
|
|
|
void BrowserContextImpl::BackfillPopupHeuristicGrants(
|
|
base::OnceCallback<void(bool)> callback) {
|
|
if (!base::FeatureList::IsEnabled(
|
|
content_settings::features::kTpcdHeuristicsGrants) ||
|
|
!content_settings::features::kTpcdBackfillPopupHeuristicsGrants.Get()
|
|
.is_positive()) {
|
|
std::move(callback).Run(false);
|
|
return;
|
|
}
|
|
|
|
// TODO: crbug.com/1502264 - ensure backfill is completed if Chrome is
|
|
// shutdown or crashes.
|
|
GetDipsService()
|
|
->storage()
|
|
->AsyncCall(&BtmStorage::ReadRecentPopupsWithInteraction)
|
|
.WithArgs(
|
|
content_settings::features::kTpcdBackfillPopupHeuristicsGrants.Get())
|
|
.Then(base::BindOnce(&CreatePopupHeuristicGrants, self_->GetWeakPtr(),
|
|
std::move(callback)));
|
|
}
|
|
} // namespace content
|