| // Copyright 2020 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/attribution_reporting/attribution_manager_impl.h" |
| |
| #include <cmath> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/functional/overloaded.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/observer_list.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/task/updateable_sequenced_task_runner.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "build/buildflag.h" |
| #include "components/attribution_reporting/source_registration.h" |
| #include "components/attribution_reporting/source_registration_error.mojom.h" |
| #include "components/attribution_reporting/suitable_origin.h" |
| #include "components/attribution_reporting/trigger_registration.h" |
| #include "content/browser/aggregation_service/aggregation_service.h" |
| #include "content/browser/aggregation_service/aggregation_service_impl.h" |
| #include "content/browser/aggregation_service/report_scheduler_timer.h" |
| #include "content/browser/attribution_reporting/aggregatable_attribution_utils.h" |
| #include "content/browser/attribution_reporting/attribution_cookie_checker.h" |
| #include "content/browser/attribution_reporting/attribution_cookie_checker_impl.h" |
| #include "content/browser/attribution_reporting/attribution_data_host_manager_impl.h" |
| #include "content/browser/attribution_reporting/attribution_debug_report.h" |
| #include "content/browser/attribution_reporting/attribution_features.h" |
| #include "content/browser/attribution_reporting/attribution_info.h" |
| #include "content/browser/attribution_reporting/attribution_observer.h" |
| #include "content/browser/attribution_reporting/attribution_os_level_manager.h" |
| #include "content/browser/attribution_reporting/attribution_report.h" |
| #include "content/browser/attribution_reporting/attribution_report_network_sender.h" |
| #include "content/browser/attribution_reporting/attribution_report_sender.h" |
| #include "content/browser/attribution_reporting/attribution_reporting.mojom.h" |
| #include "content/browser/attribution_reporting/attribution_storage.h" |
| #include "content/browser/attribution_reporting/attribution_storage_delegate.h" |
| #include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h" |
| #include "content/browser/attribution_reporting/attribution_storage_sql.h" |
| #include "content/browser/attribution_reporting/attribution_trigger.h" |
| #include "content/browser/attribution_reporting/create_report_result.h" |
| #include "content/browser/attribution_reporting/os_registration.h" |
| #include "content/browser/attribution_reporting/send_result.h" |
| #include "content/browser/attribution_reporting/storable_source.h" |
| #include "content/browser/attribution_reporting/store_source_result.h" |
| #include "content/browser/attribution_reporting/stored_source.h" |
| #include "content/browser/browsing_data/browsing_data_filter_builder_impl.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/attribution_data_model.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browsing_data_filter_builder.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "net/base/schemeful_site.h" |
| #include "services/network/public/cpp/attribution_utils.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/mojom/attribution.mojom.h" |
| #include "services/network/public/mojom/network_change_manager.mojom-forward.h" |
| #include "storage/browser/quota/special_storage_policy.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "content/browser/attribution_reporting/attribution_os_level_manager_android.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| using ScopedUseInMemoryStorageForTesting = |
| ::content::AttributionManagerImpl::ScopedUseInMemoryStorageForTesting; |
| |
| using ::attribution_reporting::mojom::OsRegistrationResult; |
| using ::attribution_reporting::mojom::OsRegistrationType; |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class ConversionReportSendOutcome { |
| kSent = 0, |
| kFailed = 1, |
| kDropped = 2, |
| kFailedToAssemble = 3, |
| kMaxValue = kFailedToAssemble, |
| }; |
| |
| // This class consolidates logic regarding when to schedule the browser to send |
| // attribution reports. It talks directly to the `AttributionStorage` to help |
| // make these decisions. |
| // |
| // While the class does not make large changes to the underlying database, it |
| // is responsible for notifying the `AttributionStorage` when the browser comes |
| // back online, which mutates report times for some scheduled reports. |
| // |
| // TODO(apaseltiner): Consider making this class an observer to allow it to |
| // manage when to schedule things. |
| class AttributionReportScheduler : public ReportSchedulerTimer::Delegate { |
| public: |
| AttributionReportScheduler( |
| base::RepeatingClosure send_reports, |
| base::RepeatingClosure on_reporting_paused_cb, |
| base::SequenceBound<AttributionStorage>& attribution_storage) |
| : send_reports_(std::move(send_reports)), |
| on_reporting_paused_cb_(std::move(on_reporting_paused_cb)), |
| attribution_storage_(attribution_storage) {} |
| ~AttributionReportScheduler() override = default; |
| |
| AttributionReportScheduler(const AttributionReportScheduler&) = delete; |
| AttributionReportScheduler& operator=(const AttributionReportScheduler&) = |
| delete; |
| AttributionReportScheduler(AttributionReportScheduler&&) = delete; |
| AttributionReportScheduler& operator=(AttributionReportScheduler&&) = delete; |
| |
| private: |
| // ReportSchedulerTimer::Delegate: |
| void GetNextReportTime( |
| base::OnceCallback<void(absl::optional<base::Time>)> callback, |
| base::Time now) override { |
| attribution_storage_->AsyncCall(&AttributionStorage::GetNextReportTime) |
| .WithArgs(now) |
| .Then(std::move(callback)); |
| } |
| void OnReportingTimeReached(base::Time now) override { send_reports_.Run(); } |
| void AdjustOfflineReportTimes( |
| base::OnceCallback<void(absl::optional<base::Time>)> maybe_set_timer_cb) |
| override { |
| // Add delay to all reports that should have been sent while the browser was |
| // offline so they are not temporally joinable. We do this in storage to |
| // avoid pulling an unbounded number of reports into memory, only to |
| // immediately issue async storage calls to modify their report times. |
| attribution_storage_ |
| ->AsyncCall(&AttributionStorage::AdjustOfflineReportTimes) |
| .Then(std::move(maybe_set_timer_cb)); |
| } |
| |
| void OnReportingPaused() override { on_reporting_paused_cb_.Run(); } |
| |
| base::RepeatingClosure send_reports_; |
| base::RepeatingClosure on_reporting_paused_cb_; |
| const raw_ref<base::SequenceBound<AttributionStorage>> attribution_storage_; |
| }; |
| |
| bool IsStorageKeySessionOnly( |
| scoped_refptr<storage::SpecialStoragePolicy> storage_policy, |
| const blink::StorageKey& storage_key) { |
| // TODO(johnidel): This conversion is unfortunate but necessary. Storage |
| // partition clear data logic uses storage key keyed deletion, while the |
| // storage policy uses GURLs. Ideally these would be coalesced. |
| const GURL& url = storage_key.origin().GetURL(); |
| if (storage_policy->IsStorageProtected(url)) { |
| return false; |
| } |
| |
| if (storage_policy->IsStorageSessionOnly(url)) { |
| return true; |
| } |
| return false; |
| } |
| |
| void RecordStoreSourceStatus(StoreSourceResult result) { |
| static_assert(StorableSource::Result::kMaxValue == |
| StorableSource::Result::kSuccessNoised, |
| "Bump version of Conversions.SourceStoredStatus2 histogram."); |
| base::UmaHistogramEnumeration("Conversions.SourceStoredStatus2", |
| result.status); |
| } |
| |
| void RecordCreateReportStatus(CreateReportResult result) { |
| static_assert(AttributionTrigger::EventLevelResult::kMaxValue == |
| AttributionTrigger::EventLevelResult::kNotRegistered, |
| "Bump version of Conversions.CreateReportStatus7 histogram."); |
| base::UmaHistogramEnumeration("Conversions.CreateReportStatus7", |
| result.event_level_status()); |
| static_assert( |
| AttributionTrigger::AggregatableResult::kMaxValue == |
| AttributionTrigger::AggregatableResult::kExcessiveReports, |
| "Bump version of Conversions.AggregatableReport.CreateReportStatus4 " |
| "histogram."); |
| base::UmaHistogramEnumeration( |
| "Conversions.AggregatableReport.CreateReportStatus4", |
| result.aggregatable_status()); |
| } |
| |
| ConversionReportSendOutcome ConvertToConversionReportSendOutcome( |
| SendResult::Status status) { |
| switch (status) { |
| case SendResult::Status::kSent: |
| return ConversionReportSendOutcome::kSent; |
| case SendResult::Status::kTransientFailure: |
| case SendResult::Status::kFailure: |
| return ConversionReportSendOutcome::kFailed; |
| case SendResult::Status::kDropped: |
| return ConversionReportSendOutcome::kDropped; |
| case SendResult::Status::kFailedToAssemble: |
| return ConversionReportSendOutcome::kFailedToAssemble; |
| } |
| } |
| |
| void RecordNetworkConnectionTypeOnFailure( |
| AttributionReport::Type report_type, |
| network::mojom::ConnectionType connection_type) { |
| switch (report_type) { |
| case AttributionReport::Type::kEventLevel: |
| base::UmaHistogramEnumeration( |
| "Conversions.EventLevelReport.NetworkConnectionTypeOnFailure", |
| connection_type); |
| break; |
| case AttributionReport::Type::kAggregatableAttribution: |
| base::UmaHistogramEnumeration( |
| "Conversions.AggregatableReport.NetworkConnectionTypeOnFailure", |
| connection_type); |
| break; |
| case AttributionReport::Type::kNullAggregatable: |
| break; |
| } |
| } |
| |
| void RecordAssembleAggregatableReportStatus( |
| AssembleAggregatableReportStatus status) { |
| base::UmaHistogramEnumeration( |
| "Conversions.AggregatableReport.AssembleReportStatus", status); |
| } |
| |
| // Called when |report| is to be sent over network for event-level reports or |
| // to be assembled for aggregatable reports, for logging metrics. |
| void LogMetricsOnReportSend(const AttributionReport& report, base::Time now) { |
| switch (report.GetReportType()) { |
| case AttributionReport::Type::kEventLevel: { |
| // Use a large time range to capture users that might not open the browser |
| // for a long time while a conversion report is pending. Revisit this |
| // range if it is non-ideal for real world data. |
| const AttributionInfo& attribution_info = report.attribution_info(); |
| base::TimeDelta time_since_original_report_time = |
| now - report.initial_report_time(); |
| base::UmaHistogramCustomTimes( |
| "Conversions.ExtraReportDelay2", time_since_original_report_time, |
| base::Seconds(1), base::Days(24), /*buckets=*/100); |
| |
| base::TimeDelta time_from_conversion_to_report_send = |
| report.report_time() - attribution_info.time; |
| UMA_HISTOGRAM_COUNTS_1000("Conversions.TimeFromConversionToReportSend", |
| time_from_conversion_to_report_send.InHours()); |
| |
| UMA_HISTOGRAM_CUSTOM_TIMES("Conversions.SchedulerReportDelay", |
| now - report.report_time(), base::Seconds(1), |
| base::Days(1), 50); |
| break; |
| } |
| case AttributionReport::Type::kAggregatableAttribution: { |
| base::TimeDelta time_from_conversion_to_report_assembly = |
| report.report_time() - report.attribution_info().time; |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "Conversions.AggregatableReport.TimeFromTriggerToReportAssembly2", |
| time_from_conversion_to_report_assembly, base::Minutes(1), |
| base::Days(24), 50); |
| |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "Conversions.AggregatableReport.ExtraReportDelay", |
| now - report.initial_report_time(), base::Seconds(1), base::Days(24), |
| 50); |
| |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "Conversions.AggregatableReport.SchedulerReportDelay", |
| now - report.report_time(), base::Seconds(1), base::Days(1), 50); |
| break; |
| } |
| case AttributionReport::Type::kNullAggregatable: |
| break; |
| } |
| } |
| |
| // Called when |report| is sent, failed or dropped, for logging metrics. |
| void LogMetricsOnReportCompleted(const AttributionReport& report, |
| SendResult::Status status) { |
| switch (report.GetReportType()) { |
| case AttributionReport::Type::kEventLevel: |
| base::UmaHistogramEnumeration( |
| "Conversions.ReportSendOutcome3", |
| ConvertToConversionReportSendOutcome(status)); |
| break; |
| case AttributionReport::Type::kAggregatableAttribution: |
| base::UmaHistogramEnumeration( |
| "Conversions.AggregatableReport.ReportSendOutcome2", |
| ConvertToConversionReportSendOutcome(status)); |
| break; |
| case AttributionReport::Type::kNullAggregatable: |
| break; |
| } |
| } |
| |
| // Called when `report` is sent successfully. |
| void LogMetricsOnReportSent(const AttributionReport& report) { |
| base::Time now = base::Time::Now(); |
| base::TimeDelta time_from_conversion_to_report_sent = |
| now - report.attribution_info().time; |
| base::TimeDelta time_since_original_report_time = |
| now - report.initial_report_time(); |
| |
| switch (report.GetReportType()) { |
| case AttributionReport::Type::kEventLevel: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "Conversions.ExtraReportDelayForSuccessfulSend", |
| time_since_original_report_time, base::Seconds(1), base::Days(24), |
| /*bucket_count=*/100); |
| |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Conversions.TimeFromTriggerToReportSentSuccessfully", |
| time_from_conversion_to_report_sent.InHours()); |
| break; |
| case AttributionReport::Type::kAggregatableAttribution: |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "Conversions.AggregatableReport." |
| "TimeFromTriggerToReportSentSuccessfully", |
| time_from_conversion_to_report_sent, base::Minutes(1), base::Days(24), |
| 50); |
| |
| UMA_HISTOGRAM_CUSTOM_TIMES( |
| "Conversions.AggregatableReport.ExtraReportDelayForSuccessfulSend", |
| time_since_original_report_time, base::Seconds(1), base::Days(24), |
| /*bucket_count=*/50); |
| break; |
| case AttributionReport::Type::kNullAggregatable: |
| break; |
| } |
| } |
| |
| std::unique_ptr<AttributionStorageDelegate> MakeStorageDelegate() { |
| bool debug_mode = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAttributionReportingDebugMode); |
| |
| if (debug_mode) { |
| return std::make_unique<AttributionStorageDelegateImpl>( |
| AttributionNoiseMode::kNone, AttributionDelayMode::kNone); |
| } |
| |
| return std::make_unique<AttributionStorageDelegateImpl>( |
| AttributionNoiseMode::kDefault, AttributionDelayMode::kDefault); |
| } |
| |
| bool IsOperationAllowed( |
| StoragePartitionImpl* storage_partition, |
| ContentBrowserClient::AttributionReportingOperation operation, |
| content::RenderFrameHost* rfh, |
| const url::Origin* source_origin, |
| const url::Origin* destination_origin, |
| const url::Origin* reporting_origin) { |
| DCHECK(storage_partition); |
| return GetContentClient()->browser()->IsAttributionReportingOperationAllowed( |
| storage_partition->browser_context(), operation, rfh, source_origin, |
| destination_origin, reporting_origin); |
| } |
| |
| std::unique_ptr<AttributionOsLevelManager> CreateOsLevelManager() { |
| #if BUILDFLAG(IS_ANDROID) |
| if (base::FeatureList::IsEnabled( |
| network::features::kAttributionReportingCrossAppWeb)) { |
| return std::make_unique<AttributionOsLevelManagerAndroid>(); |
| } |
| #endif |
| return std::make_unique<NoOpAttributionOsLevelManager>(); |
| } |
| |
| bool g_run_in_memory = false; |
| |
| } // namespace |
| |
| struct AttributionManagerImpl::PendingReportTimings { |
| base::Time creation_time; |
| base::Time report_time; |
| }; |
| |
| absl::optional<base::TimeDelta> GetFailedReportDelay(int failed_send_attempts) { |
| DCHECK_GT(failed_send_attempts, 0); |
| |
| const int kMaxFailedSendAttempts = 2; |
| const base::TimeDelta kInitialReportDelay = base::Minutes(5); |
| const int kDelayFactor = 3; |
| |
| if (failed_send_attempts > kMaxFailedSendAttempts) { |
| return absl::nullopt; |
| } |
| |
| return kInitialReportDelay * std::pow(kDelayFactor, failed_send_attempts - 1); |
| } |
| |
| ScopedUseInMemoryStorageForTesting::ScopedUseInMemoryStorageForTesting() |
| : previous_(g_run_in_memory) { |
| g_run_in_memory = true; |
| } |
| |
| ScopedUseInMemoryStorageForTesting::~ScopedUseInMemoryStorageForTesting() { |
| g_run_in_memory = previous_; |
| } |
| |
| bool AttributionManagerImpl::IsReportAllowed( |
| const AttributionReport& report) const { |
| const attribution_reporting::SuitableOrigin* source_origin = absl::visit( |
| base::Overloaded{ |
| [](const AttributionReport::EventLevelData& data) { |
| return &data.source.common_info().source_origin(); |
| }, |
| [](const AttributionReport::AggregatableAttributionData& data) { |
| return &data.source.common_info().source_origin(); |
| }, |
| [&](const AttributionReport::NullAggregatableData&) { |
| return &report.attribution_info().context_origin; |
| }, |
| }, |
| report.data()); |
| return IsOperationAllowed( |
| storage_partition_.get(), |
| ContentBrowserClient::AttributionReportingOperation::kReport, |
| /*rfh=*/nullptr, &**source_origin, |
| &*report.attribution_info().context_origin, |
| &*report.GetReportingOrigin()); |
| } |
| |
| // static |
| std::unique_ptr<AttributionManagerImpl> |
| AttributionManagerImpl::CreateForTesting( |
| const base::FilePath& user_data_directory, |
| size_t max_pending_events, |
| scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, |
| std::unique_ptr<AttributionStorageDelegate> storage_delegate, |
| std::unique_ptr<AttributionCookieChecker> cookie_checker, |
| std::unique_ptr<AttributionReportSender> report_sender, |
| std::unique_ptr<AttributionOsLevelManager> os_level_manager, |
| StoragePartitionImpl* storage_partition, |
| scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner) { |
| return base::WrapUnique(new AttributionManagerImpl( |
| storage_partition, user_data_directory, max_pending_events, |
| std::move(special_storage_policy), std::move(storage_delegate), |
| std::move(cookie_checker), std::move(report_sender), |
| std::move(os_level_manager), std::move(storage_task_runner))); |
| } |
| |
| AttributionManagerImpl::AttributionManagerImpl( |
| StoragePartitionImpl* storage_partition, |
| const base::FilePath& user_data_directory, |
| scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy) |
| : AttributionManagerImpl( |
| storage_partition, |
| user_data_directory, |
| /*max_pending_events=*/1000, |
| std::move(special_storage_policy), |
| MakeStorageDelegate(), |
| std::make_unique<AttributionCookieCheckerImpl>(storage_partition), |
| std::make_unique<AttributionReportNetworkSender>( |
| storage_partition->GetURLLoaderFactoryForBrowserProcess()), |
| CreateOsLevelManager(), |
| // This uses BLOCK_SHUTDOWN as some data deletion operations may be |
| // running when the browser is closed, and we want to ensure all data |
| // is deleted correctly. Additionally, we use MUST_USE_FOREGROUND to |
| // avoid priority inversions if a task is already running when the |
| // priority is increased. |
| base::ThreadPool::CreateUpdateableSequencedTaskRunner( |
| base::TaskTraits(base::TaskPriority::BEST_EFFORT, |
| base::MayBlock(), |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN, |
| base::ThreadPolicy::MUST_USE_FOREGROUND))) { |
| } // namespace content |
| |
| AttributionManagerImpl::AttributionManagerImpl( |
| StoragePartitionImpl* storage_partition, |
| const base::FilePath& user_data_directory, |
| size_t max_pending_events, |
| scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, |
| std::unique_ptr<AttributionStorageDelegate> storage_delegate, |
| std::unique_ptr<AttributionCookieChecker> cookie_checker, |
| std::unique_ptr<AttributionReportSender> report_sender, |
| std::unique_ptr<AttributionOsLevelManager> os_level_manager, |
| scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner) |
| : throttler_(DestinationThrottler::Policy()), |
| storage_partition_(storage_partition), |
| max_pending_events_(max_pending_events), |
| storage_task_runner_(std::move(storage_task_runner)), |
| attribution_storage_(base::SequenceBound<AttributionStorageSql>( |
| storage_task_runner_, |
| g_run_in_memory ? base::FilePath() : user_data_directory, |
| std::move(storage_delegate))), |
| scheduler_timer_(std::make_unique<AttributionReportScheduler>( |
| base::BindRepeating(&AttributionManagerImpl::GetReportsToSend, |
| base::Unretained(this)), |
| base::BindRepeating( |
| &AttributionManagerImpl::RecordPendingAggregatableReportsTimings, |
| base::Unretained(this)), |
| attribution_storage_)), |
| data_host_manager_( |
| std::make_unique<AttributionDataHostManagerImpl>(this)), |
| special_storage_policy_(std::move(special_storage_policy)), |
| cookie_checker_(std::move(cookie_checker)), |
| report_sender_(std::move(report_sender)), |
| os_level_manager_(std::move(os_level_manager)) { |
| DCHECK(storage_partition_); |
| DCHECK_GT(max_pending_events_, 0u); |
| DCHECK(storage_task_runner_); |
| DCHECK(cookie_checker_); |
| DCHECK(report_sender_); |
| DCHECK(os_level_manager_); |
| } |
| |
| AttributionManagerImpl::~AttributionManagerImpl() { |
| RecordPendingAggregatableReportsTimings(); |
| |
| // Browser contexts are not required to have a special storage policy. |
| if (!special_storage_policy_ || |
| !special_storage_policy_->HasSessionOnlyOrigins()) { |
| return; |
| } |
| |
| // Delete stored data for all session only origins given by |
| // |special_storage_policy|. |
| StoragePartition::StorageKeyMatcherFunction |
| session_only_storage_key_predicate = base::BindRepeating( |
| &IsStorageKeySessionOnly, std::move(special_storage_policy_)); |
| ClearData(base::Time::Min(), base::Time::Max(), |
| std::move(session_only_storage_key_predicate), |
| /*filter_builder=*/nullptr, |
| /*delete_rate_limit_data=*/true, /*done=*/base::DoNothing()); |
| } |
| |
| void AttributionManagerImpl::AddObserver(AttributionObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void AttributionManagerImpl::RemoveObserver(AttributionObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| AttributionDataHostManager* AttributionManagerImpl::GetDataHostManager() { |
| DCHECK(data_host_manager_); |
| return data_host_manager_.get(); |
| } |
| |
| void AttributionManagerImpl::HandleSource( |
| StorableSource source, |
| GlobalRenderFrameHostId render_frame_id) { |
| bool allowed = IsOperationAllowed( |
| storage_partition_.get(), |
| ContentBrowserClient::AttributionReportingOperation::kSource, |
| RenderFrameHost::FromID(render_frame_id), |
| &*source.common_info().source_origin(), |
| /*destination_origin=*/nullptr, |
| &*source.common_info().reporting_origin()); |
| if (!allowed) { |
| OnSourceStored( |
| source, |
| /*cleared_debug_key=*/absl::nullopt, |
| /*is_debug_cookie_set=*/false, |
| StoreSourceResult(StorableSource::Result::kProhibitedByBrowserPolicy)); |
| return; |
| } |
| |
| // TODO(csharrison): Consider enforcing this limit after checking metrics. |
| bool allowed_by_destination_window = throttler_.UpdateAndGetAllowed( |
| source.registration().destination_set, |
| net::SchemefulSite(source.common_info().source_origin()), |
| net::SchemefulSite(source.common_info().reporting_origin())); |
| base::UmaHistogramBoolean("Conversions.SourceAllowedByDestinationWindowLimit", |
| allowed_by_destination_window); |
| |
| MaybeEnqueueEvent(std::move(source)); |
| } |
| |
| void AttributionManagerImpl::StoreSource(StorableSource source, |
| bool is_debug_cookie_set) { |
| absl::optional<uint64_t> cleared_debug_key; |
| if (!is_debug_cookie_set) { |
| cleared_debug_key = |
| std::exchange(source.registration().debug_key, absl::nullopt); |
| } |
| |
| attribution_storage_.AsyncCall(&AttributionStorage::StoreSource) |
| .WithArgs(source) |
| .Then(base::BindOnce(&AttributionManagerImpl::OnSourceStored, |
| weak_factory_.GetWeakPtr(), std::move(source), |
| cleared_debug_key, is_debug_cookie_set)); |
| } |
| |
| void AttributionManagerImpl::RecordPendingAggregatableReportsTimings() { |
| const base::Time now = base::Time::Now(); |
| |
| for (const auto& [key, timing] : pending_aggregatable_reports_) { |
| UMA_HISTOGRAM_LONG_TIMES( |
| "Conversions.AggregatableReport.PendingAndBrowserWentOffline." |
| "TimeSinceCreation", |
| now - timing.creation_time); |
| UMA_HISTOGRAM_LONG_TIMES( |
| "Conversions.AggregatableReport.PendingAndBrowserWentOffline." |
| "TimeUntilReportTime", |
| timing.report_time - now); |
| } |
| pending_aggregatable_reports_.clear(); |
| } |
| |
| void AttributionManagerImpl::OnSourceStored( |
| const StorableSource& source, |
| absl::optional<uint64_t> cleared_debug_key, |
| bool is_debug_cookie_set, |
| StoreSourceResult result) { |
| RecordStoreSourceStatus(result); |
| |
| base::Time now = base::Time::Now(); |
| for (auto& observer : observers_) { |
| observer.OnSourceHandled(source, now, cleared_debug_key, result.status); |
| } |
| |
| scheduler_timer_.MaybeSet(result.min_fake_report_time); |
| |
| NotifySourcesChanged(); |
| |
| MaybeSendVerboseDebugReport(source, is_debug_cookie_set, result); |
| } |
| |
| void AttributionManagerImpl::HandleTrigger( |
| AttributionTrigger trigger, |
| GlobalRenderFrameHostId render_frame_id) { |
| bool allowed = IsOperationAllowed( |
| storage_partition_.get(), |
| ContentBrowserClient::AttributionReportingOperation::kTrigger, |
| RenderFrameHost::FromID(render_frame_id), |
| /*source_origin=*/nullptr, &*trigger.destination_origin(), |
| &*trigger.reporting_origin()); |
| if (!allowed) { |
| OnReportStored( |
| trigger, |
| /*cleared_debug_key=*/absl::nullopt, /*is_debug_cookie_set=*/false, |
| CreateReportResult( |
| /*trigger_time=*/base::Time::Now(), |
| AttributionTrigger::EventLevelResult::kProhibitedByBrowserPolicy, |
| AttributionTrigger::AggregatableResult:: |
| kProhibitedByBrowserPolicy)); |
| return; |
| } |
| |
| MaybeEnqueueEvent(std::move(trigger)); |
| } |
| |
| void AttributionManagerImpl::StoreTrigger(AttributionTrigger trigger, |
| bool is_debug_cookie_set) { |
| absl::optional<uint64_t> cleared_debug_key; |
| if (!is_debug_cookie_set) { |
| cleared_debug_key = |
| std::exchange(trigger.registration().debug_key, absl::nullopt); |
| } |
| |
| attribution_storage_.AsyncCall(&AttributionStorage::MaybeCreateAndStoreReport) |
| .WithArgs(trigger) |
| .Then(base::BindOnce(&AttributionManagerImpl::OnReportStored, |
| weak_factory_.GetWeakPtr(), std::move(trigger), |
| cleared_debug_key, is_debug_cookie_set)); |
| } |
| |
| void AttributionManagerImpl::MaybeEnqueueEvent(SourceOrTrigger event) { |
| const size_t size_before_push = pending_events_.size(); |
| |
| // Avoid unbounded memory growth with adversarial input. |
| bool allowed = size_before_push < max_pending_events_; |
| base::UmaHistogramBoolean("Conversions.EnqueueEventAllowed", allowed); |
| if (!allowed) { |
| return; |
| } |
| |
| pending_events_.push_back(std::move(event)); |
| |
| // Only process the new event if it is the only one in the queue. Otherwise, |
| // there's already an async cookie-check in progress. |
| if (size_before_push == 0) { |
| ProcessEvents(); |
| } |
| } |
| |
| void AttributionManagerImpl::ProcessEvents() { |
| // Process as many events not requiring a cookie check (synchronously) as |
| // possible. Once reaching the first to require a cookie check, start the |
| // async check and stop processing further events. |
| while (!pending_events_.empty()) { |
| const attribution_reporting::SuitableOrigin* cookie_origin = absl::visit( |
| base::Overloaded{ |
| [](const StorableSource& source) { |
| return source.registration().debug_key.has_value() || |
| source.registration().debug_reporting |
| ? &source.common_info().reporting_origin() |
| : nullptr; |
| }, |
| [](const AttributionTrigger& trigger) { |
| const attribution_reporting::TriggerRegistration& registration = |
| trigger.registration(); |
| return registration.debug_key.has_value() || |
| registration.debug_reporting |
| ? &trigger.reporting_origin() |
| : nullptr; |
| }, |
| }, |
| pending_events_.front()); |
| if (cookie_origin) { |
| cookie_checker_->IsDebugCookieSet( |
| *cookie_origin, |
| base::BindOnce( |
| [](base::WeakPtr<AttributionManagerImpl> manager, |
| bool is_debug_cookie_set) { |
| if (manager) { |
| manager->ProcessNextEvent(is_debug_cookie_set); |
| manager->ProcessEvents(); |
| } |
| }, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| ProcessNextEvent(/*is_debug_cookie_set=*/false); |
| } |
| } |
| |
| void AttributionManagerImpl::ProcessNextEvent(bool is_debug_cookie_set) { |
| DCHECK(!pending_events_.empty()); |
| |
| absl::visit(base::Overloaded{ |
| [&](StorableSource& source) { |
| StoreSource(std::move(source), is_debug_cookie_set); |
| }, |
| [&](AttributionTrigger& trigger) { |
| StoreTrigger(std::move(trigger), is_debug_cookie_set); |
| }, |
| }, |
| pending_events_.front()); |
| |
| pending_events_.pop_front(); |
| } |
| |
| void AttributionManagerImpl::AddPendingAggregatableReportTiming( |
| const AttributionReport& report) { |
| // The maximum number of pending reports that should be considered. Past this |
| // value, events will be ignored. |
| constexpr size_t kMaxPendingReportsTimings = 50; |
| if (pending_aggregatable_reports_.size() >= kMaxPendingReportsTimings) { |
| return; |
| } |
| |
| DCHECK_EQ(report.GetReportType(), |
| AttributionReport::Type::kAggregatableAttribution); |
| |
| auto [it, inserted] = pending_aggregatable_reports_.try_emplace( |
| report.id(), PendingReportTimings{ |
| .creation_time = base::Time::Now(), |
| .report_time = report.report_time(), |
| }); |
| DCHECK(inserted); |
| } |
| |
| void AttributionManagerImpl::OnReportStored( |
| const AttributionTrigger& trigger, |
| absl::optional<uint64_t> cleared_debug_key, |
| bool is_debug_cookie_set, |
| CreateReportResult result) { |
| RecordCreateReportStatus(result); |
| |
| absl::optional<base::Time> min_new_report_time; |
| |
| if (auto& report = result.new_event_level_report()) { |
| min_new_report_time = report->report_time(); |
| MaybeSendDebugReport(std::move(*report)); |
| } |
| |
| if (auto& report = result.new_aggregatable_report()) { |
| min_new_report_time = AttributionReport::MinReportTime( |
| min_new_report_time, report->report_time()); |
| |
| AddPendingAggregatableReportTiming(*report); |
| |
| MaybeSendDebugReport(std::move(*report)); |
| } |
| |
| min_new_report_time = AttributionReport::MinReportTime( |
| min_new_report_time, result.min_null_aggregatable_report_time()); |
| |
| scheduler_timer_.MaybeSet(min_new_report_time); |
| |
| bool notify_reports_changed = false; |
| |
| if (result.event_level_status() != |
| AttributionTrigger::EventLevelResult::kInternalError || |
| result.aggregatable_status() == |
| AttributionTrigger::AggregatableResult::kSuccess) { |
| // Sources are changed here because storing an event-level report or |
| // aggregatable report can cause sources to reach event-level attribution |
| // limit or become associated with a dedup key. |
| NotifySourcesChanged(); |
| |
| notify_reports_changed = true; |
| } |
| |
| if (notify_reports_changed || |
| result.min_null_aggregatable_report_time().has_value()) { |
| NotifyReportsChanged(); |
| } |
| |
| for (auto& observer : observers_) { |
| observer.OnTriggerHandled(trigger, cleared_debug_key, result); |
| } |
| |
| MaybeSendVerboseDebugReport(trigger, is_debug_cookie_set, result); |
| } |
| |
| void AttributionManagerImpl::MaybeSendDebugReport(AttributionReport&& report) { |
| const AttributionInfo& attribution_info = report.attribution_info(); |
| const StoredSource* source = report.GetStoredSource(); |
| DCHECK(source); |
| if (!attribution_info.debug_key || !source->debug_key() || |
| !IsReportAllowed(report)) { |
| return; |
| } |
| |
| // We don't delete from storage for debug reports. |
| PrepareToSendReport(std::move(report), /*is_debug_report=*/true, |
| base::BindOnce(&AttributionManagerImpl::NotifyReportSent, |
| weak_factory_.GetWeakPtr(), |
| /*is_debug_report=*/true)); |
| } |
| |
| void AttributionManagerImpl::GetActiveSourcesForWebUI( |
| base::OnceCallback<void(std::vector<StoredSource>)> callback) { |
| const int kMaxSources = 1000; |
| attribution_storage_.AsyncCall(&AttributionStorage::GetActiveSources) |
| .WithArgs(kMaxSources) |
| .Then(std::move(callback)); |
| } |
| |
| void AttributionManagerImpl::GetPendingReportsForInternalUse( |
| int limit, |
| base::OnceCallback<void(std::vector<AttributionReport>)> callback) { |
| attribution_storage_.AsyncCall(&AttributionStorage::GetAttributionReports) |
| .WithArgs(/*max_report_time=*/base::Time::Max(), limit) |
| .Then(std::move(callback)); |
| } |
| |
| void AttributionManagerImpl::SendReportsForWebUI( |
| const std::vector<AttributionReport::Id>& ids, |
| base::OnceClosure done) { |
| DCHECK(done); |
| attribution_storage_.AsyncCall(&AttributionStorage::GetReports) |
| .WithArgs(ids) |
| .Then(base::BindOnce(&AttributionManagerImpl::OnGetReportsToSendFromWebUI, |
| weak_factory_.GetWeakPtr(), std::move(done))); |
| } |
| |
| void AttributionManagerImpl::ClearData( |
| base::Time delete_begin, |
| base::Time delete_end, |
| StoragePartition::StorageKeyMatcherFunction filter, |
| BrowsingDataFilterBuilder* filter_builder, |
| bool delete_rate_limit_data, |
| base::OnceClosure done) { |
| auto barrier = base::BarrierClosure(2, std::move(done)); |
| done = barrier; |
| |
| if (filter_builder) { |
| auto* filter_builder_impl = |
| static_cast<BrowsingDataFilterBuilderImpl*>(filter_builder); |
| os_level_manager_->ClearData( |
| delete_begin, delete_end, filter_builder_impl->GetOrigins(), |
| filter_builder_impl->GetRegisterableDomains(), |
| filter_builder->GetMode(), delete_rate_limit_data, std::move(barrier)); |
| } else { |
| // When there is not filter_builder, we clear all the data. |
| os_level_manager_->ClearData(delete_begin, delete_end, /*origins=*/{}, |
| /*domains=*/{}, |
| // By preserving data only from an empty list, |
| // we are effectively clearing all the data. |
| BrowsingDataFilterBuilder::Mode::kPreserve, |
| delete_rate_limit_data, std::move(barrier)); |
| } |
| |
| // When a clear data task is queued or running, we use a higher priority. |
| ++num_pending_clear_data_tasks_; |
| storage_task_runner_->UpdatePriority(base::TaskPriority::USER_VISIBLE); |
| |
| attribution_storage_.AsyncCall(&AttributionStorage::ClearData) |
| .WithArgs(delete_begin, delete_end, std::move(filter), |
| delete_rate_limit_data) |
| .Then(std::move(done).Then( |
| base::BindOnce(&AttributionManagerImpl::OnClearDataComplete, |
| weak_factory_.GetWeakPtr()))); |
| } |
| |
| void AttributionManagerImpl::OnClearDataComplete() { |
| DCHECK_GT(num_pending_clear_data_tasks_, 0); |
| --num_pending_clear_data_tasks_; |
| |
| // No more clear data tasks, so we can reset the priority. |
| if (num_pending_clear_data_tasks_ == 0) { |
| storage_task_runner_->UpdatePriority(base::TaskPriority::BEST_EFFORT); |
| } |
| |
| NotifySourcesChanged(); |
| NotifyReportsChanged(); |
| } |
| |
| void AttributionManagerImpl::GetAllDataKeys( |
| base::OnceCallback<void(std::vector<AttributionManager::DataKey>)> |
| callback) { |
| attribution_storage_.AsyncCall(&AttributionStorage::GetAllDataKeys) |
| .Then(std::move(callback)); |
| } |
| |
| void AttributionManagerImpl::RemoveAttributionDataByDataKey( |
| const AttributionManager::DataKey& data_key, |
| base::OnceClosure callback) { |
| attribution_storage_.AsyncCall(&AttributionStorage::DeleteByDataKey) |
| .WithArgs(data_key) |
| .Then(std::move(callback)); |
| } |
| |
| void AttributionManagerImpl::GetReportsToSend() { |
| // We only get the next report time strictly after now, because if we are |
| // sending a report now but haven't finished doing so and it is still present |
| // in storage, storage will return the report time for the same report. |
| // Deduplication via `reports_being_sent_` will ensure that the report isn't |
| // sent twice, but it will result in wasted processing. |
| // |
| // TODO(apaseltiner): Consider limiting the number of reports being sent at |
| // once, to avoid pulling an arbitrary number of reports into memory. |
| attribution_storage_.AsyncCall(&AttributionStorage::GetAttributionReports) |
| .WithArgs(/*max_report_time=*/base::Time::Now(), /*limit=*/-1) |
| .Then(base::BindOnce(&AttributionManagerImpl::SendReports, |
| weak_factory_.GetWeakPtr(), |
| /*web_ui_callback=*/base::NullCallback())); |
| } |
| |
| void AttributionManagerImpl::OnGetReportsToSendFromWebUI( |
| base::OnceClosure done, |
| std::vector<AttributionReport> reports) { |
| DCHECK(done); |
| |
| if (reports.empty()) { |
| std::move(done).Run(); |
| return; |
| } |
| |
| // Give all reports the same report time for consistency in the internals UI. |
| const base::Time now = base::Time::Now(); |
| for (AttributionReport& report : reports) { |
| report.set_report_time(now); |
| } |
| |
| auto barrier = base::BarrierClosure(reports.size(), std::move(done)); |
| SendReports(std::move(barrier), std::move(reports)); |
| } |
| |
| // If `web_ui_callback` is null, assumes that `reports` are being sent at their |
| // intended time, and logs metrics for them. Otherwise, does not log metrics. |
| void AttributionManagerImpl::SendReports( |
| base::RepeatingClosure web_ui_callback, |
| std::vector<AttributionReport> reports) { |
| const base::Time now = base::Time::Now(); |
| for (AttributionReport& report : reports) { |
| DCHECK_LE(report.report_time(), now); |
| |
| bool inserted = reports_being_sent_.emplace(report.id()).second; |
| if (!inserted) { |
| if (web_ui_callback) { |
| web_ui_callback.Run(); |
| } |
| |
| continue; |
| } |
| |
| if (report.GetReportType() == |
| AttributionReport::Type::kAggregatableAttribution) { |
| pending_aggregatable_reports_.erase(report.id()); |
| } |
| |
| if (!IsReportAllowed(report)) { |
| // If measurement is disallowed, just drop the report on the floor. We |
| // need to make sure we forward that the report was "sent" to ensure it is |
| // deleted from storage, etc. This simulates sending the report through a |
| // null channel. |
| OnReportSent(web_ui_callback, std::move(report), |
| SendResult(SendResult::Status::kDropped)); |
| continue; |
| } |
| |
| if (!web_ui_callback) { |
| LogMetricsOnReportSend(report, now); |
| } |
| |
| PrepareToSendReport( |
| std::move(report), /*is_debug_report=*/false, |
| base::BindOnce(&AttributionManagerImpl::OnReportSent, |
| weak_factory_.GetWeakPtr(), web_ui_callback)); |
| } |
| } |
| |
| void AttributionManagerImpl::MarkReportCompleted( |
| AttributionReport::Id report_id) { |
| size_t num_removed = reports_being_sent_.erase(report_id); |
| DCHECK_EQ(num_removed, 1u); |
| } |
| |
| void AttributionManagerImpl::PrepareToSendReport(AttributionReport report, |
| bool is_debug_report, |
| ReportSentCallback callback) { |
| switch (report.GetReportType()) { |
| case AttributionReport::Type::kEventLevel: |
| report_sender_->SendReport(std::move(report), is_debug_report, |
| std::move(callback)); |
| break; |
| case AttributionReport::Type::kAggregatableAttribution: |
| case AttributionReport::Type::kNullAggregatable: |
| AssembleAggregatableReport(std::move(report), is_debug_report, |
| std::move(callback)); |
| break; |
| } |
| } |
| |
| void AttributionManagerImpl::OnReportSent(base::OnceClosure done, |
| const AttributionReport& report, |
| SendResult info) { |
| // If there was a transient failure, and another attempt is allowed, |
| // update the report's DB state to reflect that. Otherwise, delete the report |
| // from storage. |
| |
| absl::optional<base::Time> new_report_time; |
| if (info.status == SendResult::Status::kTransientFailure) { |
| if (absl::optional<base::TimeDelta> delay = |
| GetFailedReportDelay(report.failed_send_attempts() + 1)) { |
| new_report_time = base::Time::Now() + *delay; |
| } |
| } |
| |
| if (info.status == SendResult::Status::kTransientFailure || |
| info.status == SendResult::Status::kFailure) { |
| RecordNetworkConnectionTypeOnFailure(report.GetReportType(), |
| scheduler_timer_.connection_type()); |
| } |
| |
| base::OnceCallback then = base::BindOnce( |
| [](base::OnceClosure done, base::WeakPtr<AttributionManagerImpl> manager, |
| AttributionReport::Id report_id, |
| absl::optional<base::Time> new_report_time, bool success) { |
| if (done) { |
| std::move(done).Run(); |
| } |
| |
| if (manager && success) { |
| manager->MarkReportCompleted(report_id); |
| manager->scheduler_timer_.MaybeSet(new_report_time); |
| manager->NotifyReportsChanged(); |
| } |
| }, |
| std::move(done), weak_factory_.GetWeakPtr(), report.id(), |
| new_report_time); |
| |
| if (new_report_time) { |
| attribution_storage_ |
| .AsyncCall(&AttributionStorage::UpdateReportForSendFailure) |
| .WithArgs(report.id(), *new_report_time) |
| .Then(std::move(then)); |
| |
| // TODO(apaseltiner): Consider surfacing retry attempts in internals UI. |
| |
| return; |
| } |
| |
| attribution_storage_.AsyncCall(&AttributionStorage::DeleteReport) |
| .WithArgs(report.id()) |
| .Then(std::move(then)); |
| |
| LogMetricsOnReportCompleted(report, info.status); |
| |
| if (info.status == SendResult::Status::kSent) { |
| LogMetricsOnReportSent(report); |
| } |
| |
| NotifyReportSent(/*is_debug_report=*/false, report, info); |
| } |
| |
| void AttributionManagerImpl::NotifyReportSent(bool is_debug_report, |
| const AttributionReport& report, |
| SendResult info) { |
| for (auto& observer : observers_) { |
| observer.OnReportSent(report, /*is_debug_report=*/is_debug_report, info); |
| } |
| } |
| |
| void AttributionManagerImpl::NotifyDebugReportSent( |
| const AttributionDebugReport& report, |
| const int status) { |
| // Use the same time for all observers. |
| const base::Time time = base::Time::Now(); |
| for (auto& observer : observers_) { |
| observer.OnDebugReportSent(report, status, time); |
| } |
| } |
| |
| void AttributionManagerImpl::AssembleAggregatableReport( |
| AttributionReport report, |
| bool is_debug_report, |
| ReportSentCallback callback) { |
| AggregationService* aggregation_service = |
| storage_partition_->GetAggregationService(); |
| if (!aggregation_service) { |
| RecordAssembleAggregatableReportStatus( |
| AssembleAggregatableReportStatus::kAggregationServiceUnavailable); |
| std::move(callback).Run(std::move(report), |
| SendResult(SendResult::Status::kFailedToAssemble)); |
| return; |
| } |
| |
| absl::optional<AggregatableReportRequest> request = |
| CreateAggregatableReportRequest(report); |
| if (!request.has_value()) { |
| RecordAssembleAggregatableReportStatus( |
| AssembleAggregatableReportStatus::kCreateRequestFailed); |
| std::move(callback).Run(std::move(report), |
| SendResult(SendResult::Status::kFailedToAssemble)); |
| return; |
| } |
| |
| aggregation_service->AssembleReport( |
| std::move(*request), |
| base::BindOnce(&AttributionManagerImpl::OnAggregatableReportAssembled, |
| weak_factory_.GetWeakPtr(), std::move(report), |
| is_debug_report, std::move(callback))); |
| } |
| |
| void AttributionManagerImpl::OnAggregatableReportAssembled( |
| AttributionReport report, |
| bool is_debug_report, |
| ReportSentCallback callback, |
| AggregatableReportRequest, |
| absl::optional<AggregatableReport> assembled_report, |
| AggregationService::AssemblyStatus) { |
| if (!assembled_report.has_value()) { |
| RecordAssembleAggregatableReportStatus( |
| AssembleAggregatableReportStatus::kAssembleReportFailed); |
| std::move(callback).Run(std::move(report), |
| SendResult(SendResult::Status::kFailedToAssemble)); |
| return; |
| } |
| |
| absl::visit( |
| base::Overloaded{ |
| [](const AttributionReport::EventLevelData&) { NOTREACHED(); }, |
| [&](AttributionReport::AggregatableAttributionData& data) { |
| data.common_data.assembled_report = std::move(assembled_report); |
| }, |
| [&](AttributionReport::NullAggregatableData& data) { |
| data.common_data.assembled_report = std::move(assembled_report); |
| }, |
| }, |
| report.data()); |
| |
| RecordAssembleAggregatableReportStatus( |
| AssembleAggregatableReportStatus::kSuccess); |
| |
| report_sender_->SendReport(std::move(report), is_debug_report, |
| std::move(callback)); |
| } |
| |
| void AttributionManagerImpl::NotifySourcesChanged() { |
| for (auto& observer : observers_) { |
| observer.OnSourcesChanged(); |
| } |
| } |
| |
| void AttributionManagerImpl::NotifyReportsChanged() { |
| for (auto& observer : observers_) { |
| observer.OnReportsChanged(); |
| } |
| } |
| |
| void AttributionManagerImpl::NotifyFailedSourceRegistration( |
| const std::string& header_value, |
| const attribution_reporting::SuitableOrigin& source_origin, |
| const attribution_reporting::SuitableOrigin& reporting_origin, |
| attribution_reporting::mojom::SourceType source_type, |
| attribution_reporting::mojom::SourceRegistrationError error) { |
| base::Time source_time = base::Time::Now(); |
| for (auto& observer : observers_) { |
| observer.OnFailedSourceRegistration(header_value, source_time, |
| source_origin, reporting_origin, |
| source_type, error); |
| } |
| } |
| |
| void AttributionManagerImpl::MaybeSendVerboseDebugReport( |
| const StorableSource& source, |
| bool is_debug_cookie_set, |
| const StoreSourceResult& result) { |
| if (!base::FeatureList::IsEnabled(kAttributionVerboseDebugReporting)) { |
| return; |
| } |
| |
| if (!IsOperationAllowed(storage_partition_.get(), |
| ContentBrowserClient::AttributionReportingOperation:: |
| kSourceVerboseDebugReport, |
| /*rfh=*/nullptr, |
| &*source.common_info().source_origin(), |
| /*destination_origin=*/nullptr, |
| &*source.common_info().reporting_origin())) { |
| return; |
| } |
| |
| if (absl::optional<AttributionDebugReport> debug_report = |
| AttributionDebugReport::Create(source, is_debug_cookie_set, result)) { |
| report_sender_->SendReport( |
| std::move(*debug_report), |
| base::BindOnce(&AttributionManagerImpl::NotifyDebugReportSent, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void AttributionManagerImpl::MaybeSendVerboseDebugReport( |
| const AttributionTrigger& trigger, |
| bool is_debug_cookie_set, |
| const CreateReportResult& result) { |
| if (!base::FeatureList::IsEnabled(kAttributionVerboseDebugReporting)) { |
| return; |
| } |
| |
| if (!IsOperationAllowed(storage_partition_.get(), |
| ContentBrowserClient::AttributionReportingOperation:: |
| kTriggerVerboseDebugReport, |
| /*rfh=*/nullptr, |
| /*source_origin=*/nullptr, |
| &*trigger.destination_origin(), |
| &*trigger.reporting_origin())) { |
| return; |
| } |
| |
| if (absl::optional<AttributionDebugReport> debug_report = |
| AttributionDebugReport::Create(trigger, is_debug_cookie_set, |
| result)) { |
| report_sender_->SendReport( |
| std::move(*debug_report), |
| base::BindOnce(&AttributionManagerImpl::NotifyDebugReportSent, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void AttributionManagerImpl::HandleOsRegistration( |
| OsRegistration registration, |
| GlobalRenderFrameHostId render_frame_id) { |
| if (!network::HasAttributionOsSupport(GetSupport())) { |
| NotifyOsRegistration(registration, |
| /*is_debug_key_allowed=*/false, |
| OsRegistrationResult::kUnsupported); |
| return; |
| } |
| |
| const auto registration_origin = |
| url::Origin::Create(registration.registration_url); |
| if (registration_origin.opaque()) { |
| NotifyOsRegistration(registration, |
| /*is_debug_key_allowed=*/false, |
| OsRegistrationResult::kInvalidRegistrationUrl); |
| return; |
| } |
| |
| ContentBrowserClient::AttributionReportingOperation operation; |
| const url::Origin* source_origin; |
| const url::Origin* destination_origin; |
| switch (registration.GetType()) { |
| case OsRegistrationType::kSource: |
| operation = |
| ContentBrowserClient::AttributionReportingOperation::kOsSource; |
| source_origin = ®istration.top_level_origin; |
| destination_origin = nullptr; |
| break; |
| case OsRegistrationType::kTrigger: |
| operation = |
| ContentBrowserClient::AttributionReportingOperation::kOsTrigger; |
| source_origin = nullptr; |
| destination_origin = ®istration.top_level_origin; |
| break; |
| } |
| |
| if (!IsOperationAllowed(storage_partition_.get(), operation, |
| RenderFrameHost::FromID(render_frame_id), |
| source_origin, destination_origin, |
| /*reporting_origin=*/®istration_origin)) { |
| NotifyOsRegistration(registration, |
| /*is_debug_key_allowed=*/false, |
| OsRegistrationResult::kProhibitedByBrowserPolicy); |
| return; |
| } |
| |
| const size_t size_before_push = pending_os_events_.size(); |
| |
| // Avoid unbounded memory growth with adversarial input. |
| bool allowed = size_before_push < max_pending_events_; |
| base::UmaHistogramBoolean("Conversions.EnqueueOsEventAllowed", allowed); |
| if (!allowed) { |
| NotifyOsRegistration(registration, |
| /*is_debug_key_allowed=*/false, |
| OsRegistrationResult::kExcessiveQueueSize); |
| return; |
| } |
| |
| pending_os_events_.push_back(std::move(registration)); |
| |
| // Only process the new event if it is the only one in the queue. Otherwise, |
| // there's already an async cookie-check in progress. |
| if (size_before_push == 0) { |
| ProcessNextOsEvent(); |
| } |
| } |
| |
| void AttributionManagerImpl::ProcessNextOsEvent() { |
| DCHECK(!pending_os_events_.empty()); |
| |
| cookie_checker_->IsDebugCookieSet( |
| url::Origin::Create(pending_os_events_.front().registration_url), |
| base::BindOnce( |
| [](base::WeakPtr<AttributionManagerImpl> manager, |
| bool is_debug_key_allowed) { |
| if (!manager) { |
| return; |
| } |
| |
| DCHECK(!manager->pending_os_events_.empty()); |
| |
| { |
| const auto& event = manager->pending_os_events_.front(); |
| manager->os_level_manager_->Register( |
| event, is_debug_key_allowed, |
| base::BindOnce(&AttributionManagerImpl::OnOsRegistration, |
| manager, event, is_debug_key_allowed)); |
| } |
| |
| manager->pending_os_events_.pop_front(); |
| if (!manager->pending_os_events_.empty()) { |
| manager->ProcessNextOsEvent(); |
| } |
| }, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void AttributionManagerImpl::NotifyOsRegistration( |
| const OsRegistration& registration, |
| bool is_debug_key_allowed, |
| OsRegistrationResult result) { |
| base::Time now = base::Time::Now(); |
| for (auto& observer : observers_) { |
| observer.OnOsRegistration(now, registration, is_debug_key_allowed, result); |
| } |
| } |
| |
| void AttributionManagerImpl::OnOsRegistration( |
| const OsRegistration& registration, |
| bool is_debug_key_allowed, |
| bool success) { |
| base::UmaHistogramBoolean("Conversions.AttributionOsRegistrationResult", |
| success); |
| |
| NotifyOsRegistration(registration, is_debug_key_allowed, |
| success ? OsRegistrationResult::kPassedToOs |
| : OsRegistrationResult::kRejectedByOs); |
| } |
| |
| } // namespace content |