| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/browsing_data/content/browsing_data_model.h" |
| |
| #include "base/barrier_closure.h" |
| #include "base/containers/enum_set.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/overloaded.h" |
| #include "base/memory/weak_ptr.h" |
| #include "components/browsing_data/content/browsing_data_quota_helper.h" |
| #include "components/browsing_data/core/features.h" |
| #include "components/services/storage/public/mojom/storage_usage_info.mojom.h" |
| #include "components/services/storage/shared_storage/shared_storage_manager.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/storage_partition_config.h" |
| #include "services/network/network_context.h" |
| #include "services/network/public/mojom/clear_data_filter.mojom.h" |
| #include "services/network/public/mojom/trust_tokens.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/variant.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/interest_group/interest_group.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| // A number of bytes used to represent data which takes up a practically |
| // imperceptible, but non-0 amount of space, such as Trust Tokens. |
| constexpr int kSmallAmountOfDataInBytes = 100; |
| |
| // An estimate of storage size of an Interest Group object. |
| constexpr int kModerateAmountOfDataInBytes = 1024; |
| |
| // Visitor which returns the appropriate data owner for a given `data_key` |
| // and `storage_type`. |
| struct GetDataOwner { |
| GetDataOwner(BrowsingDataModel::Delegate* delegate, |
| BrowsingDataModel::StorageType storage_type) |
| : delegate_(delegate), storage_type_(storage_type) {} |
| |
| template <class T> |
| BrowsingDataModel::DataOwner operator()(const T& data_key) const { |
| if (delegate_) { |
| absl::optional<BrowsingDataModel::DataOwner> owner = |
| delegate_->GetDataOwner(data_key, storage_type_); |
| if (owner.has_value()) { |
| return *owner; |
| } |
| } |
| // By default all data is owned by its host. |
| return GetOwningHost(data_key); |
| } |
| |
| private: |
| template <class T> |
| std::string GetOwningHost(const T& data_key) const; |
| |
| raw_ptr<BrowsingDataModel::Delegate> delegate_; |
| BrowsingDataModel::StorageType storage_type_; |
| }; |
| |
| template <> |
| std::string GetDataOwner::GetOwningHost<url::Origin>( |
| const url::Origin& data_key) const { |
| if (storage_type_ == BrowsingDataModel::StorageType::kTrustTokens) { |
| return data_key.host(); |
| } |
| |
| NOTREACHED() << "Unexpected StorageType: " << static_cast<int>(storage_type_); |
| return ""; |
| } |
| |
| template <> |
| std::string GetDataOwner::GetOwningHost<blink::StorageKey>( |
| const blink::StorageKey& data_key) const { |
| // TODO(crbug.com/1271155): This logic is useful for testing during the |
| // implementation of the model, but ultimately these storage types may not |
| // coexist. |
| switch (storage_type_) { |
| case BrowsingDataModel::StorageType::kPartitionedQuotaStorage: |
| return data_key.top_level_site().GetURL().host(); |
| |
| case BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage: |
| case BrowsingDataModel::StorageType::kSharedStorage: |
| return data_key.origin().host(); |
| default: |
| NOTREACHED() << "Unexpected StorageType: " |
| << static_cast<int>(storage_type_); |
| return ""; |
| } |
| } |
| |
| template <> |
| std::string GetDataOwner::GetOwningHost< |
| content::InterestGroupManager::InterestGroupDataKey>( |
| const content::InterestGroupManager::InterestGroupDataKey& data_key) const { |
| DCHECK_EQ(BrowsingDataModel::StorageType::kInterestGroup, storage_type_); |
| return data_key.owner.host(); |
| } |
| |
| template <> |
| std::string GetDataOwner::GetOwningHost<content::AttributionDataModel::DataKey>( |
| const content::AttributionDataModel::DataKey& data_key) const { |
| DCHECK_EQ(BrowsingDataModel::StorageType::kAttributionReporting, |
| storage_type_); |
| return data_key.reporting_origin().host(); |
| } |
| |
| // Helper which allows the lifetime management of a deletion action to occur |
| // separately from the BrowsingDataModel itself. |
| struct StorageRemoverHelper { |
| explicit StorageRemoverHelper( |
| content::StoragePartition* storage_partition, |
| scoped_refptr<BrowsingDataQuotaHelper> quota_helper, |
| BrowsingDataModel::Delegate* delegate |
| // TODO(crbug.com/1271155): Inject other dependencies. |
| ) |
| : storage_partition_(storage_partition), |
| quota_helper_(quota_helper), |
| delegate_(delegate) {} |
| |
| void RemoveByPrimaryHost( |
| const BrowsingDataModel::DataOwner& data_owner, |
| const BrowsingDataModel::DataKeyEntries& data_key_entries, |
| base::OnceClosure completed); |
| |
| private: |
| // Visitor struct to hold information used for deletion. absl::visit doesn't |
| // support multiple arguments elegently. |
| struct Visitor { |
| raw_ptr<StorageRemoverHelper> helper; |
| BrowsingDataModel::StorageTypeSet types; |
| |
| template <class T> |
| void operator()(const T& data_key); |
| }; |
| |
| // Returns a OnceClosure which can be passed to a storage backend for calling |
| // on deletion completion. |
| base::OnceClosure GetCompleteCallback(); |
| |
| void BackendFinished(); |
| |
| bool removing_ = false; |
| base::OnceClosure completed_; |
| size_t callbacks_expected_ = 0; |
| size_t callbacks_seen_ = 0; |
| |
| raw_ptr<content::StoragePartition> storage_partition_; |
| scoped_refptr<BrowsingDataQuotaHelper> quota_helper_; |
| raw_ptr<BrowsingDataModel::Delegate, DanglingUntriaged> delegate_; |
| base::WeakPtrFactory<StorageRemoverHelper> weak_ptr_factory_{this}; |
| }; |
| |
| void StorageRemoverHelper::RemoveByPrimaryHost( |
| const BrowsingDataModel::DataOwner& data_owner, |
| const BrowsingDataModel::DataKeyEntries& data_key_entries, |
| base::OnceClosure completed) { |
| // At a helper level, only a single deletion may occur at a time. However |
| // multiple helpers may be associated with a single model. |
| DCHECK(!removing_); |
| removing_ = true; |
| |
| completed_ = std::move(completed); |
| |
| // Creating a synchronous callback to hold off running `completed_` callback |
| // until the loop has completed visiting all its entries whether deletion is |
| // synchronous or asynchronous. |
| auto sync_completion = GetCompleteCallback(); |
| for (const auto& [key, details] : data_key_entries) { |
| absl::visit(Visitor{this, details.storage_types}, key); |
| if (delegate_) { |
| delegate_->RemoveDataKey(key, details.storage_types, |
| GetCompleteCallback()); |
| } |
| } |
| |
| std::move(sync_completion).Run(); |
| } |
| |
| template <> |
| void StorageRemoverHelper::Visitor::operator()<url::Origin>( |
| const url::Origin& origin) { |
| if (types.Has(BrowsingDataModel::StorageType::kTrustTokens)) { |
| helper->storage_partition_->GetNetworkContext()->DeleteStoredTrustTokens( |
| origin, base::BindOnce( |
| [](base::OnceClosure complete_callback, |
| ::network::mojom::DeleteStoredTrustTokensStatus status) { |
| std::move(complete_callback).Run(); |
| }, |
| helper->GetCompleteCallback())); |
| } |
| } |
| |
| template <> |
| void StorageRemoverHelper::Visitor::operator()<blink::StorageKey>( |
| const blink::StorageKey& storage_key) { |
| if (types.Has(BrowsingDataModel::StorageType::kSharedStorage)) { |
| helper->storage_partition_->GetSharedStorageManager()->Clear( |
| storage_key.origin(), |
| base::BindOnce( |
| [](base::OnceClosure complete_callback, |
| storage::SharedStorageDatabase::OperationResult result) { |
| std::move(complete_callback).Run(); |
| }, |
| helper->GetCompleteCallback())); |
| } |
| |
| if (types.Has(BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage)) { |
| const blink::mojom::StorageType quota_types[] = { |
| blink::mojom::StorageType::kTemporary, |
| blink::mojom::StorageType::kSyncable}; |
| for (auto type : quota_types) { |
| helper->quota_helper_->DeleteHostData(storage_key.origin().host(), type); |
| } |
| } |
| } |
| |
| template <> |
| void StorageRemoverHelper::Visitor::operator()< |
| content::InterestGroupManager::InterestGroupDataKey>( |
| const content::InterestGroupManager::InterestGroupDataKey& data_key) { |
| if (types.Has(BrowsingDataModel::StorageType::kInterestGroup)) { |
| helper->storage_partition_->GetInterestGroupManager() |
| ->RemoveInterestGroupsByDataKey( |
| data_key, base::BindOnce( |
| [](base::OnceClosure complete_callback) { |
| std::move(complete_callback).Run(); |
| }, |
| helper->GetCompleteCallback())); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| template <> |
| void StorageRemoverHelper::Visitor::operator()< |
| content::AttributionDataModel::DataKey>( |
| const content::AttributionDataModel::DataKey& data_key) { |
| if (types.Has(BrowsingDataModel::StorageType::kAttributionReporting)) { |
| helper->storage_partition_->GetAttributionDataModel() |
| ->RemoveAttributionDataByDataKey(data_key, |
| helper->GetCompleteCallback()); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| base::OnceClosure StorageRemoverHelper::GetCompleteCallback() { |
| callbacks_expected_++; |
| return base::BindOnce(&StorageRemoverHelper::BackendFinished, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| void StorageRemoverHelper::BackendFinished() { |
| DCHECK(callbacks_expected_ > callbacks_seen_); |
| callbacks_seen_++; |
| |
| if (callbacks_seen_ == callbacks_expected_) |
| std::move(completed_).Run(); |
| } |
| |
| void OnTrustTokenIssuanceInfoLoaded( |
| BrowsingDataModel* model, |
| base::OnceClosure loaded_callback, |
| std::vector<::network::mojom::StoredTrustTokensForIssuerPtr> tokens) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| for (const auto& token : tokens) { |
| if (token->count == 0) |
| continue; |
| |
| model->AddBrowsingData(token->issuer, |
| BrowsingDataModel::StorageType::kTrustTokens, |
| kSmallAmountOfDataInBytes); |
| } |
| std::move(loaded_callback).Run(); |
| } |
| |
| void OnSharedStorageLoaded( |
| BrowsingDataModel* model, |
| base::OnceClosure loaded_callback, |
| std::vector<::storage::mojom::StorageUsageInfoPtr> storage_usage_info) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| for (const auto& info : storage_usage_info) { |
| model->AddBrowsingData(info->storage_key, |
| BrowsingDataModel::StorageType::kSharedStorage, |
| info->total_size_bytes); |
| } |
| std::move(loaded_callback).Run(); |
| } |
| |
| void OnInterestGroupsLoaded( |
| BrowsingDataModel* model, |
| base::OnceClosure loaded_callback, |
| std::vector<content::InterestGroupManager::InterestGroupDataKey> |
| interest_groups) { |
| for (const auto& data_key : interest_groups) { |
| model->AddBrowsingData(data_key, |
| BrowsingDataModel::StorageType::kInterestGroup, |
| kModerateAmountOfDataInBytes); |
| } |
| std::move(loaded_callback).Run(); |
| } |
| |
| void OnAttributionReportingLoaded( |
| BrowsingDataModel* model, |
| base::OnceClosure loaded_callback, |
| std::vector<content::AttributionDataModel::DataKey> attribution_reporting) { |
| for (const auto& data_key : attribution_reporting) { |
| model->AddBrowsingData( |
| data_key, BrowsingDataModel::StorageType::kAttributionReporting, |
| kSmallAmountOfDataInBytes); |
| } |
| std::move(loaded_callback).Run(); |
| } |
| |
| void OnQuotaManagedDataLoaded( |
| BrowsingDataModel* model, |
| base::OnceClosure loaded_callback, |
| const std::list<BrowsingDataQuotaHelper::QuotaInfo>& quota_info) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| for (auto entry : quota_info) { |
| model->AddBrowsingData( |
| entry.storage_key, |
| BrowsingDataModel::StorageType::kUnpartitionedQuotaStorage, |
| entry.syncable_usage + entry.temporary_usage); |
| } |
| std::move(loaded_callback).Run(); |
| } |
| |
| void OnDelegateDataLoaded( |
| BrowsingDataModel* model, |
| base::OnceClosure loaded_callback, |
| std::vector<BrowsingDataModel::Delegate::DelegateEntry> delegated_entries) { |
| for (const auto& entry : delegated_entries) { |
| model->AddBrowsingData(entry.data_key, entry.storage_type, |
| entry.storage_size); |
| } |
| std::move(loaded_callback).Run(); |
| } |
| |
| } // namespace |
| |
| BrowsingDataModel::DataDetails::~DataDetails() = default; |
| bool BrowsingDataModel::DataDetails::operator==( |
| const DataDetails& other) const { |
| return storage_types == other.storage_types && |
| storage_size == other.storage_size && |
| cookie_count == other.cookie_count; |
| } |
| |
| BrowsingDataModel::BrowsingDataEntryView::BrowsingDataEntryView( |
| const DataOwner& data_owner, |
| const DataKey& data_key, |
| const DataDetails& data_details) |
| : data_owner(data_owner), data_key(data_key), data_details(data_details) {} |
| BrowsingDataModel::BrowsingDataEntryView::~BrowsingDataEntryView() = default; |
| |
| bool BrowsingDataModel::BrowsingDataEntryView::Matches( |
| const url::Origin& origin) const { |
| return absl::visit(base::Overloaded{[&](const std::string& entry_host) { |
| return entry_host == origin.host(); |
| }, |
| [&](const url::Origin& entry_origin) { |
| return entry_origin == origin; |
| }}, |
| *data_owner); |
| } |
| |
| BrowsingDataModel::Delegate::DelegateEntry::DelegateEntry( |
| DataKey data_key, |
| StorageType storage_type, |
| uint64_t storage_size) |
| : data_key(data_key), |
| storage_type(storage_type), |
| storage_size(storage_size) {} |
| BrowsingDataModel::Delegate::DelegateEntry::DelegateEntry( |
| const DelegateEntry& other) = default; |
| BrowsingDataModel::Delegate::DelegateEntry::~DelegateEntry() = default; |
| |
| BrowsingDataModel::Iterator::Iterator(const Iterator& iterator) = default; |
| BrowsingDataModel::Iterator::~Iterator() = default; |
| |
| bool BrowsingDataModel::Iterator::operator==(const Iterator& other) const { |
| if (outer_iterator_ == outer_end_iterator_ && |
| other.outer_iterator_ == other.outer_end_iterator_) { |
| // Special case the == .end() scenario, because the inner iterators may |
| // not have been set. |
| return outer_iterator_ == other.outer_iterator_; |
| } |
| return outer_iterator_ == other.outer_iterator_ && |
| inner_iterator_ == other.inner_iterator_; |
| } |
| |
| bool BrowsingDataModel::Iterator::operator!=(const Iterator& other) const { |
| return !operator==(other); |
| } |
| |
| BrowsingDataModel::BrowsingDataEntryView |
| BrowsingDataModel::Iterator::operator*() const { |
| DCHECK(outer_iterator_ != outer_end_iterator_); |
| DCHECK(inner_iterator_ != outer_iterator_->second.end()); |
| return BrowsingDataEntryView(outer_iterator_->first, inner_iterator_->first, |
| inner_iterator_->second); |
| } |
| |
| BrowsingDataModel::Iterator& BrowsingDataModel::Iterator::operator++() { |
| inner_iterator_++; |
| if (inner_iterator_ == outer_iterator_->second.end()) { |
| outer_iterator_++; |
| if (outer_iterator_ != outer_end_iterator_) |
| inner_iterator_ = outer_iterator_->second.begin(); |
| } |
| return *this; |
| } |
| |
| BrowsingDataModel::Iterator::Iterator( |
| BrowsingDataEntries::const_iterator outer_iterator, |
| BrowsingDataEntries::const_iterator outer_end_iterator) |
| : outer_iterator_(outer_iterator), outer_end_iterator_(outer_end_iterator) { |
| if (outer_iterator_ != outer_end_iterator_) { |
| inner_iterator_ = outer_iterator_->second.begin(); |
| } |
| } |
| |
| BrowsingDataModel::Iterator BrowsingDataModel::begin() const { |
| return Iterator(browsing_data_entries_.begin(), browsing_data_entries_.end()); |
| } |
| |
| BrowsingDataModel::Iterator BrowsingDataModel::end() const { |
| return Iterator(browsing_data_entries_.end(), browsing_data_entries_.end()); |
| } |
| |
| BrowsingDataModel::~BrowsingDataModel() = default; |
| |
| void BrowsingDataModel::BuildFromDisk( |
| content::BrowserContext* browser_context, |
| std::unique_ptr<Delegate> delegate, |
| base::OnceCallback<void(std::unique_ptr<BrowsingDataModel>)> |
| complete_callback) { |
| BuildFromStoragePartition(browser_context->GetDefaultStoragePartition(), |
| std::move(delegate), std::move(complete_callback)); |
| } |
| |
| void BrowsingDataModel::BuildFromNonDefaultStoragePartition( |
| content::StoragePartition* storage_partition, |
| std::unique_ptr<Delegate> delegate, |
| base::OnceCallback<void(std::unique_ptr<BrowsingDataModel>)> |
| complete_callback) { |
| DCHECK(!storage_partition->GetConfig().is_default()); |
| BuildFromStoragePartition(storage_partition, std::move(delegate), |
| std::move(complete_callback)); |
| } |
| |
| void BrowsingDataModel::BuildFromStoragePartition( |
| content::StoragePartition* storage_partition, |
| std::unique_ptr<Delegate> delegate, |
| base::OnceCallback<void(std::unique_ptr<BrowsingDataModel>)> |
| complete_callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| auto model = BuildEmpty(storage_partition, std::move(delegate)); |
| auto* model_pointer = model.get(); |
| |
| // This functor will own the unique_ptr for the model during construction, |
| // after which it hands it to the initial caller. This ownership semantic |
| // ensures that raw `this` pointers provided to backends for fetching remain |
| // valid. |
| base::OnceClosure completion = base::BindOnce( |
| [](std::unique_ptr<BrowsingDataModel> model, |
| base::OnceCallback<void(std::unique_ptr<BrowsingDataModel>)> |
| callback) { std::move(callback).Run(std::move(model)); }, |
| std::move(model), std::move(complete_callback)); |
| |
| model_pointer->PopulateFromDisk(std::move(completion)); |
| } |
| |
| std::unique_ptr<BrowsingDataModel> BrowsingDataModel::BuildEmpty( |
| content::StoragePartition* storage_partition, |
| std::unique_ptr<Delegate> delegate) { |
| return base::WrapUnique(new BrowsingDataModel( |
| storage_partition, std::move(delegate))); // Private constructor |
| } |
| |
| void BrowsingDataModel::AddBrowsingData(const DataKey& data_key, |
| StorageType storage_type, |
| uint64_t storage_size, |
| uint64_t cookie_count) { |
| DataOwner data_owner = |
| absl::visit(GetDataOwner(delegate_.get(), storage_type), data_key); |
| |
| // Find the existing entry if it exists, constructing any missing components. |
| auto& entry = browsing_data_entries_[data_owner][data_key]; |
| |
| entry.storage_size += storage_size; |
| entry.cookie_count += cookie_count; |
| entry.storage_types.Put(storage_type); |
| } |
| |
| void BrowsingDataModel::RemoveBrowsingData(const DataOwner& data_owner, |
| base::OnceClosure completed) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Bind the lifetime of the helper to the lifetime of the callback. |
| auto helper = std::make_unique<StorageRemoverHelper>( |
| storage_partition_, quota_helper_, delegate_.get()); |
| auto* helper_pointer = helper.get(); |
| |
| base::OnceClosure wrapped_completed = base::BindOnce( |
| [](std::unique_ptr<StorageRemoverHelper> storage_remover, |
| base::OnceClosure completed) { std::move(completed).Run(); }, |
| std::move(helper), std::move(completed)); |
| |
| helper_pointer->RemoveByPrimaryHost(data_owner, |
| browsing_data_entries_[data_owner], |
| std::move(wrapped_completed)); |
| |
| // Immediately remove the affected entries from the in-memory model. Different |
| // UI elements have different sync vs. async expectations. Exposing a |
| // completed callback, but updating the model synchronously, serves both. |
| browsing_data_entries_.erase(data_owner); |
| } |
| |
| void BrowsingDataModel::PopulateFromDisk(base::OnceClosure finished_callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| bool is_shared_storage_enabled = |
| base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI); |
| bool is_interest_group_enabled = |
| base::FeatureList::IsEnabled(blink::features::kAdInterestGroupAPI); |
| bool is_attribution_reporting_enabled = |
| base::FeatureList::IsEnabled(blink::features::kConversionMeasurement); |
| bool is_cookies_tree_model_deprecated = base::FeatureList::IsEnabled( |
| browsing_data::features::kDeprecateCookiesTreeModel); |
| |
| // TODO(crbug.com/1271155): Derive this from the StorageTypeSet directly. |
| int storage_backend_count = 2; |
| if (is_shared_storage_enabled) { |
| storage_backend_count++; |
| } |
| if (is_interest_group_enabled) { |
| storage_backend_count++; |
| } |
| |
| if (is_attribution_reporting_enabled) { |
| storage_backend_count++; |
| } |
| |
| if (is_cookies_tree_model_deprecated) { |
| storage_backend_count++; |
| } |
| |
| base::RepeatingClosure completion = |
| base::BarrierClosure(storage_backend_count, std::move(finished_callback)); |
| |
| // The public build interfaces for the model ensure that `this` remains valid |
| // until `finished_callback` has been run. Thus, it's safe to pass raw `this` |
| // to backend callbacks. |
| |
| // Issued Trust Tokens: |
| storage_partition_->GetNetworkContext()->GetStoredTrustTokenCounts( |
| base::BindOnce(&OnTrustTokenIssuanceInfoLoaded, this, completion)); |
| |
| // Shared storage origins |
| if (is_shared_storage_enabled) { |
| storage_partition_->GetSharedStorageManager()->FetchOrigins( |
| base::BindOnce(&OnSharedStorageLoaded, this, completion)); |
| } |
| |
| // Interest Groups |
| if (is_interest_group_enabled) { |
| storage_partition_->GetInterestGroupManager()->GetAllInterestGroupDataKeys( |
| base::BindOnce(&OnInterestGroupsLoaded, this, completion)); |
| } |
| |
| // Attribution Reporting |
| if (is_attribution_reporting_enabled) { |
| storage_partition_->GetAttributionDataModel()->GetAllDataKeys( |
| base::BindOnce(&OnAttributionReportingLoaded, this, completion)); |
| } |
| |
| if (is_cookies_tree_model_deprecated) { |
| quota_helper_->StartFetching( |
| base::BindOnce(&OnQuotaManagedDataLoaded, this, completion)); |
| } |
| |
| // Data loaded from non-components storage types via the delegate. |
| if (delegate_) { |
| delegate_->GetAllDataKeys( |
| base::BindOnce(&OnDelegateDataLoaded, this, completion)); |
| } |
| } |
| |
| BrowsingDataModel::BrowsingDataModel( |
| content::StoragePartition* storage_partition, |
| std::unique_ptr<Delegate> delegate) |
| : storage_partition_(storage_partition), delegate_(std::move(delegate)) { |
| if (storage_partition_) { |
| quota_helper_ = BrowsingDataQuotaHelper::Create(storage_partition_); |
| } |
| } |