blob: 68c891effc9dde62ef61ebf151785d5cfe10d24f [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/destination_set.h"
#include "components/attribution_reporting/event_trigger_data.h"
#include "components/attribution_reporting/os_registration.h"
#include "components/attribution_reporting/registration_eligibility.mojom.h"
#include "components/attribution_reporting/source_registration.h"
#include "components/attribution_reporting/source_type.mojom.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/test_utils.h"
#include "components/attribution_reporting/trigger_registration.h"
#include "components/ukm/content/source_url_recorder.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/attribution_reporting/attribution_data_host_manager_impl.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/attribution_trigger.h"
#include "content/browser/attribution_reporting/os_registration.h"
#include "content/browser/attribution_reporting/test/mock_attribution_host.h"
#include "content/browser/attribution_reporting/test/mock_attribution_manager.h"
#include "content/browser/attribution_reporting/test/mock_content_browser_client.h"
#include "content/browser/attribution_reporting/test/source_observer.h"
#include "content/browser/renderer_host/render_frame_host_impl.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/navigation_handle.h"
#include "content/public/browser/navigation_throttle.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/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/base/net_errors.h"
#include "net/base/schemeful_site.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/navigation/impression.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
#include "url/gurl.h"
namespace content {
namespace {
using ::attribution_reporting::DestinationSet;
using ::attribution_reporting::EventTriggerData;
using ::attribution_reporting::SourceRegistration;
using ::attribution_reporting::SuitableOrigin;
using ::attribution_reporting::TriggerRegistration;
using ::attribution_reporting::mojom::RegistrationEligibility;
using ::attribution_reporting::mojom::SourceType;
using ::net::test_server::EmbeddedTestServer;
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::Property;
using ::testing::StrictMock;
using attribution_reporting::kAttributionReportingRegisterSourceHeader;
constexpr char kRegistrationMethod[] = "Conversions.RegistrationMethod2";
constexpr char
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName[] =
"Conversions.NumDataHostsRegisteredOnClientBounce.UserActivation.5s";
constexpr char
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName[] =
"Conversions.NumDataHostsRegisteredOnClientBounce.UserInteraction.5s";
} // namespace
class AttributionSrcBrowserTest : public ContentBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
explicit AttributionSrcBrowserTest(
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);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server_ = CreateAttributionTestHttpsServer();
ASSERT_TRUE(https_server_->Start());
auto mock_manager = std::make_unique<StrictMock<MockAttributionManager>>();
auto data_host_manager =
std::make_unique<AttributionDataHostManagerImpl>(mock_manager.get());
mock_manager->SetDataHostManager(std::move(data_host_manager));
EXPECT_CALL(*mock_manager, UpdateLastNavigationTime)
.Times(testing::AnyNumber());
static_cast<StoragePartitionImpl*>(
web_contents()->GetBrowserContext()->GetDefaultStoragePartition())
->OverrideAttributionManagerForTesting(std::move(mock_manager));
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Sets up the blink runtime feature for ConversionMeasurement.
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
void PreRunTestOnMainThread() override {
content::ContentBrowserTest::PreRunTestOnMainThread();
ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
}
WebContents* web_contents() { return shell()->web_contents(); }
protected:
net::EmbeddedTestServer* https_server() { return https_server_.get(); }
StrictMock<MockAttributionManager>& mock_attribution_manager() {
return *static_cast<StrictMock<MockAttributionManager>*>(
AttributionManager::FromWebContents(web_contents()));
}
// By default, the attribution_host isn't mocked. A real attribution host will
// be used and assertions should be on the `mock_attribution_manager`. If a
// test case wants to ensure that the attribution_host isn't reached, it can
// setup the mock attribution host with this method.
void SetupMockAttributionHost() {
MockAttributionHost::Override(web_contents());
attribution_host_mocked_ = true;
}
MockAttributionHost& mock_attribution_host() {
CHECK(attribution_host_mocked_);
AttributionHost* attribution_host =
AttributionHost::FromWebContents(web_contents());
return *static_cast<MockAttributionHost*>(attribution_host);
}
std::unique_ptr<EmbeddedTestServer> CreateAttributionTestHttpsServer() {
auto https_server =
std::make_unique<EmbeddedTestServer>(EmbeddedTestServer::TYPE_HTTPS);
https_server->SetSSLConfig(EmbeddedTestServer::CERT_TEST_NAMES);
RegisterDefaultHandlers(https_server.get());
https_server->ServeFilesFromSourceDirectory(
"content/test/data/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
return https_server;
}
private:
bool attribution_host_mocked_ = false;
AttributionManagerImpl::ScopedUseInMemoryStorageForTesting
attribution_manager_in_memory_setting_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All, AttributionSrcBrowserTest, ::testing::Bool());
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest, SourceRegistered) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
AllOf(SourceTypeIs(SourceType::kEvent),
ImpressionOriginIs(*SuitableOrigin::Create(page_url)),
ReportingOriginIs(*SuitableOrigin::Create(register_url))),
web_contents()->GetPrimaryMainFrame()->GetGlobalId()))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
SourceRegisteredViaEligibilityHeader) {
const char* kTestCases[] = {
"createAttributionEligibleImgSrc($1);", "createAttributionSrcScript($1);",
"doAttributionEligibleFetch($1);", "doAttributionEligibleXHR($1);",
"createAttributionEligibleScriptSrc($1);"};
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
for (const char* registration_js : kTestCases) {
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
AllOf(SourceTypeIs(SourceType::kEvent),
ImpressionOriginIs(*SuitableOrigin::Create(page_url)),
ReportingOriginIs(*SuitableOrigin::Create(register_url))),
web_contents()->GetPrimaryMainFrame()->GetGlobalId()))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
EXPECT_TRUE(
ExecJs(web_contents(), JsReplace(registration_js, register_url)));
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest, ForegroundRegistration) {
base::HistogramTester histograms;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource).WillOnce([&run_loop]() {
run_loop.Quit();
});
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("createAttributionEligibleImgSrc($1);", register_url)));
run_loop.Run();
// kForegroundBlink = 6
histograms.ExpectBucketCount(kRegistrationMethod, 6, /*expected_count=*/1);
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_MultipleFeatures_RequestsAll) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/source1");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/source2");
ASSERT_TRUE(https_server->Start());
SourceObserver source_observer(web_contents());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
ASSERT_TRUE(ExecJs(web_contents(), R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc=/source1 attributionsrc=/source2");
)"));
register_response1->WaitForRequest();
register_response2->WaitForRequest();
register_response1->Done();
register_response2->Done();
}
// See crbug.com/1322450
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_URLEncoded_SourceRegistered) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source?a=b&c=d");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
// This attributionsrc will only be handled properly if the value is
// URL-decoded before being passed to the attributionsrc loader.
EXPECT_TRUE(ExecJs(web_contents(), R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc=register_source%3Fa%3Db%26c%3Dd");)"));
register_response->WaitForRequest();
register_response->Done();
EXPECT_EQ(register_response->http_request()->relative_url,
"/register_source?a=b&c=d");
}
// See crbug.com/1338698
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_RetainsOriginalURLCase) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source?a=B&C=d");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
// This attributionsrc will only be handled properly if the URL's original
// case is retained before being passed to the attributionsrc loader.
EXPECT_TRUE(ExecJs(web_contents(), R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc=register_source%3Fa%3DB%26C%3Dd");)"));
register_response->WaitForRequest();
register_response->Done();
EXPECT_EQ(register_response->http_request()->relative_url,
"/register_source?a=B&C=d");
}
// See crbug.com/1338698
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_NonAsciiUrl) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/%F0%9F%98%80");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
// Ensure that the special handling of the original case for attributionsrc
// features works with non-ASCII characters.
EXPECT_TRUE(ExecJs(web_contents(), R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc=😀");)"));
register_response->WaitForRequest();
register_response->Done();
EXPECT_EQ(register_response->http_request()->relative_url, "/%F0%9F%98%80");
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
AttributionSrcWindowOpenNoUserGesture_NoBackgroundRequestNoImpression) {
SetupMockAttributionHost();
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/source");
ASSERT_TRUE(https_server->Start());
SourceObserver source_observer(web_contents());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
EXPECT_CALL(mock_attribution_host(), RegisterNavigationDataHost).Times(0);
EXPECT_CALL(mock_attribution_host(),
NotifyNavigationWithBackgroundRegistrationsWillStart)
.Times(0);
ASSERT_TRUE(ExecJs(web_contents(), R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc=/source");
)",
EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_TRUE(source_observer.WaitForNavigationWithNoImpression());
EXPECT_FALSE(register_response->has_received_request());
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcImgRedirect_MultipleSourcesRegistered) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
base::RunLoop run_loop;
const auto on_source = base::BarrierClosure(2, run_loop.QuitClosure());
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
SourceRegistrationIs(
AllOf(Field(&SourceRegistration::source_event_id, 1u),
Field(&SourceRegistration::destination_set,
Property(&DestinationSet::destinations,
ElementsAre(net::SchemefulSite::Deserialize(
"https://d.test")))))),
_))
.Times(1)
.WillOnce([&on_source]() { on_source.Run(); });
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
SourceRegistrationIs(
AllOf(Field(&SourceRegistration::source_event_id, 5u),
Field(&SourceRegistration::destination_set,
Property(&DestinationSet::destinations,
ElementsAre(net::SchemefulSite::Deserialize(
"https://d.test")))))),
_))
.Times(1)
.WillOnce([&on_source]() { on_source.Run(); });
GURL register_url = https_server()->GetURL(
"c.test", "/register_source_headers_and_redirect.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcImgRedirect_InvalidJsonIgnored) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
SourceRegistrationIs(
AllOf(Field(&SourceRegistration::source_event_id, 5u),
Field(&SourceRegistration::destination_set,
Property(&DestinationSet::destinations,
ElementsAre(net::SchemefulSite::Deserialize(
"https://d.test")))))),
_))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
GURL register_url = https_server()->GetURL(
"c.test", "/register_source_headers_and_redirect_invalid.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
// Only the second source is registered.
run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcImgSlowResponse_SourceRegistered) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
SourceRegistrationIs(
AllOf(Field(&SourceRegistration::source_event_id, 5u),
Field(&SourceRegistration::destination_set,
Property(&DestinationSet::destinations,
ElementsAre(net::SchemefulSite::Deserialize(
"https://d.test")))))),
_))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
GURL register_url = https_server->GetURL("d.test", "/register_source");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
// Navigate cross-site before sending a response.
GURL page2_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page2_url));
register_response->WaitForRequest();
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader("Access-Control-Allow-Origin", "*");
http_response->AddCustomHeader(
kAttributionReportingRegisterSourceHeader,
R"({"source_event_id":"5", "destination":"https://d.test"})");
register_response->Send(http_response->ToResponseString());
register_response->Done();
// Only the second source is registered.
run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
NoReferrerPolicy_UsesDefault) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server->GetURL("d.test", "/register_source");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
register_response->WaitForRequest();
const net::test_server::HttpRequest* request =
register_response->http_request();
EXPECT_EQ(request->headers.at("Referer"), page_url.GetWithEmptyPath());
}
// Regression test for crbug.com/1345955.
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
UntrustworthyUrl_DoesNotSetEligibleHeader) {
auto http_server = std::make_unique<net::EmbeddedTestServer>();
net::test_server::RegisterDefaultHandlers(http_server.get());
auto response1 = std::make_unique<net::test_server::ControllableHttpResponse>(
http_server.get(), "/register_source1");
auto response2 = std::make_unique<net::test_server::ControllableHttpResponse>(
http_server.get(), "/register_source2");
ASSERT_TRUE(http_server->Start());
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url1 = http_server->GetURL("d.test", "/register_source1");
ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAndClickAttributionSrcAnchor({url: $1, attributionsrc: '', target: '_blank'});)",
register_url1)));
response1->WaitForRequest();
ASSERT_FALSE(base::Contains(response1->http_request()->headers,
"Attribution-Reporting-Eligible"));
ASSERT_FALSE(base::Contains(response1->http_request()->headers,
"Attribution-Reporting-Support"));
GURL register_url2 = http_server->GetURL("d.test", "/register_source2");
ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
window.open($1, '_blank', 'attributionsrc=');)",
register_url2)));
response2->WaitForRequest();
ASSERT_FALSE(base::Contains(response2->http_request()->headers,
"Attribution-Reporting-Eligible"));
ASSERT_FALSE(base::Contains(response2->http_request()->headers,
"Attribution-Reporting-Support"));
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
ReferrerPolicy_RespectsDocument) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source");
ASSERT_TRUE(https_server->Start());
GURL page_url = https_server->GetURL(
"b.test", "/page_with_impression_creator_no_referrer.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server->GetURL("d.test", "/register_source");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
register_response->WaitForRequest();
const net::test_server::HttpRequest* request =
register_response->http_request();
EXPECT_FALSE(base::Contains(request->headers, "Referer"));
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
BasicTrigger_TriggerRegistered) {
const char* kTestCases[] = {"createAttributionSrcImg($1)",
"window.fetch($1, {mode:'no-cors'})"};
for (const char* js_template : kTestCases) {
SCOPED_TRACE(js_template);
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleTrigger)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
GURL register_url =
https_server()->GetURL("c.test", "/register_trigger_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(js_template, register_url)));
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
PermissionsPolicyDisabled_SourceNotRegistered) {
SetupMockAttributionHost();
GURL page_url = https_server()->GetURL(
"b.test", "/page_with_conversion_measurement_disabled.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
EXPECT_CALL(mock_attribution_host(), RegisterDataHost).Times(0);
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
// If a data host were registered, it would arrive in the browser process
// before the navigation finished.
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
AttributionSrcImg_InvalidTriggerJsonIgnored) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleTrigger(
Property(&AttributionTrigger::registration,
Field(&TriggerRegistration::event_triggers,
ElementsAre(Field(&EventTriggerData::data, 7u)))),
_))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
GURL register_url = https_server()->GetURL(
"c.test", "/register_trigger_headers_then_redirect_invalid.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
ImgNoneSupported_EligibleHeaderNotSet) {
MockAttributionReportingContentBrowserClientBase<
ContentBrowserTestContentBrowserClient>
browser_client;
EXPECT_CALL(
browser_client,
GetAttributionSupport(
ContentBrowserClient::AttributionReportingOsApiState::kDisabled,
/*client_os_disabled=*/false))
.WillRepeatedly(
testing::Return(network::mojom::AttributionSupport::kNone));
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server->GetURL("d.test", "/register_source");
ASSERT_TRUE(
ExecJs(web_contents(),
JsReplace("createAttributionEligibleImgSrc($1);", register_url)));
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);
}
// Regression test for https://crbug.com/1498717.
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
ResponseReceivedInDetachedFrame_NoCrash) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source");
ASSERT_TRUE(https_server->Start());
GURL page_url = https_server->GetURL("b.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL subframe_url =
https_server->GetURL("c.test", "/page_with_impression_creator.html");
NavigateIframeToURL(web_contents(), "test_iframe", subframe_url);
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
RenderFrameHost* subframe =
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
GURL register_url = https_server->GetURL("d.test", "/register_source");
ASSERT_TRUE(ExecJs(subframe,
JsReplace("createAttributionSrcImg($1);", register_url)));
register_response->WaitForRequest();
ASSERT_TRUE(ExecJs(web_contents(),
R"(
const iframe = document.getElementById('test_iframe');
iframe.remove();
)"));
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader(kAttributionReportingRegisterSourceHeader,
R"({"destination":"https://d.test"})");
register_response->Send(http_response->ToResponseString());
register_response->Done();
run_loop.Run();
}
// Regression test for https://crbug.com/1520612.
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
ForegroundNavigationRedirectCancelled_SourceRegistered) {
base::HistogramTester histograms;
TestNavigationThrottleInserter throttle_inserter(
web_contents(),
base::BindLambdaForTesting(
[&](NavigationThrottleRegistry& registry) -> void {
auto throttle = std::make_unique<TestNavigationThrottle>(registry);
throttle->SetResponse(TestNavigationThrottle::WILL_REDIRECT_REQUEST,
TestNavigationThrottle::SYNCHRONOUS,
NavigationThrottle::CANCEL_AND_IGNORE);
registry.AddThrottle(std::move(throttle));
}));
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source_redirect");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_source_url =
https_server->GetURL("d.test", "/register_source_redirect");
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleSource(
ReportingOriginIs(*SuitableOrigin::Create(register_source_url)), _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAttributionSrcAnchor({id: 'link',
url: $1,
attributionsrc: '',
target: $2});)",
register_source_url, "_top")));
ASSERT_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"({"destination":"https://a.test"})");
http_response->AddCustomHeader(
"Location",
https_server
->GetURL("c.test",
"/attribution_reporting/page_with_conversion_redirect.html")
.spec());
register_response->Send(http_response->ToResponseString());
register_response->Done();
run_loop.Run();
// kNavForegrounnd = 0
histograms.ExpectBucketCount(kRegistrationMethod, 0, /*expected_count=*/1);
}
IN_PROC_BROWSER_TEST_P(AttributionSrcBrowserTest,
MultipleBackgroundRequest_AllRegistered) {
const char* kTestCases[] = {"createAttributionSrcImg($1)",
"createAttributionSrcScript($1)"};
for (const char* js_template : kTestCases) {
base::HistogramTester histograms;
SCOPED_TRACE(js_template);
// Create a separate server as we cannot register a
// `ControllableHttpResponse` after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/source1");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/trigger1");
ASSERT_TRUE(https_server->Start());
base::RunLoop run_loop;
const auto receive_registration =
base::BarrierClosure(2, run_loop.QuitClosure());
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&]() { receive_registration.Run(); });
EXPECT_CALL(mock_attribution_manager(), HandleTrigger)
.Times(1)
.WillOnce([&]() { receive_registration.Run(); });
SourceObserver source_observer(web_contents());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
ASSERT_TRUE(ExecJs(
web_contents(),
JsReplace(js_template, "/source1 http://invalid.test /trigger1")));
register_response1->WaitForRequest();
register_response2->WaitForRequest();
{
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader(kAttributionReportingRegisterSourceHeader,
R"({"destination":"https://d.test"})");
register_response1->Send(http_response->ToResponseString());
register_response1->Done();
}
{
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->AddCustomHeader("Attribution-Reporting-Register-Trigger",
R"({})");
register_response2->Send(http_response->ToResponseString());
register_response2->Done();
}
run_loop.Run();
// kBackgroundBlink = 8, kForegroundOrBackgroundBrowser = 10
histograms.ExpectBucketCount(kRegistrationMethod, GetParam() ? 10 : 8,
/*expected_count=*/2);
}
}
class AttributionSrcPrerenderBrowserTest : public AttributionSrcBrowserTest {
public:
AttributionSrcPrerenderBrowserTest()
: prerender_helper_(
base::BindRepeating(&AttributionSrcBrowserTest::web_contents,
base::Unretained(this))) {}
~AttributionSrcPrerenderBrowserTest() override = default;
protected:
content::test::PrerenderTestHelper prerender_helper_;
};
INSTANTIATE_TEST_SUITE_P(All,
AttributionSrcPrerenderBrowserTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(AttributionSrcPrerenderBrowserTest,
SourceNotRegisteredOnPrerender) {
SetupMockAttributionHost();
EXPECT_CALL(mock_attribution_host(), RegisterDataHost).Times(0);
const GURL kInitialUrl =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), kInitialUrl));
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(page_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(page_url);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(ExecJs(
prerender_rfh,
JsReplace(
"createAttributionSrcImg($1);",
https_server()->GetURL("c.test", "/register_source_headers.html"))));
// If a data host were registered, it would arrive in the browser process
// before the navigation finished.
EXPECT_TRUE(NavigateToURL(web_contents(), kInitialUrl));
}
IN_PROC_BROWSER_TEST_P(AttributionSrcPrerenderBrowserTest,
SourceRegisteredOnActivatedPrerender) {
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(),
HandleSource(SourceRegistrationIs(
Field(&SourceRegistration::source_event_id, 5u)),
_))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
const GURL kInitialUrl =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), kInitialUrl));
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(page_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(page_url);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(ExecJs(
prerender_rfh,
JsReplace(
"createAttributionSrcImg($1);",
https_server()->GetURL("c.test", "/register_source_headers.html"))));
prerender_helper_.NavigatePrimaryPage(page_url);
ASSERT_EQ(page_url, web_contents()->GetLastCommittedURL());
run_loop.Run();
}
IN_PROC_BROWSER_TEST_P(AttributionSrcPrerenderBrowserTest,
SubresourceTriggerNotRegisteredOnPrerender) {
SetupMockAttributionHost();
EXPECT_CALL(mock_attribution_host(), RegisterDataHost).Times(0);
const GURL kInitialUrl =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), kInitialUrl));
GURL page_url =
https_server()->GetURL("b.test", "/page_with_conversion_redirect.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(page_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(page_url);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(ExecJs(
prerender_rfh,
JsReplace(
"createTrackingPixel($1);",
https_server()->GetURL("c.test", "/register_trigger_headers.html"))));
// If a data host were registered, it would arrive in the browser process
// before the navigation finished.
EXPECT_TRUE(NavigateToURL(web_contents(), kInitialUrl));
}
IN_PROC_BROWSER_TEST_P(AttributionSrcPrerenderBrowserTest,
SubresourceTriggerRegisteredOnActivatedPrerender) {
base::RunLoop loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleTrigger(
Property(&AttributionTrigger::registration,
Field(&TriggerRegistration::event_triggers,
ElementsAre(Field(&EventTriggerData::data, 7u)))),
_))
.Times(1)
.WillOnce([&loop]() { loop.Quit(); });
const GURL kInitialUrl =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), kInitialUrl));
GURL page_url =
https_server()->GetURL("b.test", "/page_with_conversion_redirect.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(page_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(page_url);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
EXPECT_TRUE(ExecJs(
prerender_rfh,
JsReplace(
"createTrackingPixel($1);",
https_server()->GetURL("c.test", "/register_trigger_headers.html"))));
// Delay prerender activation so that subresource response is received
// earlier than that.
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
run_loop.Run();
prerender_helper_.NavigatePrimaryPage(page_url);
ASSERT_EQ(page_url, web_contents()->GetLastCommittedURL());
ASSERT_TRUE(host_observer.was_activated());
loop.Run();
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcPrerenderBrowserTest,
SourceRegisteredOnActivatedPrerender_ClientBounceMetricRecorded) {
base::HistogramTester histograms;
ukm::TestAutoSetUkmRecorder ukm_recorder;
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
// Navigate to a starting same origin page with the to-be-prerendered page
// url.
const GURL kEmptyUrl = https_server()->GetURL("b.test", "/empty.html");
{
auto url_loader_interceptor =
content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
"content/test/data", kEmptyUrl.DeprecatedGetOriginAsURL());
EXPECT_TRUE(NavigateToURL(web_contents(), kEmptyUrl));
}
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
FrameTreeNodeId host_id = prerender_helper_.AddPrerender(page_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
prerender_helper_.WaitForPrerenderLoadCompletion(page_url);
content::RenderFrameHost* prerender_rfh =
prerender_helper_.GetPrerenderedMainFrameHost(host_id);
ExecuteScriptAsyncWithoutUserGesture(
prerender_rfh, JsReplace("createAttributionSrcImg($1);",
https_server()->GetURL(
"c.test", "/register_source_headers.html")));
prerender_helper_.NavigatePrimaryPage(page_url);
ASSERT_EQ(page_url, web_contents()->GetLastCommittedURL());
run_loop.Run();
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents(),
redirected_url));
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 1, 1);
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 1, 1);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> ukm_entries =
ukm_recorder.GetEntries("Conversions.ClientBounce",
{"UserActivation.5s", "UserInteraction.5s"});
ASSERT_EQ(ukm_entries.size(), 1u);
EXPECT_EQ(ukm_recorder.GetSourceForSourceId(ukm_entries[0].source_id)->url(),
page_url);
EXPECT_THAT(ukm_entries[0].metrics,
ElementsAre(testing::Pair("UserActivation.5s", 1),
testing::Pair("UserInteraction.5s", 1)));
}
class AttributionSrcFencedFrameBrowserTest : public AttributionSrcBrowserTest {
public:
AttributionSrcFencedFrameBrowserTest() {
fenced_frame_helper_ = std::make_unique<test::FencedFrameTestHelper>();
}
~AttributionSrcFencedFrameBrowserTest() override = default;
protected:
std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_helper_;
};
INSTANTIATE_TEST_SUITE_P(All,
AttributionSrcFencedFrameBrowserTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(AttributionSrcFencedFrameBrowserTest,
DefaultMode_SourceNotRegistered) {
SetupMockAttributionHost();
GURL main_url = https_server()->GetURL("b.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
GURL fenced_frame_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
RenderFrameHost* parent = web_contents()->GetPrimaryMainFrame();
RenderFrameHost* fenced_frame_host =
fenced_frame_helper_->CreateFencedFrame(parent, fenced_frame_url);
ASSERT_NE(fenced_frame_host, nullptr);
EXPECT_TRUE(fenced_frame_host->IsFencedFrameRoot());
EXPECT_CALL(mock_attribution_host(), RegisterDataHost).Times(0);
EXPECT_TRUE(ExecJs(
fenced_frame_host,
JsReplace(
"createAttributionSrcImg($1);",
https_server()->GetURL("c.test", "/register_source_headers.html"))));
// If a data host were registered, it would arrive in the browser process
// before the navigation finished.
EXPECT_TRUE(NavigateToURL(shell(), main_url));
}
IN_PROC_BROWSER_TEST_P(AttributionSrcFencedFrameBrowserTest,
OpaqueAdsMode_SourceRegistered) {
GURL main_url = https_server()->GetURL("b.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* root_rfh =
static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame());
GURL fenced_frame_url(https_server()->GetURL(
"b.test", "/attribution_reporting/page_with_impression_creator.html"));
FencedFrameURLMapping& url_mapping =
root_rfh->GetPage().fenced_frame_urls_map();
auto fenced_frame_urn =
test::AddAndVerifyFencedFrameURL(&url_mapping, fenced_frame_url);
RenderFrameHostWrapper fenced_frame_host(
fenced_frame_helper_->CreateFencedFrame(
root_rfh, GURL(url::kAboutBlankURL), net::OK,
blink::FencedFrame::DeprecatedFencedFrameMode::kOpaqueAds));
FrameTreeNode* fenced_frame_node =
static_cast<RenderFrameHostImpl*>(&(*fenced_frame_host))
->frame_tree_node();
bool rfh_should_change =
fenced_frame_host->ShouldChangeRenderFrameHostOnSameSiteNavigation();
TestFrameNavigationObserver observer(fenced_frame_host.get());
EXPECT_TRUE(ExecJs(root_rfh, JsReplace("document.querySelector('fencedframe')"
".config = new FencedFrameConfig($1);",
fenced_frame_urn.spec())));
observer.Wait();
if (rfh_should_change) {
EXPECT_TRUE(fenced_frame_host.WaitUntilRenderFrameDeleted());
} else {
ASSERT_NE(fenced_frame_node->current_frame_host(), nullptr);
}
RenderFrameHostWrapper fenced_frame_host2(
content::test::FencedFrameTestHelper::GetMostRecentlyAddedFencedFrame(
root_rfh));
EXPECT_TRUE(fenced_frame_host2->IsFencedFrameRoot());
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
EXPECT_TRUE(ExecJs(
fenced_frame_host2.get(),
JsReplace(
"createAttributionSrcImg($1);",
https_server()->GetURL("c.test", "/register_source_headers.html"))));
run_loop.Run();
}
class AttributionSrcCrossAppWebEnabledBrowserTest
: public AttributionSrcBrowserTest {};
INSTANTIATE_TEST_SUITE_P(All,
AttributionSrcCrossAppWebEnabledBrowserTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(AttributionSrcCrossAppWebEnabledBrowserTest,
Img_SetsSupportHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source1");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source2");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server->GetURL("d.test", "/register_source1");
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
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_source2");
register_response1->Send(http_response->ToResponseString());
register_response1->Done();
// Ensure that redirect requests also contain the headers.
register_response2->WaitForRequest();
ExpectValidAttributionReportingSupportHeader(
register_response2->http_request()->headers.at(
"Attribution-Reporting-Support"),
/*web_expected=*/true,
/*os_expected=*/false);
}
IN_PROC_BROWSER_TEST_P(AttributionSrcCrossAppWebEnabledBrowserTest,
Subresource_Register) {
const char* kTestCases[] = {
"createAttributionSrcImg($1)", "createAttributionSrcScript($1)",
"createAttributionEligibleImgSrc($1)", "createAttributionSrcScript($1)",
"doAttributionEligibleFetch($1)", "doAttributionEligibleXHR($1)"};
for (const char* js_template : kTestCases) {
SCOPED_TRACE(js_template);
// Create a separate server as we cannot register a
// `ControllableHttpResponse` after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source1");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source2");
ASSERT_TRUE(https_server->Start());
AttributionOsLevelManager::ScopedApiStateForTesting
scoped_api_state_setting(AttributionOsLevelManager::ApiState::kEnabled);
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server->GetURL("b.test", "/register_source1");
ASSERT_TRUE(ExecJs(web_contents(), JsReplace(js_template, register_url)));
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_source2");
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);
}
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcCrossAppWebEnabledBrowserTest,
OsLevelEnabledPostRendererInitialization_SetsSupportHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source1");
auto register_response2 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source2");
ASSERT_TRUE(https_server->Start());
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
AttributionOsLevelManager::ScopedApiStateForTesting scoped_api_state_setting(
AttributionOsLevelManager::ApiState::kEnabled);
GURL register_url = https_server->GetURL("d.test", "/register_source1");
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
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_source2");
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);
}
IN_PROC_BROWSER_TEST_P(AttributionSrcCrossAppWebEnabledBrowserTest,
OsRegistration_Register) {
struct OsRegistrationTestCase {
const char* name;
const char* header;
std::vector<attribution_reporting::OsRegistrationItem>
expected_os_registrations;
};
const OsRegistrationTestCase kTestCases[] = {
OsRegistrationTestCase{
.name = "source",
.header = "Attribution-Reporting-Register-OS-Source",
.expected_os_registrations =
{
attribution_reporting::OsRegistrationItem{
.url = GURL("https://r1.test/x")},
attribution_reporting::OsRegistrationItem{
.url = GURL("https://r2.test/y"),
.debug_reporting = true,
},
},
},
OsRegistrationTestCase{
.name = "trigger",
.header = "Attribution-Reporting-Register-OS-Trigger",
.expected_os_registrations =
{
attribution_reporting::OsRegistrationItem{
.url = GURL("https://r1.test/x")},
attribution_reporting::OsRegistrationItem{
.url = GURL("https://r2.test/y"),
.debug_reporting = true,
},
},
}};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.name);
// Create a separate server as we cannot register a
// `ControllableHttpResponse` after the server starts.
std::unique_ptr<EmbeddedTestServer> https_server =
CreateAttributionTestHttpsServer();
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleOsRegistration(Field(&OsRegistration::registration_items,
test_case.expected_os_registrations)))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register");
ASSERT_TRUE(https_server->Start());
AttributionOsLevelManager::ScopedApiStateForTesting
scoped_api_state_setting(AttributionOsLevelManager::ApiState::kEnabled);
GURL page_url =
https_server->GetURL("b.test", "/page_with_impression_creator.html");
ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server->GetURL("d.test", "/register");
ASSERT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_url)));
register_response->WaitForRequest();
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->AddCustomHeader(
test_case.header,
R"("https://r1.test/x", "https://r2.test/y"; debug-reporting)");
register_response->Send(http_response->ToResponseString());
register_response->Done();
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_P(AttributionSrcCrossAppWebEnabledBrowserTest,
WebAndOsHeadersAndPreferOs_OsRegistered) {
AttributionOsLevelManager::ScopedApiStateForTesting scoped_api_state_setting(
AttributionOsLevelManager::ApiState::kEnabled);
const char* kTestCases[] = {
"/register_source_headers_preferred_os.html",
"/register_trigger_headers_preferred_os.html",
};
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
for (const char* path : kTestCases) {
SCOPED_TRACE(path);
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url = https_server()->GetURL("c.test", path);
base::RunLoop run_loop;
EXPECT_CALL(
mock_attribution_manager(),
HandleOsRegistration(Field(
&OsRegistration::registration_items,
ElementsAre(Field(&attribution_reporting::OsRegistrationItem::url,
GURL("https://r.test"))))))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createAttributionSrcImg($1);",
register_url)));
run_loop.Run();
}
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
NoUserActivationAndNavigatedWithoutUserGesture_ClientBounceMetricRecorded) {
base::HistogramTester histograms;
ukm::TestAutoSetUkmRecorder ukm_recorder;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
ExecuteScriptAsyncWithoutUserGesture(
web_contents(), JsReplace("createAttributionSrcImg($1);", register_url));
run_loop.Run();
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents(),
redirected_url));
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 1, 1);
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 1, 1);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> ukm_entries =
ukm_recorder.GetEntries("Conversions.ClientBounce",
{"UserActivation.5s", "UserInteraction.5s"});
ASSERT_EQ(ukm_entries.size(), 1u);
EXPECT_EQ(ukm_recorder.GetSourceForSourceId(ukm_entries[0].source_id)->url(),
page_url);
EXPECT_THAT(ukm_entries[0].metrics,
ElementsAre(testing::Pair("UserActivation.5s", 1),
testing::Pair("UserInteraction.5s", 1)));
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
UserActivationAndNavigatedWithoutUserGesture_ClientBounceMetricNotRecorded) {
base::HistogramTester histograms;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
run_loop.Run();
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents(),
redirected_url));
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 0);
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 0);
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
NoUserActivationAndNavigatedWithUserGesture_ClientBounceMetricNotRecorded) {
base::HistogramTester histograms;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
ExecuteScriptAsyncWithoutUserGesture(
web_contents(), JsReplace("createAttributionSrcImg($1);", register_url));
run_loop.Run();
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), redirected_url));
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 0);
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 0);
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
NoUserActivationAndNavigatedByBrowser_ClientBounceMetricNotRecorded) {
base::HistogramTester histograms;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
ExecuteScriptAsyncWithoutUserGesture(
web_contents(), JsReplace("createAttributionSrcImg($1);", register_url));
run_loop.Run();
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURL(web_contents(), redirected_url));
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 0);
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 0);
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
NoUserActivationAndNavigatedWithoutUserGestureAfterTimeout_ClientBounceMetricNotRecorded) {
base::HistogramTester histograms;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
ExecuteScriptAsyncWithoutUserGesture(
web_contents(), JsReplace("createAttributionSrcImg($1);", register_url));
run_loop.Run();
base::RunLoop run_loop_timeout;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop_timeout.QuitClosure(), base::Seconds(2));
run_loop_timeout.Run();
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents(),
redirected_url));
histograms.ExpectTotalCount(
"Conversions.NumDataHostsRegisteredOnClientBounce.UserActivation.1s", 0);
histograms.ExpectTotalCount(
"Conversions.NumDataHostsRegisteredOnClientBounce.UserInteraction.1s", 0);
// Not timed out yet for 5s and 10s.
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 1, 1);
histograms.ExpectBucketCount(
"Conversions.NumDataHostsRegisteredOnClientBounce.UserActivation.10s", 1,
1);
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 1, 1);
histograms.ExpectBucketCount(
"Conversions.NumDataHostsRegisteredOnClientBounce.UserInteraction.10s", 1,
1);
}
IN_PROC_BROWSER_TEST_P(
AttributionSrcBrowserTest,
ScrollAndNavigatedWithoutUserGesture_ClientBounceMetricRecorded) {
base::HistogramTester histograms;
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
base::RunLoop run_loop;
EXPECT_CALL(mock_attribution_manager(), HandleSource)
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
ExecuteScriptAsyncWithoutUserGesture(
web_contents(), JsReplace("createAttributionSrcImg($1);", register_url));
run_loop.Run();
SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents());
SimulateGestureScrollSequence(web_contents(), gfx::Point(100, 100),
gfx::Vector2dF(0, 15));
RunUntilInputProcessed(
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
GURL redirected_url(https_server()->GetURL("a.test", "/title2.html"));
EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents(),
redirected_url));
// Scroll is considered as an user interaction, but not user activation.
histograms.ExpectBucketCount(
kNumDataHostsRegisteredOnClientBounceUserActivation5sMetricName, 1, 1);
histograms.ExpectTotalCount(
kNumDataHostsRegisteredOnClientBounceUserInteraction5sMetricName, 0);
}
} // namespace content