| // 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 "content/public/test/attribution_simulator.h" |
| |
| #include <stddef.h> |
| #include <limits> |
| #include <memory> |
| #include <ostream> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/overloaded.h" |
| #include "base/guid.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/scoped_observation.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/values_test_util.h" |
| #include "base/thread_annotations.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/time/time_to_iso8601.h" |
| #include "base/values.h" |
| #include "content/browser/aggregation_service/aggregation_service_features.h" |
| #include "content/browser/aggregation_service/aggregation_service_impl.h" |
| #include "content/browser/aggregation_service/aggregation_service_test_utils.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_default_random_generator.h" |
| #include "content/browser/attribution_reporting/attribution_insecure_random_generator.h" |
| #include "content/browser/attribution_reporting/attribution_manager_impl.h" |
| #include "content/browser/attribution_reporting/attribution_observer.h" |
| #include "content/browser/attribution_reporting/attribution_observer_types.h" |
| #include "content/browser/attribution_reporting/attribution_random_generator.h" |
| #include "content/browser/attribution_reporting/attribution_report.h" |
| #include "content/browser/attribution_reporting/attribution_report_sender.h" |
| #include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h" |
| #include "content/browser/attribution_reporting/attribution_test_utils.h" |
| #include "content/browser/attribution_reporting/attribution_trigger.h" |
| #include "content/browser/attribution_reporting/common_source_info.h" |
| #include "content/browser/attribution_reporting/send_result.h" |
| #include "content/browser/attribution_reporting/stored_source.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/attribution_config.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/test/attribution_simulator_input_parser.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_access_result.h" |
| #include "net/cookies/cookie_options.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| #include "storage/browser/quota/special_storage_policy.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/abseil-cpp/absl/types/variant.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| base::Time GetEventTime(const AttributionSimulationEventAndValue& event) { |
| return absl::visit( |
| base::Overloaded{ |
| [](const StorableSource& source) { |
| return source.common_info().source_time(); |
| }, |
| [](const AttributionTriggerAndTime& trigger) { return trigger.time; }, |
| [](const AttributionSimulatorCookie& cookie) { |
| return cookie.cookie.CreationDate(); |
| }, |
| [](const AttributionDataClear& clear) { return clear.time; }, |
| }, |
| event.first); |
| } |
| |
| class AlwaysSetCookieChecker : public AttributionCookieChecker { |
| public: |
| AlwaysSetCookieChecker() = default; |
| |
| ~AlwaysSetCookieChecker() override = default; |
| |
| AlwaysSetCookieChecker(const AlwaysSetCookieChecker&) = delete; |
| AlwaysSetCookieChecker(AlwaysSetCookieChecker&&) = delete; |
| |
| AlwaysSetCookieChecker& operator=(const AlwaysSetCookieChecker&) = delete; |
| AlwaysSetCookieChecker& operator=(AlwaysSetCookieChecker&&) = delete; |
| |
| private: |
| // AttributionManagerImpl::CookieChecker: |
| void IsDebugCookieSet(const url::Origin& origin, |
| base::OnceCallback<void(bool)> callback) override { |
| std::move(callback).Run(true); |
| } |
| }; |
| |
| struct AttributionReportJsonConverter { |
| AttributionReportJsonConverter(bool remove_report_ids, |
| AttributionReportTimeFormat report_time_format, |
| bool remove_assembled_report, |
| base::Time time_origin) |
| : remove_report_ids(remove_report_ids), |
| report_time_format(report_time_format), |
| remove_assembled_report(remove_assembled_report), |
| time_origin(time_origin) {} |
| AttributionReportJsonConverter(const AttributionReportJsonConverter&) = |
| delete; |
| AttributionReportJsonConverter& operator=( |
| const AttributionReportJsonConverter&) = delete; |
| |
| base::Value::Dict ToJson( |
| const AttributionReport& report, |
| bool is_debug_report, |
| const absl::optional<base::GUID>& replaced_by = absl::nullopt) const { |
| base::Value::Dict report_body = report.ReportBody(); |
| if (remove_report_ids) |
| report_body.Remove("report_id"); |
| |
| if (remove_assembled_report && |
| absl::holds_alternative<AttributionReport::AggregatableAttributionData>( |
| report.data())) { |
| // Output attribution_destination from the shared_info field. |
| absl::optional<base::Value> shared_info = |
| report_body.Extract("shared_info"); |
| DCHECK(shared_info); |
| std::string* shared_info_str = shared_info->GetIfString(); |
| DCHECK(shared_info_str); |
| |
| base::Value shared_info_value = base::test::ParseJson(*shared_info_str); |
| DCHECK(shared_info_value.is_dict()); |
| |
| static constexpr char kKeyAttributionDestination[] = |
| "attribution_destination"; |
| std::string* attribution_destination = |
| shared_info_value.GetDict().FindString(kKeyAttributionDestination); |
| DCHECK(attribution_destination); |
| DCHECK(!report_body.contains(kKeyAttributionDestination)); |
| report_body.Set(kKeyAttributionDestination, |
| std::move(*attribution_destination)); |
| |
| report_body.Remove("aggregation_service_payloads"); |
| report_body.Remove("source_registration_time"); |
| } |
| |
| base::Value::Dict value; |
| value.Set("report", std::move(report_body)); |
| value.Set("report_url", report.ReportURL(is_debug_report).spec()); |
| |
| const char* time_key = replaced_by ? "replacement_time" : "report_time"; |
| |
| base::TimeDelta time_delta = base::Time::Now() - time_origin; |
| switch (report_time_format) { |
| case AttributionReportTimeFormat::kMillisecondsSinceUnixEpoch: |
| value.Set(time_key, base::NumberToString(time_delta.InMilliseconds())); |
| break; |
| case AttributionReportTimeFormat::kISO8601: |
| value.Set(time_key, |
| base::TimeToISO8601(base::Time::UnixEpoch() + time_delta)); |
| break; |
| } |
| |
| base::Value::Dict test_info; |
| if (absl::holds_alternative<AttributionReport::EventLevelData>( |
| report.data())) { |
| test_info.Set("randomized_trigger", |
| report.attribution_info().source.attribution_logic() == |
| StoredSource::AttributionLogic::kFalsely); |
| } else { |
| auto* aggregatable_data = |
| absl::get_if<AttributionReport::AggregatableAttributionData>( |
| &report.data()); |
| DCHECK(aggregatable_data); |
| base::Value::List list; |
| for (const auto& contribution : aggregatable_data->contributions) { |
| base::Value::Dict dict; |
| dict.Set("key", HexEncodeAggregationKey(contribution.key())); |
| dict.Set("value", base::checked_cast<int>(contribution.value())); |
| |
| list.Append(std::move(dict)); |
| } |
| test_info.Set("histograms", std::move(list)); |
| } |
| value.Set("test_info", std::move(test_info)); |
| |
| if (!remove_report_ids && replaced_by) { |
| value.Set("replaced_by", replaced_by->AsLowercaseString()); |
| } |
| |
| return value; |
| } |
| |
| const bool remove_report_ids; |
| const AttributionReportTimeFormat report_time_format; |
| const bool remove_assembled_report; |
| const base::Time time_origin; |
| }; |
| |
| class SentReportAccumulator : public AttributionReportSender { |
| public: |
| SentReportAccumulator(base::Value::List& event_level_reports, |
| base::Value::List& debug_event_level_reports, |
| base::Value::List& aggregatable_reports, |
| base::Value::List& debug_aggregatable_reports, |
| const AttributionReportJsonConverter& json_converter) |
| : event_level_reports_(event_level_reports), |
| debug_event_level_reports_(debug_event_level_reports), |
| aggregatable_reports_(aggregatable_reports), |
| debug_aggregatable_reports_(debug_aggregatable_reports), |
| json_converter_(json_converter) {} |
| |
| ~SentReportAccumulator() override = default; |
| |
| SentReportAccumulator(const SentReportAccumulator&) = delete; |
| SentReportAccumulator(SentReportAccumulator&&) = delete; |
| |
| SentReportAccumulator& operator=(const SentReportAccumulator&) = delete; |
| SentReportAccumulator& operator=(SentReportAccumulator&&) = delete; |
| |
| private: |
| // AttributionManagerImpl::ReportSender: |
| void SendReport(AttributionReport report, |
| bool is_debug_report, |
| ReportSentCallback sent_callback) override { |
| base::Value::List* reports; |
| switch (report.GetReportType()) { |
| case AttributionReport::ReportType::kEventLevel: |
| reports = is_debug_report ? &debug_event_level_reports_ |
| : &event_level_reports_; |
| break; |
| case AttributionReport::ReportType::kAggregatableAttribution: |
| reports = is_debug_report ? &debug_aggregatable_reports_ |
| : &aggregatable_reports_; |
| break; |
| } |
| |
| reports->Append(json_converter_.ToJson(report, is_debug_report)); |
| |
| std::move(sent_callback) |
| .Run(std::move(report), SendResult(SendResult::Status::kSent, |
| /*http_response_code=*/200)); |
| } |
| |
| base::Value::List& event_level_reports_; |
| base::Value::List& debug_event_level_reports_; |
| base::Value::List& aggregatable_reports_; |
| base::Value::List& debug_aggregatable_reports_; |
| const AttributionReportJsonConverter& json_converter_; |
| }; |
| |
| // Registers sources and triggers in the `AttributionManagerImpl` and records |
| // rejected sources in a JSON list. |
| class AttributionEventHandler : public AttributionObserver { |
| public: |
| AttributionEventHandler(AttributionManagerImpl* manager, |
| StoragePartitionImpl* storage_partition, |
| const AttributionReportJsonConverter& json_converter, |
| base::Value::List& rejected_sources, |
| base::Value::List& rejected_triggers, |
| base::Value::List& replaced_event_level_reports) |
| : manager_(manager), |
| storage_partition_(storage_partition), |
| json_converter_(json_converter), |
| rejected_sources_(rejected_sources), |
| rejected_triggers_(rejected_triggers), |
| replaced_event_level_reports_(replaced_event_level_reports) { |
| DCHECK(manager_); |
| DCHECK(storage_partition_); |
| |
| observation_.Observe(manager); |
| } |
| |
| ~AttributionEventHandler() override = default; |
| |
| void Handle(AttributionSimulationEventAndValue event) { |
| // Sources and triggers are handled in order; this includes observer |
| // invocations. Therefore, we can track the original `base::Value` |
| // associated with the event using a queue. |
| |
| input_values_.push_back(std::move(event.second)); |
| absl::visit(*this, std::move(event.first)); |
| } |
| |
| // For use with `absl::visit()`. |
| void operator()(StorableSource source) { |
| manager_->HandleSource(std::move(source)); |
| FlushCookies(); |
| } |
| |
| // For use with `absl::visit()`. |
| void operator()(AttributionTriggerAndTime trigger) { |
| manager_->HandleTrigger(std::move(trigger.trigger)); |
| FlushCookies(); |
| } |
| |
| // For use with `absl::visit()`. |
| void operator()(AttributionSimulatorCookie cookie) { |
| DCHECK(!input_values_.empty()); |
| input_values_.pop_front(); |
| |
| // TODO(apaseltiner): Consider surfacing `net::CookieAccessResult` in |
| // output. |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &network::mojom::CookieManager::SetCanonicalCookie, |
| base::Unretained( |
| storage_partition_->GetCookieManagerForBrowserProcess()), |
| cookie.cookie, cookie.source_url, |
| net::CookieOptions::MakeAllInclusive(), base::DoNothing())); |
| } |
| |
| // For use with `absl::visit()`. |
| void operator()(AttributionDataClear clear) { |
| DCHECK(!input_values_.empty()); |
| input_values_.pop_front(); |
| |
| StoragePartition::StorageKeyMatcherFunction filter; |
| if (clear.origins.has_value()) { |
| filter = |
| base::BindLambdaForTesting([origins = std::move(*clear.origins)]( |
| const blink::StorageKey& storage_key) { |
| return origins.contains(storage_key.origin()); |
| }); |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AttributionManagerImpl::ClearData, |
| base::Unretained(manager_), clear.delete_begin, |
| clear.delete_end, std::move(filter), |
| /*delete_rate_limit_data=*/true, base::DoNothing())); |
| } |
| |
| private: |
| void FlushCookies() { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &network::mojom::CookieManager::FlushCookieStore, |
| base::Unretained( |
| storage_partition_->GetCookieManagerForBrowserProcess()), |
| base::DoNothing())); |
| } |
| |
| // AttributionObserver: |
| |
| void OnSourceHandled(const StorableSource& source, |
| StorableSource::Result result) override { |
| DCHECK(!input_values_.empty()); |
| base::Value input_value = std::move(input_values_.front()); |
| input_values_.pop_front(); |
| |
| std::ostringstream reason; |
| switch (result) { |
| case StorableSource::Result::kSuccess: |
| return; |
| case StorableSource::Result::kInternalError: |
| case StorableSource::Result::kInsufficientSourceCapacity: |
| case StorableSource::Result::kInsufficientUniqueDestinationCapacity: |
| case StorableSource::Result::kExcessiveReportingOrigins: |
| case StorableSource::Result::kProhibitedByBrowserPolicy: |
| reason << result; |
| break; |
| } |
| |
| base::Value::Dict dict; |
| dict.Set("reason", reason.str()); |
| dict.Set("source", std::move(input_value)); |
| |
| rejected_sources_.Append(std::move(dict)); |
| } |
| |
| void OnTriggerHandled(const AttributionTrigger& trigger, |
| const CreateReportResult& result) override { |
| DCHECK(!input_values_.empty()); |
| base::Value input_value = std::move(input_values_.front()); |
| input_values_.pop_front(); |
| |
| std::ostringstream event_level_reason; |
| switch (result.event_level_status()) { |
| case AttributionTrigger::EventLevelResult::kSuccess: |
| break; |
| case AttributionTrigger::EventLevelResult::kSuccessDroppedLowerPriority: |
| replaced_event_level_reports_.Append(json_converter_.ToJson( |
| *result.replaced_event_level_report(), |
| /*is_debug_report=*/false, |
| result.new_event_level_report()->external_report_id())); |
| break; |
| case AttributionTrigger::EventLevelResult::kInternalError: |
| case AttributionTrigger::EventLevelResult:: |
| kNoCapacityForConversionDestination: |
| case AttributionTrigger::EventLevelResult::kNoMatchingImpressions: |
| case AttributionTrigger::EventLevelResult::kDeduplicated: |
| case AttributionTrigger::EventLevelResult::kExcessiveAttributions: |
| case AttributionTrigger::EventLevelResult::kPriorityTooLow: |
| case AttributionTrigger::EventLevelResult::kDroppedForNoise: |
| case AttributionTrigger::EventLevelResult::kExcessiveReportingOrigins: |
| case AttributionTrigger::EventLevelResult::kNoMatchingSourceFilterData: |
| case AttributionTrigger::EventLevelResult::kProhibitedByBrowserPolicy: |
| case AttributionTrigger::EventLevelResult::kNoMatchingConfigurations: |
| event_level_reason << result.event_level_status(); |
| break; |
| } |
| |
| std::ostringstream aggregatable_reason; |
| switch (result.aggregatable_status()) { |
| case AttributionTrigger::AggregatableResult::kSuccess: |
| case AttributionTrigger::AggregatableResult::kNotRegistered: |
| break; |
| case AttributionTrigger::AggregatableResult::kInternalError: |
| case AttributionTrigger::AggregatableResult:: |
| kNoCapacityForConversionDestination: |
| case AttributionTrigger::AggregatableResult::kNoMatchingImpressions: |
| case AttributionTrigger::AggregatableResult::kExcessiveAttributions: |
| case AttributionTrigger::AggregatableResult::kExcessiveReportingOrigins: |
| case AttributionTrigger::AggregatableResult::kInsufficientBudget: |
| case AttributionTrigger::AggregatableResult::kNoMatchingSourceFilterData: |
| case AttributionTrigger::AggregatableResult::kNoHistograms: |
| case AttributionTrigger::AggregatableResult::kProhibitedByBrowserPolicy: |
| aggregatable_reason << result.aggregatable_status(); |
| break; |
| } |
| |
| std::string event_level_reason_str = event_level_reason.str(); |
| std::string aggregatable_reason_str = aggregatable_reason.str(); |
| |
| if (event_level_reason_str.empty() && aggregatable_reason_str.empty()) |
| return; |
| |
| base::Value::Dict dict; |
| if (!event_level_reason_str.empty()) |
| dict.Set("event_level_reason", std::move(event_level_reason_str)); |
| |
| if (!aggregatable_reason_str.empty()) |
| dict.Set("aggregatable_reason", std::move(aggregatable_reason_str)); |
| |
| dict.Set("trigger", std::move(input_value)); |
| |
| rejected_triggers_.Append(std::move(dict)); |
| } |
| |
| base::ScopedObservation<AttributionManagerImpl, AttributionObserver> |
| observation_{this}; |
| |
| const base::raw_ptr<AttributionManagerImpl> manager_; |
| const base::raw_ptr<StoragePartitionImpl> storage_partition_; |
| const AttributionReportJsonConverter& json_converter_; |
| |
| base::Value::List& rejected_sources_; |
| base::Value::List& rejected_triggers_; |
| base::Value::List& replaced_event_level_reports_; |
| |
| base::circular_deque<base::Value> input_values_; |
| }; |
| |
| class SimulatorStorageDelegate : public AttributionStorageDelegateImpl { |
| public: |
| SimulatorStorageDelegate(AttributionNoiseMode noise_mode, |
| AttributionDelayMode delay_mode, |
| std::unique_ptr<AttributionRandomGenerator> rng, |
| AttributionConfig config) |
| : AttributionStorageDelegateImpl(noise_mode, delay_mode, std::move(rng)), |
| config_(config) { |
| DCHECK(config.Validate()); |
| } |
| |
| ~SimulatorStorageDelegate() override = default; |
| |
| int GetMaxAttributionsPerSource( |
| AttributionSourceType source_type) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| switch (source_type) { |
| case AttributionSourceType::kNavigation: |
| return config_.event_level_limit.max_attributions_per_navigation_source; |
| case AttributionSourceType::kEvent: |
| return config_.event_level_limit.max_attributions_per_event_source; |
| } |
| } |
| |
| int GetMaxSourcesPerOrigin() const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return config_.max_sources_per_origin; |
| } |
| |
| int GetMaxReportsPerDestination( |
| AttributionReport::ReportType report_type) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| switch (report_type) { |
| case AttributionReport::ReportType::kEventLevel: |
| return config_.event_level_limit.max_reports_per_destination; |
| case AttributionReport::ReportType::kAggregatableAttribution: |
| return config_.aggregate_limit.max_reports_per_destination; |
| } |
| } |
| |
| int GetMaxDestinationsPerSourceSiteReportingOrigin() const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return config_.max_destinations_per_source_site_reporting_origin; |
| } |
| |
| AttributionRateLimitConfig GetRateLimits() const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return config_.rate_limit; |
| } |
| |
| double GetRandomizedResponseRate( |
| AttributionSourceType source_type) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| switch (source_type) { |
| case AttributionSourceType::kNavigation: |
| return config_.event_level_limit |
| .navigation_source_randomized_response_rate; |
| case AttributionSourceType::kEvent: |
| return config_.event_level_limit.event_source_randomized_response_rate; |
| } |
| } |
| |
| int64_t GetAggregatableBudgetPerSource() const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return config_.aggregate_limit.aggregatable_budget_per_source; |
| } |
| |
| uint64_t TriggerDataCardinality( |
| AttributionSourceType source_type) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| switch (source_type) { |
| case AttributionSourceType::kNavigation: |
| return config_.event_level_limit |
| .navigation_source_trigger_data_cardinality; |
| case AttributionSourceType::kEvent: |
| return config_.event_level_limit.event_source_trigger_data_cardinality; |
| } |
| } |
| |
| uint64_t SanitizeSourceEventId(uint64_t source_event_id) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!config_.source_event_id_cardinality) |
| return source_event_id; |
| |
| return source_event_id % *config_.source_event_id_cardinality; |
| } |
| |
| base::Time GetAggregatableReportTime(base::Time trigger_time) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| switch (delay_mode_) { |
| case AttributionDelayMode::kDefault: |
| switch (noise_mode_) { |
| case AttributionNoiseMode::kDefault: |
| return trigger_time + config_.aggregate_limit.min_delay + |
| rng_->RandDouble() * config_.aggregate_limit.delay_span; |
| case AttributionNoiseMode::kNone: |
| return trigger_time + config_.aggregate_limit.min_delay + |
| config_.aggregate_limit.delay_span; |
| } |
| |
| case AttributionDelayMode::kNone: |
| return trigger_time; |
| } |
| } |
| |
| private: |
| const AttributionConfig config_ GUARDED_BY_CONTEXT(sequence_checker_); |
| }; |
| |
| } // namespace |
| |
| base::Value RunAttributionSimulation( |
| base::Value input, |
| const AttributionSimulationOptions& options, |
| std::ostream& error_stream) { |
| // Prerequisites for using an environment with mock time. |
| content::BrowserTaskEnvironment task_environment( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME); |
| TestBrowserContext browser_context; |
| const base::Time time_origin = base::Time::Now(); |
| |
| absl::optional<AttributionSimulationEventAndValues> events = |
| ParseAttributionSimulationInput(std::move(input), base::Time::Now(), |
| error_stream); |
| if (!events) |
| return base::Value(); |
| |
| if (events->empty()) |
| return base::Value(base::Value::Dict()); |
| |
| base::ranges::stable_sort(*events, /*comp=*/{}, &GetEventTime); |
| task_environment.FastForwardBy(GetEventTime(events->at(0)) - time_origin); |
| |
| // Avoid creating an on-disk sqlite DB. |
| content::AttributionManagerImpl::RunInMemoryForTesting(); |
| |
| // This isn't needed because the DB is completely in memory for testing. |
| const base::FilePath user_data_directory; |
| |
| std::unique_ptr<AttributionRandomGenerator> rng; |
| if (options.noise_seed.has_value()) { |
| rng = std::make_unique<AttributionInsecureRandomGenerator>( |
| *options.noise_seed); |
| } else { |
| rng = std::make_unique<AttributionDefaultRandomGenerator>(); |
| } |
| |
| const AttributionReportJsonConverter json_converter( |
| options.remove_report_ids, options.report_time_format, |
| options.remove_assembled_report, time_origin); |
| |
| base::Value::List event_level_reports; |
| base::Value::List debug_event_level_reports; |
| base::Value::List aggregatable_reports; |
| base::Value::List debug_aggregatable_reports; |
| |
| auto* storage_partition = static_cast<StoragePartitionImpl*>( |
| browser_context.GetDefaultStoragePartition()); |
| |
| std::unique_ptr<AttributionCookieChecker> cookie_checker; |
| if (options.skip_debug_cookie_checks) { |
| cookie_checker = std::make_unique<AlwaysSetCookieChecker>(); |
| } else { |
| cookie_checker = |
| std::make_unique<AttributionCookieCheckerImpl>(storage_partition); |
| } |
| |
| auto manager = AttributionManagerImpl::CreateForTesting( |
| user_data_directory, |
| /*max_pending_events=*/std::numeric_limits<size_t>::max(), |
| /*special_storage_policy=*/nullptr, |
| std::make_unique<SimulatorStorageDelegate>( |
| options.noise_mode, options.delay_mode, std::move(rng), |
| options.config), |
| std::move(cookie_checker), |
| std::make_unique<SentReportAccumulator>( |
| event_level_reports, debug_event_level_reports, aggregatable_reports, |
| debug_aggregatable_reports, json_converter), |
| storage_partition); |
| |
| base::Value::List rejected_sources; |
| base::Value::List rejected_triggers; |
| base::Value::List replaced_event_level_reports; |
| AttributionEventHandler handler( |
| manager.get(), storage_partition, json_converter, rejected_sources, |
| rejected_triggers, replaced_event_level_reports); |
| |
| static_cast<AggregationServiceImpl*>( |
| storage_partition->GetAggregationService()) |
| ->SetPublicKeysForTesting( |
| GURL(kPrivacySandboxAggregationServiceTrustedServerUrlParam.Get()), |
| PublicKeyset({aggregation_service::GenerateKey().public_key}, |
| /*fetch_time=*/base::Time::Now(), |
| /*expiry_time=*/base::Time::Max())); |
| |
| base::Time last_event_time = GetEventTime(events->back()); |
| |
| for (auto& event : *events) { |
| base::Time event_time = GetEventTime(event); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AttributionEventHandler::Handle, |
| base::Unretained(&handler), std::move(event)), |
| event_time - base::Time::Now()); |
| } |
| |
| task_environment.FastForwardBy(last_event_time - base::Time::Now()); |
| |
| std::vector<AttributionReport> pending_reports = |
| GetAttributionReportsForTesting(manager.get()); |
| |
| if (!pending_reports.empty()) { |
| base::Time last_report_time = |
| base::ranges::max(pending_reports, /*comp=*/{}, |
| &AttributionReport::report_time) |
| .report_time(); |
| task_environment.FastForwardBy(last_report_time - base::Time::Now()); |
| } |
| |
| base::Value::Dict output; |
| |
| if (!event_level_reports.empty()) |
| output.Set("event_level_reports", std::move(event_level_reports)); |
| |
| if (!debug_event_level_reports.empty()) { |
| output.Set("debug_event_level_reports", |
| std::move(debug_event_level_reports)); |
| } |
| |
| if (!aggregatable_reports.empty()) |
| output.Set("aggregatable_reports", std::move(aggregatable_reports)); |
| |
| if (!debug_aggregatable_reports.empty()) { |
| output.Set("debug_aggregatable_reports", |
| std::move(debug_aggregatable_reports)); |
| } |
| |
| if (!rejected_sources.empty()) |
| output.Set("rejected_sources", std::move(rejected_sources)); |
| |
| if (!rejected_triggers.empty()) |
| output.Set("rejected_triggers", std::move(rejected_triggers)); |
| |
| if (!replaced_event_level_reports.empty()) { |
| output.Set("replaced_event_level_reports", |
| std::move(replaced_event_level_reports)); |
| } |
| |
| return base::Value(std::move(output)); |
| } |
| |
| } // namespace content |