| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/dips/dips_navigation_flow_detector.h" |
| |
| #include "base/base64.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/test_future.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/types/expected.h" |
| #include "components/ukm/content/source_url_recorder.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/browser/dips/dips_browsertest_utils.h" |
| #include "content/browser/dips/dips_test_utils.h" |
| #include "content/public/browser/attribution_data_model.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/common/content_features.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/hit_test_region_observer.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/network/test/trust_token_request_handler.h" |
| #include "services/network/test/trust_token_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/switches.h" |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "components/network_session_configurator/common/network_switches.h" |
| #include "content/public/browser/scoped_authenticator_environment_for_testing.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/content_mock_cert_verifier.h" |
| #include "device/fido/virtual_ctap2_device.h" |
| #include "device/fido/virtual_fido_device_factory.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| using AttributionData = std::set<AttributionDataModel::DataKey>; |
| |
| std::vector<url::Origin> GetOrigins(const AttributionData& data) { |
| std::vector<url::Origin> origins; |
| std::ranges::transform(data, std::back_inserter(origins), |
| &AttributionDataModel::DataKey::reporting_origin); |
| return origins; |
| } |
| |
| const char* StringifyBooleanMetric(ukm::TestAutoSetUkmRecorder* ukm_recorder, |
| const ukm::mojom::UkmEntry* entry, |
| std::string metric_name) { |
| const std::int64_t* metric = ukm_recorder->GetEntryMetric(entry, metric_name); |
| if (metric == nullptr) { |
| return "null"; |
| } |
| return *metric ? "true" : "false"; |
| } |
| |
| const std::string StringifyNumericMetric( |
| ukm::TestAutoSetUkmRecorder* ukm_recorder, |
| const ukm::mojom::UkmEntry* entry, |
| std::string metric_name) { |
| const std::int64_t* metric = ukm_recorder->GetEntryMetric(entry, metric_name); |
| if (metric == nullptr) { |
| return "null"; |
| } |
| return base::NumberToString(*metric); |
| } |
| |
| std::string StringifyNavigationFlowNodeEntry( |
| ukm::TestAutoSetUkmRecorder* ukm_recorder, |
| const ukm::mojom::UkmEntry* entry) { |
| return base::StringPrintf( |
| "source url: %s, metrics: {\n" |
| " WerePreviousAndNextSiteSame: %s\n" |
| " DidHaveUserActivation: %s\n" |
| " DidHaveSuccessfulWAA: %s\n" |
| " WasEntryUserInitiated: %s\n" |
| " WasExitUserInitiated: %s\n" |
| " WereEntryAndExitRendererInitiated: %s\n" |
| " VisitDurationMilliseconds: %s\n" |
| "}", |
| ukm_recorder->GetSourceForSourceId(entry->source_id) |
| ->url() |
| .spec() |
| .c_str(), |
| StringifyBooleanMetric(ukm_recorder, entry, |
| "WerePreviousAndNextSiteSame"), |
| StringifyBooleanMetric(ukm_recorder, entry, "DidHaveUserActivation"), |
| StringifyBooleanMetric(ukm_recorder, entry, "DidHaveSuccessfulWAA"), |
| StringifyBooleanMetric(ukm_recorder, entry, "WasEntryUserInitiated"), |
| StringifyBooleanMetric(ukm_recorder, entry, "WasExitUserInitiated"), |
| StringifyBooleanMetric(ukm_recorder, entry, |
| "WereEntryAndExitRendererInitiated"), |
| StringifyNumericMetric(ukm_recorder, entry, "VisitDurationMilliseconds") |
| .c_str()); |
| } |
| |
| std::string_view kNavigationFlowNodeUkmEventName = "DIPS.NavigationFlowNode"; |
| std::string_view kSuspectedTrackerFlowReferrerUkmEventName = |
| "DIPS.SuspectedTrackerFlowReferrerV2"; |
| std::string_view kSuspectedTrackerFlowEntrypointUkmEventName = |
| "DIPS.SuspectedTrackerFlowEntrypointV2"; |
| std::string_view kInFlowInteractionUkmEventName = |
| "DIPS.TrustIndicator.InFlowInteractionV2"; |
| std::string_view kInFlowSuccessorInteractionUkmEventName = |
| "DIPS.TrustIndicator.InFlowSuccessorInteraction"; |
| std::string_view kDirectNavigationUkmEventName = |
| "DIPS.TrustIndicator.DirectNavigationV2"; |
| std::string_view kSiteA = "a.test"; |
| std::string_view kSiteB = "b.test"; |
| std::string_view kSiteC = "c.test"; |
| std::string_view kSiteD = "d.test"; |
| } // namespace |
| |
| class BtmNavigationFlowDetectorTest : public ContentBrowserTest { |
| public: |
| BtmNavigationFlowDetectorTest() |
| : embedded_https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| |
| ~BtmNavigationFlowDetectorTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_https_test_server_.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("content/test/data"))); |
| embedded_https_test_server_.SetSSLConfig( |
| net::EmbeddedTestServer::CERT_TEST_NAMES); |
| ASSERT_TRUE(embedded_https_test_server_.Start()); |
| |
| ukm_recorder_.emplace(); |
| |
| SetTestClock(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Prevents flakiness by handling clicks even before content is drawn. |
| command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); |
| } |
| |
| void PreRunTestOnMainThread() override { |
| ContentBrowserTest::PreRunTestOnMainThread(); |
| ukm::InitializeSourceUrlRecorderForWebContents(GetActiveWebContents()); |
| } |
| |
| WebContents* GetActiveWebContents() { return shell()->web_contents(); } |
| |
| protected: |
| // TODO: crbug.com/1509946 - When embedded_https_test_server() is added to |
| // AndroidBrowserTest, switch to using |
| // PlatformBrowserTest::embedded_https_test_server() and delete this. |
| net::EmbeddedTestServer embedded_https_test_server_; |
| base::SimpleTestClock test_clock_; |
| |
| ukm::TestAutoSetUkmRecorder& ukm_recorder() { return ukm_recorder_.value(); } |
| |
| void ExpectNoNavigationFlowNodeUkmEvents() { |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| EXPECT_TRUE(ukm_entries.empty()) |
| << "UKM entry count was " << ukm_entries.size() |
| << ". First UKM entry below.\n" |
| << StringifyNavigationFlowNodeEntry(&ukm_recorder(), ukm_entries.at(0)); |
| } |
| |
| void ExpectNoUkmEventsOfType(std::string_view event_name) { |
| auto ukm_entries = ukm_recorder().GetEntriesByName(event_name); |
| EXPECT_TRUE(ukm_entries.empty()); |
| } |
| |
| GURL GetSetCookieUrlForSite(std::string_view site) { |
| // Path set in dips_test_utils.cc's NavigateToSetCookie(). |
| return embedded_https_test_server_.GetURL(site, "/set-cookie?name=value"); |
| } |
| |
| [[nodiscard]] testing::AssertionResult |
| NavigateToSetCookieAndAwaitAccessNotification(WebContents* web_contents, |
| std::string_view site) { |
| URLCookieAccessObserver observer( |
| web_contents, GetSetCookieUrlForSite(site), |
| network::mojom::CookieAccessDetails_Type::kChange); |
| bool success = NavigateToSetCookie( |
| web_contents, &embedded_https_test_server_, site, false, false); |
| if (success) { |
| observer.Wait(); |
| } |
| return testing::AssertionResult(success); |
| } |
| |
| [[nodiscard]] testing::AssertionResult WaitUntilTransientActivationLost( |
| RenderFrameHost* rfh, |
| base::TimeDelta timeout) { |
| const base::Time start_time; |
| while (rfh->HasTransientUserActivation()) { |
| if (base::Time() - start_time >= timeout) { |
| return testing::AssertionFailure() |
| << "Timed out waiting for the RFH to lose transient activation " |
| "after " |
| << timeout.InSeconds() << " seconds"; |
| } |
| Sleep(base::Milliseconds(50)); |
| } |
| return testing::AssertionSuccess(); |
| } |
| |
| BtmNavigationFlowDetector* GetDetector() { |
| return BtmNavigationFlowDetector::FromWebContents(GetActiveWebContents()); |
| } |
| |
| void SimulateBookmarkNavigation(WebContents* web_contents, const GURL& url) { |
| NavigationController::LoadURLParams navigation_params(url); |
| navigation_params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK; |
| NavigateToURLBlockUntilNavigationsComplete(web_contents, navigation_params, |
| 1); |
| } |
| |
| private: |
| std::optional<ukm::TestAutoSetUkmRecorder> ukm_recorder_; |
| void SetTestClock() { GetDetector()->SetClockForTesting(&test_clock_); } |
| |
| static void Sleep(base::TimeDelta delay) { |
| base::RunLoop run_loop; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), delay); |
| run_loop.Run(); |
| } |
| }; |
| |
| class BtmNavigationFlowDetectorPrerenderTest |
| : public BtmNavigationFlowDetectorTest { |
| public: |
| BtmNavigationFlowDetectorPrerenderTest() { |
| prerender_test_helper_ = |
| std::make_unique<test::PrerenderTestHelper>(base::BindRepeating( |
| &BtmNavigationFlowDetectorTest::GetActiveWebContents, |
| base::Unretained(this))); |
| } |
| ~BtmNavigationFlowDetectorPrerenderTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| prerender_test_helper_->RegisterServerRequestMonitor( |
| embedded_https_test_server_); |
| BtmNavigationFlowDetectorTest::SetUpOnMainThread(); |
| } |
| |
| protected: |
| test::PrerenderTestHelper* prerender_test_helper() { |
| return prerender_test_helper_.get(); |
| } |
| |
| private: |
| std::unique_ptr<test::PrerenderTestHelper> prerender_test_helper_; |
| }; |
| |
| class BtmNavigationFlowDetectorPATApiTest |
| : public BtmNavigationFlowDetectorTest { |
| public: |
| BtmNavigationFlowDetectorPATApiTest() { |
| // Enable Privacy Sandbox APIs on all sites. |
| scoped_feature_list_.InitWithFeatures( |
| {features::kPrivacySandboxAdsAPIsOverride}, {}); |
| } |
| |
| void SetUpOnMainThread() override { |
| RegisterTrustTokenTestHandler(&trust_token_request_handler_); |
| browser_client_.emplace(); |
| browser_client().SetBlockThirdPartyCookiesByDefault(true); |
| BtmNavigationFlowDetectorTest::SetUpOnMainThread(); |
| } |
| |
| base::expected<std::vector<url::Origin>, std::string> |
| WaitForInterestGroupData() { |
| WebContents* web_contents = GetActiveWebContents(); |
| InterestGroupManager* interest_group_manager = |
| web_contents->GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetInterestGroupManager(); |
| if (!interest_group_manager) { |
| return base::unexpected("null interest group manager"); |
| } |
| // Poll until data appears, failing if action_timeout() passes |
| base::Time deadline = base::Time::Now() + TestTimeouts::action_timeout(); |
| while (base::Time::Now() < deadline) { |
| base::test::TestFuture<std::vector<url::Origin>> future; |
| interest_group_manager->GetAllInterestGroupJoiningOrigins( |
| future.GetCallback()); |
| std::vector<url::Origin> data = future.Get(); |
| if (!data.empty()) { |
| return data; |
| } |
| Sleep(TestTimeouts::tiny_timeout()); |
| } |
| return base::unexpected("timed out waiting for interest group data"); |
| } |
| |
| base::expected<AttributionData, std::string> WaitForAttributionData() { |
| WebContents* web_contents = GetActiveWebContents(); |
| AttributionDataModel* model = web_contents->GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetAttributionDataModel(); |
| if (!model) { |
| return base::unexpected("null attribution data model"); |
| } |
| // Poll until data appears, failing if action_timeout() passes |
| base::Time deadline = base::Time::Now() + TestTimeouts::action_timeout(); |
| while (base::Time::Now() < deadline) { |
| base::test::TestFuture<AttributionData> future; |
| model->GetAllDataKeys(future.GetCallback()); |
| AttributionData data = future.Get(); |
| if (!data.empty()) { |
| return data; |
| } |
| Sleep(TestTimeouts::tiny_timeout()); |
| } |
| return base::unexpected("timed out waiting for attribution data"); |
| } |
| |
| void ProvideRequestHandlerKeyCommitmentsToNetworkService( |
| std::vector<std::string_view> hosts) { |
| base::flat_map<url::Origin, std::string_view> origins_and_commitments; |
| std::string key_commitments = |
| trust_token_request_handler_.GetKeyCommitmentRecord(); |
| |
| for (std::string_view host : hosts) { |
| origins_and_commitments.insert_or_assign( |
| embedded_https_test_server_.GetOrigin(std::string(host)), |
| key_commitments); |
| } |
| |
| if (origins_and_commitments.empty()) { |
| origins_and_commitments = { |
| {embedded_https_test_server_.GetOrigin(), key_commitments}}; |
| } |
| |
| base::RunLoop run_loop; |
| GetNetworkService()->SetTrustTokenKeyCommitments( |
| network::WrapKeyCommitmentsForIssuers( |
| std::move(origins_and_commitments)), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| TpcBlockingBrowserClient& browser_client() { return browser_client_->impl(); } |
| |
| private: |
| void RegisterTrustTokenTestHandler( |
| network::test::TrustTokenRequestHandler* handler) { |
| embedded_https_test_server_.RegisterRequestHandler( |
| base::BindLambdaForTesting( |
| [handler, this](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url != "/issue") { |
| return nullptr; |
| } |
| if (!base::Contains(request.headers, "Sec-Private-State-Token") || |
| !base::Contains(request.headers, |
| "Sec-Private-State-Token-Crypto-Version")) { |
| return MakeTrustTokenFailureResponse(); |
| } |
| |
| std::optional<std::string> operation_result = |
| handler->Issue(request.headers.at("Sec-Private-State-Token")); |
| |
| if (!operation_result) { |
| return MakeTrustTokenFailureResponse(); |
| } |
| |
| return MakeTrustTokenResponse(*operation_result); |
| })); |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> |
| MakeTrustTokenFailureResponse() { |
| // No need to report a failure HTTP code here: returning a vanilla OK should |
| // fail the Trust Tokens operation client-side. |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| return response; |
| } |
| |
| // Constructs and returns an HTTP response bearing the given base64-encoded |
| // Trust Tokens issuance or redemption protocol response message. |
| std::unique_ptr<net::test_server::HttpResponse> MakeTrustTokenResponse( |
| std::string_view contents) { |
| std::string temp; |
| CHECK(base::Base64Decode(contents, &temp)); |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("Sec-Private-State-Token", std::string(contents)); |
| response->AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| return response; |
| } |
| |
| static void Sleep(base::TimeDelta delay) { |
| base::RunLoop run_loop; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), delay); |
| run_loop.Run(); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| network::test::TrustTokenRequestHandler trust_token_request_handler_; |
| std::optional<ContentBrowserTestTpcBlockingBrowserClient> browser_client_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| DirectNavigationEmittedForTypedUrl) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL url = embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kDirectNavigationUkmEventName, |
| ukm_loop.QuitClosure()); |
| ASSERT_TRUE(NavigateToURL(web_contents, url)); |
| ukm_loop.Run(); |
| |
| // Expect DirectNavigation UKM entry to be accurate. |
| auto direct_navigation_entries = |
| ukm_recorder().GetEntriesByName(kDirectNavigationUkmEventName); |
| ASSERT_EQ(direct_navigation_entries.size(), 1u); |
| auto direct_navigation_entry = direct_navigation_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(direct_navigation_entry, url); |
| ukm_recorder().ExpectEntryMetric( |
| direct_navigation_entry, "NavigationSource", |
| static_cast<int64_t>(dips::DirectNavigationSource::kOmnibar)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| DirectNavigationEmittedForBookmark) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL url = embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kDirectNavigationUkmEventName, |
| ukm_loop.QuitClosure()); |
| SimulateBookmarkNavigation(web_contents, url); |
| ukm_loop.Run(); |
| |
| // Expect DirectNavigation UKM entry to be accurate. |
| auto direct_navigation_entries = |
| ukm_recorder().GetEntriesByName(kDirectNavigationUkmEventName); |
| ASSERT_EQ(direct_navigation_entries.size(), 1u); |
| auto direct_navigation_entry = direct_navigation_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(direct_navigation_entry, url); |
| ukm_recorder().ExpectEntryMetric( |
| direct_navigation_entry, "NavigationSource", |
| static_cast<int64_t>(dips::DirectNavigationSource::kBookmark)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| DirectNavigationEmittedForServerRedirect) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL redirector_url = embedded_https_test_server_.GetURL( |
| kSiteA, "/cross-site-with-cookie/b.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kDirectNavigationUkmEventName, |
| ukm_loop.QuitClosure()); |
| ASSERT_TRUE(NavigateToURL(web_contents, redirector_url, final_url)); |
| ukm_loop.Run(); |
| |
| // Expect DirectNavigation UKM entry to be accurate. |
| auto direct_navigation_entries = |
| ukm_recorder().GetEntriesByName(kDirectNavigationUkmEventName); |
| ASSERT_EQ(direct_navigation_entries.size(), 1u); |
| auto direct_navigation_entry = direct_navigation_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(direct_navigation_entry, |
| redirector_url); |
| ukm_recorder().ExpectEntryMetric( |
| direct_navigation_entry, "NavigationSource", |
| static_cast<int64_t>(dips::DirectNavigationSource::kOmnibar)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| DirectNavigationNotEmittedWhenNoPageCommits) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL url = embedded_https_test_server_.GetURL(kSiteA, "/page204.html"); |
| base::RunLoop ukm_loop; |
| ASSERT_TRUE(NavigateToURL(web_contents, url, GURL(""))); |
| |
| ExpectNoUkmEventsOfType(kDirectNavigationUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| DirectNavigationNotEmittedForLinkClick) { |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL initial_url = embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kDirectNavigationUkmEventName, |
| ukm_loop.QuitClosure()); |
| ASSERT_TRUE(NavigateToURL(web_contents, initial_url)); |
| ukm_loop.Run(); |
| GURL link_target_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, link_target_url)); |
| |
| // Expect DirectNavigation UKM entry to be accurate. |
| auto direct_navigation_entries = |
| ukm_recorder().GetEntriesByName(kDirectNavigationUkmEventName); |
| ASSERT_EQ(direct_navigation_entries.size(), 1u); |
| auto direct_navigation_entry = direct_navigation_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(direct_navigation_entry, initial_url); |
| ukm_recorder().ExpectEntryMetric( |
| direct_navigation_entry, "NavigationSource", |
| static_cast<int64_t>(dips::DirectNavigationSource::kOmnibar)); |
| // Implied assert: no DirectNavigation UKM entry for link_target_url. |
| } |
| |
| // TODO - crbug.com/388718419: Flaky on release builds |
| #if defined(NDEBUG) |
| #define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExit \ |
| DISABLED_SuspectedTrackerFlowEmittedForServerRedirectExit |
| #else |
| #define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExit \ |
| SuspectedTrackerFlowEmittedForServerRedirectExit |
| #endif |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExit) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Visit B, which writes a cookie in the server response, and also server |
| // redirects to C. Wait for cookie access to register and for UKM to emit. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/cross-site-with-cookie/c.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| URLCookieAccessObserver cookie_observer(web_contents, entrypoint_url, |
| CookieOperation::kChange); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback( |
| kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure()); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, entrypoint_url, final_url)); |
| cookie_observer.Wait(); |
| ukm_loop.Run(); |
| |
| // Expect referrer event to be accurate. |
| auto referrer_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowReferrerUkmEventName); |
| ASSERT_EQ(referrer_entries.size(), 1u); |
| auto referrer_entry = referrer_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(referrer_entry, referrer_url); |
| const int64_t* flow_id = |
| ukm_recorder().GetEntryMetric(referrer_entry, "FlowId"); |
| // Expect entrypoint event to be accurate. |
| auto entrypoint_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowEntrypointUkmEventName); |
| ASSERT_EQ(entrypoint_entries.size(), 1u); |
| auto entrypoint_entry = entrypoint_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(entrypoint_entry, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric( |
| entrypoint_entry, "ExitRedirectType", |
| static_cast<int64_t>(BtmRedirectType::kServer)); |
| ukm_recorder().ExpectEntryMetric(entrypoint_entry, "FlowId", *flow_id); |
| // It's not possible to interact with a page that server-redirected. |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| // TODO - crbug.com/388718419: Flaky on release builds |
| #if defined(NDEBUG) |
| #define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents \ |
| DISABLED_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents |
| #else |
| #define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents \ |
| SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Visit B, which writes a cookie in the server response, and also server |
| // redirects to C. Wait for cookie access to register and for UKM to emit. |
| GURL first_entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/cross-site-with-cookie/c.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| URLCookieAccessObserver cookie_observer_1(web_contents, first_entrypoint_url, |
| CookieOperation::kChange); |
| base::RunLoop ukm_loop_1; |
| ukm_recorder().SetOnAddEntryCallback( |
| kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop_1.QuitClosure()); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, first_entrypoint_url, final_url)); |
| cookie_observer_1.Wait(); |
| ukm_loop_1.Run(); |
| // Repeat a similar navigation pattern to generate a second set of UKM events. |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| GURL second_entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteD, "/cross-site-with-cookie/c.test/title1.html"); |
| URLCookieAccessObserver cookie_observer_2(web_contents, second_entrypoint_url, |
| CookieOperation::kChange); |
| base::RunLoop ukm_loop_2; |
| ukm_recorder().SetOnAddEntryCallback( |
| kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop_2.QuitClosure()); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, second_entrypoint_url, final_url)); |
| cookie_observer_2.Wait(); |
| ukm_loop_2.Run(); |
| |
| // Expect referrer events to be accurate. |
| auto referrer_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowReferrerUkmEventName); |
| ASSERT_EQ(referrer_entries.size(), 2u); |
| auto first_referrer_entry = referrer_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(first_referrer_entry, referrer_url); |
| const int64_t* first_flow_id = |
| ukm_recorder().GetEntryMetric(first_referrer_entry, "FlowId"); |
| auto second_referrer_entry = referrer_entries.at(1); |
| ukm_recorder().ExpectEntrySourceHasUrl(second_referrer_entry, referrer_url); |
| const int64_t* second_flow_id = |
| ukm_recorder().GetEntryMetric(second_referrer_entry, "FlowId"); |
| EXPECT_NE(first_flow_id, second_flow_id); |
| // Expect entrypoint events to be accurate. |
| auto entrypoint_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowEntrypointUkmEventName); |
| ASSERT_EQ(entrypoint_entries.size(), 2u); |
| auto first_entrypoint_entry = entrypoint_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(first_entrypoint_entry, |
| first_entrypoint_url); |
| ukm_recorder().ExpectEntryMetric( |
| first_entrypoint_entry, "ExitRedirectType", |
| static_cast<int64_t>(BtmRedirectType::kServer)); |
| ukm_recorder().ExpectEntryMetric(first_entrypoint_entry, "FlowId", |
| *first_flow_id); |
| auto second_entrypoint_entry = entrypoint_entries.at(1); |
| ukm_recorder().ExpectEntrySourceHasUrl(second_entrypoint_entry, |
| second_entrypoint_url); |
| ukm_recorder().ExpectEntryMetric( |
| second_entrypoint_entry, "ExitRedirectType", |
| static_cast<int64_t>(BtmRedirectType::kServer)); |
| ukm_recorder().ExpectEntryMetric(second_entrypoint_entry, "FlowId", |
| *second_flow_id); |
| // It's not possible to interact with a page that server-redirected. |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| namespace { |
| enum ClientRedirectType { |
| kMetaTag = 0, |
| kJsWindowLocationReplace = 1, |
| kRedirectLikeNavigation = 2, |
| }; |
| |
| const std::vector<std::string_view> kClientRedirectTypeNames = { |
| "MetaTag", "JsWindowLocationReplace", "RedirectLikeNavigation"}; |
| } // namespace |
| |
| class BtmNavigationFlowDetectorClientRedirectTest |
| : public BtmNavigationFlowDetectorTest, |
| public testing::WithParamInterface<ClientRedirectType> { |
| protected: |
| ClientRedirectType client_redirect_type() { return GetParam(); } |
| void PerformClientRedirect(WebContents* web_contents, const GURL& final_url) { |
| switch (client_redirect_type()) { |
| case kMetaTag: |
| ASSERT_TRUE(ClientSideRedirectViaMetaTag( |
| web_contents, web_contents->GetPrimaryMainFrame(), final_url)); |
| break; |
| case kJsWindowLocationReplace: |
| ASSERT_TRUE(ClientSideRedirectViaJS( |
| web_contents, web_contents->GetPrimaryMainFrame(), final_url)); |
| break; |
| case kRedirectLikeNavigation: |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| final_url)); |
| break; |
| } |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P( |
| BtmNavigationFlowDetectorClientRedirectTest, |
| SuspectedTrackerFlowEmittedForClientRedirectWithInteraction) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Make A client-redirect to B, where B commits and writes cookies with JS. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';", |
| EXECUTE_SCRIPT_NO_USER_GESTURE); |
| observer.Wait(); |
| // Interact with B. |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Make B client-redirect to C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback( |
| kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure()); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| PerformClientRedirect(web_contents, final_url); |
| ukm_loop.Run(); |
| |
| // Expect referrer event to be accurate. |
| auto referrer_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowReferrerUkmEventName); |
| ASSERT_EQ(referrer_entries.size(), 1u); |
| auto referrer_entry = referrer_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(referrer_entry, referrer_url); |
| const int64_t* flow_id = |
| ukm_recorder().GetEntryMetric(referrer_entry, "FlowId"); |
| // Expect entrypoint event to be accurate. |
| auto entrypoint_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowEntrypointUkmEventName); |
| ASSERT_EQ(entrypoint_entries.size(), 1u); |
| auto entrypoint_entry = entrypoint_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(entrypoint_entry, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric( |
| entrypoint_entry, "ExitRedirectType", |
| static_cast<int64_t>(BtmRedirectType::kClient)); |
| ukm_recorder().ExpectEntryMetric(entrypoint_entry, "FlowId", *flow_id); |
| // Expect InFlowInteraction to have been emitted appropriately. |
| auto in_flow_interaction_entries = |
| ukm_recorder().GetEntriesByName(kInFlowInteractionUkmEventName); |
| ASSERT_EQ(in_flow_interaction_entries.size(), 1u); |
| auto in_flow_interaction_entry = in_flow_interaction_entries.front(); |
| ukm_recorder().ExpectEntrySourceHasUrl(in_flow_interaction_entry, |
| entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(in_flow_interaction_entry, "FlowId", |
| *flow_id); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| BtmNavigationFlowDetectorClientRedirectTest, |
| SuspectedTrackerFlowEmittedForClientRedirectWithoutInteraction) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Make A client-redirect to B, where B commits and writes cookies with JS. |
| // Don't generate user activation on B. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';", |
| EXECUTE_SCRIPT_NO_USER_GESTURE); |
| observer.Wait(); |
| // Make B client-redirect to C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback( |
| kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure()); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| PerformClientRedirect(web_contents, final_url); |
| ukm_loop.Run(); |
| |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| // Expect referrer event to be accurate. |
| auto referrer_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowReferrerUkmEventName); |
| ASSERT_EQ(referrer_entries.size(), 1u); |
| auto referrer_entry = referrer_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(referrer_entry, referrer_url); |
| const int64_t* flow_id = |
| ukm_recorder().GetEntryMetric(referrer_entry, "FlowId"); |
| // Expect entrypoint event to be accurate. |
| auto entrypoint_entries = ukm_recorder().GetEntriesByName( |
| kSuspectedTrackerFlowEntrypointUkmEventName); |
| ASSERT_EQ(entrypoint_entries.size(), 1u); |
| auto entrypoint_entry = entrypoint_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(entrypoint_entry, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric( |
| entrypoint_entry, "ExitRedirectType", |
| static_cast<int64_t>(BtmRedirectType::kClient)); |
| ukm_recorder().ExpectEntryMetric(entrypoint_entry, "FlowId", *flow_id); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedWhenServerRedirectIsMultiHop) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Visit B, which writes a cookie in the server response, and performs a |
| // multi-hop server redirect to C. Wait for cookie access to register. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, |
| "/cross-site-with-cookie/d.test/cross-site-with-cookie/c.test/" |
| "title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| URLCookieAccessObserver cookie_observer(web_contents, entrypoint_url, |
| CookieOperation::kChange); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, entrypoint_url, final_url)); |
| cookie_observer.Wait(); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedWhenRedirectDoesNotWriteCookies) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Visit B, which does not write a cookie in the server response, and also |
| // server redirects to C. Wait for cookie access to register. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/cross-site/c.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, entrypoint_url, final_url)); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedForSameSiteReferral) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Have A client-redirect to another page on A, which writes a cookie in the |
| // server response, and also server redirects to C. Wait for cookie access to |
| // register. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteA, "/cross-site-with-cookie/c.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| URLCookieAccessObserver cookie_observer(web_contents, entrypoint_url, |
| CookieOperation::kChange); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, entrypoint_url, final_url)); |
| cookie_observer.Wait(); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedForSameSiteExit) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Have A client-redirect to B, which writes a cookie in the server response, |
| // and also server redirects to another page on B. Wait for cookie access to |
| // register. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/cross-site-with-cookie/b.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| URLCookieAccessObserver cookie_observer(web_contents, entrypoint_url, |
| CookieOperation::kChange); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, entrypoint_url, final_url)); |
| cookie_observer.Wait(); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedWhenReferralIsUserInitiated) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Simulate user-initiated navigation from A to B, which writes a cookie in |
| // the server response, and also server redirects to C. Wait for cookie access |
| // to register. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/cross-site-with-cookie/c.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| URLCookieAccessObserver cookie_observer(web_contents, entrypoint_url, |
| CookieOperation::kChange); |
| ASSERT_TRUE( |
| NavigateToURLFromRenderer(web_contents, entrypoint_url, final_url)); |
| cookie_observer.Wait(); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedWhenReferralIsBrowserInitiated) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Simulate browser-initiated navigation from A to B, which writes a cookie in |
| // the server response, and also server redirects to C. Wait for cookie access |
| // to register and for UKM to emit. |
| GURL entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/cross-site-with-cookie/c.test/title1.html"); |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| URLCookieAccessObserver cookie_observer(web_contents, entrypoint_url, |
| CookieOperation::kChange); |
| ASSERT_TRUE(NavigateToURL(web_contents, entrypoint_url, final_url)); |
| cookie_observer.Wait(); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedWhenEntrypointDidNotAccessStorage) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Make A client-redirect to B, where B commits but does not access cookies. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| // Make B client-redirect to C. |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, final_url)); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedForSameSiteClientSideExit) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Make A client-redirect to B, where B commits and reads cookies with JS. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';", |
| EXECUTE_SCRIPT_NO_USER_GESTURE); |
| observer.Wait(); |
| // Make B client-redirect to another page on B. |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, final_url)); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedForUserInitiatedReferral) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Simulate a user-initiated navigation from A to B, where B commits and reads |
| // cookies with JS. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, entrypoint_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';", |
| EXECUTE_SCRIPT_NO_USER_GESTURE); |
| observer.Wait(); |
| // Make B client-redirect to C. |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, final_url)); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| SuspectedTrackerFlowNotEmittedForBrowserInitiatedReferral) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Simulate a user-initiated navigation from A to B, where B commits and reads |
| // cookies with JS. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, entrypoint_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';", |
| EXECUTE_SCRIPT_NO_USER_GESTURE); |
| observer.Wait(); |
| // Make B client-redirect to C. |
| GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, final_url)); |
| |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowReferrerUkmEventName); |
| ExpectNoUkmEventsOfType(kSuspectedTrackerFlowEntrypointUkmEventName); |
| ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(BtmNavigationFlowDetectorClientRedirectTest, |
| InFlowSuccessorInteractionEmittedForAllClientRedirects) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to B, the entrypoint for this flow. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| PerformClientRedirect(web_contents, entrypoint_url); |
| // Client-redirect to another page on B, the successor for this flow, and |
| // interact with the page. |
| GURL successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| PerformClientRedirect(web_contents, successor_url); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, ending the flow, and wait for UKM to emit. |
| GURL flow_end_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kInFlowSuccessorInteractionUkmEventName, |
| ukm_loop.QuitClosure()); |
| PerformClientRedirect(web_contents, flow_end_url); |
| ukm_loop.Run(); |
| |
| // Expect InFlowSuccessorInteraction to have been emitted correctly. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kInFlowSuccessorInteractionUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "SuccessorRedirectIndex", 1); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidEntrypointAccessStorage", |
| false); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| BtmNavigationFlowDetectorClientRedirectTest, |
| InFlowSuccessorInteractionEmittedForMixOfClientAndServerRedirects) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to B, the entrypoint for this flow. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| PerformClientRedirect(web_contents, entrypoint_url); |
| // Client-redirect to another page on B, which server-redirects to yet another |
| // page on B (the successor for this flow), and interact with the page. |
| GURL successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| GURL server_redirector_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/server-redirect?/title2.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, server_redirector_url, successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, ending the flow, and wait for UKM to emit. |
| GURL flow_end_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kInFlowSuccessorInteractionUkmEventName, |
| ukm_loop.QuitClosure()); |
| PerformClientRedirect(web_contents, flow_end_url); |
| ukm_loop.Run(); |
| |
| // Expect InFlowSuccessorInteraction to have been emitted correctly. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kInFlowSuccessorInteractionUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "SuccessorRedirectIndex", 2); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidEntrypointAccessStorage", |
| false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| InFlowSuccessorInteractionEmittedForMultipleSuccessorInteractions) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to B, the entrypoint for this flow. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| // Client-redirect to another page on B, the first successor for this flow, |
| // and interact with the page. |
| GURL first_successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| first_successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to yet another page on B, the second successor for this |
| // flow, and interact with the page. |
| GURL second_successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title3.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| first_successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, ending the flow, and wait for UKM to emit. |
| GURL flow_end_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kInFlowSuccessorInteractionUkmEventName, |
| ukm_loop.QuitClosure()); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, flow_end_url)); |
| ukm_loop.Run(); |
| |
| // Expect InFlowSuccessorInteraction entries to have been emitted correctly. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kInFlowSuccessorInteractionUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 2u); |
| auto ukm_entry_1 = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry_1, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry_1, "SuccessorRedirectIndex", 1); |
| ukm_recorder().ExpectEntryMetric(ukm_entry_1, "DidEntrypointAccessStorage", |
| false); |
| auto ukm_entry_2 = ukm_entries.at(1); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry_2, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry_2, "SuccessorRedirectIndex", 2); |
| ukm_recorder().ExpectEntryMetric(ukm_entry_2, "DidEntrypointAccessStorage", |
| false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| InFlowSuccessorInteractionEmittedForConsecutiveFlows) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to B, the entrypoint for the first flow. |
| GURL first_entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, first_entrypoint_url)); |
| // Client-redirect to another page on B, the successor for the first flow, and |
| // interact with the page. |
| GURL first_successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| first_successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, ending the first flow, and wait for UKM to emit. |
| GURL flow_end_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| base::RunLoop ukm_loop_1; |
| ukm_recorder().SetOnAddEntryCallback(kInFlowSuccessorInteractionUkmEventName, |
| ukm_loop_1.QuitClosure()); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, flow_end_url)); |
| ukm_loop_1.Run(); |
| // Client-redirect to a page on site D, the entrypoint for the second flow, |
| // and server-redirect within site D. |
| GURL second_entrypoint_url = embedded_https_test_server_.GetURL( |
| kSiteD, |
| "/server-redirect-with-secure-cookie?/server-redirect%3F/title1.html"); |
| GURL entrypoint_nav_commit_url = |
| embedded_https_test_server_.GetURL(kSiteD, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, second_entrypoint_url, entrypoint_nav_commit_url)); |
| // Client-redirect to another page on site D, the successor for the second |
| // flow, and interact with the page. |
| GURL second_successor_url = |
| embedded_https_test_server_.GetURL(kSiteD, "/title2.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, second_successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, ending the second flow, and wait for UKM to emit. |
| base::RunLoop ukm_loop_2; |
| ukm_recorder().SetOnAddEntryCallback(kInFlowSuccessorInteractionUkmEventName, |
| ukm_loop_2.QuitClosure()); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, flow_end_url)); |
| ukm_loop_2.Run(); |
| |
| // Expect InFlowSuccessorInteraction to have been emitted correctly. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kInFlowSuccessorInteractionUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 2u); |
| auto first_ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(first_ukm_entry, first_entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(first_ukm_entry, "SuccessorRedirectIndex", |
| 1); |
| ukm_recorder().ExpectEntryMetric(first_ukm_entry, |
| "DidEntrypointAccessStorage", false); |
| auto second_ukm_entry = ukm_entries.at(1); |
| ukm_recorder().ExpectEntrySourceHasUrl(second_ukm_entry, |
| second_entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(second_ukm_entry, "SuccessorRedirectIndex", |
| 3); |
| // TODO - crbug.com/388718419: Uncomment this assertion — currently flaky on |
| // release builds. |
| // ukm_recorder().ExpectEntryMetric(second_ukm_entry, |
| // "DidEntrypointAccessStorage", true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| InFlowSuccessorInteractionNotEmittedWhenNoFlowEnd) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to B, the entrypoint for this flow. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| // Client-redirect to another page on B, the successor for this flow, and |
| // interact with the page. |
| GURL successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| |
| ExpectNoUkmEventsOfType(kInFlowSuccessorInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| InFlowSuccessorInteractionNotEmittedWhenMultipleCrossSiteServerRedirects) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to C, which server-redirects to D, which server-redirects |
| // to B, the would-be entrypoint. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| GURL first_server_redirector_url = embedded_https_test_server_.GetURL( |
| kSiteC, "/cross-site/d.test/cross-site/b.test/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, first_server_redirector_url, entrypoint_url)); |
| // Client-redirect to another page on B, the would-be successor, and interact |
| // with the page. |
| GURL successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, which would end a valid flow. |
| GURL flow_end_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, flow_end_url)); |
| |
| ExpectNoUkmEventsOfType(kInFlowSuccessorInteractionUkmEventName); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| InFlowSuccessorInteractionOnlyEmittedOncePerSuccessor) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL referrer_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, referrer_url)); |
| // Client-redirect to B, the entrypoint for this flow. |
| GURL entrypoint_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, |
| entrypoint_url)); |
| // Client-redirect to another page on B, which server-redirects to yet another |
| // page on B (the successor for this flow), and interact with the page |
| // multiple times. |
| GURL successor_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| GURL server_redirector_url = embedded_https_test_server_.GetURL( |
| kSiteB, "/server-redirect?/title2.html"); |
| ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture( |
| web_contents, server_redirector_url, successor_url)); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| SimulateUserActivation(web_contents); |
| // TODO - crbug.com/389048223: Speed up this step |
| ASSERT_TRUE(WaitUntilTransientActivationLost( |
| web_contents->GetPrimaryMainFrame(), base::Seconds(5))); |
| // Client-redirect to C, ending the flow, and wait for UKM to emit. |
| GURL flow_end_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kInFlowSuccessorInteractionUkmEventName, |
| ukm_loop.QuitClosure()); |
| ASSERT_TRUE( |
| NavigateToURLFromRendererWithoutUserGesture(web_contents, flow_end_url)); |
| ukm_loop.Run(); |
| |
| // Expect only one InFlowSuccessorInteraction event. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kInFlowSuccessorInteractionUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, entrypoint_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "SuccessorRedirectIndex", 2); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidEntrypointAccessStorage", |
| false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeNotEmittedWhenLessThanThreePagesVisited) { |
| // Visit a page on site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B that writes a cookie in its response headers. |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeNotEmittedWhenSameSiteWithPriorPage) { |
| // Visit a page on site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a second page on site A that writes a cookie in its response headers. |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| // Visit site B. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeNotEmittedWhenSameSiteWithNextPage) { |
| // Visit a page on site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B that writes a cookie in its response headers. |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| // Visit a second page on site B. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title2.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeNotEmittedWhenSiteDidNotAccessStorage) { |
| // Visit A->B->C without storage access on B. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) |
| #define MAYBE_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders \ |
| DISABLED_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders |
| #else |
| #define MAYBE_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders \ |
| NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| MAYBE_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders) { |
| // Pre-write a cookie for site B so it can be passed in request headers later. |
| WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| |
| // Visit A. |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, and wait to be notified of the cookie read event. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| URLCookieAccessObserver read_cookie_observer(web_contents, second_page_url, |
| CookieOperation::kRead); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| read_cookie_observer.Wait(); |
| // Visit C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| // TODO - crbug.com/353556432: flaky on Linux release builds and on Android |
| #if (BUILDFLAG(IS_LINUX) && defined(NDEBUG)) || BUILDFLAG(IS_ANDROID) || \ |
| BUILDFLAG(IS_MAC) |
| #define MAYBE_NavigationFlowNodeNotEmittedForCookieAccessInPrerenders \ |
| DISABLED_NavigationFlowNodeNotEmittedForCookieAccessInPrerenders |
| #else |
| #define MAYBE_NavigationFlowNodeNotEmittedForCookieAccessInPrerenders \ |
| NavigationFlowNodeNotEmittedForCookieAccessInPrerenders |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorPrerenderTest, |
| MAYBE_NavigationFlowNodeNotEmittedForCookieAccessInPrerenders) { |
| // Visit site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| // While still on that site B page, prerender a different page on site B that |
| // accesses cookies with both response headers and Javascript. |
| const GURL prerendering_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/set-cookie?name=value"); |
| const FrameTreeNodeId host_id = |
| prerender_test_helper()->AddPrerender(prerendering_url); |
| prerender_test_helper()->WaitForPrerenderLoadCompletion(prerendering_url); |
| test::PrerenderHostObserver prerender_observer(*web_contents, host_id); |
| EXPECT_FALSE(prerender_observer.was_activated()); |
| RenderFrameHost* prerender_frame = |
| prerender_test_helper()->GetPrerenderedMainFrameHost(host_id); |
| EXPECT_NE(prerender_frame, nullptr); |
| FrameCookieAccessObserver observer(web_contents, prerender_frame, |
| CookieOperation::kChange); |
| ASSERT_TRUE(ExecJs(prerender_frame, "document.cookie = 'name=newvalue;';")); |
| observer.Wait(); |
| prerender_test_helper()->CancelPrerenderedPage(host_id); |
| prerender_observer.WaitForDestroyed(); |
| // Visit a page on site C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorPATApiTest, |
| NavigationFlowNodeNotEmittedWhenOnlyStorageAccessIsTopicsApi) { |
| // Visit site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B that accesses storage via the Topics API. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| ASSERT_TRUE(ExecJs(web_contents, |
| R"( |
| (async () => { |
| await document.browsingTopics(); |
| })(); |
| )", |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| // Visit site C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorPATApiTest, |
| NavigationFlowNodeNotEmittedWhenOnlyStorageAccessIsProtectedAudienceApi) { |
| // Visit site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B that accesses storage by joining an ad interest |
| // group via the Protected Audiences API. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| ASSERT_TRUE(ExecJs(web_contents->GetPrimaryMainFrame(), |
| JsReplace(R"( |
| const pageOrigin = new URL($1).origin; |
| const interestGroup = { |
| name: "exampleInterestGroup", |
| owner: pageOrigin, |
| }; |
| |
| navigator.joinAdInterestGroup( |
| interestGroup, |
| // Pick an arbitrarily high duration to |
| // guarantee that we never leave the ad |
| // interest group while the test runs. |
| /*durationSeconds=*/3000000); |
| )", |
| second_page_url), |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| ASSERT_OK_AND_ASSIGN(std::vector<url::Origin> interest_group_joining_origins, |
| WaitForInterestGroupData()); |
| ASSERT_THAT(interest_group_joining_origins, |
| testing::ElementsAre(url::Origin::Create(second_page_url))); |
| // Visit site C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorPATApiTest, |
| NavigationFlowNodeNotEmittedWhenOnlyStorageAccessIsPrivateStateTokensApi) { |
| // Visit site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B that accesses storage via the Private State Tokens |
| // API. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ProvideRequestHandlerKeyCommitmentsToNetworkService({kSiteB}); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| ASSERT_TRUE( |
| ExecJs(web_contents, |
| JsReplace( |
| R"( |
| (async () => { |
| await fetch("/issue", { |
| privateToken: { |
| operation: "token-request", |
| version: 1 |
| } |
| }); |
| return await document.hasPrivateToken($1); |
| })(); |
| )", |
| embedded_https_test_server_.GetOrigin(std::string(kSiteB)) |
| .Serialize()), |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| // Visit site C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorPATApiTest, |
| NavigationFlowNodeNotEmittedWhenOnlyStorageAccessIsAttributionReportingApi) { |
| // Visit site A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit a page on site B that accesses storage via the Attribution Reporting |
| // API. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| GURL attribution_url = embedded_https_test_server_.GetURL( |
| kSiteD, "/attribution_reporting/register_source_headers.html"); |
| ASSERT_TRUE(ExecJs(web_contents, |
| JsReplace( |
| R"( |
| let img = document.createElement('img'); |
| img.attributionSrc = $1; |
| document.body.appendChild(img);)", |
| attribution_url), |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| ASSERT_OK_AND_ASSIGN(AttributionData data, WaitForAttributionData()); |
| ASSERT_THAT(GetOrigins(data), |
| testing::ElementsAre(url::Origin::Create(attribution_url))); |
| // Visit site C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeEmitsWhenVisitingABA) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B changes cookies with JS. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';"); |
| observer.Wait(); |
| base::TimeDelta visit_duration = base::Seconds(1); |
| test_clock_.Advance(visit_duration); |
| // Visit A again, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", |
| ukm::GetExponentialBucketMinForUserTiming( |
| visit_duration.InMilliseconds())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeEmitsWhenWritingCookiesInHeaders) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B writes a cookie in its response headers. |
| GURL second_page_url = GetSetCookieUrlForSite(kSiteB); |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| base::TimeDelta visit_duration = base::Minutes(1); |
| test_clock_.Advance(visit_duration); |
| // Visit C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", |
| ukm::GetExponentialBucketMinForUserTiming( |
| visit_duration.InMilliseconds())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeEmitsWhenIframeWritesCookiesInHeaders) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B has an iframe that writes cookies in its response headers. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/iframe_clipped.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| GURL iframe_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/set-cookie?name=value"); |
| URLCookieAccessObserver observer( |
| web_contents, iframe_url, |
| network::mojom::CookieAccessDetails_Type::kChange); |
| ASSERT_TRUE(NavigateIframeToURL(web_contents, "iframe", iframe_url)); |
| observer.Wait(); |
| base::TimeDelta visit_duration = base::Milliseconds(1); |
| test_clock_.Advance(visit_duration); |
| // Visit C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", |
| ukm::GetExponentialBucketMinForUserTiming( |
| visit_duration.InMilliseconds())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeNotEmittedWhenReadingNonexistentCookiesWithJavascript) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B reads cookies with JS, but no cookies exist for B. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| EvalJsResult result = EvalJs(web_contents, "const cookie = document.cookie;"); |
| // Visit C. |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| |
| ExpectNoNavigationFlowNodeUkmEvents(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeEmitsWhenReadingCookiesWithJavascript) { |
| // Pre-write a cookie for site B so it can be read later. |
| WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| // Visit A. |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B reads cookies with JS. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver cookie_read_observer(web_contents, frame, |
| CookieOperation::kRead); |
| EvalJsResult result = EvalJs(frame, "const cookie = document.cookie;"); |
| cookie_read_observer.Wait(); |
| // Visit C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", 0l); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeEmitsWhenWritingCookiesWithJavascript) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B changes cookies with JS. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';"); |
| observer.Wait(); |
| base::TimeDelta visit_duration = base::Hours(1); |
| test_clock_.Advance(visit_duration); |
| // Visit C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", |
| ukm::GetExponentialBucketMinForUserTiming( |
| visit_duration.InMilliseconds())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeEmitsWhenLocalStorageAccessed) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B writes to local storage. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| ASSERT_TRUE(ExecJs(web_contents->GetPrimaryMainFrame(), |
| JsReplace("localStorage.setItem('value', 'abc123');"), |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| base::TimeDelta visit_duration = base::Minutes(70); |
| test_clock_.Advance(visit_duration); |
| // Visit C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", |
| ukm::GetExponentialBucketMinForUserTiming( |
| visit_duration.InMilliseconds())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeCorrectWhenEntryAndExitRendererInitiated) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B with a renderer-initiated navigation, where B changes cookies with |
| // JS. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, second_page_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';"); |
| observer.Wait(); |
| // Visit C with a renderer-initiated navigation, and wait for UKM to be |
| // recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", 0l); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeCorrectWhenOnlyEntryRendererInitiated) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B with a renderer-initiated navigation, where B changes cookies with |
| // JS. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, second_page_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';"); |
| observer.Wait(); |
| // Visit C with a browser-initiated navigation, and wait for UKM to be |
| // recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", 0l); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeCorrectWhenOnlyExitRendererInitiated) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B with a browser-initiated navigation, where B changes cookies with |
| // JS. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, second_page_url)); |
| RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); |
| FrameCookieAccessObserver observer(web_contents, frame, |
| CookieOperation::kChange); |
| EvalJsResult result = EvalJs(frame, "document.cookie = 'name=value;';"); |
| observer.Wait(); |
| // Visit C with a renderer-initiated navigation, and wait for UKM to be |
| // recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", 0l); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest, |
| NavigationFlowNodeReportsNegativeDurationAsZero) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B writes a cookie in its response headers. Fake a clock |
| // rewind to cause a negative visit duration. |
| GURL second_page_url = GetSetCookieUrlForSite(kSiteB); |
| ASSERT_TRUE( |
| NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB)); |
| test_clock_.Advance(base::Milliseconds(-1)); |
| // Visit C, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteC, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "VisitDurationMilliseconds", 0l); |
| } |
| |
| // WebAuthn tests do not work on Android because there is currently no way to |
| // install a virtual authenticator. |
| // TODO(crbug.com/40269763): Implement automated testing once the infrastructure |
| // permits it (Requires mocking the Android Platform Authenticator i.e. GMS |
| // Core). |
| #if !BUILDFLAG(IS_ANDROID) |
| class BtmNavigationFlowDetectorWebAuthnTest : public ContentBrowserTest { |
| public: |
| BtmNavigationFlowDetectorWebAuthnTest() |
| : embedded_https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| |
| BtmNavigationFlowDetectorWebAuthnTest( |
| const BtmNavigationFlowDetectorWebAuthnTest&) = delete; |
| BtmNavigationFlowDetectorWebAuthnTest& operator=( |
| const BtmNavigationFlowDetectorWebAuthnTest&) = delete; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ContentBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| command_line->AppendSwitch(switches::kIgnoreCertificateErrors); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); |
| } |
| |
| void TearDownInProcessBrowserTestFixture() override { |
| mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); |
| } |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| // Allowlist all certs for the HTTPS server. |
| mock_cert_verifier()->set_default_result(net::OK); |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_https_test_server_.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL("content/test/data"))); |
| embedded_https_test_server_.SetSSLConfig( |
| net::EmbeddedTestServer::CERT_TEST_NAMES); |
| ASSERT_TRUE(embedded_https_test_server_.Start()); |
| |
| auto virtual_device_factory = |
| std::make_unique<device::test::VirtualFidoDeviceFactory>(); |
| |
| virtual_device_factory->mutable_state()->InjectResidentKey( |
| std::vector<uint8_t>{1, 2, 3, 4}, authn_hostname, |
| std::vector<uint8_t>{5, 6, 7, 8}, "Foo", "Foo Bar"); |
| |
| device::VirtualCtap2Device::Config config; |
| config.resident_key_support = true; |
| virtual_device_factory->SetCtap2Config(std::move(config)); |
| |
| auth_env_ = std::make_unique<ScopedAuthenticatorEnvironmentForTesting>( |
| std::move(virtual_device_factory)); |
| |
| ukm_recorder_.emplace(); |
| } |
| |
| void PreRunTestOnMainThread() override { |
| ContentBrowserTest::PreRunTestOnMainThread(); |
| ukm::InitializeSourceUrlRecorderForWebContents(GetActiveWebContents()); |
| } |
| |
| void TearDownOnMainThread() override { |
| ContentBrowserTest::TearDownOnMainThread(); |
| } |
| |
| void PostRunTestOnMainThread() override { |
| auth_env_.reset(); |
| ContentBrowserTest::PostRunTestOnMainThread(); |
| } |
| |
| WebContents* GetActiveWebContents() { return shell()->web_contents(); } |
| |
| void GetWebAuthnAssertion() { |
| ASSERT_EQ("OK", EvalJs(GetActiveWebContents(), R"( |
| let cred_id = new Uint8Array([1,2,3,4]); |
| navigator.credentials.get({ |
| publicKey: { |
| challenge: cred_id, |
| userVerification: 'preferred', |
| allowCredentials: [{ |
| type: 'public-key', |
| id: cred_id, |
| transports: ['usb', 'nfc', 'ble'], |
| }], |
| timeout: 10000 |
| } |
| }).then(c => 'OK', |
| e => e.toString()); |
| )", |
| EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| } |
| |
| ukm::TestAutoSetUkmRecorder& ukm_recorder() { return ukm_recorder_.value(); } |
| |
| ContentMockCertVerifier::CertVerifier* mock_cert_verifier() { |
| return mock_cert_verifier_.mock_cert_verifier(); |
| } |
| |
| protected: |
| const std::string authn_hostname = std::string(kSiteB); |
| net::EmbeddedTestServer embedded_https_test_server_; |
| |
| private: |
| ContentMockCertVerifier mock_cert_verifier_; |
| std::unique_ptr<ScopedAuthenticatorEnvironmentForTesting> auth_env_; |
| std::optional<ukm::TestAutoSetUkmRecorder> ukm_recorder_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorWebAuthnTest, |
| NavigationFlowNodeReportsWAA) { |
| // Visit A. |
| WebContents* web_contents = GetActiveWebContents(); |
| GURL first_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, first_page_url)); |
| // Visit B, where B writes a cookie in its response headers. |
| GURL second_page_url = |
| embedded_https_test_server_.GetURL(kSiteB, "/set-cookie?name=value"); |
| URLCookieAccessObserver observer( |
| web_contents, second_page_url, |
| network::mojom::CookieAccessDetails_Type::kChange); |
| ASSERT_TRUE(NavigateToSetCookie(web_contents, &embedded_https_test_server_, |
| kSiteB, false, false)); |
| observer.Wait(); |
| GetWebAuthnAssertion(); |
| // Visit A again, and wait for UKM to be recorded. |
| base::RunLoop ukm_loop; |
| ukm_recorder().SetOnAddEntryCallback(kNavigationFlowNodeUkmEventName, |
| ukm_loop.QuitClosure()); |
| GURL third_page_url = |
| embedded_https_test_server_.GetURL(kSiteA, "/title1.html"); |
| ASSERT_TRUE(NavigateToURL(web_contents, third_page_url)); |
| ukm_loop.Run(); |
| |
| // Expect metrics to be accurate. |
| auto ukm_entries = |
| ukm_recorder().GetEntriesByName(kNavigationFlowNodeUkmEventName); |
| ASSERT_EQ(ukm_entries.size(), 1u); |
| auto ukm_entry = ukm_entries.at(0); |
| ukm_recorder().ExpectEntrySourceHasUrl(ukm_entry, second_page_url); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WerePreviousAndNextSiteSame", |
| true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveUserActivation", false); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "DidHaveSuccessfulWAA", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasEntryUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, "WasExitUserInitiated", true); |
| ukm_recorder().ExpectEntryMetric(ukm_entry, |
| "WereEntryAndExitRendererInitiated", false); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| BtmNavigationFlowDetectorClientRedirectTest, |
| testing::Values(ClientRedirectType::kMetaTag, |
| ClientRedirectType::kJsWindowLocationReplace, |
| ClientRedirectType::kRedirectLikeNavigation), |
| [](const testing::TestParamInfo< |
| BtmNavigationFlowDetectorClientRedirectTest::ParamType>& param_info) { |
| ClientRedirectType client_redirect_type = param_info.param; |
| CHECK(client_redirect_type >= 0 && |
| client_redirect_type < kClientRedirectTypeNames.size()); |
| return std::string(kClientRedirectTypeNames[client_redirect_type]); |
| }); |
| |
| } // namespace content |