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