Implementation for send after new successful navigation feature
Introduces standby mode for the report scheduler timer, which halts all report sends until a new successful navigation is received. It is believed this will aid report delivery loss we're seeing, particularly on the Android platform. This feature is separate from the connection tracker, allowing us to compare the two features and judge which will be more beneficial. This feature is Finch flag-gated and will be tested on in an upcoming experiment. The cutoff for what's considered to be in standby is defaulted to [2 minutes] from the latest successful navigation, but this value will also be experimented on. Bug: 384870263 Change-Id: Idd7a4edbe92d2b9acea24ec360f0bad87f740e78 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6106631 Commit-Queue: Thomas Quintanilla <tquintanilla@chromium.org> Reviewed-by: Nan Lin <linnan@chromium.org> Reviewed-by: Andrew Paseltiner <apaseltiner@chromium.org> Cr-Commit-Position: refs/heads/main@{#1442786}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a033cbd191
commit
debdb1138e
content
browser
aggregation_service
attribution_reporting
test
@ -20,6 +20,20 @@
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
bool InStandby(base::TimeDelta difference, base::TimeDelta cutoff) {
|
||||
return difference > cutoff;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReportSchedulerTimer::ReportSchedulerTimer(std::unique_ptr<Delegate> delegate,
|
||||
base::TimeDelta navigation_window)
|
||||
: delegate_(std::move(delegate)), navigation_window_(navigation_window) {
|
||||
CHECK(delegate_);
|
||||
}
|
||||
|
||||
ReportSchedulerTimer::ReportSchedulerTimer(std::unique_ptr<Delegate> delegate)
|
||||
: delegate_(std::move(delegate)) {
|
||||
CHECK(delegate_);
|
||||
@ -49,7 +63,8 @@ network::mojom::ConnectionType ReportSchedulerTimer::connection_type() const {
|
||||
void ReportSchedulerTimer::MaybeSet(std::optional<base::Time> reporting_time) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (!reporting_time.has_value() || IsOffline()) {
|
||||
if (!reporting_time.has_value() ||
|
||||
(!IsNavigationFeatureEnabled() && IsOffline()) || standby_mode_) {
|
||||
return;
|
||||
}
|
||||
if (!reporting_time_reached_timer_.IsRunning() ||
|
||||
@ -60,7 +75,8 @@ void ReportSchedulerTimer::MaybeSet(std::optional<base::Time> reporting_time) {
|
||||
}
|
||||
|
||||
void ReportSchedulerTimer::Refresh(base::Time now) {
|
||||
if (IsOffline()) {
|
||||
CHECK(!standby_mode_);
|
||||
if (!IsNavigationFeatureEnabled() && IsOffline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -73,6 +89,15 @@ void ReportSchedulerTimer::OnTimerFired() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
base::Time now = base::Time::Now();
|
||||
if (IsNavigationFeatureEnabled()) {
|
||||
standby_mode_ =
|
||||
!last_navigation_time_.has_value() ||
|
||||
InStandby(now - *last_navigation_time_, *navigation_window_);
|
||||
if (standby_mode_) {
|
||||
// Nothing needs to be queued until a new navigation is received.
|
||||
return;
|
||||
}
|
||||
}
|
||||
delegate_->OnReportingTimeReached(
|
||||
now, reporting_time_reached_timer_.desired_run_time());
|
||||
Refresh(now);
|
||||
@ -97,6 +122,27 @@ void ReportSchedulerTimer::OnConnectionChanged(
|
||||
}
|
||||
}
|
||||
|
||||
void ReportSchedulerTimer::OnNewNavigation() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (!IsNavigationFeatureEnabled()) {
|
||||
return;
|
||||
}
|
||||
last_navigation_time_ = base::Time::Now();
|
||||
|
||||
// Standby mode only starts when the timer fires while past the standby
|
||||
// cutoff, i.e. when a report is scheduled to be sent but is prevented due to
|
||||
// not having a recent enough navigation. Therefore if we're in standby mode
|
||||
// there must be a report ready to be sent.
|
||||
if (standby_mode_) {
|
||||
standby_mode_ = false;
|
||||
|
||||
// Add delay to all reports that should have been sent while the report
|
||||
// timer was in standby so they are not temporally joinable.
|
||||
delegate_->AdjustOfflineReportTimes(base::BindOnce(
|
||||
&ReportSchedulerTimer::MaybeSet, weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
}
|
||||
|
||||
bool ReportSchedulerTimer::IsOffline() const {
|
||||
return connection_type_ == network::mojom::ConnectionType::CONNECTION_NONE;
|
||||
}
|
||||
|
@ -19,10 +19,6 @@
|
||||
#include "services/network/public/cpp/network_connection_tracker.h"
|
||||
#include "services/network/public/mojom/network_change_manager.mojom.h"
|
||||
|
||||
namespace base {
|
||||
class Time;
|
||||
} // namespace base
|
||||
|
||||
namespace content {
|
||||
|
||||
// This class consolidates logic regarding when to schedule the browser to send
|
||||
@ -63,6 +59,11 @@ class CONTENT_EXPORT ReportSchedulerTimer
|
||||
|
||||
explicit ReportSchedulerTimer(std::unique_ptr<Delegate> delegate);
|
||||
|
||||
// Initiates the timer with navigation properties, firing report sends only
|
||||
// if there's a recent enough navigation to support the send.
|
||||
ReportSchedulerTimer(std::unique_ptr<Delegate> delegate,
|
||||
base::TimeDelta navigation_window);
|
||||
|
||||
ReportSchedulerTimer(const ReportSchedulerTimer&) = delete;
|
||||
ReportSchedulerTimer& operator=(const ReportSchedulerTimer&) = delete;
|
||||
ReportSchedulerTimer(ReportSchedulerTimer&&) = delete;
|
||||
@ -76,6 +77,10 @@ class CONTENT_EXPORT ReportSchedulerTimer
|
||||
// timer is already set to fire earlier.
|
||||
void MaybeSet(std::optional<base::Time> reporting_time);
|
||||
|
||||
// Updates `last_navigation_time_` and notifies delegate if any report was
|
||||
// pending.
|
||||
void OnNewNavigation();
|
||||
|
||||
private:
|
||||
void OnTimerFired();
|
||||
void Refresh(base::Time now) VALID_CONTEXT_REQUIRED(sequence_checker_);
|
||||
@ -90,6 +95,10 @@ class CONTENT_EXPORT ReportSchedulerTimer
|
||||
|
||||
bool IsOffline() const VALID_CONTEXT_REQUIRED(sequence_checker_);
|
||||
|
||||
bool IsNavigationFeatureEnabled() const {
|
||||
return navigation_window_.has_value();
|
||||
}
|
||||
|
||||
// Fires whenever a reporting time is reached for a report. Must be updated
|
||||
// whenever the next report time changes.
|
||||
base::WallClockTimer reporting_time_reached_timer_
|
||||
@ -106,6 +115,12 @@ class CONTENT_EXPORT ReportSchedulerTimer
|
||||
network::NetworkConnectionTracker::NetworkConnectionObserver>
|
||||
obs_ GUARDED_BY_CONTEXT(sequence_checker_){this};
|
||||
|
||||
std::optional<base::Time> last_navigation_time_;
|
||||
|
||||
std::optional<base::TimeDelta> navigation_window_;
|
||||
|
||||
bool standby_mode_ = false;
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
base::WeakPtrFactory<ReportSchedulerTimer> weak_ptr_factory_{this};
|
||||
|
@ -4,7 +4,19 @@
|
||||
|
||||
#include "content/browser/attribution_reporting/attribution_features.h"
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#include "base/time/time.h"
|
||||
|
||||
namespace content {
|
||||
// TODO(crbug.com/384870263): Add feature flag to gate report delivery on
|
||||
// navigation.
|
||||
|
||||
BASE_FEATURE(kAttributionReportDeliveryOnNewNavigation,
|
||||
"AttributionReportDeliveryOnNewNavigation",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
const base::FeatureParam<base::TimeDelta>
|
||||
kAttributionReportingNavigationForReportDeliveryWindow{
|
||||
&kAttributionReportDeliveryOnNewNavigation, "navigation_window",
|
||||
base::Minutes(2)};
|
||||
|
||||
} // namespace content
|
||||
|
@ -5,9 +5,21 @@
|
||||
#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_FEATURES_H_
|
||||
#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_FEATURES_H_
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#include "content/common/content_export.h"
|
||||
|
||||
namespace base {
|
||||
class TimeDelta;
|
||||
} // namespace base
|
||||
|
||||
namespace content {
|
||||
// TODO(crbug.com/384870263): Add feature flag to gate report delivery on
|
||||
// navigation.
|
||||
|
||||
CONTENT_EXPORT BASE_DECLARE_FEATURE(kAttributionReportDeliveryOnNewNavigation);
|
||||
|
||||
CONTENT_EXPORT extern const base::FeatureParam<base::TimeDelta>
|
||||
kAttributionReportingNavigationForReportDeliveryWindow;
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_FEATURES_H_
|
||||
|
@ -697,8 +697,13 @@ AttributionManagerImpl::AttributionManagerImpl(
|
||||
DCHECK(report_sender_);
|
||||
DCHECK(os_level_manager_);
|
||||
|
||||
scheduler_timer_ = std::make_unique<ReportSchedulerTimer>(
|
||||
std::make_unique<ReportScheduler>(weak_factory_.GetWeakPtr()));
|
||||
scheduler_timer_ =
|
||||
base::FeatureList::IsEnabled(kAttributionReportDeliveryOnNewNavigation)
|
||||
? std::make_unique<ReportSchedulerTimer>(
|
||||
std::make_unique<ReportScheduler>(weak_factory_.GetWeakPtr()),
|
||||
kAttributionReportingNavigationForReportDeliveryWindow.Get())
|
||||
: std::make_unique<ReportSchedulerTimer>(
|
||||
std::make_unique<ReportScheduler>(weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
AttributionManagerImpl::~AttributionManagerImpl() {
|
||||
@ -1095,6 +1100,7 @@ void AttributionManagerImpl::RemoveAttributionDataByDataKey(
|
||||
void AttributionManagerImpl::UpdateLastNavigationTime(
|
||||
base::Time navigation_time) {
|
||||
last_navigation_time_ = navigation_time;
|
||||
scheduler_timer_->OnNewNavigation();
|
||||
}
|
||||
|
||||
void AttributionManagerImpl::GetReportsToSend() {
|
||||
|
@ -2285,6 +2285,53 @@ TEST_F(AttributionManagerImplTest,
|
||||
histograms.ExpectUniqueSample("Conversions.ReportSendOutcome3", 1, 1);
|
||||
}
|
||||
|
||||
class AttributionManagerImplTestDeliverOnNewNavigation
|
||||
: public AttributionManagerImplTest {
|
||||
public:
|
||||
AttributionManagerImplTestDeliverOnNewNavigation() {
|
||||
scoped_feature_list_.Reset();
|
||||
scoped_feature_list_.InitAndEnableFeature(
|
||||
kAttributionReportDeliveryOnNewNavigation);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AttributionManagerImplTestDeliverOnNewNavigation,
|
||||
SendReportAfterNewNavigation) {
|
||||
base::HistogramTester histograms;
|
||||
|
||||
Checkpoint checkpoint;
|
||||
{
|
||||
InSequence seq;
|
||||
|
||||
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
|
||||
.Times(0);
|
||||
EXPECT_CALL(checkpoint, Call(1));
|
||||
EXPECT_CALL(*report_sender_, SendReport(_, /*is_debug_report=*/false, _))
|
||||
.Times(2);
|
||||
}
|
||||
|
||||
base::Time start = base::Time::Now();
|
||||
attribution_manager_->UpdateLastNavigationTime(start);
|
||||
|
||||
attribution_manager_->HandleSource(
|
||||
SourceBuilder().SetExpiry(kImpressionExpiry).Build(), kFrameId);
|
||||
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
|
||||
attribution_manager_->HandleTrigger(DefaultTrigger(), kFrameId);
|
||||
|
||||
EXPECT_THAT(StoredReports(), SizeIs(2));
|
||||
|
||||
task_environment_.FastForwardBy(base::Days(20));
|
||||
checkpoint.Call(1);
|
||||
|
||||
SetConnectionTypeAndWaitForObserversToBeNotified(
|
||||
network::mojom::ConnectionType::CONNECTION_NONE);
|
||||
|
||||
attribution_manager_->UpdateLastNavigationTime(base::Time::Now());
|
||||
|
||||
// Should call SendReport even though the connection is offline.
|
||||
task_environment_.FastForwardBy(kDefaultOfflineReportDelay.max);
|
||||
}
|
||||
|
||||
TEST_F(AttributionManagerImplTest, SendReportsFromWebUI_DoesNotRecordMetrics) {
|
||||
base::HistogramTester histograms;
|
||||
|
||||
|
@ -52,28 +52,30 @@ constexpr char kDefaultConfigFileName[] = "default_config.json";
|
||||
|
||||
const aggregation_service::TestHpkeKey kHpkeKey;
|
||||
|
||||
base::FilePath GetInputDir() {
|
||||
base::FilePath input_dir;
|
||||
std::vector<base::FilePath> GetInputDirs() {
|
||||
base::FilePath input_dir, interop_dir, interop_private_dir;
|
||||
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &input_dir);
|
||||
return input_dir.AppendASCII(
|
||||
"content/test/data/attribution_reporting/interop");
|
||||
interop_dir =
|
||||
input_dir.AppendASCII("content/test/data/attribution_reporting/interop");
|
||||
interop_private_dir = input_dir.AppendASCII(
|
||||
"content/test/data/attribution_reporting/interop_private");
|
||||
return {interop_dir, interop_private_dir};
|
||||
}
|
||||
|
||||
std::vector<base::FilePath> GetInputs() {
|
||||
base::FilePath input_dir = GetInputDir();
|
||||
|
||||
std::vector<base::FilePath> input_paths;
|
||||
for (base::FilePath input_dir : GetInputDirs()) {
|
||||
base::FileEnumerator e(input_dir, /*recursive=*/false,
|
||||
base::FileEnumerator::FILES,
|
||||
FILE_PATH_LITERAL("*.json"));
|
||||
|
||||
base::FileEnumerator e(input_dir, /*recursive=*/false,
|
||||
base::FileEnumerator::FILES,
|
||||
FILE_PATH_LITERAL("*.json"));
|
||||
for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
|
||||
if (name.BaseName().MaybeAsASCII() == kDefaultConfigFileName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
|
||||
if (name.BaseName().MaybeAsASCII() == kDefaultConfigFileName) {
|
||||
continue;
|
||||
input_paths.push_back(std::move(name));
|
||||
}
|
||||
|
||||
input_paths.push_back(std::move(name));
|
||||
}
|
||||
|
||||
return input_paths;
|
||||
@ -243,7 +245,7 @@ class AttributionInteropTest : public ::testing::TestWithParam<base::FilePath> {
|
||||
static void SetUpTestSuite() {
|
||||
ASSERT_OK_AND_ASSIGN(
|
||||
g_config_, ParseAttributionInteropConfig(ParseDictFromFile(
|
||||
GetInputDir().AppendASCII(kDefaultConfigFileName))));
|
||||
GetInputDirs()[0].AppendASCII(kDefaultConfigFileName))));
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -173,6 +173,8 @@ class AttributionInteropParser {
|
||||
bool required) && {
|
||||
interop_config.needs_cross_app_web =
|
||||
ParseBool(dict, "needs_cross_app_web").value_or(false);
|
||||
interop_config.needs_delivery_after_new_navigation =
|
||||
ParseBool(dict, "needs_delivery_after_new_navigation").value_or(false);
|
||||
|
||||
AttributionConfig& config = interop_config.attribution_config;
|
||||
|
||||
@ -379,6 +381,10 @@ class AttributionInteropParser {
|
||||
/*previous_time=*/events.empty() ? base::Time::Min()
|
||||
: events.back().time,
|
||||
/*strictly_greater=*/true);
|
||||
if (dict.FindBool("navigation").value_or(false)) {
|
||||
events.emplace_back(time, AttributionSimulationEvent::Navigation());
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<SuitableOrigin> context_origin;
|
||||
AttributionReportingEligibility eligibility;
|
||||
|
@ -72,7 +72,9 @@ struct AttributionSimulationEvent {
|
||||
int64_t request_id;
|
||||
};
|
||||
|
||||
using Data = std::variant<StartRequest, Response, EndRequest>;
|
||||
struct Navigation {};
|
||||
|
||||
using Data = std::variant<StartRequest, Response, EndRequest, Navigation>;
|
||||
|
||||
base::Time time;
|
||||
Data data;
|
||||
@ -100,6 +102,7 @@ struct AttributionInteropConfig {
|
||||
double max_event_level_epsilon = 0;
|
||||
uint32_t max_trigger_state_cardinality = 0;
|
||||
bool needs_cross_app_web = false;
|
||||
bool needs_delivery_after_new_navigation = false;
|
||||
std::vector<url::Origin> aggregation_coordinator_origins;
|
||||
|
||||
AttributionInteropConfig();
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "content/browser/aggregation_service/public_key.h"
|
||||
#include "content/browser/attribution_reporting/attribution_background_registrations_id.h"
|
||||
#include "content/browser/attribution_reporting/attribution_data_host_manager.h"
|
||||
#include "content/browser/attribution_reporting/attribution_features.h"
|
||||
#include "content/browser/attribution_reporting/attribution_manager_impl.h"
|
||||
#include "content/browser/attribution_reporting/attribution_os_level_manager.h"
|
||||
#include "content/browser/attribution_reporting/attribution_report.h"
|
||||
@ -351,13 +352,14 @@ class ControllableStorageDelegate : public AttributionResolverDelegateImpl {
|
||||
};
|
||||
|
||||
void Handle(const AttributionSimulationEvent::StartRequest& event,
|
||||
AttributionDataHostManager& data_host_manager) {
|
||||
AttributionManager& manager) {
|
||||
std::optional<RegistrationEligibility> eligibility =
|
||||
attribution_reporting::GetRegistrationEligibility(event.eligibility);
|
||||
if (!eligibility.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& data_host_manager = *manager.GetDataHostManager();
|
||||
auto suitable_context = AttributionSuitableContext::CreateForTesting(
|
||||
event.context_origin, event.fenced, kFrameId,
|
||||
/*last_navigation_id=*/kNavigationId);
|
||||
@ -382,18 +384,23 @@ void Handle(const AttributionSimulationEvent::StartRequest& event,
|
||||
}
|
||||
|
||||
void Handle(const AttributionSimulationEvent::Response& event,
|
||||
AttributionDataHostManager& data_host_manager) {
|
||||
data_host_manager.NotifyBackgroundRegistrationData(
|
||||
AttributionManager& manager) {
|
||||
manager.GetDataHostManager()->NotifyBackgroundRegistrationData(
|
||||
BackgroundRegistrationsId(event.request_id), event.response_headers.get(),
|
||||
event.url);
|
||||
}
|
||||
|
||||
void Handle(const AttributionSimulationEvent::EndRequest& event,
|
||||
AttributionDataHostManager& data_host_manager) {
|
||||
data_host_manager.NotifyBackgroundRegistrationCompleted(
|
||||
AttributionManager& manager) {
|
||||
manager.GetDataHostManager()->NotifyBackgroundRegistrationCompleted(
|
||||
BackgroundRegistrationsId(event.request_id));
|
||||
}
|
||||
|
||||
void Handle(const AttributionSimulationEvent::Navigation& event,
|
||||
AttributionManager& manager) {
|
||||
manager.UpdateLastNavigationTime(base::Time::Now());
|
||||
}
|
||||
|
||||
void FastForwardUntilReportsConsumed(AttributionManager& manager,
|
||||
BrowserTaskEnvironment& task_environment) {
|
||||
while (true) {
|
||||
@ -414,6 +421,7 @@ void FastForwardUntilReportsConsumed(AttributionManager& manager,
|
||||
run_loop.Run();
|
||||
|
||||
if (delta.is_negative()) {
|
||||
task_environment.FastForwardBy(base::TimeDelta());
|
||||
break;
|
||||
}
|
||||
task_environment.FastForwardBy(delta);
|
||||
@ -443,6 +451,10 @@ RunAttributionInteropSimulation(
|
||||
scoped_api_state.emplace(AttributionOsLevelManager::ApiState::kEnabled);
|
||||
}
|
||||
|
||||
if (run.config.needs_delivery_after_new_navigation) {
|
||||
enabled_features.emplace_back(kAttributionReportDeliveryOnNewNavigation);
|
||||
}
|
||||
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitWithFeatures(enabled_features,
|
||||
/*disabled_features=*/{});
|
||||
@ -551,10 +563,7 @@ RunAttributionInteropSimulation(
|
||||
|
||||
for (const auto& event : run.events) {
|
||||
task_environment.FastForwardBy(event.time - base::Time::Now());
|
||||
|
||||
std::visit(
|
||||
[&](const auto& data) { Handle(data, *manager->GetDataHostManager()); },
|
||||
event.data);
|
||||
std::visit([&](const auto& data) { Handle(data, *manager); }, event.data);
|
||||
}
|
||||
|
||||
FastForwardUntilReportsConsumed(*manager, task_environment);
|
||||
|
@ -6343,6 +6343,7 @@ data/attribution_reporting/interop/trigger_header_error_debug_report.json
|
||||
data/attribution_reporting/interop/trigger_verbose_debug_report_source_debug_permission.json
|
||||
data/attribution_reporting/interop/unsuitable_response_url.json
|
||||
data/attribution_reporting/interop/verbose_debug_report_multiple_data.json
|
||||
data/attribution_reporting/interop_private/deliver_after_new_navigation.json
|
||||
data/attribution_reporting/page_with_conversion_measurement_disabled.html
|
||||
data/attribution_reporting/page_with_conversion_measurement_disabled.html.mock-http-headers
|
||||
data/attribution_reporting/page_with_conversion_redirect.html
|
||||
|
127
content/test/data/attribution_reporting/interop_private/deliver_after_new_navigation.json
Normal file
127
content/test/data/attribution_reporting/interop_private/deliver_after_new_navigation.json
Normal file
@ -0,0 +1,127 @@
|
||||
{
|
||||
"description": "Deliver report in standby upon new navigation received",
|
||||
"api_config": {
|
||||
"needs_delivery_after_new_navigation": true
|
||||
},
|
||||
"input": {
|
||||
"registrations": [
|
||||
{
|
||||
"timestamp": "0",
|
||||
"registration_request": {
|
||||
"context_origin": "https://source.test",
|
||||
"Attribution-Reporting-Eligible": "navigation-source"
|
||||
},
|
||||
"responses": [
|
||||
{
|
||||
"url": "https://reporter.test/register-source",
|
||||
"debug_permission": true,
|
||||
"response": {
|
||||
"Attribution-Reporting-Register-Source": {
|
||||
"destination": "https://destination.test",
|
||||
"source_event_id": "123",
|
||||
"event_report_windows": {
|
||||
"start_time": 0,
|
||||
"end_times": [3600, 86400]
|
||||
},
|
||||
"debug_reporting": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": "1000",
|
||||
"registration_request": {
|
||||
"context_origin": "https://destination.test"
|
||||
},
|
||||
"responses": [
|
||||
{
|
||||
"url": "https://reporter.test/register-trigger",
|
||||
"response": {
|
||||
"Attribution-Reporting-Register-Trigger": {
|
||||
"event_trigger_data": [
|
||||
{
|
||||
"trigger_data": "2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// Report time - last navigation <= 1 minute, report not on standby.
|
||||
{
|
||||
"timestamp": "3540000",
|
||||
"navigation": true
|
||||
},
|
||||
// Report time - last navigation > 1 minute, report on standby.
|
||||
{
|
||||
"timestamp": "86339999",
|
||||
"registration_request": {
|
||||
"context_origin": "https://destination.test"
|
||||
},
|
||||
"responses": [
|
||||
{
|
||||
"url": "https://reporter.test/register-trigger",
|
||||
"response": {
|
||||
"Attribution-Reporting-Register-Trigger": {
|
||||
"event_trigger_data": [
|
||||
{
|
||||
"trigger_data": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// New navigation, second report now sends after delay.
|
||||
{
|
||||
"timestamp": "90000000",
|
||||
"navigation": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"reports": [
|
||||
{
|
||||
"payload": [
|
||||
{
|
||||
"body": {
|
||||
"attribution_destination": "https://destination.test",
|
||||
"source_event_id": "123",
|
||||
"source_site": "https://source.test"
|
||||
},
|
||||
"type": "source-success"
|
||||
}
|
||||
],
|
||||
"report_time": "0",
|
||||
"report_url": "https://reporter.test/.well-known/attribution-reporting/debug/verbose"
|
||||
},
|
||||
{
|
||||
"payload": {
|
||||
"attribution_destination": "https://destination.test",
|
||||
"randomized_trigger_rate": 0.0008051,
|
||||
"scheduled_report_time": "3600",
|
||||
"source_event_id": "123",
|
||||
"source_type": "navigation",
|
||||
"trigger_data": "2"
|
||||
},
|
||||
"report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
|
||||
"report_time": "3600000"
|
||||
},
|
||||
{
|
||||
"payload": {
|
||||
"attribution_destination": "https://destination.test",
|
||||
"randomized_trigger_rate": 0.0008051,
|
||||
"scheduled_report_time": "86400",
|
||||
"source_event_id": "123",
|
||||
"source_type": "navigation",
|
||||
"trigger_data": "3"
|
||||
},
|
||||
"report_url": "https://reporter.test/.well-known/attribution-reporting/report-event-attribution",
|
||||
"report_time": "90000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user