blob: 0c59942cd8b8dff057f020e8cf708f543eec915c [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 "base/containers/contains.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/test/scoped_feature_list.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/test_utils.h"
#include "content/browser/attribution_reporting/attribution_manager_impl.h"
#include "content/browser/attribution_reporting/attribution_test_utils.h"
#include "content/public/browser/navigation_handle.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_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/shell/browser/shell.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/base/net_errors.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/abseil-cpp/absl/numeric/int128.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/navigation/impression.h"
#include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom.h"
#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
#include "url/gurl.h"
namespace content {
namespace {
using ::attribution_reporting::SuitableOrigin;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
MATCHER_P(AggregationKeyPieceIs, matcher, "") {
return ExplainMatchResult(matcher, arg.key_piece, result_listener);
}
MATCHER_P(SourceKeysAre, matcher, "") {
return ExplainMatchResult(matcher, arg.source_keys, result_listener);
}
MATCHER_P(FiltersAre, matcher, "") {
return ExplainMatchResult(matcher, arg.filters, result_listener);
}
MATCHER_P(NotFiltersAre, matcher, "") {
return ExplainMatchResult(matcher, arg.not_filters, result_listener);
}
MATCHER_P(FilterValuesAre, matcher, "") {
return ExplainMatchResult(matcher, arg.filter_values, result_listener);
}
} // namespace
class AttributionSrcBrowserTest : public ContentBrowserTest {
public:
AttributionSrcBrowserTest() = default;
void SetUpOnMainThread() override {
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/attribution_reporting");
https_server_->ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server_->Start());
MockAttributionHost::Override(web_contents());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Sets up the blink runtime feature for ConversionMeasurement.
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
WebContents* web_contents() { return shell()->web_contents(); }
net::EmbeddedTestServer* https_server() { return https_server_.get(); }
MockAttributionHost& mock_attribution_host() {
AttributionHost* attribution_host =
AttributionHost::FromWebContents(web_contents());
return *static_cast<MockAttributionHost*>(attribution_host);
}
private:
AttributionManagerImpl::ScopedUseInMemoryStorageForTesting
attribution_manager_in_memory_setting_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
};
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest, SourceRegistered) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.front()->source_event_id, 5UL);
EXPECT_EQ(source_data.front()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
EXPECT_EQ(source_data.front()->priority, 0);
EXPECT_EQ(source_data.front()->expiry, absl::nullopt);
EXPECT_FALSE(source_data.front()->debug_key);
EXPECT_THAT(source_data.front()->filter_data->filter_values, IsEmpty());
EXPECT_THAT(source_data.front()->aggregation_keys->keys, IsEmpty());
EXPECT_FALSE(source_data.front()->debug_reporting);
}
IN_PROC_BROWSER_TEST_F(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));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop, disconnect_loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
data_host->receiver().set_disconnect_handler(
disconnect_loop.QuitClosure());
loop.Quit();
});
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
EXPECT_TRUE(
ExecJs(web_contents(), JsReplace(registration_js, register_url)));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
// Regression test for crbug.com/1336797. This will timeout flakily if the
// data host isn't disconnected promptly.
disconnect_loop.Run();
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.front()->source_event_id, 5UL);
EXPECT_EQ(source_data.front()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
EXPECT_EQ(source_data.front()->priority, 0);
EXPECT_EQ(source_data.front()->expiry, absl::nullopt);
EXPECT_FALSE(source_data.front()->debug_key);
EXPECT_THAT(source_data.front()->filter_data->filter_values, IsEmpty());
EXPECT_THAT(source_data.front()->aggregation_keys->keys, IsEmpty());
EXPECT_FALSE(source_data.front()->debug_reporting);
}
}
// TODO(johnidel): Remove when redirect chains consistently register sources or
// triggers. Currently, responses not handled via attributionsrc="url" use
// their own independent data host, so we do not enforce consistency on
// these redirect chains.
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
SourceTriggerRegistered_ImgSrc) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> source_data_host;
std::unique_ptr<MockDataHost> trigger_data_host;
base::RunLoop source_loop;
base::RunLoop trigger_loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillRepeatedly(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
if (!source_data_host) {
source_data_host = GetRegisteredDataHost(std::move(host));
source_loop.Quit();
} else {
trigger_data_host = GetRegisteredDataHost(std::move(host));
trigger_loop.Quit();
}
});
GURL register_url = https_server()->GetURL(
"c.test", "/register_source_trigger_redirect_chain.html");
EXPECT_TRUE(
ExecJs(web_contents(),
JsReplace("createAttributionEligibleImgSrc($1);", register_url)));
if (!source_data_host)
source_loop.Run();
source_data_host->WaitForSourceData(/*num_source_data=*/1);
if (!trigger_data_host)
trigger_loop.Run();
trigger_data_host->WaitForTriggerData(/*num_trigger_data=*/1);
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcAnchor_SourceRegistered) {
SourceObserver source_observer(web_contents());
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
blink::AttributionSrcToken expected_token;
EXPECT_CALL(mock_attribution_host(), RegisterNavigationDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host,
const blink::AttributionSrcToken& attribution_src_token,
blink::mojom::AttributionNavigationType nav_type) {
data_host = GetRegisteredDataHost(std::move(host));
expected_token = attribution_src_token;
EXPECT_EQ(nav_type,
blink::mojom::AttributionNavigationType::kAnchor);
});
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
createAndClickAttributionSrcAnchor({url: 'page_with_conversion_redirect.html',
attributionsrc: $1});)",
register_url)));
// Wait for the impression to be seen by the observer.
blink::Impression last_impression = source_observer.Wait();
// Verify we received the correct token for this source.
EXPECT_EQ(last_impression.attribution_src_token, expected_token);
EXPECT_EQ(last_impression.nav_type,
blink::mojom::AttributionNavigationType::kAnchor);
// Verify the attributionsrc data was registered with the browser process.
EXPECT_TRUE(data_host);
// TODO(johnidel): Verify that the data host receives the correct callback.
// Direct use of MockDataHost flakes rarely. See
// AttributionSrcNavigationSourceAndTrigger_ReportSent in
// AttributionsBrowserTest.
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_SourceRegistered) {
SourceObserver source_observer(web_contents());
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
blink::AttributionSrcToken expected_token;
EXPECT_CALL(mock_attribution_host(), RegisterNavigationDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host,
const blink::AttributionSrcToken& attribution_src_token,
blink::mojom::AttributionNavigationType nav_type) {
data_host = GetRegisteredDataHost(std::move(host));
expected_token = attribution_src_token;
EXPECT_EQ(nav_type,
blink::mojom::AttributionNavigationType::kWindowOpen);
});
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc="+$1);)",
register_url)));
blink::Impression last_impression = source_observer.Wait();
// Verify we received the correct token for this source.
EXPECT_EQ(last_impression.attribution_src_token, expected_token);
EXPECT_EQ(last_impression.nav_type,
blink::mojom::AttributionNavigationType::kWindowOpen);
// Verify the attributionsrc data was registered with the browser process.
EXPECT_TRUE(data_host);
// TODO(johnidel): Verify that the data host receives the correct callback.
// Direct use of MockDataHost flakes rarely. See
// AttributionSrcNavigationSourceAndTrigger_ReportSent in
// AttributionsBrowserTest.
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AnchorClickEmptyAttributionSrc_ImpressionReceived) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
EXPECT_CALL(mock_attribution_host(), RegisterNavigationDataHost).Times(0);
SourceObserver source_observer(web_contents());
EXPECT_TRUE(ExecJs(web_contents(), R"(
createAndClickAttributionSrcAnchor({url: 'page_with_conversion_redirect.html',
attributionsrc: ''});)"));
// Wait for the impression to be seen by the observer.
source_observer.Wait();
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
WindowOpenAttributionSrc_ImpressionReceived) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
EXPECT_CALL(mock_attribution_host(), RegisterNavigationDataHost).Times(0);
SourceObserver source_observer(web_contents());
EXPECT_TRUE(ExecJs(web_contents(), R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc=");)"));
// Wait for the impression to be seen by the observer.
source_observer.Wait();
}
// See crbug.com/1322450
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_URLEncoded_SourceRegistered) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
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/attribution_reporting");
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));
TestNavigationObserver observer(web_contents());
// 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();
// TODO(crbug.com/1322525): Remove this once we use a pure mock.
observer.Wait();
EXPECT_EQ(register_response->http_request()->relative_url,
"/register_source?a=b&c=d");
}
// See crbug.com/1338698
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_RetainsOriginalURLCase) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
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/attribution_reporting");
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));
TestNavigationObserver observer(web_contents());
// 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();
// TODO(crbug.com/1322525): Remove this once we use a pure mock.
observer.Wait();
EXPECT_EQ(register_response->http_request()->relative_url,
"/register_source?a=B&C=d");
}
// See crbug.com/1338698
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcWindowOpen_NonAsciiUrl) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
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/attribution_reporting");
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));
TestNavigationObserver observer(web_contents());
// 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();
// TODO(crbug.com/1322525): Remove this once we use a pure mock.
observer.Wait();
EXPECT_EQ(register_response->http_request()->relative_url, "/%F0%9F%98%80");
}
IN_PROC_BROWSER_TEST_F(
AttributionSrcBrowserTest,
AttributionSrcWindowOpenNoUserGesture_SourceNotRegistered) {
SourceObserver source_observer(web_contents());
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
blink::AttributionSrcToken expected_token;
EXPECT_CALL(mock_attribution_host(), RegisterNavigationDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host,
const blink::AttributionSrcToken& attribution_src_token,
blink::mojom::AttributionNavigationType nav_type) {
data_host = GetRegisteredDataHost(std::move(host));
expected_token = attribution_src_token;
});
GURL register_url =
https_server()->GetURL("c.test", "/register_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace(R"(
window.open("page_with_conversion_redirect.html", "_top",
"attributionsrc="+$1);)",
register_url),
EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_TRUE(source_observer.WaitForNavigationWithNoImpression());
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImg_SourceRegisteredWithOptionalParams) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server()->GetURL(
"c.test", "/register_source_headers_all_params.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.front()->source_event_id, 5UL);
EXPECT_EQ(source_data.front()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
EXPECT_EQ(source_data.front()->priority, 10);
EXPECT_EQ(source_data.front()->expiry, base::Seconds(1000));
EXPECT_EQ(source_data.front()->debug_key, 789u);
EXPECT_THAT(source_data.front()->filter_data->filter_values,
UnorderedElementsAre(Pair("a", IsEmpty()),
Pair("b", ElementsAre("1", "2"))));
EXPECT_TRUE(source_data.front()->debug_reporting);
}
IN_PROC_BROWSER_TEST_F(
AttributionSrcBrowserTest,
AttributionSrcImg_SourceRegisteredWithAggregatableSource) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server()->GetURL(
"c.test", "/register_aggregatable_source_headers.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.front()->source_event_id, 5UL);
EXPECT_EQ(source_data.front()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
EXPECT_EQ(source_data.front()->priority, 0);
EXPECT_EQ(source_data.front()->expiry, absl::nullopt);
EXPECT_FALSE(source_data.front()->debug_key);
EXPECT_THAT(source_data.front()->aggregation_keys->keys,
UnorderedElementsAre(
Pair("key1", absl::MakeUint128(/*high=*/0, /*low=*/5)),
Pair("key2", absl::MakeUint128(/*high=*/0, /*low=*/345))));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImgRedirect_MultipleSourcesRegistered) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server()->GetURL(
"c.test", "/register_source_headers_and_redirect.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/2);
const auto& source_data = data_host->source_data();
EXPECT_EQ(source_data.size(), 2u);
EXPECT_EQ(source_data.front()->source_event_id, 1UL);
EXPECT_EQ(source_data.front()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
EXPECT_EQ(source_data.back()->source_event_id, 5UL);
EXPECT_EQ(source_data.back()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImgRedirect_InvalidJsonIgnored) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
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)));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
// Only the second source is registered.
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.back()->source_event_id, 5UL);
EXPECT_EQ(source_data.back()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImgSlowResponse_SourceRegistered) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
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(
"Attribution-Reporting-Register-Source",
R"({"source_event_id":"5", "destination":"https://d.test"})");
register_response->Send(http_response->ToResponseString());
register_response->Done();
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
// Only the second source is registered.
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.back()->source_event_id, 5UL);
EXPECT_EQ(source_data.back()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
NoReferrerPolicy_UsesDefault) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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());
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
Img_SetsAttributionReportingEligibleHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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();
ASSERT_EQ(register_response1->http_request()->headers.at(
"Attribution-Reporting-Eligible"),
"event-source, trigger");
ASSERT_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_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();
ASSERT_EQ(register_response2->http_request()->headers.at(
"Attribution-Reporting-Eligible"),
"event-source, trigger");
ASSERT_FALSE(base::Contains(register_response2->http_request()->headers,
"Attribution-Reporting-Support"));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
ImgSrcWithAttributionSrc_SetsEligibleHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
auto register_response1 =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_source1");
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("createAttributionEligibleImgSrc($1);", register_url)));
register_response1->WaitForRequest();
ASSERT_EQ(register_response1->http_request()->headers.at(
"Attribution-Reporting-Eligible"),
"event-source, trigger");
ASSERT_FALSE(base::Contains(register_response1->http_request()->headers,
"Attribution-Reporting-Support"));
}
// Regression test for crbug.com/1345955.
IN_PROC_BROWSER_TEST_F(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_F(AttributionSrcBrowserTest,
ReferrerPolicy_RespectsDocument) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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_TRUE(request->headers.find("Referer") == request->headers.end());
}
class AttributionSrcBasicTriggerBrowserTest
: public AttributionSrcBrowserTest,
public ::testing::WithParamInterface<
std::pair<std::string, std::string>> {};
INSTANTIATE_TEST_SUITE_P(
All,
AttributionSrcBasicTriggerBrowserTest,
::testing::Values(
std::make_pair("attributionsrcimg", "createAttributionSrcImg($1)"),
std::make_pair("fetch", "window.fetch($1, {mode:'no-cors'})")),
[](const auto& info) { return info.param.first; }); // test name generator
IN_PROC_BROWSER_TEST_P(AttributionSrcBasicTriggerBrowserTest,
TriggerRegistered) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url =
https_server()->GetURL("c.test", "/register_trigger_headers.html");
const std::string& js_template = GetParam().second;
EXPECT_TRUE(ExecJs(web_contents(), JsReplace(js_template, register_url)));
if (!data_host)
loop.Run();
data_host->WaitForTriggerData(/*num_trigger_data=*/1);
const auto& trigger_data = data_host->trigger_data();
EXPECT_EQ(trigger_data.size(), 1u);
EXPECT_EQ(trigger_data.front()->reporting_origin,
*SuitableOrigin::Create(register_url));
EXPECT_THAT(trigger_data.front()->filters->filter_values, IsEmpty());
EXPECT_FALSE(trigger_data.front()->debug_key);
EXPECT_EQ(trigger_data.front()->event_triggers.size(), 1u);
EXPECT_EQ(trigger_data.front()->event_triggers.front()->data, 7u);
EXPECT_THAT(
trigger_data.front()->event_triggers.front()->filters->filter_values,
IsEmpty());
EXPECT_THAT(
trigger_data.front()->event_triggers.front()->not_filters->filter_values,
IsEmpty());
EXPECT_THAT(trigger_data.front()->aggregatable_trigger_data, IsEmpty());
EXPECT_THAT(trigger_data.front()->aggregatable_values, IsEmpty());
EXPECT_FALSE(trigger_data.front()->aggregatable_dedup_key);
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
PermissionsPolicyDisabled_SourceNotRegistered) {
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_F(AttributionSrcBrowserTest,
AttributionSrcImg_TriggerRegisteredAllParams) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server()->GetURL(
"c.test", "/register_trigger_headers_all_params.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForTriggerData(/*num_trigger_data=*/1);
const auto& trigger_data = data_host->trigger_data();
EXPECT_EQ(trigger_data.size(), 1u);
EXPECT_EQ(trigger_data.front()->reporting_origin,
*SuitableOrigin::Create(register_url));
EXPECT_THAT(
trigger_data.front()->filters->filter_values,
ElementsAre(Pair("w", IsEmpty()), Pair("x", ElementsAre("y", "z"))));
EXPECT_EQ(trigger_data.front()->debug_key, 789u);
EXPECT_EQ(trigger_data.front()->event_triggers.size(), 2u);
// Verify first trigger.
const auto& event_trigger_datas = trigger_data.front()->event_triggers;
EXPECT_EQ(event_trigger_datas.front()->data, 1u);
EXPECT_EQ(event_trigger_datas.front()->priority, 5);
EXPECT_EQ(event_trigger_datas.front()->dedup_key, 1024u);
EXPECT_THAT(event_trigger_datas.front()->filters->filter_values,
ElementsAre(Pair("a", ElementsAre("b"))));
EXPECT_THAT(event_trigger_datas.front()->not_filters->filter_values,
ElementsAre(Pair("c", IsEmpty())));
// Verify second trigger.
EXPECT_EQ(event_trigger_datas.back()->data, 2u);
EXPECT_EQ(event_trigger_datas.back()->priority, 10);
EXPECT_FALSE(event_trigger_datas.back()->dedup_key);
EXPECT_THAT(event_trigger_datas.back()->filters->filter_values, IsEmpty());
EXPECT_THAT(
event_trigger_datas.back()->not_filters->filter_values,
ElementsAre(Pair("d", ElementsAre("e", "f")), Pair("g", IsEmpty())));
EXPECT_THAT(trigger_data.front()->aggregatable_trigger_data,
ElementsAre(Pointee(AllOf(AggregationKeyPieceIs(absl::MakeUint128(
/*high=*/0, /*low=*/1)),
SourceKeysAre(ElementsAre("key"))))));
EXPECT_THAT(trigger_data.front()->aggregatable_values,
ElementsAre(Pair("key", 123)));
EXPECT_EQ(trigger_data.front()->aggregatable_dedup_key, 123u);
}
IN_PROC_BROWSER_TEST_F(
AttributionSrcBrowserTest,
AttributionSrcImg_TriggerRegisteredWithAggregatableTrigger) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server()->GetURL(
"c.test", "/register_aggregatable_trigger_data_headers.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForTriggerData(/*num_trigger_data=*/1);
const auto& trigger_data = data_host->trigger_data();
EXPECT_EQ(trigger_data.size(), 1u);
EXPECT_EQ(trigger_data.front()->reporting_origin,
*SuitableOrigin::Create(register_url));
EXPECT_THAT(trigger_data.front()->event_triggers, IsEmpty());
EXPECT_THAT(
trigger_data.front()->aggregatable_trigger_data,
ElementsAre(
Pointee(AllOf(
AggregationKeyPieceIs(absl::MakeUint128(/*high=*/0, /*low=*/1)),
SourceKeysAre(ElementsAre("key1")),
FiltersAre(Pointee(
FilterValuesAre(ElementsAre(Pair("a", ElementsAre("b")))))),
NotFiltersAre(Pointee(
FilterValuesAre(ElementsAre(Pair("c", IsEmpty()))))))),
Pointee(AllOf(
AggregationKeyPieceIs(absl::MakeUint128(/*high=*/0, /*low=*/0)),
SourceKeysAre(IsEmpty()),
FiltersAre(Pointee(FilterValuesAre(IsEmpty()))),
NotFiltersAre(Pointee(
FilterValuesAre(ElementsAre(Pair("d", ElementsAre("e", "f")),
Pair("g", IsEmpty())))))))));
EXPECT_THAT(trigger_data.front()->aggregatable_values,
ElementsAre(Pair("key1", 123), Pair("key2", 456)));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImg_InvalidTriggerJsonIgnored) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
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)));
if (!data_host)
loop.Run();
data_host->WaitForTriggerData(/*num_trigger_data=*/1);
const auto& trigger_data = data_host->trigger_data();
EXPECT_EQ(trigger_data.size(), 1u);
EXPECT_EQ(trigger_data.front()->reporting_origin,
*SuitableOrigin::Create(register_url));
EXPECT_EQ(trigger_data.front()->event_triggers.size(), 1u);
EXPECT_EQ(trigger_data.front()->event_triggers.front()->data, 7u);
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImg_InvalidAggregatableTriggerDropped) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
auto register_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server.get(), "/register_trigger");
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));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server->GetURL("d.test", "/register_trigger");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
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-Aggregatable-Trigger-Data", "");
http_response->AddCustomHeader(
"Attribution-Reporting-Register-Aggregatable-Values", "");
http_response->AddCustomHeader(
"Location", "/register_aggregatable_trigger_data_headers.html");
register_response->Send(http_response->ToResponseString());
register_response->Done();
if (!data_host)
loop.Run();
data_host->WaitForTriggerData(/*num_trigger_data=*/1);
const auto& trigger_data = data_host->trigger_data();
// Only the second trigger is registered.
EXPECT_EQ(trigger_data.size(), 1u);
EXPECT_THAT(trigger_data.front()->aggregatable_trigger_data, SizeIs(2));
EXPECT_THAT(trigger_data.front()->aggregatable_values, SizeIs(2));
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImgTriggerThenSource_SourceIgnored) {
GURL page_url =
https_server()->GetURL("b.test", "/page_with_impression_creator.html");
EXPECT_TRUE(NavigateToURL(web_contents(), page_url));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url =
https_server()->GetURL("c.test", "/register_trigger_source_trigger.html");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
if (!data_host)
loop.Run();
data_host->WaitForTriggerData(/*num_trigger_data=*/2);
const auto& trigger_data = data_host->trigger_data();
EXPECT_EQ(trigger_data.size(), 2u);
EXPECT_EQ(trigger_data.front()->reporting_origin,
*SuitableOrigin::Create(register_url));
// Both triggers should be processed.
EXPECT_EQ(trigger_data.front()->event_triggers.front()->data, 5u);
EXPECT_EQ(trigger_data.back()->event_triggers.front()->data, 7u);
// Middle redirect source should be ignored.
EXPECT_EQ(data_host->source_data().size(), 0u);
}
IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
AttributionSrcImg_InvalidAggregatableSourceDropped) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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));
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
GURL register_url = https_server->GetURL("d.test", "/register_source");
EXPECT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
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":"9", "destination":"https://d.test", "aggregation_keys":""})");
http_response->AddCustomHeader("Location",
"/register_aggregatable_source_headers.html");
register_response->Send(http_response->ToResponseString());
register_response->Done();
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
// Only the second source is registered.
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.back()->source_event_id, 5UL);
EXPECT_EQ(source_data.back()->destination,
*SuitableOrigin::Deserialize("https://d.test"));
EXPECT_THAT(source_data.back()->aggregation_keys->keys, SizeIs(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_;
};
IN_PROC_BROWSER_TEST_F(AttributionSrcPrerenderBrowserTest,
SourceNotRegisteredOnPrerender) {
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");
int 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_F(AttributionSrcPrerenderBrowserTest,
SourceRegisteredOnActivatedPrerender) {
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
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");
int 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());
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
const auto& source_data = data_host->source_data();
EXPECT_EQ(source_data.size(), 1u);
EXPECT_EQ(source_data.front()->source_event_id, 5UL);
}
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_;
};
IN_PROC_BROWSER_TEST_F(AttributionSrcFencedFrameBrowserTest,
DefaultMode_SourceNotRegistered) {
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_F(AttributionSrcFencedFrameBrowserTest,
OpaqueAdsMode_SourceRegistered) {
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, net::OK,
blink::mojom::FencedFrameMode::kOpaqueAds);
ASSERT_NE(fenced_frame_host, nullptr);
EXPECT_TRUE(fenced_frame_host->IsFencedFrameRoot());
std::unique_ptr<MockDataHost> data_host;
base::RunLoop loop;
EXPECT_CALL(mock_attribution_host(), RegisterDataHost)
.WillOnce(
[&](mojo::PendingReceiver<blink::mojom::AttributionDataHost> host) {
data_host = GetRegisteredDataHost(std::move(host));
loop.Quit();
});
EXPECT_TRUE(ExecJs(
fenced_frame_host,
JsReplace(
"createAttributionSrcImg($1);",
https_server()->GetURL("c.test", "/register_source_headers.html"))));
if (!data_host)
loop.Run();
data_host->WaitForSourceData(/*num_source_data=*/1);
EXPECT_EQ(data_host->source_data().size(), 1u);
}
class AttributionSrcCrossAppWebEnabledBrowserTest
: public AttributionSrcBrowserTest {
public:
AttributionSrcCrossAppWebEnabledBrowserTest() = default;
private:
base::test::ScopedFeatureList scoped_feature_list{
blink::features::kAttributionReportingCrossAppWeb};
};
IN_PROC_BROWSER_TEST_F(AttributionSrcCrossAppWebEnabledBrowserTest,
Img_SetsSupportHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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();
ASSERT_EQ(register_response1->http_request()->headers.at(
"Attribution-Reporting-Support"),
"web");
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();
ASSERT_EQ(register_response2->http_request()->headers.at(
"Attribution-Reporting-Support"),
"web");
}
IN_PROC_BROWSER_TEST_F(
AttributionSrcCrossAppWebEnabledBrowserTest,
OsLevelEnabledPriorToRendererInitialization_SetsSupportHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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());
AttributionManagerImpl::ScopedOsSupportForTesting scoped_os_support_setting(
blink::mojom::AttributionOsSupport::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_source1");
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
register_response1->WaitForRequest();
ASSERT_EQ(register_response1->http_request()->headers.at(
"Attribution-Reporting-Support"),
"web, os");
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();
ASSERT_EQ(register_response2->http_request()->headers.at(
"Attribution-Reporting-Support"),
"web, os");
}
IN_PROC_BROWSER_TEST_F(
AttributionSrcCrossAppWebEnabledBrowserTest,
OsLevelEnabledPostRendererInitialization_SetsSupportHeader) {
// Create a separate server as we cannot register a `ControllableHttpResponse`
// after the server starts.
auto 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/attribution_reporting");
https_server->ServeFilesFromSourceDirectory("content/test/data");
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));
AttributionManagerImpl::ScopedOsSupportForTesting scoped_os_support_setting(
blink::mojom::AttributionOsSupport::kEnabled);
GURL register_url = https_server->GetURL("d.test", "/register_source1");
ASSERT_TRUE(ExecJs(web_contents(),
JsReplace("createAttributionSrcImg($1);", register_url)));
register_response1->WaitForRequest();
ASSERT_EQ(register_response1->http_request()->headers.at(
"Attribution-Reporting-Support"),
"web, os");
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();
ASSERT_EQ(register_response2->http_request()->headers.at(
"Attribution-Reporting-Support"),
"web, os");
}
} // namespace content