#include "content/browser/btm/btm_navigation_flow_detector.h"
#include "base/base64.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.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/btm/btm_browsertest_utils.h"
#include "content/browser/btm/btm_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/public/mojom/network_service.mojom.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 "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;
}
std::string 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 base::ToString(*metric);
}
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";
}
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 {
command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
}
void PreRunTestOnMainThread() override {
ContentBrowserTest::PreRunTestOnMainThread();
ukm::InitializeSourceUrlRecorderForWebContents(GetActiveWebContents());
}
WebContents* GetActiveWebContents() { return shell()->web_contents(); }
protected:
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) {
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() {
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");
}
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");
}
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() {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->AddCustomHeader("Access-Control-Allow-Origin", "*");
return response;
}
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();
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>(btm::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();
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>(btm::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();
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>(btm::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));
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>(btm::DirectNavigationSource::kOmnibar));
}
IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
SuspectedTrackerFlowEmittedForServerRedirectExit) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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();
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");
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);
ukm_recorder().ExpectEntryMetric(entrypoint_entry, "HadActiveStorageAccess",
true);
ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName);
}
IN_PROC_BROWSER_TEST_F(
BtmNavigationFlowDetectorTest,
SuspectedTrackerFlowDoesNotReportCookieReadsInNavigationsAsStorageAccess) {
WebContents* web_contents = GetActiveWebContents();
GURL cookie_write_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, cookie_write_url));
FrameCookieAccessObserver cookie_write_observer(
web_contents, web_contents->GetPrimaryMainFrame(),
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(web_contents->GetPrimaryMainFrame(),
"document.cookie = 'name=value;';"));
cookie_write_observer.Wait();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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");
URLCookieAccessObserver cookie_read_observer(web_contents, entrypoint_url,
CookieOperation::kRead);
base::RunLoop ukm_loop;
ukm_recorder().SetOnAddEntryCallback(
kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure());
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(
web_contents, entrypoint_url, final_url));
cookie_read_observer.Wait();
ukm_loop.Run();
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");
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);
ukm_recorder().ExpectEntryMetric(entrypoint_entry, "HadActiveStorageAccess",
false);
ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName);
}
IN_PROC_BROWSER_TEST_F(
BtmNavigationFlowDetectorTest,
SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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();
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();
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);
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);
ukm_recorder().ExpectEntryMetric(first_entrypoint_entry,
"HadActiveStorageAccess", true);
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);
ukm_recorder().ExpectEntryMetric(second_entrypoint_entry,
"HadActiveStorageAccess", true);
ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName);
}
class BtmNavigationFlowDetectorClientRedirectTest
: public BtmNavigationFlowDetectorTest,
public testing::WithParamInterface<BtmClientRedirectMethod> {
protected:
BtmClientRedirectMethod client_redirect_type() { return GetParam(); }
};
IN_PROC_BROWSER_TEST_P(
BtmNavigationFlowDetectorClientRedirectTest,
SuspectedTrackerFlowEmittedForClientRedirectWithInteraction) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
entrypoint_url));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';",
EXECUTE_SCRIPT_NO_USER_GESTURE));
ASSERT_TRUE(
ExecJs(frame, "document.cookie;", EXECUTE_SCRIPT_NO_USER_GESTURE));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
base::RunLoop ukm_loop;
ukm_recorder().SetOnAddEntryCallback(
kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure());
GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html");
ASSERT_TRUE(
PerformClientRedirect(client_redirect_type(), web_contents, final_url));
ukm_loop.Run();
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");
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);
ukm_recorder().ExpectEntryMetric(entrypoint_entry, "HadActiveStorageAccess",
true);
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';",
EXECUTE_SCRIPT_NO_USER_GESTURE));
observer.Wait();
base::RunLoop ukm_loop;
ukm_recorder().SetOnAddEntryCallback(
kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure());
GURL final_url = embedded_https_test_server_.GetURL(kSiteC, "/title1.html");
ASSERT_TRUE(
PerformClientRedirect(client_redirect_type(), web_contents, final_url));
ukm_loop.Run();
ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName);
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");
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);
ukm_recorder().ExpectEntryMetric(entrypoint_entry, "HadActiveStorageAccess",
true);
}
IN_PROC_BROWSER_TEST_F(
BtmNavigationFlowDetectorTest,
SuspectedTrackerFlowNotEmittedWhenServerRedirectIsMultiHop) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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,
SuspectedTrackerFlowEmittedWhenRedirectDoesNotAccessCookies) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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");
base::RunLoop ukm_loop;
ukm_recorder().SetOnAddEntryCallback(
kSuspectedTrackerFlowEntrypointUkmEventName, ukm_loop.QuitClosure());
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(
web_contents, entrypoint_url, final_url));
ukm_loop.Run();
ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName);
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");
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);
ukm_recorder().ExpectEntryMetric(entrypoint_entry, "HadActiveStorageAccess",
false);
}
IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
SuspectedTrackerFlowNotEmittedForSameSiteReferral) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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,
SuspectedTrackerFlowNotEmittedForSameSiteServerExit) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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,
SuspectedTrackerFlowNotEmittedForSameSiteClientSideExit) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';",
EXECUTE_SCRIPT_NO_USER_GESTURE));
observer.Wait();
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_P(BtmNavigationFlowDetectorClientRedirectTest,
InFlowSuccessorInteractionEmittedForAllClientRedirects) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(PerformClientRedirect(client_redirect_type(), web_contents,
entrypoint_url));
GURL successor_url =
embedded_https_test_server_.GetURL(kSiteB, "/title2.html");
ASSERT_TRUE(PerformClientRedirect(client_redirect_type(), web_contents,
successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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(PerformClientRedirect(client_redirect_type(), web_contents,
flow_end_url));
ukm_loop.Run();
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(PerformClientRedirect(client_redirect_type(), web_contents,
entrypoint_url));
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);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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(PerformClientRedirect(client_redirect_type(), web_contents,
flow_end_url));
ukm_loop.Run();
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
entrypoint_url));
GURL first_successor_url =
embedded_https_test_server_.GetURL(kSiteB, "/title2.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
first_successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
GURL second_successor_url =
embedded_https_test_server_.GetURL(kSiteB, "/title3.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
first_successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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();
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL first_entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(
web_contents, first_entrypoint_url));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver observer(web_contents, frame,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';",
EXECUTE_SCRIPT_NO_USER_GESTURE));
observer.Wait();
GURL first_successor_url =
embedded_https_test_server_.GetURL(kSiteB, "/title2.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
first_successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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();
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));
GURL second_successor_url =
embedded_https_test_server_.GetURL(kSiteD, "/title2.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(
web_contents, second_successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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();
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", true);
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);
ukm_recorder().ExpectEntryMetric(second_ukm_entry,
"DidEntrypointAccessStorage", true);
}
IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
InFlowSuccessorInteractionNotEmittedWhenNoFlowEnd) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
entrypoint_url));
GURL successor_url =
embedded_https_test_server_.GetURL(kSiteB, "/title2.html");
ASSERT_TRUE(
NavigateToURLFromRendererWithoutUserGesture(web_contents, successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
ExpectNoUkmEventsOfType(kInFlowSuccessorInteractionUkmEventName);
}
IN_PROC_BROWSER_TEST_F(
BtmNavigationFlowDetectorTest,
InFlowSuccessorInteractionNotEmittedWhenMultipleCrossSiteServerRedirects) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
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));
GURL successor_url =
embedded_https_test_server_.GetURL(kSiteB, "/title2.html");
ASSERT_TRUE(
NavigateToURLFromRendererWithoutUserGesture(web_contents, successor_url));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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) {
WebContents* web_contents = GetActiveWebContents();
GURL referrer_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, referrer_url));
GURL entrypoint_url =
embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents,
entrypoint_url));
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);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
SimulateUserActivation(web_contents);
ASSERT_TRUE(WaitUntilTransientActivationLost(
web_contents->GetPrimaryMainFrame(), base::Seconds(5)));
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();
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) {
WebContents* web_contents = GetActiveWebContents();
GURL first_page_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, first_page_url));
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
ExpectNoNavigationFlowNodeUkmEvents();
}
IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
NavigationFlowNodeNotEmittedWhenSameSiteWithPriorPage) {
WebContents* web_contents = GetActiveWebContents();
GURL first_page_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, first_page_url));
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
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) {
WebContents* web_contents = GetActiveWebContents();
GURL first_page_url =
embedded_https_test_server_.GetURL(kSiteA, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, first_page_url));
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
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) {
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();
}
IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders) {
WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
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");
URLCookieAccessObserver read_cookie_observer(web_contents, second_page_url,
CookieOperation::kRead);
ASSERT_TRUE(NavigateToURL(web_contents, second_page_url));
read_cookie_observer.Wait();
GURL third_page_url =
embedded_https_test_server_.GetURL(kSiteC, "/title1.html");
ASSERT_TRUE(NavigateToURL(web_contents, third_page_url));
ExpectNoNavigationFlowNodeUkmEvents();
}
#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) {
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));
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();
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) {
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));
ASSERT_TRUE(ExecJs(web_contents,
R"(
(async () => {
await document.browsingTopics();
})();
)",
EXECUTE_SCRIPT_NO_USER_GESTURE));
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) {
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));
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)));
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) {
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");
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));
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) {
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 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)));
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) {
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));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver observer(web_contents, frame,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';"));
observer.Wait();
base::TimeDelta visit_duration = base::Seconds(1);
test_clock_.Advance(visit_duration);
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();
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) {
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 = GetSetCookieUrlForSite(kSiteB);
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
base::TimeDelta visit_duration = base::Minutes(1);
test_clock_.Advance(visit_duration);
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();
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) {
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, "/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);
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();
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) {
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));
ASSERT_TRUE(ExecJs(web_contents, "const cookie = document.cookie;"));
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) {
WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
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));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver cookie_read_observer(web_contents, frame,
CookieOperation::kRead);
ASSERT_TRUE(ExecJs(frame, "const cookie = document.cookie;"));
cookie_read_observer.Wait();
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();
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) {
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));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver observer(web_contents, frame,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';"));
observer.Wait();
base::TimeDelta visit_duration = base::Hours(1);
test_clock_.Advance(visit_duration);
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();
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) {
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));
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);
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();
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) {
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(NavigateToURLFromRenderer(web_contents, second_page_url));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver observer(web_contents, frame,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';"));
observer.Wait();
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();
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) {
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(NavigateToURLFromRenderer(web_contents, second_page_url));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver observer(web_contents, frame,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';"));
observer.Wait();
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();
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) {
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));
RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
FrameCookieAccessObserver observer(web_contents, frame,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';"));
observer.Wait();
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();
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) {
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 = GetSetCookieUrlForSite(kSiteB);
ASSERT_TRUE(
NavigateToSetCookieAndAwaitAccessNotification(web_contents, kSiteB));
test_clock_.Advance(base::Milliseconds(-1));
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();
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);
}
#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);
}
void SetUpInProcessBrowserTestFixture() override {
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
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) {
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, "/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();
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();
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
INSTANTIATE_TEST_SUITE_P(
All,
BtmNavigationFlowDetectorClientRedirectTest,
kAllBtmClientRedirectMethods,
[](const testing::TestParamInfo<
BtmNavigationFlowDetectorClientRedirectTest::ParamType>& param_info) {
BtmClientRedirectMethod client_redirect_method = param_info.param;
return StringifyBtmClientRedirectMethod(client_redirect_method);
});
}