blob: abc43b6c3c96f1b021442fcf27b0541e1bcf78d3 [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <functional>
#include <optional>
#include <set>
#include <utility>
#include <variant>
#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/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.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 "base/values.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "components/attribution_reporting/aggregatable_filtering_id_max_bytes.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/os_registration.h"
#include "components/attribution_reporting/registration.mojom.h"
#include "components/attribution_reporting/source_registration.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/trigger_registration.h"
#include "components/metrics/dwa/dwa_builders.h"
#include "components/metrics/dwa/dwa_recorder.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/aggregatable_debug_report.h"
#include "content/browser/attribution_reporting/attribution_constants.h"
#include "content/browser/attribution_reporting/attribution_data_host_manager.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_resolver.h"
#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
#include "content/browser/attribution_reporting/attribution_resolver_delegate_impl.h"
#include "content/browser/attribution_reporting/attribution_resolver_impl.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/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/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "net/base/schemeful_site.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.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/functional/overload.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::OsRegistrationItem;
using ::attribution_reporting::mojom::OsRegistrationResult;
using ::attribution_reporting::mojom::RegistrationType;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(ConversionReportSendOutcome)
enum class ConversionReportSendOutcome {
kSent = 0,
kFailed = 1,
kDropped = 2,
kFailedToAssemble = 3,
kExpired = 4,
kMaxValue = kExpired,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/attribution_reporting/enums.xml:ConversionReportSendOutcome)
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(ConversionReportSendRetryCount)
enum class ConversionReportSendRetryCount {
kNone = 0,
kOnce = 1,
kTwice = 2,
kThrice = 3,
kFailed = 4,
kMaxValue = kFailed,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/attribution_reporting/enums.xml:ConversionReportSendRetryCount)
constexpr base::TimeDelta kReportDeliveryFirstRetryDelay = base::Minutes(5);
constexpr base::TimeDelta kReportDeliverySecondRetryDelay = base::Minutes(15);
} // namespace
// This class consolidates logic regarding when to schedule the browser to send
// attribution reports. It talks directly to the `AttributionResolver` to help
// make these decisions.
//
// While the class does not make large changes to the underlying database, it
// is responsible for notifying the `AttributionResolver` when the browser comes
// back online, which mutates report times for some scheduled reports.
class AttributionManagerImpl::ReportScheduler
: public ReportSchedulerTimer::Delegate {
public:
explicit ReportScheduler(base::WeakPtr<AttributionManagerImpl> manager)
: manager_(manager) {}
~ReportScheduler() override = default;
ReportScheduler(const ReportScheduler&) = delete;
ReportScheduler& operator=(const ReportScheduler&) = delete;
ReportScheduler(ReportScheduler&&) = delete;
ReportScheduler& operator=(ReportScheduler&&) = delete;
private:
// ReportSchedulerTimer::Delegate:
void GetNextReportTime(
base::OnceCallback<void(std::optional<base::Time>)> callback,
base::Time now) override {
if (!manager_) {
std::move(callback).Run(std::nullopt);
return;
}
manager_->attribution_resolver_
.AsyncCall(&AttributionResolver::GetNextReportTime)
.WithArgs(now)
.Then(std::move(callback));
}
void OnReportingTimeReached(base::Time now,
base::Time timer_desired_run_time) override {
if (manager_) {
manager_->GetReportsToSend();
}
}
void AdjustOfflineReportTimes(
base::OnceCallback<void(std::optional<base::Time>)> maybe_set_timer_cb)
override {
if (!manager_) {
std::move(maybe_set_timer_cb).Run(std::nullopt);
return;
}
// 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.
manager_->attribution_resolver_
.AsyncCall(&AttributionResolver::AdjustOfflineReportTimes)
.Then(std::move(maybe_set_timer_cb));
}
base::WeakPtr<AttributionManagerImpl> manager_;
};
namespace {
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(const StoreSourceResult& result) {
base::UmaHistogramEnumeration("Conversions.SourceStoredStatus8",
result.status());
dwa::builders::AttributionConversionsStoreSource()
.SetContent(result.source().common_info().reporting_origin().Serialize())
.SetStatus(static_cast<int64_t>(result.status()))
.Record(metrics::dwa::DwaRecorder::Get());
if (ukm::SourceId ukm_source_id = result.source().ukm_source_id();
ukm_source_id != ukm::kInvalidSourceId) {
ukm::builders::Conversions_SourceRegistration(ukm_source_id)
.SetStoreSourceResult(static_cast<int64_t>(result.status()))
.Record(ukm::UkmRecorder::Get());
}
}
void RecordCreateReportStatus(const CreateReportResult& result) {
base::UmaHistogramEnumeration("Conversions.CreateReportStatus9",
result.event_level_status());
base::UmaHistogramEnumeration(
"Conversions.AggregatableReport.CreateReportStatus4",
result.aggregatable_status());
dwa::builders::AttributionConversionsCreateReport()
.SetContent(result.trigger().reporting_origin().Serialize())
.SetEventLevelStatus(static_cast<int64_t>(result.event_level_status()))
.SetAggregatableStatus(static_cast<int64_t>(result.aggregatable_status()))
.Record(metrics::dwa::DwaRecorder::Get());
if (ukm::SourceId ukm_source_id = result.trigger().ukm_source_id();
ukm_source_id != ukm::kInvalidSourceId) {
ukm::builders::Conversions_TriggerRegistration(ukm_source_id)
.SetCreateEventLevelReportStatus(
static_cast<int64_t>(result.event_level_status()))
.SetCreateAggregatableReportStatus(
static_cast<int64_t>(result.aggregatable_status()))
.Record(ukm::UkmRecorder::Get());
}
}
// If `retry_attempts` is a number, represents the number of retries before
// success. If `retry_attempts` is null, represents failure after all retries.
void RecordReportRetriesEventLevel(std::optional<int> retry_attempts) {
static constexpr char kNavigationRetryMetric[] =
"Conversions.EventLevelReport.ReportRetriesTillSuccessOrFailure2";
if (retry_attempts.has_value()) {
CHECK_LT(*retry_attempts,
static_cast<int>(ConversionReportSendRetryCount::kFailed));
base::UmaHistogramEnumeration(
kNavigationRetryMetric,
static_cast<ConversionReportSendRetryCount>(*retry_attempts));
} else {
base::UmaHistogramEnumeration(kNavigationRetryMetric,
ConversionReportSendRetryCount::kFailed);
}
}
void RecordReportRetriesAggregatable(std::optional<int> retry_attempts) {
static constexpr char kNavigationRetryMetric[] =
"Conversions.AggregatableReport.ReportRetriesTillSuccessOrFailure2";
if (retry_attempts.has_value()) {
CHECK_LT(*retry_attempts,
static_cast<int>(ConversionReportSendRetryCount::kFailed));
base::UmaHistogramEnumeration(
kNavigationRetryMetric,
static_cast<ConversionReportSendRetryCount>(*retry_attempts));
} else {
base::UmaHistogramEnumeration(kNavigationRetryMetric,
ConversionReportSendRetryCount::kFailed);
}
}
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::kExpired:
return ConversionReportSendOutcome::kExpired;
case SendResult::Status::kAssemblyFailure:
case SendResult::Status::kTransientAssemblyFailure:
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);
}
void LogAggregatableReportHistogramCustomTimes(const char* suffix,
bool has_trigger_context_id,
base::TimeDelta sample,
base::TimeDelta min,
base::TimeDelta max,
size_t buckets) {
base::UmaHistogramCustomTimes(
base::StrCat({"Conversions.AggregatableReport.", suffix}), sample, min,
max, buckets);
if (has_trigger_context_id) {
base::UmaHistogramCustomTimes(
base::StrCat({"Conversions.AggregatableReport.ContextID.", suffix}),
sample, min, max, buckets);
} else {
base::UmaHistogramCustomTimes(
base::StrCat({"Conversions.AggregatableReport.NoContextID.", suffix}),
sample, min, max, buckets);
}
}
// 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) {
std::visit(
absl::Overload{
[&](const AttributionReport::EventLevelData&) {
// 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);
},
[&](const AttributionReport::AggregatableData& data) {
if (data.is_null()) {
return;
}
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);
LogAggregatableReportHistogramCustomTimes(
"ExtraReportDelay",
data.aggregatable_trigger_config()
.trigger_context_id()
.has_value(),
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);
},
},
report.data());
}
void RecordTimeSinceLastNavigationOnReportComplete(
base::Time last_navigation,
SendResult::Status status,
std::string_view report_type_string) {
base::Time now = base::Time::Now();
switch (status) {
case SendResult::Status::kSent:
base::UmaHistogramCustomTimes(
base::StrCat(
{"Conversions.TimeFromLastNavigationToDelivery_Succeeded.",
report_type_string}),
now - last_navigation, base::Seconds(1), base::Days(24),
/*buckets=*/100);
break;
case SendResult::Status::kTransientFailure:
case SendResult::Status::kFailure:
base::UmaHistogramCustomTimes(
base::StrCat({"Conversions.TimeFromLastNavigationToDelivery_Failed.",
report_type_string}),
now - last_navigation, base::Seconds(1), base::Days(24),
/*buckets=*/100);
break;
case SendResult::Status::kDropped:
case SendResult::Status::kExpired:
case SendResult::Status::kAssemblyFailure:
case SendResult::Status::kTransientAssemblyFailure:
break;
}
}
// Called when |report| is sent, failed or dropped, for logging metrics.
void LogMetricsOnReportCompleted(const AttributionReport& report,
SendResult::Status status,
std::optional<base::Time> last_navigation) {
switch (report.GetReportType()) {
case AttributionReport::Type::kEventLevel:
base::UmaHistogramEnumeration(
"Conversions.ReportSendOutcome3",
ConvertToConversionReportSendOutcome(status));
if (last_navigation.has_value()) {
RecordTimeSinceLastNavigationOnReportComplete(
*last_navigation, status,
/*report_type_string=*/"EventLevelReport");
}
break;
case AttributionReport::Type::kAggregatableAttribution:
base::UmaHistogramEnumeration(
"Conversions.AggregatableReport.ReportSendOutcome2",
ConvertToConversionReportSendOutcome(status));
if (last_navigation.has_value()) {
RecordTimeSinceLastNavigationOnReportComplete(
*last_navigation, status,
/*report_type_string=*/"AggregatableReport");
}
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());
UMA_HISTOGRAM_BOOLEAN(
"Conversions."
"TimeFromTriggerToReportSentSuccessfullyExceeds30Days",
time_from_conversion_to_report_sent > base::Days(30));
UMA_HISTOGRAM_BOOLEAN(
"Conversions."
"ExtraReportDelayForSuccessfulSendExceeds30Days",
time_since_original_report_time > base::Days(30));
RecordReportRetriesEventLevel(report.failed_send_attempts());
// `time_since_original_report_time` can be negative when sent from the
// web UI.
dwa::builders::AttributionConversionsSendReport()
.SetContent(report.reporting_origin().Serialize())
.SetEventLevelExtraReportDelay(
ukm::GetSemanticBucketMinForDurationTiming(
std::max(time_since_original_report_time.InMilliseconds(),
int64_t(0))))
.Record(metrics::dwa::DwaRecorder::Get());
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_BOOLEAN(
"Conversions.AggregatableReport."
"TimeFromTriggerToReportSentSuccessfullyExceeds30Days",
time_from_conversion_to_report_sent > base::Days(30));
UMA_HISTOGRAM_CUSTOM_TIMES(
"Conversions.AggregatableReport.ExtraReportDelayForSuccessfulSend",
time_since_original_report_time, base::Seconds(1), base::Days(24),
/*bucket_count=*/50);
UMA_HISTOGRAM_BOOLEAN(
"Conversions.AggregatableReport."
"ExtraReportDelayForSuccessfulSendExceeds30Days",
time_since_original_report_time > base::Days(30));
RecordReportRetriesAggregatable(report.failed_send_attempts());
// `time_from_conversion_to_report_sent` can be negative in edge cases,
// e.g. the user adjusted the clock backwards and sent from the web UI.
dwa::builders::AttributionConversionsSendReport()
.SetContent(report.reporting_origin().Serialize())
.SetAggregatableReportDelayFromTrigger(
ukm::GetSemanticBucketMinForDurationTiming(
std::max(time_from_conversion_to_report_sent.InMilliseconds(),
int64_t(0))))
.Record(metrics::dwa::DwaRecorder::Get());
break;
case AttributionReport::Type::kNullAggregatable:
break;
}
}
bool HasNonDefaultFilteringId(const AttributionTrigger& trigger) {
return std::ranges::any_of(
trigger.registration().aggregatable_values, [](const auto& value) {
return std::ranges::any_of(value.values(), [](const auto& val) {
return val.second.filtering_id() !=
attribution_reporting::kDefaultFilteringId;
});
});
}
void RecordAggregatableFilteringIdUsage(const AttributionTrigger& trigger) {
base::UmaHistogramBoolean("Conversions.NonDefaultAggregatableFilteringId",
HasNonDefaultFilteringId(trigger));
base::UmaHistogramExactLinear(
"Conversions.AggregatableFilteringIdMaxBytesValue",
trigger.registration()
.aggregatable_trigger_config.aggregatable_filtering_id_max_bytes()
.value(),
/*exclusive_max=8+1=*/9);
}
std::unique_ptr<AttributionResolverDelegate> MakeResolverDelegate(
bool debug_mode) {
if (debug_mode) {
return std::make_unique<AttributionResolverDelegateImpl>(
AttributionNoiseMode::kNone, AttributionDelayMode::kNone);
}
return std::make_unique<AttributionResolverDelegateImpl>(
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,
bool* can_bypass = nullptr) {
return GetContentClient()->browser()->IsAttributionReportingOperationAllowed(
storage_partition.browser_context(), operation, rfh, source_origin,
destination_origin, reporting_origin, can_bypass);
}
std::unique_ptr<AttributionOsLevelManager> CreateOsLevelManager() {
#if BUILDFLAG(IS_ANDROID)
return std::make_unique<AttributionOsLevelManagerAndroid>();
#else
return std::make_unique<NoOpAttributionOsLevelManager>();
#endif
}
base::Time GetReportExpiryTime(const AttributionReport& report) {
return report.initial_report_time() + kReportExpiry;
}
// Returns new report time if any.
std::optional<base::Time> HandleTransientFailureOnSendReport(
const AttributionReport& report) {
if (std::optional<base::Time> report_retry_time = GetReportTimeForRetry(
/*failed_send_attempts=*/report.failed_send_attempts() + 1,
GetReportExpiryTime(report))) {
return *report_retry_time;
} else {
switch (report.GetReportType()) {
case AttributionReport::Type::kEventLevel:
RecordReportRetriesEventLevel(/*retry_attempts=*/std::nullopt);
break;
case AttributionReport::Type::kAggregatableAttribution:
RecordReportRetriesAggregatable(/*retry_attempts=*/std::nullopt);
break;
case AttributionReport::Type::kNullAggregatable:
break;
}
return std::nullopt;
}
}
bool g_run_in_memory = false;
} // namespace
std::optional<base::Time> GetReportTimeForRetry(int failed_send_attempts,
base::Time report_expiry) {
CHECK_GT(failed_send_attempts, 0);
constexpr int kMaxFailedSendAttempts = 3;
const int navigation_retry_attempt =
static_cast<int>(kAttributionReportNavigationRetryAttempt.Get());
const bool navigation_based_retry_enabled =
base::FeatureList::IsEnabled(kAttributionReportNavigationBasedRetry);
if (navigation_based_retry_enabled) {
if (failed_send_attempts == navigation_retry_attempt) {
return report_expiry;
} else if (failed_send_attempts > navigation_retry_attempt) {
return std::nullopt;
}
} else if (failed_send_attempts >= kMaxFailedSendAttempts) {
return std::nullopt;
}
return base::Time::Now() + (failed_send_attempts == 1
? kReportDeliveryFirstRetryDelay
: kReportDeliverySecondRetryDelay);
}
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 {
return IsOperationAllowed(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::kReport,
/*rfh=*/nullptr, &*report.GetSourceOrigin(),
&*report.attribution_info().context_origin, &*report.reporting_origin());
}
// static
std::unique_ptr<AttributionManagerImpl>
AttributionManagerImpl::CreateForTesting(
const base::FilePath& user_data_directory,
scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
std::unique_ptr<AttributionResolverDelegate> resolver_delegate,
std::unique_ptr<AttributionReportSender> report_sender,
std::unique_ptr<AttributionOsLevelManager> os_level_manager,
StoragePartitionImpl* storage_partition,
scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner) {
return base::WrapUnique(new AttributionManagerImpl(
storage_partition, user_data_directory, std::move(special_storage_policy),
std::move(resolver_delegate), std::move(report_sender),
std::move(os_level_manager), std::move(resolver_task_runner),
/*debug_mode=*/false));
}
AttributionManagerImpl::AttributionManagerImpl(
StoragePartitionImpl* storage_partition,
const base::FilePath& user_data_directory,
scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy)
: AttributionManagerImpl(
storage_partition,
user_data_directory,
std::move(special_storage_policy),
/*resolver_delegate=*/nullptr,
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)),
/*debug_mode=*/
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAttributionReportingDebugMode)) {}
AttributionManagerImpl::AttributionManagerImpl(
StoragePartitionImpl* storage_partition,
const base::FilePath& user_data_directory,
scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
std::unique_ptr<AttributionResolverDelegate> resolver_delegate,
std::unique_ptr<AttributionReportSender> report_sender,
std::unique_ptr<AttributionOsLevelManager> os_level_manager,
scoped_refptr<base::UpdateableSequencedTaskRunner> resolver_task_runner,
bool debug_mode)
: storage_partition_(
raw_ref<StoragePartitionImpl>::from_ptr(storage_partition)),
resolver_task_runner_(std::move(resolver_task_runner)),
attribution_resolver_(base::SequenceBound<AttributionResolverImpl>(
resolver_task_runner_,
g_run_in_memory ? base::FilePath() : user_data_directory,
resolver_delegate ? std::move(resolver_delegate)
: MakeResolverDelegate(debug_mode))),
data_host_manager_(
std::make_unique<AttributionDataHostManagerImpl>(this)),
special_storage_policy_(std::move(special_storage_policy)),
report_sender_(std::move(report_sender)),
os_level_manager_(std::move(os_level_manager)),
debug_mode_(debug_mode) {
CHECK(resolver_task_runner_);
CHECK(report_sender_);
CHECK(os_level_manager_);
scheduler_timer_ = std::make_unique<ReportSchedulerTimer>(
std::make_unique<ReportScheduler>(weak_factory_.GetWeakPtr()));
}
AttributionManagerImpl::~AttributionManagerImpl() {
// 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);
observer->OnDebugModeChanged(debug_mode_);
}
void AttributionManagerImpl::RemoveObserver(AttributionObserver* observer) {
observers_.RemoveObserver(observer);
}
AttributionDataHostManager* AttributionManagerImpl::GetDataHostManager() {
CHECK(data_host_manager_);
return data_host_manager_.get();
}
namespace {
enum class BrowserPolicy {
kProhibited,
kAllowedWithDebug,
kAllowedWithoutDebug,
};
BrowserPolicy GetBrowserPolicy(
StoragePartitionImpl& storage_partition,
ContentBrowserClient::AttributionReportingOperation registration_operation,
ContentBrowserClient::AttributionReportingOperation debug_operation,
GlobalRenderFrameHostId rfh_id,
const url::Origin* source_origin,
const url::Origin* destination_origin,
const url::Origin& reporting_origin,
bool check_debug) {
if (!IsOperationAllowed(storage_partition, registration_operation,
RenderFrameHost::FromID(rfh_id), source_origin,
destination_origin, &reporting_origin)) {
return BrowserPolicy::kProhibited;
}
if (check_debug) {
// TODO(crbug.com/40941634): Clean up `can_bypass` after the cookie
// deprecation experiment.
bool can_bypass = false;
if (IsOperationAllowed(storage_partition, debug_operation,
/*rfh=*/nullptr, source_origin, destination_origin,
&reporting_origin, &can_bypass) ||
can_bypass) {
return BrowserPolicy::kAllowedWithDebug;
}
}
return BrowserPolicy::kAllowedWithoutDebug;
}
} // namespace
void AttributionManagerImpl::HandleSource(
StorableSource source,
GlobalRenderFrameHostId render_frame_id) {
BrowserPolicy browser_policy = GetBrowserPolicy(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::kSource,
ContentBrowserClient::AttributionReportingOperation::
kSourceTransitionalDebugReporting,
render_frame_id, &*source.common_info().source_origin(),
/*destination_origin=*/nullptr, source.common_info().reporting_origin(),
/*check_debug=*/true);
std::optional<uint64_t> cleared_debug_key;
switch (browser_policy) {
case BrowserPolicy::kProhibited:
OnSourceStored(
/*cleared_debug_key=*/std::nullopt,
StoreSourceResult(std::move(source),
/*is_noised=*/false,
/*source_time=*/base::Time::Now(),
/*destination_limit=*/std::nullopt,
StoreSourceResult::ProhibitedByBrowserPolicy()));
return;
case BrowserPolicy::kAllowedWithDebug:
source.set_cookie_based_debug_allowed(/*value=*/true);
break;
case BrowserPolicy::kAllowedWithoutDebug:
source.set_cookie_based_debug_allowed(/*value=*/false);
cleared_debug_key =
std::exchange(source.registration().debug_key, std::nullopt);
break;
}
attribution_resolver_.AsyncCall(&AttributionResolver::StoreSource)
.WithArgs(std::move(source))
.Then(base::BindOnce(&AttributionManagerImpl::OnSourceStored,
weak_factory_.GetWeakPtr(), cleared_debug_key));
}
void AttributionManagerImpl::OnSourceStored(
std::optional<uint64_t> cleared_debug_key,
StoreSourceResult result) {
RecordStoreSourceStatus(result);
base::Time now = base::Time::Now();
for (auto& observer : observers_) {
observer.OnSourceHandled(result.source(), now, cleared_debug_key,
result.status());
}
if (const auto* success =
std::get_if<StoreSourceResult::Success>(&result.result())) {
scheduler_timer_->MaybeSet(success->min_fake_report_time);
if (success->min_fake_report_time.has_value()) {
NotifyReportsChanged();
}
}
NotifySourcesChanged();
MaybeSendVerboseDebugReport(result);
MaybeSendAggregatableDebugReport(result);
}
void AttributionManagerImpl::HandleTrigger(
AttributionTrigger trigger,
GlobalRenderFrameHostId render_frame_id) {
RecordAggregatableFilteringIdUsage(trigger);
const attribution_reporting::TriggerRegistration& registration =
trigger.registration();
BrowserPolicy browser_policy = GetBrowserPolicy(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::kTrigger,
ContentBrowserClient::AttributionReportingOperation::
kTriggerTransitionalDebugReporting,
render_frame_id,
/*source_origin=*/nullptr, &*trigger.destination_origin(),
trigger.reporting_origin(),
/*check_debug=*/registration.debug_key.has_value() ||
registration.debug_reporting);
std::optional<uint64_t> cleared_debug_key;
bool cookie_based_debug_allowed = false;
switch (browser_policy) {
case BrowserPolicy::kProhibited:
OnReportStored(
/*cleared_debug_key=*/std::nullopt,
/*cookie_based_debug_allowed=*/false,
CreateReportResult(
/*trigger_time=*/base::Time::Now(), std::move(trigger),
/*event_level_result=*/
CreateReportResult::ProhibitedByBrowserPolicy(),
/*aggregatable_result=*/
CreateReportResult::ProhibitedByBrowserPolicy(),
/*source=*/std::nullopt,
/*min_null_aggregatable_report_time=*/std::nullopt));
return;
case BrowserPolicy::kAllowedWithDebug:
cookie_based_debug_allowed = true;
break;
case BrowserPolicy::kAllowedWithoutDebug:
cleared_debug_key =
std::exchange(trigger.registration().debug_key, std::nullopt);
break;
}
attribution_resolver_
.AsyncCall(&AttributionResolver::MaybeCreateAndStoreReport)
.WithArgs(std::move(trigger))
.Then(base::BindOnce(&AttributionManagerImpl::OnReportStored,
weak_factory_.GetWeakPtr(), cleared_debug_key,
cookie_based_debug_allowed));
}
void AttributionManagerImpl::OnReportStored(
std::optional<uint64_t> cleared_debug_key,
bool cookie_based_debug_allowed,
CreateReportResult result) {
RecordCreateReportStatus(result);
std::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());
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(cleared_debug_key, result);
}
MaybeSendVerboseDebugReport(cookie_based_debug_allowed, result);
MaybeSendAggregatableDebugReport(result);
}
void AttributionManagerImpl::MaybeSendDebugReport(AttributionReport&& report) {
if (!report.CanDebuggingBeEnabled() || !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) {
OnUserVisibleTaskStarted();
const int kMaxSources = 1000;
attribution_resolver_
.AsyncCall(&AttributionResolver::GetActiveSourcesWithLimit)
.WithArgs(kMaxSources)
.Then(std::move(callback).Then(
base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
weak_factory_.GetWeakPtr())));
}
void AttributionManagerImpl::GetPendingReportsForInternalUse(
int limit,
base::OnceCallback<void(std::vector<AttributionReport>)> callback) {
OnUserVisibleTaskStarted();
attribution_resolver_
.AsyncCall(&AttributionResolver::GetAttributionReportsWithLimit)
.WithArgs(/*max_report_time=*/base::Time::Max(), limit)
.Then(std::move(callback).Then(
base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
weak_factory_.GetWeakPtr())));
}
void AttributionManagerImpl::SendReportForWebUI(AttributionReport::Id id,
base::OnceClosure done) {
CHECK(done);
OnUserVisibleTaskStarted();
done = std::move(done).Then(
base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
weak_factory_.GetWeakPtr()));
attribution_resolver_.AsyncCall(&AttributionResolver::GetReport)
.WithArgs(id)
.Then(base::BindOnce(&AttributionManagerImpl::OnGetReportToSendFromWebUI,
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) {
os_level_manager_->ClearData(
delete_begin, delete_end, filter_builder->GetOrigins(),
filter_builder->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));
}
// Rate-limit data is only deleted when initiated by a user, not a site via
// the Clear-Site-Data header.
if (delete_rate_limit_data) {
OnUserVisibleTaskStarted();
}
attribution_resolver_.AsyncCall(&AttributionResolver::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(),
/*was_user_visible=*/delete_rate_limit_data)));
}
void AttributionManagerImpl::OnUserVisibleTaskStarted() {
// When a user-visible task is queued or running, we use a higher priority.
++num_pending_user_visible_tasks_;
resolver_task_runner_->UpdatePriority(base::TaskPriority::USER_VISIBLE);
}
void AttributionManagerImpl::OnUserVisibleTaskComplete() {
CHECK_GT(num_pending_user_visible_tasks_, 0);
--num_pending_user_visible_tasks_;
// No more user-visible tasks, so we can reset the priority.
if (num_pending_user_visible_tasks_ == 0) {
resolver_task_runner_->UpdatePriority(base::TaskPriority::BEST_EFFORT);
}
}
void AttributionManagerImpl::OnClearDataComplete(bool was_user_visible) {
if (was_user_visible) {
OnUserVisibleTaskComplete();
}
NotifySourcesChanged();
NotifyReportsChanged();
}
void AttributionManagerImpl::GetAllDataKeys(
base::OnceCallback<void(std::set<DataKey>)> callback) {
OnUserVisibleTaskStarted();
attribution_resolver_.AsyncCall(&AttributionResolver::GetAllDataKeys)
.Then(std::move(callback).Then(
base::BindOnce(&AttributionManagerImpl::OnUserVisibleTaskComplete,
weak_factory_.GetWeakPtr())));
}
void AttributionManagerImpl::RemoveAttributionDataByDataKey(
const DataKey& data_key,
base::OnceClosure callback) {
auto barrier = base::BarrierClosure(2, std::move(callback));
callback = barrier;
os_level_manager_->ClearData(
/*delete_begin=*/base::Time::Min(), /*delete_end=*/base::Time::Max(),
/*origins=*/{data_key.reporting_origin()},
/*domains=*/{}, BrowsingDataFilterBuilder::Mode::kDelete,
/*delete_rate_limit_data=*/true, std::move(barrier));
OnUserVisibleTaskStarted();
attribution_resolver_.AsyncCall(&AttributionResolver::DeleteByDataKey)
.WithArgs(data_key)
.Then(std::move(callback).Then(base::BindOnce(
&AttributionManagerImpl::OnClearDataComplete,
weak_factory_.GetWeakPtr(), /*was_user_visible=*/true)));
}
void AttributionManagerImpl::UpdateLastNavigationTime(
base::Time navigation_time) {
last_navigation_time_ = navigation_time;
if (base::FeatureList::IsEnabled(kAttributionReportNavigationBasedRetry)) {
base::OnceCallback then = base::BindOnce(
[](base::WeakPtr<AttributionManagerImpl> manager,
std::optional<base::Time> new_report_time) {
if (manager && new_report_time.has_value()) {
manager->NotifyReportsChanged();
manager->scheduler_timer_->MaybeSet(new_report_time);
}
},
weak_factory_.GetWeakPtr());
attribution_resolver_
.AsyncCall(&AttributionResolver::AdjustNavigationRetryReportTimes)
.Then(std::move(then));
}
}
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_resolver_.AsyncCall(&AttributionResolver::GetAttributionReports)
.WithArgs(/*max_report_time=*/base::Time::Now())
.Then(base::BindOnce(&AttributionManagerImpl::SendReports,
weak_factory_.GetWeakPtr()));
}
void AttributionManagerImpl::OnGetReportToSendFromWebUI(
base::OnceClosure done,
std::optional<AttributionReport> report) {
CHECK(done);
if (!report.has_value()) {
std::move(done).Run();
return;
}
const base::Time now = base::Time::Now();
report->set_report_time(now);
SendReport(std::move(done), now, *std::move(report));
}
void AttributionManagerImpl::SendReports(
std::vector<AttributionReport> reports) {
const base::Time now = base::Time::Now();
for (auto& report : reports) {
SendReport(base::NullCallback(), now, std::move(report));
}
report_sender_->SetInFirstBatch(/*in_first_batch=*/false);
}
// If `web_ui_callback` is null, assumes that `report` is being sent at its
// intended time, and logs metrics for it. Otherwise, does not log metrics.
void AttributionManagerImpl::SendReport(base::OnceClosure web_ui_callback,
const base::Time now,
AttributionReport report) {
bool inserted = reports_being_sent_.emplace(report.id()).second;
if (!inserted) {
if (web_ui_callback) {
std::move(web_ui_callback).Run();
}
return;
}
// Drop the report on the floor if the report is expired. 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.
if (now > GetReportExpiryTime(report)) {
OnReportSent(std::move(web_ui_callback), std::move(report),
SendResult(SendResult::Expired()));
return;
}
if (!IsReportAllowed(report)) {
// If measurement is disallowed, just drop the report on the floor the same
// way we do above.
OnReportSent(std::move(web_ui_callback), std::move(report),
SendResult(SendResult::Dropped()));
return;
}
if (!web_ui_callback) {
LogMetricsOnReportSend(report, now);
}
PrepareToSendReport(
std::move(report), /*is_debug_report=*/false,
base::BindOnce(&AttributionManagerImpl::OnReportSent,
weak_factory_.GetWeakPtr(), std::move(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:
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::SendReport(AttributionReport report,
bool is_debug_report,
ReportSentCallback callback) {
report_sender_->SendReport(
std::move(report), is_debug_report,
base::BindOnce(
[](ReportSentCallback callback, const AttributionReport& report,
SendResult::Sent sent) {
std::move(callback).Run(report, SendResult(std::move(sent)));
},
std::move(callback)));
}
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.
std::optional<base::Time> new_report_time =
std::visit(absl::Overload{
[&](SendResult::Sent sent) -> std::optional<base::Time> {
switch (sent.result) {
case SendResult::Sent::Result::kSent:
LogMetricsOnReportSent(report);
return std::nullopt;
case SendResult::Sent::Result::kTransientFailure:
RecordNetworkConnectionTypeOnFailure(
report.GetReportType(),
scheduler_timer_->connection_type());
return HandleTransientFailureOnSendReport(report);
case SendResult::Sent::Result::kFailure:
RecordNetworkConnectionTypeOnFailure(
report.GetReportType(),
scheduler_timer_->connection_type());
return std::nullopt;
}
},
[](SendResult::Dropped) -> std::optional<base::Time> {
return std::nullopt;
},
[](SendResult::Expired) -> std::optional<base::Time> {
return std::nullopt;
},
[&](SendResult::AssemblyFailure failure)
-> std::optional<base::Time> {
// TODO(linnan): Retry on transient assembly failure
// isn't privacy sensitive, therefore we could consider
// subjecting these failures to a different limit.
return failure.transient
? HandleTransientFailureOnSendReport(report)
: std::nullopt;
},
},
info.result);
base::OnceCallback then = base::BindOnce(
[](base::OnceClosure done, base::WeakPtr<AttributionManagerImpl> manager,
AttributionReport::Id report_id,
std::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_resolver_
.AsyncCall(&AttributionResolver::UpdateReportForSendFailure)
.WithArgs(report.id(), *new_report_time)
.Then(std::move(then));
// TODO(apaseltiner): Consider surfacing retry attempts in internals UI.
return;
}
NotifyReportSent(/*is_debug_report=*/false, report, info);
attribution_resolver_.AsyncCall(&AttributionResolver::DeleteReport)
.WithArgs(report.id())
.Then(std::move(then));
LogMetricsOnReportCompleted(report, info.status(), last_navigation_time_);
}
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::AssemblyFailure(/*transient=*/false)));
return;
}
std::optional<AggregatableReportRequest> request =
CreateAggregatableReportRequest(report);
if (!request.has_value()) {
RecordAssembleAggregatableReportStatus(
AssembleAggregatableReportStatus::kCreateRequestFailed);
std::move(callback).Run(
std::move(report),
SendResult(SendResult::AssemblyFailure(/*transient=*/false)));
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,
std::optional<AggregatableReport> assembled_report,
AggregationService::AssemblyStatus) {
if (!assembled_report.has_value()) {
RecordAssembleAggregatableReportStatus(
AssembleAggregatableReportStatus::kAssembleReportFailed);
std::move(callback).Run(
std::move(report),
SendResult(SendResult::AssemblyFailure(/*transient=*/true)));
return;
}
auto* data = std::get_if<AttributionReport::AggregatableData>(&report.data());
CHECK(data);
data->SetAssembledReport(std::move(assembled_report));
RecordAssembleAggregatableReportStatus(
AssembleAggregatableReportStatus::kSuccess);
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::MaybeSendAggregatableDebugReport(
const StoreSourceResult& result) {
const auto is_operation_allowed = [&]() {
return IsOperationAllowed(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::
kSourceAggregatableDebugReport,
/*rfh=*/nullptr, &*result.source().common_info().source_origin(),
/*destination_origin=*/nullptr,
&*result.source().common_info().reporting_origin());
};
if (std::optional<AggregatableDebugReport> debug_report =
AggregatableDebugReport::Create(is_operation_allowed, result)) {
std::optional<StoredSource::Id> source_id;
if (const auto* success =
std::get_if<StoreSourceResult::Success>(&result.result())) {
source_id.emplace(success->source_id);
}
attribution_resolver_
.AsyncCall(&AttributionResolver::ProcessAggregatableDebugReport)
.WithArgs(*std::move(debug_report),
result.source()
.registration()
.aggregatable_debug_reporting_config.budget(),
source_id)
.Then(base::BindOnce(
&AttributionManagerImpl::OnAggregatableDebugReportProcessed,
weak_factory_.GetWeakPtr()));
}
}
void AttributionManagerImpl::MaybeSendAggregatableDebugReport(
const CreateReportResult& result) {
const auto is_operation_allowed = [&]() {
return IsOperationAllowed(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::
kTriggerAggregatableDebugReport,
/*rfh=*/nullptr,
/*source_origin=*/nullptr, &*result.trigger().destination_origin(),
&*result.trigger().reporting_origin());
};
if (std::optional<AggregatableDebugReport> debug_report =
AggregatableDebugReport::Create(is_operation_allowed, result)) {
std::optional<StoredSource::Id> source_id;
if (const std::optional<StoredSource>& source = result.source();
source.has_value()) {
source_id.emplace(source->source_id());
}
attribution_resolver_
.AsyncCall(&AttributionResolver::ProcessAggregatableDebugReport)
.WithArgs(*std::move(debug_report),
/*remaining_budget=*/std::nullopt, source_id)
.Then(base::BindOnce(
&AttributionManagerImpl::OnAggregatableDebugReportProcessed,
weak_factory_.GetWeakPtr()));
}
}
void AttributionManagerImpl::OnAggregatableDebugReportProcessed(
ProcessAggregatableDebugReportResult result) {
AggregationService* aggregation_service =
storage_partition_->GetAggregationService();
if (!aggregation_service) {
NotifyAggregatableDebugReportSent(
result.report, /*report_body=*/base::Value::Dict(), result.result,
SendAggregatableDebugReportResult(
SendAggregatableDebugReportResult::AssemblyFailed()));
return;
}
std::optional<AggregatableReportRequest> request =
result.report.CreateAggregatableReportRequest();
if (!request.has_value()) {
NotifyAggregatableDebugReportSent(
result.report, /*report_body=*/base::Value::Dict(), result.result,
SendAggregatableDebugReportResult(
SendAggregatableDebugReportResult::AssemblyFailed()));
return;
}
aggregation_service->AssembleReport(
*std::move(request),
base::BindOnce(
&AttributionManagerImpl::OnAggregatableDebugReportAssembled,
weak_factory_.GetWeakPtr(), std::move(result)));
}
void AttributionManagerImpl::OnAggregatableDebugReportAssembled(
ProcessAggregatableDebugReportResult result,
AggregatableReportRequest,
std::optional<AggregatableReport> assembled_report,
AggregationService::AssemblyStatus) {
if (!assembled_report.has_value()) {
NotifyAggregatableDebugReportSent(
result.report, /*report_body=*/base::Value::Dict(), result.result,
SendAggregatableDebugReportResult(
SendAggregatableDebugReportResult::AssemblyFailed()));
return;
}
report_sender_->SendReport(
std::move(result.report), assembled_report->GetAsJson(),
base::BindOnce(
[](base::WeakPtr<AttributionManagerImpl> manager,
attribution_reporting::mojom::ProcessAggregatableDebugReportResult
process_result,
const AggregatableDebugReport& report, base::ValueView report_body,
int status) {
if (!manager) {
return;
}
manager->NotifyAggregatableDebugReportSent(
report, report_body, process_result,
SendAggregatableDebugReportResult(
SendAggregatableDebugReportResult::Sent(status)));
},
weak_factory_.GetWeakPtr(), result.result));
}
void AttributionManagerImpl::NotifyAggregatableDebugReportSent(
const AggregatableDebugReport& report,
base::ValueView report_body,
attribution_reporting::mojom::ProcessAggregatableDebugReportResult
process_result,
SendAggregatableDebugReportResult send_result) {
for (auto& observer : observers_) {
observer.OnAggregatableDebugReportSent(report, report_body, process_result,
send_result);
}
}
void AttributionManagerImpl::MaybeSendVerboseDebugReport(
const StoreSourceResult& result) {
const auto is_operation_allowed = [&]() {
return IsOperationAllowed(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::
kSourceVerboseDebugReport,
/*rfh=*/nullptr, &*result.source().common_info().source_origin(),
/*destination_origin=*/nullptr,
&*result.source().common_info().reporting_origin());
};
if (std::optional<AttributionDebugReport> debug_report =
AttributionDebugReport::Create(is_operation_allowed, result)) {
report_sender_->SendReport(
*std::move(debug_report),
base::BindOnce(&AttributionManagerImpl::NotifyDebugReportSent,
weak_factory_.GetWeakPtr()));
}
}
void AttributionManagerImpl::MaybeSendVerboseDebugReport(
bool cookie_based_debug_allowed,
const CreateReportResult& result) {
const auto is_operation_allowed = [&]() {
return IsOperationAllowed(
*storage_partition_,
ContentBrowserClient::AttributionReportingOperation::
kTriggerVerboseDebugReport,
/*rfh=*/nullptr,
/*source_origin=*/nullptr, &*result.trigger().destination_origin(),
&*result.trigger().reporting_origin());
};
if (std::optional<AttributionDebugReport> debug_report =
AttributionDebugReport::Create(is_operation_allowed,
cookie_based_debug_allowed, result)) {
report_sender_->SendReport(
*std::move(debug_report),
base::BindOnce(&AttributionManagerImpl::NotifyDebugReportSent,
weak_factory_.GetWeakPtr()));
}
}
void AttributionManagerImpl::HandleOsRegistration(OsRegistration registration) {
ContentBrowserClient::AttributionReportingOperation registration_operation;
ContentBrowserClient::AttributionReportingOperation debug_operation;
const url::Origin* source_origin;
const url::Origin* destination_origin;
switch (registration.GetType()) {
case RegistrationType::kSource:
registration_operation =
ContentBrowserClient::AttributionReportingOperation::kOsSource;
debug_operation = ContentBrowserClient::AttributionReportingOperation::
kOsSourceTransitionalDebugReporting;
source_origin = &registration.top_level_origin;
destination_origin = nullptr;
break;
case RegistrationType::kTrigger:
registration_operation =
ContentBrowserClient::AttributionReportingOperation::kOsTrigger;
debug_operation = ContentBrowserClient::AttributionReportingOperation::
kOsTriggerTransitionalDebugReporting;
source_origin = nullptr;
destination_origin = &registration.top_level_origin;
break;
}
std::vector<bool> debug_allowed;
std::vector<url::Origin> origins;
origins.reserve(registration.registration_items.size());
std::erase_if(
registration.registration_items,
[&, now = base::Time::Now()](const OsRegistrationItem& item) {
const auto registration_origin = url::Origin::Create(item.url);
if (registration_origin.opaque()) {
NotifyOsRegistration(now, item, registration.top_level_origin,
/*is_debug_key_allowed=*/false,
registration.GetType(),
OsRegistrationResult::kInvalidRegistrationUrl);
return true;
}
BrowserPolicy browser_policy = GetBrowserPolicy(
*storage_partition_, registration_operation, debug_operation,
registration.render_frame_id, source_origin, destination_origin,
registration_origin,
/*check_debug=*/true);
switch (browser_policy) {
case BrowserPolicy::kProhibited:
NotifyOsRegistration(
now, item, registration.top_level_origin,
/*is_debug_key_allowed=*/false, registration.GetType(),
OsRegistrationResult::kProhibitedByBrowserPolicy);
return true;
case BrowserPolicy::kAllowedWithDebug:
debug_allowed.push_back(true);
origins.push_back(std::move(registration_origin));
return false;
case BrowserPolicy::kAllowedWithoutDebug:
debug_allowed.push_back(false);
origins.push_back(std::move(registration_origin));
return false;
}
NOTREACHED();
});
if (registration.registration_items.empty()) {
return;
}
attribution_resolver_.AsyncCall(&AttributionResolver::StoreOsRegistrations)
.WithArgs(std::move(origins));
os_level_manager_->Register(
std::move(registration), debug_allowed,
base::BindOnce(&AttributionManagerImpl::OnOsRegistration,
weak_factory_.GetWeakPtr(), debug_allowed));
}
void AttributionManagerImpl::NotifyOsRegistration(
base::Time time,
const OsRegistrationItem& registration,
const url::Origin& top_level_origin,
bool is_debug_key_allowed,
attribution_reporting::mojom::RegistrationType type,
attribution_reporting::mojom::OsRegistrationResult result) {
for (auto& observer : observers_) {
observer.OnOsRegistration(time, registration, top_level_origin, type,
is_debug_key_allowed, result);
}
switch (type) {
case attribution_reporting::mojom::RegistrationType::kSource:
base::UmaHistogramEnumeration("Conversions.OsRegistrationResult.Source",
result);
break;
case attribution_reporting::mojom::RegistrationType::kTrigger:
base::UmaHistogramEnumeration("Conversions.OsRegistrationResult.Trigger",
result);
break;
}
}
void AttributionManagerImpl::OnOsRegistration(
const std::vector<bool>& is_debug_key_allowed,
const OsRegistration& registration,
const std::vector<bool>& success) {
const size_t num_items = registration.registration_items.size();
CHECK_EQ(num_items, is_debug_key_allowed.size());
CHECK_EQ(num_items, success.size());
MaybeSendVerboseDebugReports(registration);
const base::Time now = base::Time::Now();
for (size_t i = 0; i < num_items; ++i) {
auto result = success[i] ? OsRegistrationResult::kPassedToOs
: OsRegistrationResult::kRejectedByOs;
NotifyOsRegistration(now, registration.registration_items[i],
registration.top_level_origin, is_debug_key_allowed[i],
registration.GetType(), result);
}
}
void AttributionManagerImpl::SetDebugMode(std::optional<bool> enabled,
base::OnceClosure done) {
bool debug_mode =
enabled.value_or(base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAttributionReportingDebugMode));
attribution_resolver_.AsyncCall(&AttributionResolver::SetDelegate)
.WithArgs(MakeResolverDelegate(debug_mode))
.Then(std::move(done).Then(base::BindOnce(
[](base::WeakPtr<AttributionManagerImpl> manager,
const bool debug_mode) {
if (manager) {
manager->debug_mode_ = debug_mode;
for (auto& observer : manager->observers_) {
observer.OnDebugModeChanged(debug_mode);
}
}
},
weak_factory_.GetWeakPtr(), debug_mode)));
}
void AttributionManagerImpl::MaybeSendVerboseDebugReports(
const OsRegistration& registration) {
ContentBrowserClient::AttributionReportingOperation operation;
const url::Origin* source_origin;
const url::Origin* destination_origin;
switch (registration.GetType()) {
case RegistrationType::kSource:
operation = ContentBrowserClient::AttributionReportingOperation::
kOsSourceVerboseDebugReport;
source_origin = &registration.top_level_origin;
destination_origin = nullptr;
break;
case RegistrationType::kTrigger:
operation = ContentBrowserClient::AttributionReportingOperation::
kOsTriggerVerboseDebugReport;
source_origin = nullptr;
destination_origin = &registration.top_level_origin;
break;
}
const auto is_operation_allowed =
[&](const url::Origin& registration_origin) {
return IsOperationAllowed(*storage_partition_, operation,
/*rfh=*/nullptr, source_origin,
destination_origin,
/*reporting_origin=*/&registration_origin);
};
for (size_t i = 0; i < registration.registration_items.size(); ++i) {
if (std::optional<AttributionDebugReport> debug_report =
AttributionDebugReport::Create(registration, /*item_index=*/i,
is_operation_allowed)) {
report_sender_->SendReport(
*std::move(debug_report),
base::BindOnce(&AttributionManagerImpl::NotifyDebugReportSent,
weak_factory_.GetWeakPtr()));
}
}
}
void AttributionManagerImpl::ReportRegistrationHeaderError(
attribution_reporting::SuitableOrigin reporting_origin,
attribution_reporting::RegistrationHeaderError error,
const attribution_reporting::SuitableOrigin& context_origin,
bool is_within_fenced_frame,
GlobalRenderFrameHostId render_frame_id) {
const auto is_operation_allowed = [&](const url::Origin& reporting_origin) {
return GetContentClient()
->browser()
->IsAttributionReportingAllowedForContext(
storage_partition_->browser_context(),
RenderFrameHost::FromID(render_frame_id), *context_origin,
reporting_origin);
};
if (std::optional<AttributionDebugReport> debug_report =
AttributionDebugReport::Create(
std::move(reporting_origin), std::move(error), context_origin,
is_within_fenced_frame, is_operation_allowed)) {
report_sender_->SendReport(
*std::move(debug_report),
base::BindOnce(&AttributionManagerImpl::NotifyDebugReportSent,
weak_factory_.GetWeakPtr()));
}
}
} // namespace content