blob: bd25fcb6296fcd8ebfced900ce949ccfa3751a29 [file] [log] [blame]
// 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