#include "content/browser/btm/btm_page_visit_observer.h"
#include "base/feature_list.h"
#include "base/test/simple_test_clock.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/ukm/content/source_url_recorder.h"
#include "content/browser/btm/btm_browsertest_utils.h"
#include "content/browser/btm/btm_page_visit_observer_test_utils.h"
#include "content/browser/btm/btm_test_utils.h"
#include "content/browser/btm/btm_utils.h"
#include "content/public/browser/web_contents.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/content_mock_cert_verifier.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/shell/browser/shell.h"
#include "net/base/features.h"
#include "net/cert/cert_verify_result.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/quic_simple_test_server.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/frame/frame.mojom-shared.h"
#include "url/gurl.h"
#if !BUILDFLAG(IS_ANDROID)
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/renderer_host/cookie_access_observers.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
using testing::AllOf;
using testing::ElementsAre;
using testing::IsEmpty;
namespace content {
namespace {
using blink::mojom::StorageTypeAccessed;
class BtmPageVisitObserverBrowserTest : public ContentBrowserTest {
public:
BtmPageVisitObserverBrowserTest() {
feature_list_.InitWithFeatures(
std::vector<base::test::FeatureRef>{
net::features::kSplitCacheByNetworkIsolationKey},
std::vector<base::test::FeatureRef>{
net::features::kMigrateSessionsOnNetworkChangeV2});
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_https_test_server().AddDefaultHandlers(GetTestDataFilePath());
embedded_https_test_server().SetSSLConfig(
net::EmbeddedTestServer::CERT_TEST_NAMES);
ASSERT_TRUE(embedded_https_test_server().Start());
ukm_recorder_.emplace();
auto test_cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "quic-chain.pem");
net::CertVerifyResult verify_result;
verify_result.verified_cert = test_cert;
mock_cert_verifier_.mock_cert_verifier()->AddResultForCert(
test_cert, verify_result, net::OK);
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ASSERT_TRUE(net::QuicSimpleTestServer::Start());
command_line->AppendSwitchASCII(
switches::kOriginToForceQuicOn,
net::QuicSimpleTestServer::GetHostPort().ToString());
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void PreRunTestOnMainThread() override {
ContentBrowserTest::PreRunTestOnMainThread();
ukm::InitializeSourceUrlRecorderForWebContents(shell()->web_contents());
}
void TearDown() override {
base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
net::QuicSimpleTestServer::Shutdown();
}
const ukm::TestAutoSetUkmRecorder& ukm_recorder() {
return ukm_recorder_.value();
}
void SetUpInProcessBrowserTestFixture() override {
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
protected:
GURL RegisterSubresourceQuicResponseWithCookies() {
constexpr static const char path[] = "/early.css";
quiche::HttpHeaderBlock response_headers;
response_headers[":path"] = path;
response_headers[":status"] = base::ToString(net::HTTP_OK);
response_headers["content-type"] = "text/css";
response_headers["cache-control"] = "max-age=3600";
response_headers["set-cookie"] = "foo=bar;";
net::QuicSimpleTestServer::AddResponse(path, std::move(response_headers),
"/* empty body */");
return net::QuicSimpleTestServer::GetFileURL(path);
}
GURL RegisterPageQuicResponseWithEarlyHints() {
constexpr static const char path[] = "/early_hints.html";
quiche::HttpHeaderBlock response_headers;
response_headers[":path"] = path;
response_headers[":status"] = base::ToString(net::HTTP_OK);
response_headers["content-type"] = "text/html";
quiche::HttpHeaderBlock early_hint_header;
early_hint_header["link"] = "</early.css>; rel=preload; as=style";
std::vector<quiche::HttpHeaderBlock> early_hints;
early_hints.push_back(std::move(early_hint_header));
net::QuicSimpleTestServer::AddResponseWithEarlyHints(
path, std::move(response_headers),
R"(<link rel="stylesheet" href="/early.css" />)",
std::move(early_hints));
return net::QuicSimpleTestServer::GetFileURL(path);
}
GURL RegisterRedirectQuicResponseWithEarlyHints(const GURL& final_url) {
static constexpr const char path[] = "/redirect_with_early_hints.html";
quiche::HttpHeaderBlock response_headers;
response_headers[":path"] = path;
response_headers[":status"] = base::ToString(net::HTTP_FOUND);
response_headers["location"] = final_url.spec();
quiche::HttpHeaderBlock early_hint_header;
early_hint_header["link"] = "</early.css>; rel=preload; as=style";
std::vector<quiche::HttpHeaderBlock> early_hints;
early_hints.push_back(std::move(early_hint_header));
net::QuicSimpleTestServer::AddResponseWithEarlyHints(
path, std::move(response_headers), "",
std::move(early_hints));
return net::QuicSimpleTestServer::GetFileURL(path);
}
GURL RegisterPageQuicResponse() {
static constexpr const char path[] = "/empty.html";
quiche::HttpHeaderBlock response_headers;
response_headers[":path"] = path;
response_headers[":status"] = base::ToString(net::HTTP_OK);
response_headers["content-type"] = "text/html";
net::QuicSimpleTestServer::AddResponse(path, std::move(response_headers),
"");
return net::QuicSimpleTestServer::GetFileURL(path);
}
std::optional<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
base::test::ScopedFeatureList feature_list_;
ContentMockCertVerifier mock_cert_verifier_;
};
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, SmokeTest) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
EXPECT_THAT(recorder.visits()[0].prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(
recorder.visits()[0].navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
EXPECT_THAT(recorder.visits()[1].prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_THAT(
recorder.visits()[1].navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_THAT(recorder.visits()[2].prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_THAT(
recorder.visits()[2].navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, CreatedWhileOnPage) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
ASSERT_TRUE(NavigateToURL(web_contents, url1));
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitRecorder::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitRecorder::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
SameDocumentNavigation) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/fragment.html");
const GURL url1b =
embedded_https_test_server().GetURL("a.test", "/fragment.html#fragment");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/fragment.html#fragment");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURL(web_contents, url1b));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(first_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_THAT(first_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, Redirects) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 = embedded_https_test_server().GetURL(
"b.test",
"/server-redirect-with-cookie?%2Fcross-site%3Fc.test%252Fempty.html");
const GURL url2b = embedded_https_test_server().GetURL(
"b.test", "/cross-site?c.test%2Fempty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURL(web_contents, url2, url3));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(first_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_EQ(second_visit.navigation.server_redirects.size(), 2u);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
const BtmServerRedirectInfo& first_server_redirect =
second_visit.navigation.server_redirects[0];
EXPECT_THAT(first_server_redirect,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(first_server_redirect.did_write_cookies);
const BtmServerRedirectInfo& second_server_redirect =
second_visit.navigation.server_redirects[1];
EXPECT_THAT(second_server_redirect,
HasUrlAndMatchingSourceId(url2b, &ukm_recorder()));
EXPECT_FALSE(second_server_redirect.did_write_cookies);
EXPECT_EQ(recorder.visits().size(), 2u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, RedirectToSelf) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetTestDataFilePath());
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
net::test_server::ControllableHttpResponse response(&https_server,
"/title1.html");
ASSERT_TRUE(https_server.Start());
const GURL url1 = https_server.GetURL("a.test", "/empty.html");
const GURL url2 = https_server.GetURL("b.test", "/title1.html");
const GURL url3 = https_server.GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
TestNavigationObserver observer(web_contents);
shell()->LoadURL(url2);
response.WaitForRequest();
response.Send(net::HTTP_MOVED_PERMANENTLY, "text/html",
"<!doctype html><p>Redirecting to /title1.html",
{},
{"Location: /title1.html", "Cache-control: no-store"});
response.Done();
observer.Wait();
ASSERT_TRUE(ExecJs(web_contents, "window.cookieStore.set('foo', 'bar');"));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_EQ(second_visit.navigation.server_redirects.size(), 1u);
const BtmServerRedirectInfo& first_server_redirect =
second_visit.navigation.server_redirects[0];
EXPECT_THAT(first_server_redirect,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(first_server_redirect.did_write_cookies);
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, IframeRedirect) {
const GURL top_level_url1 = embedded_https_test_server().GetURL(
"a.test", "/page_with_blank_iframe.html");
const std::string kIframeId = "test_iframe";
const GURL iframe_redirector_url = embedded_https_test_server().GetURL(
"b.test", "/server-redirect?%2Fcross-site%3Fc.test%252Fempty.html");
const GURL top_level_url2 =
embedded_https_test_server().GetURL("d.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, top_level_url1));
ASSERT_TRUE(
NavigateIframeToURL(web_contents, kIframeId, iframe_redirector_url));
ASSERT_TRUE(NavigateToURL(web_contents, top_level_url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(first_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(top_level_url1,
&ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(top_level_url1, &ukm_recorder()));
EXPECT_THAT(second_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(top_level_url2,
&ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, DocumentCookie) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(ExecJs(web_contents, "document.cookie = 'foo=bar';"));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_FALSE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, UserActivation) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
SimulateUserActivation(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.received_user_activation);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.received_user_activation);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, NavigationInitiation) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURLFromRenderer(web_contents, url2));
ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.navigation.was_renderer_initiated);
EXPECT_TRUE(first_visit.navigation.was_user_initiated);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.navigation.was_renderer_initiated);
EXPECT_TRUE(second_visit.navigation.was_user_initiated);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(third_visit.navigation.was_renderer_initiated);
EXPECT_FALSE(third_visit.navigation.was_user_initiated);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, VisitDuration) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
const base::TimeDelta time_elapsed_before_first_page_visit =
base::Microseconds(777);
const base::TimeDelta visit_duration1 = base::Minutes(2);
const base::TimeDelta visit_duration2 = base::Milliseconds(888);
WebContents* web_contents = shell()->web_contents();
base::SimpleTestClock test_clock;
test_clock.Advance(base::Hours(1));
BtmPageVisitRecorder recorder(web_contents, &test_clock);
test_clock.Advance(time_elapsed_before_first_page_visit);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
test_clock.Advance(visit_duration1);
ASSERT_TRUE(NavigateToURL(web_contents, url2));
test_clock.Advance(visit_duration2);
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_EQ(first_visit.prev_page.visit_duration,
time_elapsed_before_first_page_visit);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_EQ(second_visit.prev_page.visit_duration, visit_duration1);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_EQ(third_visit.prev_page.visit_duration, visit_duration2);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, PageTransition) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
const ui::PageTransition transition_type2 = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
const ui::PageTransition transition_type3 = ui::PAGE_TRANSITION_LINK;
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
NavigationController::LoadURLParams navigation_params2(url2);
navigation_params2.transition_type = transition_type2;
NavigateToURLBlockUntilNavigationsComplete(web_contents, navigation_params2,
1);
NavigationController::LoadURLParams navigation_params3(url3);
navigation_params3.transition_type = transition_type3;
NavigateToURLBlockUntilNavigationsComplete(web_contents, navigation_params3,
1);
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(first_visit.navigation.page_transition,
CoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED));
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_THAT(second_visit.navigation.page_transition,
CoreTypeIs(transition_type2));
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_THAT(third_visit.navigation.page_transition,
CoreTypeIs(transition_type3));
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
NavigationalCookieAccesses) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/set-cookie?foo=bar");
const GURL url2 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitRecorder::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitRecorder::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitRecorder::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
DelayedNavigationalCookieAccesses) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/set-cookie?foo=bar");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
CookieAccessInterceptor interceptor(*web_contents);
BtmPageVisitRecorder recorder(web_contents);
URLCookieAccessObserver access_observer(web_contents, url1,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
access_observer.Wait();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitRecorder::VisitTuple& first_visit = recorder.visits()[0];
ASSERT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitRecorder::VisitTuple& second_visit = recorder.visits()[1];
ASSERT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
ASSERT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
DelayedServerRedirectCookieAccess) {
const GURL url1 = embedded_https_test_server().GetURL(
"a.test", "/server-redirect-with-cookie?/empty.html");
const GURL url1b =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
CookieAccessInterceptor interceptor(*web_contents);
BtmPageVisitRecorder recorder(web_contents);
URLCookieAccessObserver access_observer(web_contents, url1,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURL(web_contents, url1, url1b));
access_observer.Wait();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitRecorder::VisitTuple& first_visit = recorder.visits()[0];
ASSERT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1b, &ukm_recorder()));
EXPECT_TRUE(first_visit.navigation.server_redirects[0].did_write_cookies);
const BtmPageVisitRecorder::VisitTuple& second_visit = recorder.visits()[1];
ASSERT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
ASSERT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1b, &ukm_recorder()));
EXPECT_FALSE(second_visit.prev_page.had_active_storage_access);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, SubresourceCookie) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(ExecJs(web_contents,
JsReplace(
R"(
let img = document.createElement('img');
img.src = $1;
document.body.appendChild(img);)",
"/set-cookie?foo=bar"),
EXECUTE_SCRIPT_NO_USER_GESTURE));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
IframeNavigationCookie) {
const GURL url1 = embedded_https_test_server().GetURL(
"a.test", "/page_with_blank_iframe.html");
const GURL url2 = embedded_https_test_server().GetURL(
"b.test", "/page_with_blank_iframe.html");
const GURL url3 = embedded_https_test_server().GetURL(
"c.test", "/page_with_blank_iframe.html");
const GURL url4 =
embedded_https_test_server().GetURL("d.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
const GURL iframe_1p_cookie_url =
embedded_https_test_server().GetURL(url1.host(), "/set-cookie?foo=bar");
ASSERT_TRUE(
NavigateIframeToURL(web_contents, "test_iframe", iframe_1p_cookie_url));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
const GURL iframe_3p_unpartitioned_cookie_url =
embedded_https_test_server().GetURL(
"a.test", "/set-cookie?bar=baz;SameSite=None;Secure;");
ASSERT_TRUE(NavigateIframeToURL(web_contents, "test_iframe",
iframe_3p_unpartitioned_cookie_url));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
const GURL iframe_3p_partitioned_cookie_url =
embedded_https_test_server().GetURL(
"a.test",
"/set-cookie?__Host-baz=quux;SameSite=None;Secure;Path=/"
";Partitioned;");
ASSERT_TRUE(NavigateIframeToURL(web_contents, "test_iframe",
iframe_3p_partitioned_cookie_url));
ASSERT_TRUE(NavigateToURL(web_contents, url4));
ASSERT_TRUE(recorder.WaitForSize(4));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& fourth_visit = recorder.visits()[3];
EXPECT_THAT(fourth_visit.prev_page,
HasUrlAndMatchingSourceId(url3, &ukm_recorder()));
EXPECT_TRUE(fourth_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
fourth_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url4, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 4u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest, IframeDocumentCookie) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/page_with_iframe.html");
const GURL url2 = embedded_https_test_server().GetURL(
"b.test", "/page_with_blank_iframe.html");
const GURL url3 = embedded_https_test_server().GetURL(
"c.test", "/page_with_blank_iframe.html");
const GURL url4 =
embedded_https_test_server().GetURL("d.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
bool should_await_cookie_access_notifications =
!base::FeatureList::IsEnabled(features::kBackForwardCache);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
RenderFrameHost* iframe1 = ChildFrameAt(web_contents, 0);
FrameCookieAccessObserver cookie_observer1(web_contents, iframe1,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(iframe1, "document.cookie = '1PC=1';"));
if (should_await_cookie_access_notifications) {
cookie_observer1.Wait();
}
ASSERT_TRUE(NavigateToURL(web_contents, url2));
RenderFrameHost* iframe2 = ChildFrameAt(web_contents, 0);
ASSERT_TRUE(NavigateIframeToURL(
web_contents, "test_iframe",
embedded_https_test_server().GetURL("d.test", "/title1.html")));
iframe2 = ChildFrameAt(web_contents, 0);
FrameCookieAccessObserver cookie_observer2(web_contents, iframe2,
CookieOperation::kChange);
ASSERT_TRUE(
ExecJs(iframe2,
"document.cookie = "
"'__Host-CHIPS_3PC=1;SameSite=None;Secure;Path=/;Partitioned;';"));
if (should_await_cookie_access_notifications) {
cookie_observer2.Wait();
}
ASSERT_TRUE(NavigateToURL(web_contents, url3));
RenderFrameHost* iframe3 = ChildFrameAt(web_contents, 0);
ASSERT_TRUE(NavigateIframeToURL(
web_contents, "test_iframe",
embedded_https_test_server().GetURL("d.test", "/title1.html")));
iframe3 = ChildFrameAt(web_contents, 0);
FrameCookieAccessObserver cookie_observer3(web_contents, iframe3,
CookieOperation::kChange);
ASSERT_TRUE(ExecJs(
iframe3,
"document.cookie = 'unpartitioned_3PC=1;SameSite=None;Secure;Path=/;';"));
if (should_await_cookie_access_notifications) {
cookie_observer3.Wait();
}
ASSERT_TRUE(NavigateToURL(web_contents, url4));
ASSERT_TRUE(recorder.WaitForSize(4));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& fourth_visit = recorder.visits()[3];
EXPECT_THAT(fourth_visit.prev_page,
HasUrlAndMatchingSourceId(url3, &ukm_recorder()));
EXPECT_FALSE(fourth_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
fourth_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url4, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 4u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
IframeSubresourceCookie) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/page_with_iframe.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(ExecJs(ChildFrameAt(web_contents, 0),
JsReplace(
R"(
let img = document.createElement('img');
img.src = $1;
document.body.appendChild(img);)",
"/set-cookie?foo=bar"),
EXECUTE_SCRIPT_NO_USER_GESTURE));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
EarlyHintsWithCookieWrite) {
GURL subresource_url = RegisterSubresourceQuicResponseWithCookies();
GURL url_with_early_hints = RegisterPageQuicResponseWithEarlyHints();
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
URLCookieAccessObserver access_observer(web_contents, subresource_url,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURL(web_contents, url_with_early_hints));
access_observer.Wait();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const auto& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(
url_with_early_hints, &ukm_recorder()));
const auto& second_visit = recorder.visits()[1];
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
DelayedEarlyHintsWithCookieWrite) {
GURL subresource_url = RegisterSubresourceQuicResponseWithCookies();
GURL url_with_early_hints = RegisterPageQuicResponseWithEarlyHints();
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
CookieAccessInterceptor interceptor(*web_contents);
URLCookieAccessObserver access_observer(web_contents, subresource_url,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURL(web_contents, url_with_early_hints));
access_observer.Wait();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const auto& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(
url_with_early_hints, &ukm_recorder()));
const auto& second_visit = recorder.visits()[1];
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
RedirectWithEarlyHints) {
const GURL subresource_url = RegisterSubresourceQuicResponseWithCookies();
const GURL destination_url = RegisterPageQuicResponse();
const GURL redirect_with_early_hints_url =
RegisterRedirectQuicResponseWithEarlyHints(destination_url);
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
URLCookieAccessObserver access_observer(web_contents, subresource_url,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURL(web_contents, redirect_with_early_hints_url,
destination_url));
access_observer.Wait();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const auto& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(destination_url,
&ukm_recorder()));
EXPECT_THAT(first_visit.navigation.server_redirects[0],
HasUrlAndMatchingSourceId(redirect_with_early_hints_url,
&ukm_recorder()));
EXPECT_FALSE(first_visit.navigation.server_redirects[0].did_write_cookies);
const auto& second_visit = recorder.visits()[1];
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(second_visit.prev_page.had_active_storage_access);
}
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverBrowserTest,
DelayedRedirectWithEarlyHints) {
const GURL subresource_url = RegisterSubresourceQuicResponseWithCookies();
const GURL destination_url = RegisterPageQuicResponse();
const GURL redirect_with_early_hints_url =
RegisterRedirectQuicResponseWithEarlyHints(destination_url);
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
CookieAccessInterceptor interceptor(*web_contents);
URLCookieAccessObserver access_observer(web_contents, subresource_url,
CookieOperation::kChange);
ASSERT_TRUE(NavigateToURL(web_contents, redirect_with_early_hints_url,
destination_url));
access_observer.Wait();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const auto& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(destination_url,
&ukm_recorder()));
EXPECT_THAT(first_visit.navigation.server_redirects[0],
HasUrlAndMatchingSourceId(redirect_with_early_hints_url,
&ukm_recorder()));
EXPECT_FALSE(first_visit.navigation.server_redirects[0].did_write_cookies);
const auto& second_visit = recorder.visits()[1];
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
}
class BtmPageVisitObserverClientRedirectBrowserTest
: public BtmPageVisitObserverBrowserTest,
public testing::WithParamInterface<BtmClientRedirectMethod> {
protected:
BtmClientRedirectMethod client_redirect_type() { return GetParam(); }
};
IN_PROC_BROWSER_TEST_P(BtmPageVisitObserverClientRedirectBrowserTest,
ClientRedirect) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(
PerformClientRedirect(client_redirect_type(), web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_THAT(first_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_THAT(second_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_THAT(third_visit.navigation.server_redirects, IsEmpty());
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_P(BtmPageVisitObserverClientRedirectBrowserTest,
MixOfClientAndServerRedirects) {
const GURL url1 = embedded_https_test_server().GetURL(
"a.test", "/cross-site?b.test%2Fempty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 = embedded_https_test_server().GetURL(
"c.test", "/cross-site-with-cookie?d.test%2Fempty.html");
const GURL url4 =
embedded_https_test_server().GetURL("d.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1, url2));
ASSERT_TRUE(
PerformClientRedirect(client_redirect_type(), web_contents, url3, url4));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_EQ(first_visit.navigation.server_redirects.size(), 1u);
EXPECT_THAT(first_visit.navigation.server_redirects[0],
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_FALSE(first_visit.navigation.server_redirects[0].did_write_cookies);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_EQ(second_visit.navigation.server_redirects.size(), 1u);
EXPECT_THAT(second_visit.navigation.server_redirects[0],
HasUrlAndMatchingSourceId(url3, &ukm_recorder()));
EXPECT_TRUE(second_visit.navigation.server_redirects[0].did_write_cookies);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url4, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
INSTANTIATE_TEST_SUITE_P(
All,
BtmPageVisitObserverClientRedirectBrowserTest,
kAllBtmClientRedirectMethods,
[](const testing::TestParamInfo<
BtmPageVisitObserverClientRedirectBrowserTest::ParamType>& param_info) {
BtmClientRedirectMethod client_redirect_method = param_info.param;
return StringifyBtmClientRedirectMethod(client_redirect_method);
});
class BtmPageVisitObserverSiteDataAccessBrowserTest
: public BtmPageVisitObserverBrowserTest,
public testing::WithParamInterface<StorageTypeAccessed> {
public:
BtmPageVisitObserverSiteDataAccessBrowserTest()
: prerender_test_helper_(
base::BindRepeating(&BtmPageVisitObserverSiteDataAccessBrowserTest::
GetActiveWebContents,
base::Unretained(this))) {}
void SetUpOnMainThread() override {
BtmPageVisitObserverBrowserTest::SetUpOnMainThread();
prerender_test_helper_.RegisterServerRequestMonitor(embedded_test_server());
}
WebContents* GetActiveWebContents() { return shell()->web_contents(); }
auto* fenced_frame_test_helper() { return &fenced_frame_test_helper_; }
auto* prerender_test_helper() { return &prerender_test_helper_; }
private:
test::FencedFrameTestHelper fenced_frame_test_helper_;
test::PrerenderTestHelper prerender_test_helper_;
};
IN_PROC_BROWSER_TEST_P(BtmPageVisitObserverSiteDataAccessBrowserTest,
PrimaryMainFrameAccess) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
ASSERT_TRUE(AccessStorage(web_contents->GetPrimaryMainFrame(), GetParam()));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_P(BtmPageVisitObserverSiteDataAccessBrowserTest,
IframeAccess) {
const GURL url1 = embedded_https_test_server().GetURL(
"a.test", "/page_with_blank_iframe.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
RenderFrameHost* iframe = ChildFrameAt(web_contents, 0);
ASSERT_TRUE(AccessStorage(iframe, GetParam()));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
IN_PROC_BROWSER_TEST_P(BtmPageVisitObserverSiteDataAccessBrowserTest,
FencedFrameAccess) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/title1.html");
const GURL fenced_frame_url = embedded_https_test_server().GetURL(
"a.test", "/fenced_frames/title0.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
std::unique_ptr<RenderFrameHostWrapper> fenced_frame =
std::make_unique<RenderFrameHostWrapper>(
fenced_frame_test_helper()->CreateFencedFrame(
web_contents->GetPrimaryMainFrame(), fenced_frame_url));
ASSERT_NE(fenced_frame, nullptr);
ASSERT_TRUE(AccessStorage(fenced_frame->get(), GetParam()));
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_FALSE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
IN_PROC_BROWSER_TEST_P(BtmPageVisitObserverSiteDataAccessBrowserTest,
PrerenderingAccess) {
if (GetParam() == StorageTypeAccessed::kFileSystem) {
GTEST_SKIP();
}
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/title1.html");
const GURL prerendering_url =
embedded_https_test_server().GetURL("a.test", "/title2.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
const FrameTreeNodeId host_id =
prerender_test_helper()->AddPrerender(prerendering_url);
prerender_test_helper()->WaitForPrerenderLoadCompletion(prerendering_url);
test::PrerenderHostObserver observer(*web_contents, host_id);
ASSERT_FALSE(observer.was_activated());
RenderFrameHost* prerender_frame =
prerender_test_helper()->GetPrerenderedMainFrameHost(host_id);
ASSERT_NE(prerender_frame, nullptr);
ASSERT_TRUE(AccessStorage(prerender_frame, GetParam()));
prerender_test_helper()->CancelPrerenderedPage(host_id);
observer.WaitForDestroyed();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(recorder.WaitForSize(2));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_FALSE(second_visit.prev_page.had_active_storage_access);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 2u);
}
INSTANTIATE_TEST_SUITE_P(
All,
BtmPageVisitObserverSiteDataAccessBrowserTest,
::testing::Values(StorageTypeAccessed::kLocalStorage,
StorageTypeAccessed::kSessionStorage,
StorageTypeAccessed::kCacheStorage,
StorageTypeAccessed::kFileSystem,
StorageTypeAccessed::kIndexedDB),
[](const testing::TestParamInfo<
BtmPageVisitObserverSiteDataAccessBrowserTest::ParamType>& param_info) {
return base::ToString(param_info.param).substr(1);
});
#if !BUILDFLAG(IS_ANDROID)
class BtmPageVisitObserverWebAuthnTest : public ContentBrowserTest {
public:
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(shell()->web_contents());
}
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));
}
const 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("a.test");
std::optional<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
private:
ContentMockCertVerifier mock_cert_verifier_;
std::unique_ptr<ScopedAuthenticatorEnvironmentForTesting> auth_env_;
};
IN_PROC_BROWSER_TEST_F(BtmPageVisitObserverWebAuthnTest, SuccessfulWAA) {
const GURL url1 =
embedded_https_test_server().GetURL("a.test", "/empty.html");
const GURL url2 =
embedded_https_test_server().GetURL("b.test", "/empty.html");
const GURL url3 =
embedded_https_test_server().GetURL("c.test", "/empty.html");
WebContents* web_contents = shell()->web_contents();
BtmPageVisitRecorder recorder(web_contents);
ASSERT_TRUE(NavigateToURL(web_contents, url1));
GetWebAuthnAssertion();
ASSERT_TRUE(NavigateToURL(web_contents, url2));
ASSERT_TRUE(NavigateToURL(web_contents, url3));
ASSERT_TRUE(recorder.WaitForSize(3));
const BtmPageVisitObserver::VisitTuple& first_visit = recorder.visits()[0];
EXPECT_THAT(first_visit.prev_page, HasUrlAndSourceIdForBlankPage());
EXPECT_FALSE(first_visit.prev_page.had_successful_web_authn_assertion);
EXPECT_THAT(
first_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url1, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& second_visit = recorder.visits()[1];
EXPECT_THAT(second_visit.prev_page,
HasUrlAndMatchingSourceId(url1, &ukm_recorder()));
EXPECT_TRUE(second_visit.prev_page.had_successful_web_authn_assertion);
EXPECT_THAT(
second_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url2, &ukm_recorder()));
const BtmPageVisitObserver::VisitTuple& third_visit = recorder.visits()[2];
EXPECT_THAT(third_visit.prev_page,
HasUrlAndMatchingSourceId(url2, &ukm_recorder()));
EXPECT_FALSE(third_visit.prev_page.had_successful_web_authn_assertion);
EXPECT_THAT(
third_visit.navigation,
HasDestinationUrlAndMatchingDestinationSourceId(url3, &ukm_recorder()));
EXPECT_EQ(recorder.visits().size(), 3u);
}
#endif
}
}