| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/dips/btm_page_visit_observer.h" |
| |
| #include <memory> |
| |
| #include "base/notreached.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "content/browser/dips/btm_page_visit_observer_test_utils.h" |
| #include "content/public/browser/cookie_access_details.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "content/test/test_web_contents.h" |
| #include "net/base/net_errors.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/gurl.h" |
| |
| using testing::AllOf; |
| using testing::ElementsAre; |
| using testing::IsEmpty; |
| |
| namespace content { |
| |
| class BtmPageVisitObserverTest : public testing::Test { |
| public: |
| WebContents* web_contents() { return web_contents_.get(); } |
| |
| protected: |
| BrowserTaskEnvironment task_environment_; |
| TestBrowserContext browser_context_; |
| RenderViewHostTestEnabler rvh_test_enabler_; |
| std::unique_ptr<WebContents> web_contents_ = |
| WebContentsTester::CreateTestWebContents(&browser_context_, |
| /*SiteInstance=*/nullptr); |
| }; |
| |
| TEST_F(BtmPageVisitObserverTest, PreviousPage) { |
| auto* tester = WebContentsTester::For(web_contents()); |
| const GURL url1("http://a.test/"); |
| const GURL url2("http://b.test/"); |
| BtmPageVisitRecorder recorder(web_contents()); |
| |
| tester->NavigateAndCommit(url1); |
| ASSERT_TRUE(recorder.WaitForSize(1)); |
| ASSERT_THAT(recorder.visits(), |
| ElementsAre(AllOf(PreviousPage(HasUrl(GURL())), HasUrl(url1)))); |
| |
| tester->NavigateAndCommit(url2); |
| ASSERT_TRUE(recorder.WaitForSize(2)); |
| ASSERT_THAT(recorder.visits(), |
| ElementsAre(AllOf(PreviousPage(HasUrl(GURL())), HasUrl(url1)), |
| AllOf(PreviousPage(HasUrl(url1)), HasUrl(url2)))); |
| } |
| |
| TEST_F(BtmPageVisitObserverTest, ServerRedirects) { |
| const GURL url1("http://a.test/"); |
| const GURL url2("http://b.test/"); |
| const GURL url3("http://c.test/"); |
| BtmPageVisitRecorder recorder(web_contents()); |
| |
| // Navigate to url1. |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url1); |
| // Navigate to url2, but redirect to url3. |
| auto nav = NavigationSimulator::CreateBrowserInitiated(url2, web_contents()); |
| nav->Start(); |
| nav->Redirect(url3); |
| nav->Commit(); |
| ASSERT_TRUE(recorder.WaitForSize(2)); |
| |
| // Two navigations are observed, the second with a redirect. |
| ASSERT_THAT( |
| recorder.visits(), |
| ElementsAre(AllOf(Navigation(ServerRedirects(IsEmpty())), HasUrl(url1)), |
| AllOf(PreviousPage(HasUrl(url1)), |
| Navigation(ServerRedirects(ElementsAre(HasUrl(url2)))), |
| HasUrl(url3)))); |
| } |
| |
| TEST_F(BtmPageVisitObserverTest, IgnoreUncommitted) { |
| const GURL url1("http://a.test/"); |
| const GURL url2("http://b.test/"); |
| const GURL url3("http://c.test/"); |
| BtmPageVisitRecorder recorder(web_contents()); |
| |
| // Navigate to url1 and commit. |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url1); |
| // Navigate to url2, but don't commit. |
| auto nav = NavigationSimulator::CreateBrowserInitiated(url2, web_contents()); |
| nav->Start(); |
| nav->AbortCommit(); |
| // Navigate to url3 and commit. |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url3); |
| ASSERT_TRUE(recorder.WaitForSize(2)); |
| |
| // Only url1 and url3 navigations are observed. |
| ASSERT_THAT(recorder.visits(), |
| ElementsAre(HasUrl(url1), |
| AllOf(PreviousPage(HasUrl(url1)), HasUrl(url3)))); |
| } |
| |
| TEST_F(BtmPageVisitObserverTest, IgnoreSubframes) { |
| const GURL url1("http://a.test/"); |
| const GURL url2("http://b.test/"); |
| const GURL url3("http://c.test/"); |
| BtmPageVisitRecorder recorder(web_contents()); |
| |
| // Top-level navigation to url1. |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url1); |
| // Subframe navigation to url2. |
| RenderFrameHost* iframe = |
| RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame()) |
| ->AppendChild("iframe"); |
| NavigationSimulator::NavigateAndCommitFromDocument(url2, iframe); |
| // Top-level navigation to url3. |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url3); |
| ASSERT_TRUE(recorder.WaitForSize(2)); |
| |
| // Only url1 and url3 navigations are observed. |
| ASSERT_THAT(recorder.visits(), |
| ElementsAre(HasUrl(url1), |
| AllOf(PreviousPage(HasUrl(url1)), HasUrl(url3)))); |
| } |
| |
| // Same-document navigations are ignored. |
| TEST_F(BtmPageVisitObserverTest, IgnoreSameDocument) { |
| const GURL url1a("http://a.test/"); |
| const GURL url1b("http://a.test/#top"); |
| const GURL url2("http://b.test/"); |
| BtmPageVisitRecorder recorder(web_contents()); |
| |
| // Navigate to url1a |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url1a); |
| // Navigate to same-document url1b. |
| auto nav = NavigationSimulator::CreateBrowserInitiated(url1b, web_contents()); |
| nav->Start(); |
| nav->CommitSameDocument(); |
| // Navigate to url2. |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url2); |
| ASSERT_TRUE(recorder.WaitForSize(2)); |
| |
| // Only the url1a and url2 navigations are observed. |
| ASSERT_THAT(recorder.visits(), |
| ElementsAre(HasUrl(url1a), |
| AllOf(PreviousPage(HasUrl(url1a)), HasUrl(url2)))); |
| } |
| |
| TEST_F(BtmPageVisitObserverTest, FlushPendingVisitsAtDestruction) { |
| int counter = 0; |
| |
| { |
| BtmPageVisitObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([&counter](const BtmPageVisitInfo&, |
| const BtmNavigationInfo&, |
| const GURL&) { ++counter; })); |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), |
| GURL("http://a.test/")); |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), |
| GURL("http://b.test/")); |
| NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), |
| GURL("http://c.test/")); |
| } |
| |
| // If observer's pending visits isn't flushed, `counter` will (typically) |
| // still be 0. But if they are, all three visits will be recorded. |
| ASSERT_EQ(counter, 3); |
| } |
| |
| } // namespace content |