| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/performance_manager/public/mechanisms/tab_loading_frame_navigation_scheduler.h" |
| |
| #include "base/bind.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "components/performance_manager/performance_manager_registry_impl.h" |
| #include "components/performance_manager/test_support/performance_manager_browsertest_harness.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/navigation_throttle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/shell/browser/shell_content_browser_client.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace performance_manager { |
| namespace mechanisms { |
| |
| namespace { |
| |
| class LenientMockPolicyDelegate |
| : public TabLoadingFrameNavigationScheduler::PolicyDelegate { |
| public: |
| LenientMockPolicyDelegate() = default; |
| ~LenientMockPolicyDelegate() override = default; |
| |
| // PolicyDelegate implementation: |
| MOCK_METHOD1(ShouldThrottleWebContents, bool(content::WebContents*)); |
| MOCK_METHOD1(ShouldThrottleNavigation, bool(content::NavigationHandle*)); |
| }; |
| |
| using MockPolicyDelegate = ::testing::StrictMock<LenientMockPolicyDelegate>; |
| |
| class TabLoadingFrameNavigationSchedulerTest |
| : public PerformanceManagerBrowserTestHarness { |
| using Super = PerformanceManagerBrowserTestHarness; |
| |
| public: |
| TabLoadingFrameNavigationSchedulerTest() = default; |
| ~TabLoadingFrameNavigationSchedulerTest() override = default; |
| |
| // Used as an embedder hook. Allows us to add navigation throttles to new |
| // navigations. This is hooked up to the ShellContentBrowserClient in |
| // "CreatedBrowserMainParts". |
| std::vector<std::unique_ptr<content::NavigationThrottle>> |
| CreateThrottlesForNavigation(content::NavigationHandle* handle) { |
| std::vector<std::unique_ptr<content::NavigationThrottle>> ret; |
| // Invoke the class under test here. We control the outcome of this via |
| // the MockPolicyDelegate. |
| std::unique_ptr<content::NavigationThrottle> throttle = |
| TabLoadingFrameNavigationScheduler::MaybeCreateThrottleForNavigation( |
| handle); |
| if (throttle) |
| ret.push_back(std::move(throttle)); |
| return ret; |
| } |
| |
| // content::BrowserTestBase overrides: |
| void PreRunTestOnMainThread() override { |
| Super::PreRunTestOnMainThread(); |
| |
| TabLoadingFrameNavigationScheduler::SetPolicyDelegateForTesting( |
| &mock_policy_delegate_); |
| |
| // Enable the mechanism at the beginning of all tests. |
| EXPECT_FALSE( |
| TabLoadingFrameNavigationScheduler::IsThrottlingEnabledForTesting()); |
| EXPECT_FALSE( |
| TabLoadingFrameNavigationScheduler::IsMechanismRegisteredForTesting()); |
| TabLoadingFrameNavigationScheduler::SetThrottlingEnabled(true); |
| EXPECT_TRUE( |
| TabLoadingFrameNavigationScheduler::IsThrottlingEnabledForTesting()); |
| EXPECT_TRUE( |
| TabLoadingFrameNavigationScheduler::IsMechanismRegisteredForTesting()); |
| |
| // Register a callback so we can set navigation throttles. Passing |
| // |this| is fine because we clear the callback before we are torn down. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback( |
| base::BindRepeating(&TabLoadingFrameNavigationSchedulerTest:: |
| CreateThrottlesForNavigation, |
| base::Unretained(this))); |
| } |
| void PostRunTestOnMainThread() override { |
| // Set an empty callback so we stop getting called. |
| base::RepeatingCallback< |
| std::vector<std::unique_ptr<content::NavigationThrottle>>( |
| content::NavigationHandle*)> |
| callback; |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(callback); |
| |
| // Disable at the end of all tests. Some tests may already have disabled |
| // throttling, so don't first check it is enabled. |
| TabLoadingFrameNavigationScheduler::SetThrottlingEnabled(false); |
| EXPECT_FALSE( |
| TabLoadingFrameNavigationScheduler::IsThrottlingEnabledForTesting()); |
| EXPECT_FALSE( |
| TabLoadingFrameNavigationScheduler::IsMechanismRegisteredForTesting()); |
| |
| TabLoadingFrameNavigationScheduler::SetPolicyDelegateForTesting(nullptr); |
| |
| Super::PostRunTestOnMainThread(); |
| } |
| |
| // Helper function for getting the scheduler instance associated with the |
| // given |contents|. |
| TabLoadingFrameNavigationScheduler* GetScheduler( |
| content::WebContents* contents) { |
| return TabLoadingFrameNavigationScheduler::FromWebContents(contents); |
| } |
| |
| protected: |
| MockPolicyDelegate mock_policy_delegate_; |
| }; |
| |
| } // namespace |
| |
| // TODO(crbug.com/1121748): Test is flaky. |
| IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest, |
| DISABLED_ThrottlingDisabled) { |
| GURL url(embedded_test_server()->GetURL("a.com", "/a.html")); |
| auto* contents = shell()->web_contents(); |
| |
| TabLoadingFrameNavigationScheduler::SetThrottlingEnabled(false); |
| EXPECT_FALSE( |
| TabLoadingFrameNavigationScheduler::IsThrottlingEnabledForTesting()); |
| |
| // No scheduler should be created if the mechanism is disabled. There will be |
| // no calls to the mock policy engine. |
| StartNavigation(contents, url); |
| WaitForLoad(contents); |
| testing::Mock::VerifyAndClearExpectations(this); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest, |
| SchedulerNotCreated) { |
| GURL url(embedded_test_server()->GetURL("a.com", "/a.html")); |
| auto* contents = shell()->web_contents(); |
| |
| // No scheduler should be created if "ShouldThrottleWebContents" returns |
| // false. |
| base::RunLoop run_loop; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents)) |
| .WillOnce([&run_loop](content::WebContents*) -> bool { |
| run_loop.Quit(); |
| return false; |
| }); |
| StartNavigation(contents, url); |
| run_loop.Run(); |
| auto* scheduler = GetScheduler(contents); |
| EXPECT_EQ(nullptr, scheduler); |
| |
| // Wait for the load to finish so that it's not ongoing while the test |
| // fixture tears down. |
| WaitForLoad(contents); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest, |
| SchedulerCreatedAndDestroyed) { |
| GURL url1(embedded_test_server()->GetURL("a.com", "/a.html")); |
| GURL url2(embedded_test_server()->GetURL("b.com", "/b.html")); |
| auto* contents1 = shell()->web_contents(); |
| auto* shell2 = CreateShell(); |
| auto* contents2 = shell2->web_contents(); |
| |
| // A scheduler *should* be created if "ShouldThrottleWebContents" returns |
| // true. |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents1)) |
| .WillOnce([&run_loop](content::WebContents*) -> bool { |
| run_loop.Quit(); |
| return true; |
| }); |
| StartNavigation(contents1, url1); |
| run_loop.Run(); |
| auto* scheduler = GetScheduler(contents1); |
| EXPECT_NE(nullptr, scheduler); |
| EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting()); |
| } |
| |
| // Start another navigation and expect another scheduler to be created. |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents2)) |
| .WillOnce([&run_loop](content::WebContents*) -> bool { |
| run_loop.Quit(); |
| return true; |
| }); |
| StartNavigation(contents2, url2); |
| run_loop.Run(); |
| auto* scheduler = GetScheduler(contents2); |
| EXPECT_NE(nullptr, scheduler); |
| EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting()); |
| } |
| |
| // Disable throttling and expect all schedulers to be torn down. |
| TabLoadingFrameNavigationScheduler::SetThrottlingEnabled(false); |
| EXPECT_FALSE( |
| TabLoadingFrameNavigationScheduler::IsThrottlingEnabledForTesting()); |
| EXPECT_EQ(nullptr, GetScheduler(contents1)); |
| EXPECT_EQ(nullptr, GetScheduler(contents2)); |
| |
| // Wait for the load to finish so that it's not ongoing while the test |
| // fixture tears down. |
| WaitForLoad(contents1); |
| WaitForLoad(contents2); |
| } |
| |
| // TODO(crbug.com/1121748): Test is flaky. |
| IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest, |
| DISABLED_ChildFrameThrottled) { |
| GURL url(embedded_test_server()->GetURL("a.com", "/a_embeds_b.html")); |
| auto* contents = shell()->web_contents(); |
| |
| // Throttle the navigation, and throttle the child frame. |
| base::RunLoop run_loop1; |
| base::RunLoop run_loop2; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents)) |
| .WillOnce([&run_loop1](content::WebContents*) -> bool { |
| run_loop1.Quit(); |
| return true; |
| }); |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleNavigation(testing::_)) |
| .WillOnce([&run_loop2](content::NavigationHandle*) -> bool { |
| run_loop2.Quit(); |
| return true; |
| }); |
| |
| // Start the navigation, and expect scheduler to have been created. |
| StartNavigation(contents, url); |
| run_loop1.Run(); |
| auto* scheduler = GetScheduler(contents); |
| EXPECT_NE(nullptr, scheduler); |
| EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting()); |
| |
| // Wait for the child navigation to have started. We'll know once |
| // the policy function has been invoked, which will quit the runloop. |
| run_loop2.Run(); |
| |
| // At this point the child frame navigation should be throttled, waiting for |
| // the policy object to notify that the throttles should be removed. |
| EXPECT_EQ(1u, scheduler->GetThrottleCountForTesting()); |
| |
| // Release the throttles. This also causes the scheduler to be deleted, which |
| // we confirm. |
| scheduler->StopThrottlingForTesting(); |
| scheduler = GetScheduler(contents); |
| EXPECT_EQ(nullptr, scheduler); |
| |
| // Wait for the load to finish so that it's not ongoing while the test |
| // fixture tears down. |
| WaitForLoad(contents); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest, |
| NavigationCancelsThrottling) { |
| GURL url(embedded_test_server()->GetURL("a.com", "/a_embeds_b.html")); |
| auto* contents = shell()->web_contents(); |
| |
| // Throttle the navigation, and throttle the child frame. |
| base::RunLoop run_loop1; |
| base::RunLoop run_loop2; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents)) |
| .WillOnce([&run_loop1](content::WebContents*) -> bool { |
| run_loop1.Quit(); |
| return true; |
| }); |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleNavigation(testing::_)) |
| .WillOnce([&run_loop2](content::NavigationHandle*) -> bool { |
| run_loop2.Quit(); |
| return true; |
| }); |
| |
| // Start the navigation, and expect scheduler to have been created. |
| StartNavigation(contents, url); |
| run_loop1.Run(); |
| auto* scheduler = GetScheduler(contents); |
| EXPECT_NE(nullptr, scheduler); |
| EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting()); |
| int64_t original_navigation_id = scheduler->GetNavigationIdForTesting(); |
| |
| // Wait for the child navigation to have started. We'll know once |
| // the policy function has been invoked, which will quit the runloop. |
| run_loop2.Run(); |
| |
| // At this point the child frame navigation should be throttled, waiting for |
| // the policy object to notify that the throttles should be removed. |
| EXPECT_EQ(1u, scheduler->GetThrottleCountForTesting()); |
| |
| // Reuse the contents for another navigation. This should result in another |
| // call to ShouldThrottleWebContents (which returns false), and the |
| // scheduler should be deleted. |
| url = embedded_test_server()->GetURL("b.com", "/b.html"); |
| base::RunLoop run_loop3; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents)) |
| .WillOnce([&run_loop3](content::WebContents* contents) -> bool { |
| run_loop3.Quit(); |
| return false; |
| }); |
| StartNavigation(contents, url); |
| run_loop3.Run(); |
| scheduler = GetScheduler(contents); |
| EXPECT_EQ(nullptr, scheduler); |
| |
| // Simulate a delayed arrival of a policy message for the previous navigation |
| // and expect it to do nothing. |
| TabLoadingFrameNavigationScheduler::StopThrottling(contents, |
| original_navigation_id); |
| scheduler = GetScheduler(contents); |
| EXPECT_EQ(nullptr, scheduler); |
| |
| // Wait for the load to finish so that it's not ongoing while the test |
| // fixture tears down. |
| WaitForLoad(contents); |
| } |
| |
| // TODO(crbug.com/1121748): Test is flaky. |
| IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest, |
| DISABLED_NavigationInterruptsThrottling) { |
| GURL url(embedded_test_server()->GetURL("a.com", "/a_embeds_b.html")); |
| auto* contents = shell()->web_contents(); |
| |
| // Throttle the navigation, and throttle the child frame. |
| base::RunLoop run_loop1; |
| base::RunLoop run_loop2; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents)) |
| .WillOnce([&run_loop1](content::WebContents*) -> bool { |
| run_loop1.Quit(); |
| return true; |
| }); |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleNavigation(testing::_)) |
| .WillOnce([&run_loop2](content::NavigationHandle*) -> bool { |
| run_loop2.Quit(); |
| return true; |
| }); |
| |
| // Start the navigation, and expect scheduler to have been created. |
| StartNavigation(contents, url); |
| run_loop1.Run(); |
| auto* scheduler = GetScheduler(contents); |
| EXPECT_NE(nullptr, scheduler); |
| EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting()); |
| int64_t original_navigation_id = scheduler->GetNavigationIdForTesting(); |
| |
| // Wait for the child navigation to have started. We'll know once |
| // the policy function has been invoked, which will quit the runloop. |
| run_loop2.Run(); |
| |
| // At this point the child frame navigation should be throttled, waiting for |
| // the policy object to notify that the throttles should be removed. |
| EXPECT_EQ(1u, scheduler->GetThrottleCountForTesting()); |
| |
| // Reuse the contents for another navigation. This should result in another |
| // call to ShouldThrottleWebContents, and the scheduler should be recreated |
| // with no throttles. |
| url = embedded_test_server()->GetURL("b.com", "/b.html"); |
| base::RunLoop run_loop3; |
| EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents)) |
| .WillOnce([&run_loop3](content::WebContents* contents) -> bool { |
| run_loop3.Quit(); |
| return true; |
| }); |
| StartNavigation(contents, url); |
| run_loop3.Run(); |
| scheduler = GetScheduler(contents); |
| EXPECT_NE(nullptr, scheduler); |
| EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting()); |
| |
| // Simulate a delayed arrival of a policy message for the previous navigation |
| // and expect it to do nothing. |
| TabLoadingFrameNavigationScheduler::StopThrottling(contents, |
| original_navigation_id); |
| auto* scheduler2 = GetScheduler(contents); |
| EXPECT_EQ(scheduler, scheduler2); |
| EXPECT_EQ(0u, scheduler2->GetThrottleCountForTesting()); |
| |
| // Wait for the load to finish so that it's not ongoing while the test |
| // fixture tears down. |
| WaitForLoad(contents); |
| } |
| |
| } // namespace mechanisms |
| } // namespace performance_manager |