blob: 7f45b040e0b6bf3534fd01a5f5385a0549948fb3 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sessions/closed_tab_cache.h"
#include "base/run_loop.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/sessions/closed_tab_cache_service_factory.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/sessions/tab_restore_service_load_waiter.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tab_strip_model_delegate.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/memory_pressure/fake_memory_pressure_monitor.h"
#include "components/sessions/core/session_id.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"
using content::WebContents;
class ClosedTabCacheBrowserTest : public InProcessBrowserTest {
public:
ClosedTabCacheBrowserTest() = default;
~ClosedTabCacheBrowserTest() override = default;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
feature_list_.InitWithFeaturesAndParameters(
{{features::kClosedTabCache, {}}, {kClosedTabCacheNoTimeEviction, {}}},
{});
InProcessBrowserTest::SetUpCommandLine(command_line);
}
// Add a tab to the given browser and navigate to url.
void NavigateToURL(Browser* browser, const std::string& origin) {
GURL server_url = embedded_test_server()->GetURL(origin, "/title1.html");
ui_test_utils::NavigateToURLWithDisposition(
browser, server_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
}
void CloseTabAt(int index) {
browser()->tab_strip_model()->CloseWebContentsAt(
index, TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB);
}
void RestoreMostRecentTab() {
TabRestoreServiceLoadWaiter waiter(
TabRestoreServiceFactory::GetForProfile(browser()->profile()));
chrome::RestoreTab(browser());
waiter.Wait();
}
ClosedTabCache& closed_tab_cache() {
return ClosedTabCacheServiceFactory::GetForProfile(browser()->profile())
->closed_tab_cache();
}
memory_pressure::test::FakeMemoryPressureMonitor fake_memory_pressure_monitor_;
private:
base::test::ScopedFeatureList feature_list_;
};
// Add an entry to the cache when the cache is empty.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, StoreEntryWhenEmpty) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
CloseTabAt(1);
ASSERT_EQ(browser()->tab_strip_model()->count(), 1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
}
// Add an entry to the cache when there is enough space.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, StoreEntryBasic) {
closed_tab_cache().SetCacheSizeLimitForTesting(2);
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
NavigateToURL(browser(), "b.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 2U);
}
// Add an entry to the cache when the cache is at its limit.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, StoreEntryWhenFull) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
NavigateToURL(browser(), "b.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
CloseTabAt(1);
// Expect the cache size to still be 1.
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
}
// Only add an entry to the cache when no beforeunload listeners are running.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest,
StoreEntryWithoutBeforeunloadListener) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
// Don't cache WebContents when beforeunload listeners are run.
NavigateToURL(browser(), "a.com");
content::WebContents* a = browser()->tab_strip_model()->GetWebContentsAt(1);
EXPECT_TRUE(ExecJs(a->GetPrimaryMainFrame(),
"window.addEventListener('beforeunload', function (e) "
"{e.returnValue = '';});"));
EXPECT_TRUE(a->NeedToFireBeforeUnloadOrUnloadEvents());
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 0U);
a->DispatchBeforeUnload(/*auto_cancel=*/false);
// Cache WebContents when no beforeunload listeners are run.
NavigateToURL(browser(), "b.com");
content::WebContents* b = browser()->tab_strip_model()->GetWebContentsAt(2);
EXPECT_FALSE(b->NeedToFireBeforeUnloadOrUnloadEvents());
CloseTabAt(2);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
// Ensure that the browser can shutdown. Otherwise tests might timeout.
base::RepeatingCallback<void(bool)> on_close_confirmed = base::BindRepeating(
[](bool placeholder) { LOG(ERROR) << "Should not be reached!"; });
EXPECT_FALSE(browser()->TryToCloseWindow(/*skip_beforeunload=*/true,
on_close_confirmed));
}
// Restore an entry when the cache is empty.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, RestoreEntryWhenEmpty) {
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
SessionID id = SessionID::NewUnique();
EXPECT_EQ(closed_tab_cache().RestoreEntry(id), nullptr);
}
// Restore an entry that is not in the cache.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, RestoreEntryWhenNotFound) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
SessionID id = SessionID::NewUnique();
EXPECT_EQ(closed_tab_cache().RestoreEntry(id), nullptr);
}
// Restore an entry that is in the cache.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, RestoreEntryWhenFound) {
base::HistogramTester histogram_tester;
const char kTabRestored[] = "Tab.RestoreClosedTab";
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
WebContents* wc = browser()->tab_strip_model()->GetWebContentsAt(1);
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
RestoreMostRecentTab();
EXPECT_EQ(closed_tab_cache().EntriesCount(), 0U);
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
EXPECT_EQ(browser()->tab_strip_model()->GetWebContentsAt(1), wc);
// We should store histogram kTabRestored when a tab is restored from
// ClosedTabCache with value 1.
EXPECT_EQ(histogram_tester.GetAllSamples(kTabRestored).size(), 1U);
EXPECT_THAT(histogram_tester.GetAllSamples(kTabRestored),
testing::ElementsAre(base::Bucket(1, 1)));
}
// Evict an entry after timeout.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTest, EvictEntryOnTimeout) {
scoped_refptr<base::TestMockTimeTaskRunner> task_runner =
base::MakeRefCounted<base::TestMockTimeTaskRunner>();
closed_tab_cache().SetTaskRunnerForTesting(task_runner);
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
// Fast forward to just before eviction is due.
base::TimeDelta delta = base::Milliseconds(1);
base::TimeDelta ttl = ClosedTabCache::GetTimeToLiveInClosedTabCache();
task_runner->FastForwardBy(ttl - delta);
// Expect the entry to still be in the cache.
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
// Fast forward to when eviction is due.
task_runner->FastForwardBy(delta);
// Expect the entry to have been evicted.
EXPECT_EQ(closed_tab_cache().EntriesCount(), 0U);
}
// Test for functionality of memory pressure in closed tab cache.
class ClosedTabCacheBrowserTestWithMemoryPressure
: public ClosedTabCacheBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
ClosedTabCacheBrowserTest::SetUpCommandLine(command_line);
scoped_feature_list_.InitWithFeaturesAndParameters(
{{kClosedTabCacheMemoryPressure, {}}}, {});
}
void SetMemoryPressure(
memory_pressure::test::FakeMemoryPressureMonitor::MemoryPressureLevel level) {
fake_memory_pressure_monitor_.SetAndNotifyMemoryPressure(level);
// Wait for all the pressure callbacks to be run.
base::RunLoop().RunUntilIdle();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Check that no WebContents reaches the cache if the memory pressure level is
// critical and the threshold is critical.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTestWithMemoryPressure,
MemoryPressureLevelCritical) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
SetMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 0U);
}
// Check that the cache is not cleared if the memory pressure level is moderate
// and the threshold is critical.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTestWithMemoryPressure,
MemoryPressureLevelModerate) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
SetMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
// Expect the cache to not have been cleared since the memory pressure level
// is below the threshold.
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
}
// Check that a WebContents reaches the cache if the memory pressure level is
// critical and the threshold is moderate, but gets flushed once the threshold
// reaches critical.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheBrowserTestWithMemoryPressure,
MemoryPressureLevelModerateThenCritical) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(browser(), "a.com");
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
ASSERT_TRUE(closed_tab_cache().IsEmpty())
<< "Expected cache to be empty at the start of the test.";
SetMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
CloseTabAt(1);
EXPECT_EQ(closed_tab_cache().EntriesCount(), 1U);
SetMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
// Expect the cache to have been cleared since the memory pressure level is
// at the threshold.
EXPECT_EQ(closed_tab_cache().EntriesCount(), 0U);
}