blob: 20c8b7babc3d4feedbb9fff7296c66a05b6f1eb8 [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 <stddef.h>
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/attribution_reporting/constants.h"
#include "components/ukm/content/source_url_recorder.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/attribution_reporting/attribution_manager.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_test_utils.h"
#include "content/browser/attribution_reporting/event_level_result.mojom.h"
#include "content/browser/attribution_reporting/storable_source.h"
#include "content/browser/attribution_reporting/store_source_result.mojom.h"
#include "content/browser/attribution_reporting/test/mock_attribution_observer.h"
#include "content/browser/attribution_reporting/test/mock_content_browser_client.h"
#include "content/browser/fenced_frame/fenced_frame_reporter.h"
#include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "content/browser/private_aggregation/private_aggregation_manager.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/page_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_core_observer.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/fenced_frame_test_utils.h"
#include "content/test/test_content_browser_client.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h"
#include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ::testing::_;
using ::testing::Return;
constexpr char kBaseDataDir[] = "content/test/data/";
using attribution_reporting::kAttributionReportingRegisterSourceHeader;
using attribution_reporting::mojom::EventLevelResult;
using StoreSourceStatus = attribution_reporting::mojom::StoreSourceResult;
void ExpectRegisterResultAndRun(blink::ServiceWorkerStatusCode expected,
base::RepeatingClosure continuation,
blink::ServiceWorkerStatusCode actual) {
EXPECT_EQ(expected, actual);
continuation.Run();
}
struct UkmEntry {
GURL url;
int64_t value;
};
void VerifyUkmEntries(const ukm::TestAutoSetUkmRecorder& ukm_recorder,
std::string_view entry_name,
std::string_view metric_name,
base::span<const UkmEntry> expected_entries) {
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> ukm_entries =
ukm_recorder.GetEntries(std::string(entry_name),
{std::string(metric_name)});
ASSERT_EQ(ukm_entries.size(), expected_entries.size());
for (size_t i = 0; i < ukm_entries.size(); ++i) {
EXPECT_EQ(
ukm_recorder.GetSourceForSourceId(ukm_entries[i].source_id)->url(),
expected_entries[i].url);
EXPECT_THAT(ukm_entries[i].metrics,
testing::ElementsAre(
testing::Pair(metric_name, expected_entries[i].value)));
}
}
void VerifySourceUkmEntries(const ukm::TestAutoSetUkmRecorder& ukm_recorder,
const std::vector<UkmEntry>& expected_entries) {
VerifyUkmEntries(ukm_recorder, "Conversions.SourceRegistration",
"StoreSourceResult", expected_entries);
}
void VerifyEventLevelResultUkmEntries(
const ukm::TestAutoSetUkmRecorder& ukm_recorder,
const std::vector<UkmEntry>& expected_entries) {
VerifyUkmEntries(ukm_recorder, "Conversions.TriggerRegistration",
"CreateEventLevelReportStatus", expected_entries);
}
// Observer which waits for a service worker to register in the browser process
// by observing worker activation status.
class WorkerStateObserver : public ServiceWorkerContextCoreObserver {
public:
WorkerStateObserver(scoped_refptr<ServiceWorkerContextWrapper> context,
ServiceWorkerVersion::Status target)
: context_(std::move(context)), target_(target) {
observation_.Observe(context_.get());
}
WorkerStateObserver(const WorkerStateObserver&) = delete;
WorkerStateObserver& operator=(const WorkerStateObserver&) = delete;
~WorkerStateObserver() override = default;
// ServiceWorkerContextCoreObserver overrides.
void OnVersionStateChanged(int64_t version_id,
const GURL& scope,
const blink::StorageKey& key,
ServiceWorkerVersion::Status) override {
const ServiceWorkerVersion* version = context_->GetLiveVersion(version_id);
if (version->status() == target_) {
context_->RemoveObserver(this);
run_loop_.Quit();
}
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
scoped_refptr<ServiceWorkerContextWrapper> context_;
const ServiceWorkerVersion::Status target_;
base::ScopedObservation<ServiceWorkerContextWrapper,
ServiceWorkerContextCoreObserver>
observation_{this};
};
// Waits for the a given |report_url| to be received by the test server. Wraps a
// ControllableHttpResponse so that it can wait for the server request in a
// thread-safe manner. Therefore, these must be registered prior to |server|
// starting.
struct ExpectedReportWaiter {
ExpectedReportWaiter(GURL report_url,
std::string attribution_destination,
std::string source_event_id,
std::string source_type,
std::string trigger_data,
net::EmbeddedTestServer* server)
: ExpectedReportWaiter(std::move(report_url),
base::Value::Dict(),
server) {
expected_body.Set("attribution_destination",
std::move(attribution_destination));
expected_body.Set("source_event_id", std::move(source_event_id));
expected_body.Set("source_type", std::move(source_type));
expected_body.Set("trigger_data", std::move(trigger_data));
}
// ControllableHTTPResponses can only wait for relative urls, so only supply
// the path.
ExpectedReportWaiter(GURL report_url,
base::Value::Dict body,
net::EmbeddedTestServer* server)
: expected_url(std::move(report_url)),
expected_body(std::move(body)),
response(std::make_unique<net::test_server::ControllableHttpResponse>(
server,
expected_url.path())) {}
GURL expected_url;
base::Value::Dict expected_body;
std::unique_ptr<net::test_server::ControllableHttpResponse> response;
bool HasRequest() { return !!response->http_request(); }
// Waits for a report to be received matching the report url. Verifies that
// the report url and report body were set correctly.
void WaitForReport() {
if (!response->http_request()) {
response->WaitForRequest();
}
// The embedded test server resolves all urls to 127.0.0.1, so get the real
// request host from the request headers.
const net::test_server::HttpRequest& request = *response->http_request();
DCHECK(base::Contains(request.headers, "Host"));
const GURL& request_url = request.GetURL();
GURL header_url = GURL("https://" + request.headers.at("Host"));
std::string host = header_url.host();
GURL::Replacements replace_host;
replace_host.SetHostStr(host);
base::Value::Dict body = base::test::ParseJsonDict(request.content);
EXPECT_THAT(body, base::test::DictionaryHasValues(expected_body));
// The report ID is random, so just test that the field exists here and is a
// valid GUID.
const std::string* report_id = body.FindString("report_id");
ASSERT_TRUE(report_id);
EXPECT_TRUE(base::Uuid::ParseLowercase(*report_id).is_valid());
EXPECT_TRUE(body.FindDouble("randomized_trigger_rate"));
EXPECT_FALSE(body.FindString("source_debug_key"));
EXPECT_FALSE(body.FindString("trigger_debug_key"));
// Clear the port as it is assigned by the EmbeddedTestServer at runtime.
replace_host.SetPortStr("");
// Compare the expected report url with a URL formatted with the host
// defined in the headers. This would not match |expected_url| if the host
// for report url was not set properly.
EXPECT_EQ(expected_url, request_url.ReplaceComponents(replace_host));
EXPECT_TRUE(base::Contains(request.headers, "User-Agent"));
EXPECT_EQ(request.headers.at("Content-Type"), "application/json");
}
};
} // namespace
class InterestGroupEnabledContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
explicit InterestGroupEnabledContentBrowserClient() = default;
InterestGroupEnabledContentBrowserClient(
const InterestGroupEnabledContentBrowserClient&) = delete;
InterestGroupEnabledContentBrowserClient& operator=(
const InterestGroupEnabledContentBrowserClient&) = delete;
// ContentBrowserClient overrides:
// This is needed so that the interest group related APIs can run without
// failing with the result AuctionResult::kSellerRejected.
bool IsPrivacySandboxReportingDestinationAttested(
content::BrowserContext* browser_context,
const url::Origin& destination_origin,
content::PrivacySandboxInvokingAPI invoking_api) override {
return true;
}
};
class AttributionsBrowserTestBase : public ContentBrowserTest {
public:
AttributionsBrowserTestBase() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kAttributionReportingDebugMode);
// Sets up the blink runtime feature for ConversionMeasurement.
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
void PreRunTestOnMainThread() override {
ContentBrowserTest::PreRunTestOnMainThread();
ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
}
void SetUpOnMainThread() override {
// These tests don't cover online/offline behavior; that is covered by
// `AttributionManagerImpl`'s unit tests. Here we use a fake tracker that
// always indicates online. See crbug.com/1285057 for details.
network_connection_tracker_ =
network::TestNetworkConnectionTracker::CreateInstance();
SetNetworkConnectionTrackerForTesting(nullptr);
SetNetworkConnectionTrackerForTesting(network_connection_tracker_.get());
host_resolver()->AddRule("*", "127.0.0.1");
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
net::test_server::RegisterDefaultHandlers(https_server_.get());
https_server_->ServeFilesFromSourceDirectory("content/test/data");
https_server_->ServeFilesFromSourceDirectory(
"content/test/data/attribution_reporting");
StoragePartition* partition = shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition();
wrapper_ = static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
content_browser_client_ =
std::make_unique<InterestGroupEnabledContentBrowserClient>();
}
void TearDownOnMainThread() override {
SetNetworkConnectionTrackerForTesting(nullptr);
}
WebContents* web_contents() { return shell()->web_contents(); }
net::EmbeddedTestServer* https_server() { return https_server_.get(); }
AttributionManager* attribution_manager() {
return static_cast<StoragePartitionImpl*>(
web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition())
->GetAttributionManager();
}
void RegisterSource(const GURL& attribution_src_url) {
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
EXPECT_CALL(observer,
OnSourceHandled(_, _, _, StorableSource::Result::kSuccess))
.WillOnce([&]() { loop.Quit(); });
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
attribution_src_url)));
// Wait until the source has been stored before registering the trigger;
// otherwise the trigger could be processed before the source, in which case
// there would be no matching source: crbug.com/1309173.
loop.Run();
}
void CreateAndClickSource(WebContents* web_contents,
const GURL& href,
const std::string& attribution_src) {
CreateAndClickSourceInFrame(web_contents,
web_contents->GetPrimaryMainFrame(), href,
attribution_src,
/*target=*/"_top");
}
WebContents* CreateAndClickPopupSource(WebContents* web_contents,
const GURL& href,
const std::string& attribution_src,
const std::string& target) {
return CreateAndClickSourceInFrame(nullptr,
web_contents->GetPrimaryMainFrame(),
href, attribution_src, target);
}
WebContents* CreateAndClickSourceInFrame(WebContents* web_contents,
RenderFrameHost* rfh,
const GURL& href,
const std::string& attribution_src,
const std::string& target) {
EXPECT_TRUE(ExecJs(rfh, JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: $2,
target: $3});)",
href, attribution_src, target)));
MockAttributionObserver source_observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&source_observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
bool received = false;
EXPECT_CALL(source_observer,
OnSourceHandled(_, _, _, StorableSource::Result::kSuccess))
.WillOnce([&]() {
received = true;
loop.Quit();
});
WebContents* popup_contents = nullptr;
if (!web_contents) {
ShellAddedObserver new_shell_observer;
TestNavigationObserver observer(nullptr);
observer.StartWatchingNewWebContents();
EXPECT_TRUE(ExecJs(rfh, "simulateClick('link');"));
popup_contents = new_shell_observer.GetShell()->web_contents();
observer.Wait();
} else {
TestNavigationObserver observer(web_contents);
EXPECT_TRUE(ExecJs(rfh, "simulateClick('link');"));
observer.Wait();
}
// If the source wasn't processed, wait to ensure we handle events in test
// order. See https://crbug.com/1309173.
if (!received) {
loop.Run();
}
return popup_contents;
}
ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); }
ServiceWorkerContext* public_context() { return wrapper(); }
private:
AttributionManagerImpl::ScopedUseInMemoryStorageForTesting
attribution_manager_in_memory_setting_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
std::unique_ptr<network::TestNetworkConnectionTracker>
network_connection_tracker_;
scoped_refptr<ServiceWorkerContextWrapper> wrapper_;
std::unique_ptr<InterestGroupEnabledContentBrowserClient>
content_browser_client_;
};
class AttributionsBrowserTest : public AttributionsBrowserTestBase,
public ::testing::WithParamInterface<bool> {
public:
explicit AttributionsBrowserTest(
std::vector<base::test::FeatureRef> enabled_features = {},
std::vector<base::test::FeatureRef> disabled_features = {}) {
const bool enable_in_browser_migration = GetParam();
if (enable_in_browser_migration) {
enabled_features.emplace_back(
blink::features::kKeepAliveInBrowserMigration);
enabled_features.emplace_back(
blink::features::kAttributionReportingInBrowserMigration);
} else {
disabled_features.emplace_back(
blink::features::kKeepAliveInBrowserMigration);
}
scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All, AttributionsBrowserTest, ::testing::Bool());
// Verifies that storage initialization does not hang when initialized in a
// browsertest context, see https://crbug.com/1080764).
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
FeatureEnabled_StorageInitWithoutHang) {}
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
ImpressionNavigationMultipleRedirects_BothReportsSent) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
// Expected reports must be registered before the server starts.
ExpectedReportWaiter expected_report(
GURL("https://d.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://c.test",
/*source_event_id=*/"1", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ExpectedReportWaiter expected_report2(
GURL("https://b.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://c.test",
/*source_event_id=*/"2", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
// Create an anchor tag with impression attributes and click the link. By
// default the target is set to "_top".
GURL register_source_url =
https_server()->GetURL("d.test", "/register_source_redirect");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: '',
target: $2});)",
register_source_url, "_top")));
TestNavigationObserver observer(web_contents());
EXPECT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
register_response->WaitForRequest();
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader(
kAttributionReportingRegisterSourceHeader,
R"({"source_event_id":"1","destination":"https://c.test"})");
http_response->AddCustomHeader(
"Location",
https_server()->GetURL("b.test", "/register_source_redirect").spec());
register_response->Send(http_response->ToResponseString());
register_response->Done();
register_response2->WaitForRequest();
auto http_response2 = std::make_unique<net::test_server::BasicHttpResponse>();
http_response2->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response2->AddCustomHeader(
kAttributionReportingRegisterSourceHeader,
R"({"source_event_id":"2","destination":"https://c.test"})");
GURL conversion_url = https_server()->GetURL(
"c.test", "/attribution_reporting/page_with_conversion_redirect.html");
http_response2->AddCustomHeader("Location", conversion_url.spec());
register_response2->Send(http_response2->ToResponseString());
register_response2->Done();
// Wait for navigation to complete.
observer.Wait();
GURL register_trigger_url = https_server()->GetURL(
"d.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
GURL register_trigger_url2 = https_server()->GetURL(
"b.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url2)));
expected_report2.WaitForReport();
VerifySourceUkmEntries(
ukm_recorder,
{UkmEntry(impression_url,
static_cast<int64_t>(StoreSourceStatus::kSuccess)),
UkmEntry(impression_url,
static_cast<int64_t>(StoreSourceStatus::kSuccess))});
VerifyEventLevelResultUkmEntries(
ukm_recorder,
{UkmEntry(conversion_url,
static_cast<int64_t>(EventLevelResult::kSuccess)),
UkmEntry(conversion_url,
static_cast<int64_t>(EventLevelResult::kSuccess))});
}
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
ImpressionsRegisteredOnNavigation_ReportSent) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
// Expected reports must be registered before the server starts.
ExpectedReportWaiter expected_report(
GURL("https://c.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://c.test",
/*source_event_id=*/"1", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
// 1 - Navigate on a page that can register a source
GURL impression_url = https_server()->GetURL(
"b.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
// 2 - Create an anchor tag with impression attributes and click the link.
GURL register_source_url = https_server()->GetURL(
"c.test",
"/attribution_reporting/"
"page_with_source_registration_and_conversion.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: ''});)",
register_source_url)));
TestNavigationObserver observer(web_contents());
EXPECT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
// Wait for navigation to complete.
observer.Wait();
// 3 - On the landing page and destination origin, register a trigger that
// should match the navigation-source.
GURL register_trigger_url = https_server()->GetURL(
"c.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
VerifySourceUkmEntries(
ukm_recorder,
{UkmEntry(impression_url,
static_cast<int64_t>(StoreSourceStatus::kSuccess))});
VerifyEventLevelResultUkmEntries(
ukm_recorder,
{UkmEntry(register_source_url,
static_cast<int64_t>(EventLevelResult::kSuccess))});
}
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
NonAttributionEligibleNavigation_NoEligibleHeader) {
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL register_source_url =
https_server()->GetURL("d.test", "/register_source_redirect");
// Create a non-attribution eligible anchor and click.
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
const anchor = document.createElement('a');
anchor.href = $1;
anchor.target = '_top';
anchor.id = 'link';
document.body.appendChild(anchor);)",
register_source_url)));
EXPECT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
// Verify the navigation request does not contain the eligibility header.
register_response1->WaitForRequest();
EXPECT_FALSE(base::Contains(register_response1->http_request()->headers,
"Attribution-Reporting-Eligible"));
EXPECT_FALSE(base::Contains(register_response1->http_request()->headers,
"Attribution-Reporting-Support"));
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
register_response1->Send(http_response->ToResponseString());
register_response1->Done();
}
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
ImpressionFromCrossOriginSubframe_ReportSent) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"5", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
GURL page_url = https_server()->GetURL("a.test", "/page_with_iframe.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL subframe_url = https_server()->GetURL(
"c.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(ExecJs(shell(), R"(
let frame= document.getElementById('test_iframe');
frame.setAttribute('allow', 'attribution-reporting');)"));
NavigateIframeToURL(web_contents(), "test_iframe", subframe_url);
RenderFrameHost* subframe =
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
GURL conversion_url = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
GURL register_source_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_source_headers.html");
// Create an impression tag in the subframe and target a popup window.
auto* popup_contents = CreateAndClickSourceInFrame(
/*web-contents=*/nullptr, subframe, conversion_url,
register_source_url.spec(),
/*target=*/"new_frame");
GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(popup_contents, JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
VerifySourceUkmEntries(
ukm_recorder,
{UkmEntry(page_url, static_cast<int64_t>(StoreSourceStatus::kSuccess))});
VerifyEventLevelResultUkmEntries(
ukm_recorder,
{UkmEntry(conversion_url,
static_cast<int64_t>(EventLevelResult::kSuccess))});
}
// Regression test for crbug.com/1366513.
// TODO(b/331159758): Disabled due to flakiness.
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
DISABLED_AttributionSrcInSandboxedIframe_NoCrash) {
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://a.test",
/*source_event_id=*/"5", /*source_type=*/"event",
/*trigger_data=*/"1", https_server());
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_trigger");
ASSERT_TRUE(https_server()->Start());
GURL page_url = https_server()->GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
// Setting the frame's sandbox attribute causes its origin to be opaque.
ASSERT_TRUE(ExecJs(shell(), R"(
const frame = document.getElementById('test_iframe');
frame.setAttribute('sandbox', '');
frame.setAttribute('srcdoc', '<img attributionsrc=/register_source_redirect>');
)"));
{
register_response->WaitForRequest();
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader(
kAttributionReportingRegisterSourceHeader,
R"({"source_event_id":"5","destination":"https://a.test"})");
http_response->AddCustomHeader(
"Location",
https_server()->GetURL("a.test", "/register_trigger").spec());
register_response->Send(http_response->ToResponseString());
register_response->Done();
}
{
register_response2->WaitForRequest();
auto http_response2 =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response2->AddCustomHeader(
"Attribution-Reporting-Register-Trigger",
R"({"event_trigger_data":[{"trigger_data":"1"}]})");
register_response2->Send(http_response2->ToResponseString());
register_response2->Done();
}
expected_report.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
ImpressionOnNoOpenerNavigation_ReportSent) {
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"5", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL conversion_url = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
GURL register_source_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_source_headers.html");
// target="_blank" navs are rel="noopener" by default.
CreateAndClickPopupSource(web_contents(), conversion_url,
register_source_url.spec(),
/*target=*/"_blank");
GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(
ExecJs(Shell::windows()[1]->web_contents(),
JsReplace("createAttributionSrcImg($1);", register_trigger_url)));
expected_report.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ConversionOnDifferentSubdomainThanLandingPage_ReportSent) {
// Expected reports must be registered before the server starts.
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"5", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL conversion_url = https_server()->GetURL(
"sub.d.test",
"/attribution_reporting/page_with_conversion_redirect.html");
GURL register_source_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_source_headers.html");
CreateAndClickSource(web_contents(), conversion_url,
register_source_url.spec());
// Navigate to a same domain origin that is different than the landing page
// for the click and convert there. A report should still be sent.
GURL other_conversion_url = https_server()->GetURL(
"other.d.test",
"/attribution_reporting/page_with_conversion_redirect.html");
EXPECT_TRUE(NavigateToURL(web_contents(), other_conversion_url));
GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionSrcRedirect_ReporterSet) {
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/attribution_reporting/register_source_redirect");
ExpectedReportWaiter expected_report(
GURL("https://c.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"5", /*source_type=*/"event", /*trigger_data=*/"1",
https_server());
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
// Setup our service worker.
WorkerStateObserver sw_observer(wrapper(), ServiceWorkerVersion::ACTIVATED);
blink::mojom::ServiceWorkerRegistrationOptions options(
impression_url, blink::mojom::ScriptType::kClassic,
blink::mojom::ServiceWorkerUpdateViaCache::kImports);
const blink::StorageKey key =
blink::StorageKey::CreateFirstParty(url::Origin::Create(options.scope));
public_context()->RegisterServiceWorker(
https_server()->GetURL("a.test",
"/attribution_reporting/service_worker.js"),
key, options,
base::BindOnce(&ExpectRegisterResultAndRun,
blink::ServiceWorkerStatusCode::kOk, base::DoNothing()));
sw_observer.Wait();
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
EXPECT_CALL(observer,
OnSourceHandled(_, _, _, StorableSource::Result::kSuccess))
.WillOnce([&]() { loop.Quit(); });
EXPECT_TRUE(ExecJs(
web_contents(),
JsReplace(
"createAttributionSrcImg($1);",
https_server()->GetURL(
"a.test", "/attribution_reporting/register_source_redirect"))));
register_response->WaitForRequest();
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader(
"Location",
https_server()
->GetURL("c.test",
"/attribution_reporting/register_source_headers.html")
.spec());
register_response->Send(http_response->ToResponseString());
register_response->Done();
// Wait until the source has been stored before registering the trigger;
// otherwise the trigger could be processed before the source, in which case
// there would be no matching source: crbug.com/1309173.
loop.Run();
GURL conversion_url = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
EXPECT_TRUE(NavigateToURL(web_contents(), conversion_url));
GURL register_trigger_url = https_server()->GetURL(
"c.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionEligibleRedirect_ReporterSet) {
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/attribution_reporting/register_source_redirect");
ExpectedReportWaiter expected_report(
GURL("https://c.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"5", /*source_type=*/"event", /*trigger_data=*/"1",
https_server());
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
// Setup our service worker.
WorkerStateObserver sw_observer(wrapper(), ServiceWorkerVersion::ACTIVATED);
blink::mojom::ServiceWorkerRegistrationOptions options(
impression_url, blink::mojom::ScriptType::kClassic,
blink::mojom::ServiceWorkerUpdateViaCache::kImports);
const blink::StorageKey key =
blink::StorageKey::CreateFirstParty(url::Origin::Create(options.scope));
public_context()->RegisterServiceWorker(
https_server()->GetURL("a.test",
"/attribution_reporting/service_worker.js"),
key, options,
base::BindOnce(&ExpectRegisterResultAndRun,
blink::ServiceWorkerStatusCode::kOk, base::DoNothing()));
sw_observer.Wait();
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
EXPECT_CALL(observer,
OnSourceHandled(_, _, _, StorableSource::Result::kSuccess))
.WillOnce([&]() { loop.Quit(); });
EXPECT_TRUE(ExecJs(
web_contents(),
JsReplace(
"createAttributionEligibleImgSrc($1);",
https_server()->GetURL(
"a.test", "/attribution_reporting/register_source_redirect"))));
register_response->WaitForRequest();
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader(
"Location",
https_server()
->GetURL("c.test",
"/attribution_reporting/register_source_headers.html")
.spec());
register_response->Send(http_response->ToResponseString());
register_response->Done();
// Wait until the source has been stored before registering the trigger;
// otherwise the trigger could be processed before the source, in which case
// there would be no matching source: crbug.com/1309173.
loop.Run();
GURL conversion_url = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
EXPECT_TRUE(NavigateToURL(web_contents(), conversion_url));
GURL register_trigger_url = https_server()->GetURL(
"c.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(AttributionsBrowserTest,
NavigationNoneSupported_EligibleHeaderNotSet) {
MockAttributionReportingContentBrowserClientBase<
ContentBrowserTestContentBrowserClient>
browser_client;
EXPECT_CALL(
browser_client,
GetAttributionSupport(
ContentBrowserClient::AttributionReportingOsApiState::kDisabled,
/*client_os_disabled=*/false))
.WillRepeatedly(Return(network::mojom::AttributionSupport::kNone));
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source");
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL register_source_url =
https_server()->GetURL("d.test", "/register_source");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: '',
target: $2});)",
register_source_url, "_top")));
EXPECT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
register_response->WaitForRequest();
ExpectEmptyAttributionReportingEligibleHeader(
register_response->http_request()->headers.at(
"Attribution-Reporting-Eligible"));
ExpectValidAttributionReportingSupportHeader(
register_response->http_request()->headers.at(
"Attribution-Reporting-Support"),
/*web_expected=*/false,
/*os_expected=*/false);
}
class AttributionsPrerenderBrowserTest : public AttributionsBrowserTest {
public:
AttributionsPrerenderBrowserTest()
: prerender_helper_(
base::BindRepeating(&AttributionsBrowserTest::web_contents,
base::Unretained(this))) {}
~AttributionsPrerenderBrowserTest() override = default;
protected:
content::test::PrerenderTestHelper prerender_helper_;
};
INSTANTIATE_TEST_SUITE_P(All,
AttributionsPrerenderBrowserTest,
::testing::Bool());
// TODO(crbug.com/40231714): these tests are flaky on most release bots.
#if defined(NDEBUG)
#define ATTRIBUTION_PRERENDER_BROWSER_TEST(TEST_NAME) \
IN_PROC_BROWSER_TEST_P(AttributionsPrerenderBrowserTest, DISABLED_##TEST_NAME)
#else
#define ATTRIBUTION_PRERENDER_BROWSER_TEST(TEST_NAME) \
IN_PROC_BROWSER_TEST_P(AttributionsPrerenderBrowserTest, TEST_NAME)
#endif
ATTRIBUTION_PRERENDER_BROWSER_TEST(NoConversionsOnPrerender) {
const char* kTestCases[] = {
"createAttributionSrcImg($1);",
"createTrackingPixel($1);",
R"(fetch($1, {keepalive: true}))",
};
for (const char* registration_js : kTestCases) {
auto https_server = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server->ServeFilesFromSourceDirectory("content/test/data");
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"7", /*source_type=*/"event", /*trigger_data=*/"1",
https_server.get());
ASSERT_TRUE(https_server->Start());
// Navigate to a page with impression creator.
const GURL kImpressionUrl = https_server->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), kImpressionUrl));
// Register impression for the target conversion url.
GURL register_url = https_server->GetURL(
"a.test", "/attribution_reporting/register_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_url)));
// Navigate to a starting same origin page with the conversion url.
const GURL kEmptyUrl = https_server->GetURL("d.test", "/empty.html");
{
auto url_loader_interceptor =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
kBaseDataDir, kEmptyUrl.DeprecatedGetOriginAsURL());
EXPECT_TRUE(NavigateToURL(web_contents(), kEmptyUrl));
}
// Pre-render the conversion url.
const GURL kConversionUrl = https_server->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(kConversionUrl);
content::test::PrerenderHostObserver host_observer(*web_contents(),
host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(kConversionUrl);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
// Register a conversion with the original page as the reporting origin
// during pre-rendering.
const GURL register_trigger_url = https_server->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(prerender_rfh,
JsReplace(registration_js, register_trigger_url)));
// Verify that registering a conversion had no effect on reports, as the
// impressions were never passed to the conversion URL, as the page was only
// pre-rendered.
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
run_loop.Run();
EXPECT_FALSE(expected_report.HasRequest());
}
}
ATTRIBUTION_PRERENDER_BROWSER_TEST(ConversionsRegisteredOnActivatedPrerender) {
const char* kTestCases[] = {
"createAttributionSrcImg($1);",
"createTrackingPixel($1);",
R"(fetch($1, {keepalive: true}))",
};
ASSERT_TRUE(https_server()->Start());
for (const char* registration_js : kTestCases) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
// Navigate to a starting same origin page with the conversion url.
const GURL kEmptyUrl = https_server()->GetURL("d.test", "/empty.html");
{
auto url_loader_interceptor =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
kBaseDataDir, kEmptyUrl.DeprecatedGetOriginAsURL());
EXPECT_TRUE(NavigateToURL(web_contents(), kEmptyUrl));
}
// Pre-render the conversion url.
const GURL kConversionUrl = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(kConversionUrl);
content::test::PrerenderHostObserver host_observer(*web_contents(),
host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(kConversionUrl);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
const GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(prerender_rfh,
JsReplace(registration_js, register_trigger_url)));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
EXPECT_CALL(observer, OnTriggerHandled).WillOnce([&]() { loop.Quit(); });
// Navigate to pre-rendered page, bringing it to the foreground.
prerender_helper_.NavigatePrimaryPage(kConversionUrl);
ASSERT_EQ(kConversionUrl, web_contents()->GetLastCommittedURL());
ASSERT_TRUE(host_observer.was_activated());
loop.Run();
VerifyEventLevelResultUkmEntries(
ukm_recorder,
{UkmEntry(
kConversionUrl,
static_cast<int64_t>(EventLevelResult::kNoMatchingImpressions))});
}
}
ATTRIBUTION_PRERENDER_BROWSER_TEST(NoConversionsInSubframeOnPrerender) {
const char* kTestCases[] = {
"createAttributionSrcImg($1);",
"createTrackingPixel($1);",
R"(fetch($1, {keepalive: true}))",
};
ASSERT_TRUE(https_server()->Start());
for (const char* registration_js : kTestCases) {
// Navigate to a starting same origin page with the conversion url.
const GURL kEmptyUrl = https_server()->GetURL("d.test", "/empty.html");
{
auto url_loader_interceptor =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
kBaseDataDir, kEmptyUrl.DeprecatedGetOriginAsURL());
EXPECT_TRUE(NavigateToURL(web_contents(), kEmptyUrl));
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager());
EXPECT_CALL(observer, OnTriggerHandled).Times(0);
// Pre-render the conversion url.
const GURL kConversionUrl = https_server()->GetURL(
"d.test",
"/attribution_reporting/page_with_conversion_redirect_in_iframe.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(kConversionUrl);
content::test::PrerenderHostObserver host_observer(*web_contents(),
host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(kConversionUrl);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
RenderFrameHost* subframe = ChildFrameAt(prerender_rfh, 0);
const GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(
ExecJs(subframe, JsReplace(registration_js, register_trigger_url)));
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
}
ATTRIBUTION_PRERENDER_BROWSER_TEST(
ConversionsRegisteredInSubframeActivatedPrerender) {
const char* kTestCases[] = {
"createAttributionSrcImg($1);",
"createTrackingPixel($1);",
R"(fetch($1, {keepalive: true}))",
};
ASSERT_TRUE(https_server()->Start());
for (const char* registration_js : kTestCases) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
// Navigate to a starting same origin page with the conversion url.
const GURL kEmptyUrl = https_server()->GetURL("d.test", "/empty.html");
{
auto url_loader_interceptor =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
kBaseDataDir, kEmptyUrl.DeprecatedGetOriginAsURL());
EXPECT_TRUE(NavigateToURL(web_contents(), kEmptyUrl));
}
// Pre-render the conversion url.
const GURL kConversionUrl = https_server()->GetURL(
"d.test",
"/attribution_reporting/page_with_conversion_redirect_in_iframe.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(kConversionUrl);
content::test::PrerenderHostObserver host_observer(*web_contents(),
host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(kConversionUrl);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
RenderFrameHost* subframe = ChildFrameAt(prerender_rfh, 0);
const GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(
ExecJs(subframe, JsReplace(registration_js, register_trigger_url)));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
EXPECT_CALL(observer, OnTriggerHandled).WillOnce([&]() { loop.Quit(); });
// Navigate to pre-rendered page, bringing it to the foreground.
prerender_helper_.NavigatePrimaryPage(kConversionUrl);
ASSERT_EQ(kConversionUrl, web_contents()->GetLastCommittedURL());
ASSERT_TRUE(host_observer.was_activated());
loop.Run();
VerifyEventLevelResultUkmEntries(
ukm_recorder,
{UkmEntry(
kConversionUrl,
static_cast<int64_t>(EventLevelResult::kNoMatchingImpressions))});
}
}
ATTRIBUTION_PRERENDER_BROWSER_TEST(
NoConversionsAfterSubframeNavigationActivatedPrerenderer) {
const char* kTestCases[] = {
"createAttributionSrcImg($1);",
"createTrackingPixel($1);",
R"(fetch($1, {keepalive: true}))",
};
ASSERT_TRUE(https_server()->Start());
for (const char* registration_js : kTestCases) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
// Navigate to a starting same origin page with the conversion url.
const GURL kEmptyUrl = https_server()->GetURL("d.test", "/empty.html");
{
auto url_loader_interceptor =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
kBaseDataDir, kEmptyUrl.DeprecatedGetOriginAsURL());
EXPECT_TRUE(NavigateToURL(web_contents(), kEmptyUrl));
}
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver>
observation(&observer);
observation.Observe(attribution_manager());
EXPECT_CALL(observer, OnTriggerHandled).Times(0);
// Pre-render the conversion url.
const GURL kConversionUrl = https_server()->GetURL(
"d.test",
"/attribution_reporting/page_with_conversion_redirect_in_iframe.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(kConversionUrl);
content::test::PrerenderHostObserver host_observer(*web_contents(),
host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(kConversionUrl);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
RenderFrameHost* subframe = ChildFrameAt(prerender_rfh, 0);
const GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
EXPECT_TRUE(
ExecJs(subframe, JsReplace(registration_js, register_trigger_url)));
const GURL new_subframe_url = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
// Navigate subframe.
TestNavigationManager subframe_nav_manager(web_contents(),
new_subframe_url);
ASSERT_TRUE(
ExecJs(prerender_rfh,
JsReplace("document.getElementById('test_iframe').src = $1",
new_subframe_url)));
ASSERT_TRUE(subframe_nav_manager.WaitForNavigationFinished());
// Navigate to pre-rendered page, bringing it to the foreground.
prerender_helper_.NavigatePrimaryPage(kConversionUrl);
ASSERT_EQ(kConversionUrl, web_contents()->GetLastCommittedURL());
ASSERT_TRUE(host_observer.was_activated());
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
run_loop.Run();
}
}
class AttributionsCrossAppWebEnabledBrowserTest
: public AttributionsBrowserTest {};
INSTANTIATE_TEST_SUITE_P(All,
AttributionsCrossAppWebEnabledBrowserTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(AttributionsCrossAppWebEnabledBrowserTest,
AttributionEligibleNavigation_SetsSupportHeader) {
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect2");
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL register_source_url =
https_server()->GetURL("d.test", "/register_source_redirect");
// Don't use `CreateAndClickSource()` as we need to observe navigation
// redirects prior to the navigation finishing.
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: '',
target: $2});)",
register_source_url, "_top")));
EXPECT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
// Verify the navigation redirects contain the support header.
register_response1->WaitForRequest();
ExpectValidAttributionReportingSupportHeader(
register_response1->http_request()->headers.at(
"Attribution-Reporting-Support"),
/*web_expected=*/true,
/*os_expected=*/false);
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", "/register_source_redirect2");
register_response1->Send(http_response->ToResponseString());
register_response1->Done();
// Ensure that redirect requests also contain the header.
register_response2->WaitForRequest();
ExpectValidAttributionReportingSupportHeader(
register_response2->http_request()->headers.at(
"Attribution-Reporting-Support"),
/*web_expected=*/true,
/*os_expected=*/false);
}
IN_PROC_BROWSER_TEST_P(
AttributionsCrossAppWebEnabledBrowserTest,
AttributionEligibleNavigationOsLevelEnabled_SetsSupportHeader) {
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect2");
ASSERT_TRUE(https_server()->Start());
AttributionOsLevelManager::ScopedApiStateForTesting scoped_api_state_setting(
AttributionOsLevelManager::ApiState::kEnabled);
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL register_source_url =
https_server()->GetURL("d.test", "/register_source_redirect");
// Don't use `CreateAndClickSource()` as we need to observe navigation
// redirects prior to the navigation finishing.
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: '',
target: $2});)",
register_source_url, "_top")));
EXPECT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
// Verify the navigation redirects contain the support header.
register_response1->WaitForRequest();
ExpectValidAttributionReportingSupportHeader(
register_response1->http_request()->headers.at(
"Attribution-Reporting-Support"),
/*web_expected=*/true,
/*os_expected=*/true);
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader("Location", "/register_source_redirect2");
register_response1->Send(http_response->ToResponseString());
register_response1->Done();
// Ensure that redirect requests also contain the header.
register_response2->WaitForRequest();
ExpectValidAttributionReportingSupportHeader(
register_response2->http_request()->headers.at(
"Attribution-Reporting-Support"),
/*web_expected=*/true,
/*os_expected=*/true);
}
class AttributionsFencedFrameBrowserTest : public AttributionsBrowserTest {
public:
AttributionsFencedFrameBrowserTest()
: AttributionsBrowserTest(
/*enabled_features=*/{blink::features::kFencedFrames,
features::kPrivacySandboxAdsAPIsOverride,
blink::features::kFencedFramesAPIChanges,
blink::features::kFencedFramesDefaultMode}) {}
FrameTreeNode* AddFencedFrame(
FrameTreeNode* root,
const GURL& fenced_frame_url,
scoped_refptr<FencedFrameReporter> fenced_frame_reporter) {
static constexpr char kAddFencedFrameScript[] = R"({
var f = document.createElement('fencedframe');
document.body.appendChild(f);
})";
EXPECT_TRUE(ExecJs(root, kAddFencedFrameScript));
EXPECT_EQ(1U, root->child_count());
FrameTreeNode* fenced_frame_root_node =
GetFencedFrameRootNode(root->child_at(0));
EXPECT_TRUE(fenced_frame_root_node->IsFencedFrameRoot());
EXPECT_TRUE(fenced_frame_root_node->IsInFencedFrameTree());
// Get the urn mapping object.
FencedFrameURLMapping& url_mapping =
root->current_frame_host()->GetPage().fenced_frame_urls_map();
// Add url and its reporting metadata to fenced frame url mapping.
auto urn_uuid = AddAndVerifyFencedFrameURL(
&url_mapping, fenced_frame_url, std::move(fenced_frame_reporter));
TestFrameNavigationObserver observer(
fenced_frame_root_node->current_frame_host());
// Navigate the fenced frame.
EXPECT_TRUE(ExecJs(
root, JsReplace("f.config = new FencedFrameConfig($1);", urn_uuid)));
observer.WaitForCommit();
return fenced_frame_root_node;
}
scoped_refptr<FencedFrameReporter> CreateFencedFrameReporter() {
return FencedFrameReporter::CreateForFledge(
web_contents()
->GetPrimaryMainFrame()
->GetStoragePartition()
->GetURLLoaderFactoryForBrowserProcess(),
web_contents()->GetBrowserContext(),
/*direct_seller_is_seller=*/false,
PrivateAggregationManager::GetManager(
*web_contents()->GetBrowserContext()),
/*main_frame_origin=*/
web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
/*winner_origin=*/url::Origin::Create(GURL("https://a.test")),
/*winner_aggregation_coordinator_origin=*/std::nullopt);
}
private:
GURL AddAndVerifyFencedFrameURL(
FencedFrameURLMapping* fenced_frame_url_mapping,
const GURL& https_url,
scoped_refptr<FencedFrameReporter> fenced_frame_reporter) {
std::optional<GURL> urn_uuid =
fenced_frame_url_mapping->AddFencedFrameURLForTesting(
https_url, std::move(fenced_frame_reporter));
EXPECT_TRUE(urn_uuid.has_value());
EXPECT_TRUE(urn_uuid->is_valid());
return urn_uuid.value();
}
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
AttributionsFencedFrameBrowserTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(AttributionsFencedFrameBrowserTest,
ReportEvent_ReportSent) {
// Expected reports must be registered before the server starts.
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://a.test",
/*source_event_id=*/"5", /*source_type=*/"event",
/*trigger_data=*/"1", https_server());
ASSERT_TRUE(https_server()->Start());
GURL main_url =
https_server()->GetURL("a.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
GURL fenced_frame_url(
https_server()->GetURL("a.test", "/page_with_impression_creator.html"));
GURL reporting_url = https_server()->GetURL(
"a.test", "/register_source_headers_trigger_same_origin.html");
GURL buyer_url = https_server()->GetURL("c.test", "/");
scoped_refptr<FencedFrameReporter> fenced_frame_reporter =
CreateFencedFrameReporter();
// Set valid reporting metadata for buyer.
fenced_frame_reporter->OnUrlMappingReady(
blink::FencedFrame::ReportingDestination::kBuyer,
url::Origin::Create(GURL(buyer_url)), {{"click", reporting_url}});
FrameTreeNode* fenced_frame_root_node =
AddFencedFrame(root, fenced_frame_url, std::move(fenced_frame_reporter));
MockAttributionObserver observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
EXPECT_CALL(observer,
OnSourceHandled(_, _, _, StorableSource::Result::kSuccess))
.WillOnce([&]() { loop.Quit(); });
ASSERT_TRUE(ExecJs(fenced_frame_root_node, R"(
window.fence.reportEvent({
eventType: 'click',
eventData: 'this is a click',
destination: ['buyer'],
});
)"));
loop.Run();
ASSERT_TRUE(ExecJs(root, JsReplace("createAttributionSrcImg($1);",
https_server()->GetURL(
"a.test",
"/attribution_reporting/"
"register_trigger_headers.html"))));
expected_report.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(AttributionsFencedFrameBrowserTest,
ReportEventRedirect_BothReportsSent) {
MockAttributionObserver attribution_manager_observer;
base::ScopedObservation<AttributionManager, AttributionObserver> observation(
&attribution_manager_observer);
observation.Observe(attribution_manager());
base::RunLoop loop;
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source_redirect");
// Expected reports must be registered before the server starts.
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://a.test",
/*source_event_id=*/"1", /*source_type=*/"event",
/*trigger_data=*/"1", https_server());
ExpectedReportWaiter expected_report2(
GURL("https://b.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://a.test",
/*source_event_id=*/"5", /*source_type=*/"event",
/*trigger_data=*/"1", https_server());
ASSERT_TRUE(https_server()->Start());
GURL main_url =
https_server()->GetURL("a.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
GURL fenced_frame_url(
https_server()->GetURL("a.test", "/page_with_impression_creator.html"));
GURL reporting_url =
https_server()->GetURL("a.test", "/register_source_redirect");
GURL buyer_url = https_server()->GetURL("c.test", "/");
scoped_refptr<FencedFrameReporter> fenced_frame_reporter =
CreateFencedFrameReporter();
// Set valid reporting metadata for buyer.
fenced_frame_reporter->OnUrlMappingReady(
blink::FencedFrame::ReportingDestination::kBuyer,
url::Origin::Create(buyer_url), {{"click", reporting_url}});
FrameTreeNode* fenced_frame_root_node =
AddFencedFrame(root, fenced_frame_url, std::move(fenced_frame_reporter));
// Perform the reportEvent call, with a unique body. "this is a click");
ASSERT_TRUE(ExecJs(fenced_frame_root_node, R"(
window.fence.reportEvent({
eventType: 'click',
eventData: 'this is a click',
destination: ['buyer'],
});
)"));
register_response->WaitForRequest();
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader(
kAttributionReportingRegisterSourceHeader,
R"({"source_event_id":"1","destination":"https://a.test"})");
http_response->AddCustomHeader(
"Location",
https_server()
->GetURL("b.test",
"/register_source_headers_trigger_same_origin.html")
.spec());
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
register_response->Send(http_response->ToResponseString());
register_response->Done();
// We wait for the 2 sources to be processed before registering triggers.
EXPECT_CALL(attribution_manager_observer,
OnSourceHandled(_, _, _, StorableSource::Result::kSuccess))
.WillOnce([]() {})
.WillOnce([&loop]() { loop.Quit(); });
loop.Run();
GURL register_trigger_url = https_server()->GetURL(
"a.test", "/attribution_reporting/register_trigger_headers.html");
ASSERT_TRUE(ExecJs(
root, JsReplace("createAttributionSrcImg($1);", register_trigger_url)));
expected_report.WaitForReport();
GURL register_trigger_url2 = https_server()->GetURL(
"b.test", "/attribution_reporting/register_trigger_headers.html");
ASSERT_TRUE(ExecJs(
root, JsReplace("createAttributionSrcImg($1);", register_trigger_url2)));
expected_report2.WaitForReport();
}
IN_PROC_BROWSER_TEST_P(AttributionsFencedFrameBrowserTest,
AutomaticBeacon_ReportSent) {
// Expected reports must be registered before the server starts.
ExpectedReportWaiter expected_report(
GURL("https://a.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://a.test",
/*source_event_id=*/"5", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
GURL main_url =
https_server()->GetURL("a.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
TestFrameNavigationObserver root_observer(root);
GURL fenced_frame_url(
https_server()->GetURL("a.test", "/page_with_impression_creator.html"));
GURL reporting_url = https_server()->GetURL(
"a.test", "/register_source_headers_trigger_same_origin.html");
GURL buyer_url = https_server()->GetURL("c.test", "/");
scoped_refptr<FencedFrameReporter> fenced_frame_reporter =
CreateFencedFrameReporter();
// Set valid reporting metadata for buyer.
fenced_frame_reporter->OnUrlMappingReady(
blink::FencedFrame::ReportingDestination::kBuyer,
url::Origin::Create(GURL(buyer_url)),
{{blink::kDeprecatedFencedFrameTopNavigationBeaconType, reporting_url}});
FrameTreeNode* fenced_frame_root_node =
AddFencedFrame(root, fenced_frame_url, std::move(fenced_frame_reporter));
ASSERT_TRUE(
ExecJs(fenced_frame_root_node,
JsReplace(R"(
window.fence.setReportEventDataForAutomaticBeacons({
eventType: $1,
eventData: 'This is the event data!',
destination: ['buyer']
});
)",
blink::kDeprecatedFencedFrameTopNavigationBeaconType)));
GURL navigation_url(
https_server()->GetURL("a.test", "/page_with_impression_creator.html"));
ASSERT_TRUE(
ExecJs(fenced_frame_root_node,
JsReplace("window.open($1, '_unfencedTop');", navigation_url)));
// The page must fully load before it can do anything involving attribution
// reporting.
root_observer.Wait();
ASSERT_TRUE(ExecJs(root, JsReplace("createAttributionSrcImg($1);",
https_server()->GetURL(
"a.test",
"/attribution_reporting/"
"register_trigger_headers.html"))));
expected_report.WaitForReport();
}
class AttributionsBrowserTestWithKeepAliveMigration
: public AttributionsBrowserTestBase {
public:
AttributionsBrowserTestWithKeepAliveMigration() {
scoped_feature_list_.InitWithFeatures(
{blink::features::kKeepAliveInBrowserMigration,
blink::features::kAttributionReportingInBrowserMigration},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Regression test for crbug.com/1374121.
IN_PROC_BROWSER_TEST_F(AttributionsBrowserTestWithKeepAliveMigration,
SourceRegisteredAfterNavigation) {
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), "/register_source");
ExpectedReportWaiter expected_report(
GURL("https://d.test/.well-known/attribution-reporting/"
"report-event-attribution"),
/*attribution_destination=*/"https://d.test",
/*source_event_id=*/"1", /*source_type=*/"navigation",
/*trigger_data=*/"7", https_server());
ASSERT_TRUE(https_server()->Start());
GURL impression_url = https_server()->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), impression_url));
GURL register_source_url =
https_server()->GetURL("d.test", "/register_source");
GURL conversion_url = https_server()->GetURL(
"d.test", "/attribution_reporting/page_with_conversion_redirect.html");
ASSERT_TRUE(
ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: $2,
target: '_top'});)",
conversion_url, register_source_url)));
TestNavigationObserver observer(web_contents());
ASSERT_TRUE(ExecJs(web_contents(), "simulateClick('link');"));
// Wait for navigation to complete before registering the source.
observer.Wait();
register_response->WaitForRequest();
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
http_response->AddCustomHeader(
"Attribution-Reporting-Register-Source",
R"({"source_event_id":"1","destination":"https://d.test"})");
register_response->Send(http_response->ToResponseString());
register_response->Done();
GURL register_trigger_url = https_server()->GetURL(
"d.test", "/attribution_reporting/register_trigger_headers.html");
ASSERT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_trigger_url)));
expected_report.WaitForReport();
}
void TestServiceWorker(const char* registration_js,
WebContents* web_contents,
ServiceWorkerContextWrapper* sw_wrapper,
net::EmbeddedTestServer* https_server) {
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server, "/attribution_reporting/register_source");
ASSERT_TRUE(https_server->Start());
GURL page_url = https_server->GetURL(
"a.test", "/attribution_reporting/page_with_impression_creator.html");
// Setup our service worker.
WorkerStateObserver sw_observer(sw_wrapper, ServiceWorkerVersion::ACTIVATED);
blink::mojom::ServiceWorkerRegistrationOptions options(
page_url, blink::mojom::ScriptType::kClassic,
blink::mojom::ServiceWorkerUpdateViaCache::kImports);
const blink::StorageKey key =
blink::StorageKey::CreateFirstParty(url::Origin::Create(options.scope));
sw_wrapper->RegisterServiceWorker(
https_server->GetURL("a.test",
"/attribution_reporting/service_worker.js"),
key, options,
base::BindOnce(&ExpectRegisterResultAndRun,
blink::ServiceWorkerStatusCode::kOk, base::DoNothing()));
sw_observer.Wait();
EXPECT_TRUE(NavigateToURL(web_contents, page_url));
EXPECT_TRUE(ExecJs(
web_contents,
JsReplace(registration_js,
https_server->GetURL(
"a.test", "/attribution_reporting/register_source"))));
register_response->WaitForRequest();
EXPECT_TRUE(base::Contains(register_response->http_request()->headers,
"Attribution-Reporting-Eligible"));
EXPECT_TRUE(base::Contains(register_response->http_request()->headers,
"Attribution-Reporting-Support"));
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionSrcRegistration_SupportHeaderSet_createAttributionEligibleImgSrc) {
TestServiceWorker("createAttributionEligibleImgSrc($1);", web_contents(),
wrapper(), https_server());
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionSrcRegistration_SupportHeaderSet_createAttributionSrcScript) {
TestServiceWorker("createAttributionSrcScript($1);", web_contents(),
wrapper(), https_server());
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionSrcRegistration_SupportHeaderSet_doAttributionEligibleFetch) {
TestServiceWorker("doAttributionEligibleFetch($1);", web_contents(),
wrapper(), https_server());
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionSrcRegistration_SupportHeaderSet_doAttributionEligibleXHR) {
TestServiceWorker("doAttributionEligibleXHR($1);", web_contents(), wrapper(),
https_server());
}
IN_PROC_BROWSER_TEST_P(
AttributionsBrowserTest,
ServiceWorkerPerformsAttributionSrcRegistration_SupportHeaderSet_createAttributionEligibleScriptSrc) {
TestServiceWorker("createAttributionEligibleScriptSrc($1);", web_contents(),
wrapper(), https_server());
}
} // namespace content