Commit 742f8f97 authored by Andreea Bacanu's avatar Andreea Bacanu Committed by Commit Bot

Add basic ClosedTabCache implementation

This patch implements the following functionality:
- Adding an entry to the cache
- Restoring an entry from the cache
- Evicting an entry by SessionID
- Getting the time to live in the cache
- Getting a pointer to an entry from a given SessionID
- Evicting an entry after timeout

Design doc:
https://docs.google.com/document/d/1SF230MYWgroe4WikDMn82ETd3-HFRzylKtIk0HZRaOU/edit?usp=sharing

BUG=1100946

Change-Id: Ic1f137c255a9695df080868d217cd3fec7d18690
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2322967
Commit-Queue: Andreea Bacanu <aebacanu@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarSreeja Kamishetty <sreejakshetty@chromium.org>
Reviewed-by: default avatarAlexander Timin <altimin@chromium.org>
Reviewed-by: default avatarCarlos Caballero <carlscab@google.com>
Cr-Commit-Position: refs/heads/master@{#797607}
parent b5517d13
...@@ -1494,6 +1494,8 @@ static_library("browser") { ...@@ -1494,6 +1494,8 @@ static_library("browser") {
"sessions/chrome_serialized_navigation_driver.h", "sessions/chrome_serialized_navigation_driver.h",
"sessions/chrome_tab_restore_service_client.cc", "sessions/chrome_tab_restore_service_client.cc",
"sessions/chrome_tab_restore_service_client.h", "sessions/chrome_tab_restore_service_client.h",
"sessions/closed_tab_cache.cc",
"sessions/closed_tab_cache.h",
"sessions/restore_on_startup_policy_handler.cc", "sessions/restore_on_startup_policy_handler.cc",
"sessions/restore_on_startup_policy_handler.h", "sessions/restore_on_startup_policy_handler.h",
"sessions/session_common_utils.cc", "sessions/session_common_utils.cc",
......
// 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 "chrome/browser/sessions/closed_tab_cache.h"
#include <memory>
#include "base/bind.h"
#include "base/metrics/field_trial_params.h"
#include "chrome/browser/browser_features.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
namespace {
// The number of entries the ClosedTabCache can hold.
static constexpr size_t kClosedTabCacheLimit = 1;
// The default time to live in seconds for entries in the ClosedTabCache.
static constexpr base::TimeDelta kDefaultTimeToLiveInClosedTabCacheInSeconds =
base::TimeDelta::FromSeconds(15);
} // namespace
ClosedTabCache::Entry::Entry(SessionID id,
std::unique_ptr<content::WebContents> wc,
base::TimeTicks timestamp)
: id(id), web_contents(std::move(wc)), tab_closure_timestamp(timestamp) {}
ClosedTabCache::Entry::~Entry() = default;
ClosedTabCache::ClosedTabCache()
: cache_size_limit_(kClosedTabCacheLimit),
task_runner_(
content::GetUIThreadTaskRunner(content::BrowserTaskTraits())) {}
ClosedTabCache::~ClosedTabCache() = default;
base::TimeDelta ClosedTabCache::GetTimeToLiveInClosedTabCache() {
// We use the following order of priority if multiple values exist:
// - The programmatical value set in params. Used in specific tests.
// - Default value otherwise, kDefaultTimeToLiveInClosedTabCacheInSeconds.
return base::TimeDelta::FromSeconds(base::GetFieldTrialParamByFeatureAsInt(
features::kClosedTabCache, "time_to_live_in_closed_tab_cache_in_seconds",
kDefaultTimeToLiveInClosedTabCacheInSeconds.InSeconds()));
}
void ClosedTabCache::StoreEntry(SessionID id,
std::unique_ptr<content::WebContents> wc,
base::TimeTicks timestamp) {
TRACE_EVENT2("browser", "ClosedTabCache::StoreEntry", "SessionID", id.id(),
"URL", wc->GetURL().spec());
auto entry = std::make_unique<Entry>(id, std::move(wc), timestamp);
// TODO: Dispatch pagehide() before freezing.
entry->web_contents->SetPageFrozen(/*frozen=*/true);
StartEvictionTimer(entry.get());
entries_.push_front(std::move(entry));
// Evict least recently used tab if the ClosedTabCache is full.
if (entries_.size() > cache_size_limit_) {
entries_.pop_back();
}
}
std::unique_ptr<content::WebContents> ClosedTabCache::RestoreEntry(
SessionID id) {
TRACE_EVENT1("browser", "ClosedTabCache::RestoreEntry", "SessionID", id.id());
auto matching_entry = std::find_if(
entries_.begin(), entries_.end(),
[id](const std::unique_ptr<Entry>& entry) { return entry->id == id; });
if (matching_entry == entries_.end())
return nullptr;
std::unique_ptr<Entry> entry = std::move(*matching_entry);
entries_.erase(matching_entry);
entry->web_contents->SetPageFrozen(/*frozen=*/false);
// TODO: Dispatch pageshow() after unfreezing.
return std::move(entry->web_contents);
}
const content::WebContents* ClosedTabCache::GetWebContents(SessionID id) const {
auto matching_entry = std::find_if(
entries_.begin(), entries_.end(),
[id](const std::unique_ptr<Entry>& entry) { return entry->id == id; });
if (matching_entry == entries_.end())
return nullptr;
return (*matching_entry).get()->web_contents.get();
}
void ClosedTabCache::StartEvictionTimer(Entry* entry) {
base::TimeDelta evict_after = GetTimeToLiveInClosedTabCache();
entry->eviction_timer.SetTaskRunner(task_runner_);
entry->eviction_timer.Start(
FROM_HERE, evict_after,
base::BindOnce(&ClosedTabCache::EvictEntryById, base::Unretained(this),
entry->id));
}
void ClosedTabCache::EvictEntryById(SessionID id) {
auto matching_entry = std::find_if(
entries_.begin(), entries_.end(),
[id](const std::unique_ptr<Entry>& entry) { return entry->id == id; });
if (matching_entry == entries_.end())
return;
std::unique_ptr<Entry> entry = std::move(*matching_entry);
entries_.erase(matching_entry);
}
void ClosedTabCache::SetCacheSizeLimitForTesting(size_t limit) {
cache_size_limit_ = limit;
}
void ClosedTabCache::SetTaskRunnerForTesting(
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
task_runner_ = task_runner;
}
bool ClosedTabCache::IsEmpty() {
return entries_.empty();
}
size_t ClosedTabCache::EntriesCount() {
return entries_.size();
}
\ No newline at end of file
// 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.
#ifndef CHROME_BROWSER_SESSIONS_CLOSED_TAB_CACHE_H_
#define CHROME_BROWSER_SESSIONS_CLOSED_TAB_CACHE_H_
#endif
#include <list>
#include <memory>
#include "base/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/sessions/core/session_id.h"
namespace content {
class WebContents;
} // namespace content
// ClosedTabCache:
//
// A browser feature implemented with the purpose of instantaneously restoring
// recently closed tabs. The main use case of ClosedTabCache is to immediately
// restore accidentally closed tabs.
//
// Main functionality:
// - stores WebContents instances uniquely identified by a SessionID.
// - evicts cache entries after a timeout.
// - evicts the least recently closed tab when the cache is full.
//
// TODO(aebacanu): Make ClosedTabCache listen to MemoryPressureListener and
// clear the cache if memory is tight. Hook ClosedTabCache into the navigation
// flow.
class ClosedTabCache {
public:
ClosedTabCache();
ClosedTabCache(const ClosedTabCache&) = delete;
ClosedTabCache& operator=(const ClosedTabCache&) = delete;
~ClosedTabCache();
// Creates a ClosedTabCache::Entry from the given |id|, |wc| and |timestamp|.
// Moves the entry into the ClosedTabCache and evicts one if necessary.
void StoreEntry(SessionID id,
std::unique_ptr<content::WebContents> wc,
base::TimeTicks timestamp);
// Moves a WebContents out of ClosedTabCache knowing its |id|. Returns nullptr
// if none is found.
std::unique_ptr<content::WebContents> RestoreEntry(SessionID id);
// Returns a pointer to a cached WebContents whose entry is matching |id| if
// it exists in the ClosedTabCache. Returns nullptr if no matching is found.
const content::WebContents* GetWebContents(SessionID id) const;
// We evict tabs from the ClosedTabCache after the time to live, which can be
// controlled via experiment.
static base::TimeDelta GetTimeToLiveInClosedTabCache();
// Set a different cache size limit that is only used in tests.
void SetCacheSizeLimitForTesting(size_t limit);
// Inject a task runner for timing control within browser tests.
void SetTaskRunnerForTesting(
scoped_refptr<base::SingleThreadTaskRunner> task_runner);
// Whether the entries list is empty or not.
bool IsEmpty();
// Get the number of currently stored entries.
size_t EntriesCount();
private:
struct Entry {
Entry(SessionID id,
std::unique_ptr<content::WebContents> wc,
base::TimeTicks timestamp);
Entry(const Entry&) = delete;
Entry& operator=(const Entry&) = delete;
~Entry();
// The tab's unique SessionID used in TabRestoreService::Entry.
SessionID id;
// The tab being stored.
std::unique_ptr<content::WebContents> web_contents;
// Timestamp of tab closure.
base::TimeTicks tab_closure_timestamp;
base::OneShotTimer eviction_timer;
};
// Start the given entry's eviction timer.
void StartEvictionTimer(Entry* entry);
// Evict an entry from the ClosedTabCache based on the given SessionID. Does
// nothing if the entry cannot be found.
void EvictEntryById(SessionID id);
// The set of stored Entries.
// Invariants:
// - Ordered from the most recently closed tab to the least recently closed.
// - Once the list is full, the least recently closed tab is evicted.
std::list<std::unique_ptr<Entry>> entries_;
size_t cache_size_limit_;
// Task runner used for evicting cache entries after timeout.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
};
\ No newline at end of file
// 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 "chrome/browser/sessions/closed_tab_cache.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/sessions/core/session_id.h"
#include "content/public/test/browser_test.h"
using content::WebContents;
class ClosedTabCacheTest : public InProcessBrowserTest {
public:
ClosedTabCacheTest() = default;
ClosedTabCacheTest(const ClosedTabCacheTest&) = delete;
ClosedTabCacheTest& operator=(const ClosedTabCacheTest&) = delete;
protected:
// Add a tab to the given browser.
void AddTab(Browser* browser) {
ui_test_utils::NavigateToURLWithDisposition(
browser, GURL(chrome::kChromeUINewTabURL),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
}
};
// Add an entry to the cache when the cache is empty.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, StoreEntryWhenEmpty) {
ClosedTabCache cache;
AddTab(browser());
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
std::unique_ptr<WebContents> wc =
browser()->tab_strip_model()->DetachWebContentsAt(0);
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
cache.StoreEntry(SessionID::NewUnique(), std::move(wc),
base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 1U);
}
// Add an entry to the cache when there is enough space.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, StoreEntryBasic) {
ClosedTabCache cache;
cache.SetCacheSizeLimitForTesting(2);
AddTab(browser());
AddTab(browser());
ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
std::unique_ptr<WebContents> wc1 =
browser()->tab_strip_model()->DetachWebContentsAt(0);
std::unique_ptr<WebContents> wc2 =
browser()->tab_strip_model()->DetachWebContentsAt(0);
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
cache.StoreEntry(SessionID::NewUnique(), std::move(wc1),
base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 1U);
cache.StoreEntry(SessionID::NewUnique(), std::move(wc2),
base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 2U);
}
// Add an entry to the cache when the cache is at its limit.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, StoreEntryWhenFull) {
ClosedTabCache cache;
AddTab(browser());
AddTab(browser());
ASSERT_EQ(browser()->tab_strip_model()->count(), 3);
std::unique_ptr<WebContents> wc1 =
browser()->tab_strip_model()->DetachWebContentsAt(0);
std::unique_ptr<WebContents> wc2 =
browser()->tab_strip_model()->DetachWebContentsAt(0);
SessionID id1 = SessionID::NewUnique();
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
cache.StoreEntry(id1, std::move(wc1), base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 1U);
cache.StoreEntry(SessionID::NewUnique(), std::move(wc2),
base::TimeTicks::Now());
// Expect the cache size to still be 1 and the removed entry to be entry1.
EXPECT_EQ(cache.EntriesCount(), 1U);
EXPECT_EQ(cache.GetWebContents(id1), nullptr);
}
// Restore an entry when the cache is empty.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, RestoreEntryWhenEmpty) {
ClosedTabCache cache;
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
SessionID id = SessionID::NewUnique();
EXPECT_EQ(cache.RestoreEntry(id), nullptr);
}
// Restore an entry that is not in the cache.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, RestoreEntryWhenNotFound) {
ClosedTabCache cache;
AddTab(browser());
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
std::unique_ptr<WebContents> wc =
browser()->tab_strip_model()->DetachWebContentsAt(0);
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
cache.StoreEntry(SessionID::NewUnique(), std::move(wc),
base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 1U);
SessionID id = SessionID::NewUnique();
EXPECT_EQ(cache.RestoreEntry(id), nullptr);
}
// Restore an entry that is in the cache.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, RestoreEntryWhenFound) {
ClosedTabCache cache;
AddTab(browser());
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
std::unique_ptr<WebContents> wc =
browser()->tab_strip_model()->DetachWebContentsAt(0);
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
SessionID id = SessionID::NewUnique();
cache.StoreEntry(id, std::move(wc), base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 1U);
EXPECT_NE(cache.RestoreEntry(id), nullptr);
}
// Evict an entry after timeout.
IN_PROC_BROWSER_TEST_F(ClosedTabCacheTest, EvictEntryOnTimeout) {
ClosedTabCache cache;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner =
base::MakeRefCounted<base::TestMockTimeTaskRunner>();
cache.SetTaskRunnerForTesting(task_runner);
AddTab(browser());
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
std::unique_ptr<WebContents> wc =
browser()->tab_strip_model()->DetachWebContentsAt(0);
ASSERT_TRUE(cache.IsEmpty())
<< "Expected cache to be empty at the start of the test.";
cache.StoreEntry(SessionID::NewUnique(), std::move(wc),
base::TimeTicks::Now());
EXPECT_EQ(cache.EntriesCount(), 1U);
// Fast forward to just before eviction is due.
base::TimeDelta delta = base::TimeDelta::FromMilliseconds(1);
base::TimeDelta ttl = ClosedTabCache::GetTimeToLiveInClosedTabCache();
task_runner->FastForwardBy(ttl - delta);
// Expect the entry to still be in the cache.
EXPECT_EQ(cache.EntriesCount(), 1U);
// Fast forward to when eviction is due.
task_runner->FastForwardBy(delta);
// Expect the entry to have been evicted.
EXPECT_EQ(cache.EntriesCount(), 0U);
}
\ No newline at end of file
...@@ -1191,6 +1191,7 @@ if (!is_android) { ...@@ -1191,6 +1191,7 @@ if (!is_android) {
"../browser/secure_origin_allowlist_browsertest.cc", "../browser/secure_origin_allowlist_browsertest.cc",
"../browser/serial/chrome_serial_browsertest.cc", "../browser/serial/chrome_serial_browsertest.cc",
"../browser/sessions/better_session_restore_browsertest.cc", "../browser/sessions/better_session_restore_browsertest.cc",
"../browser/sessions/closed_tab_cache_browsertest.cc",
"../browser/sessions/session_restore_browsertest.cc", "../browser/sessions/session_restore_browsertest.cc",
"../browser/sessions/session_restore_browsertest_chromeos.cc", "../browser/sessions/session_restore_browsertest_chromeos.cc",
"../browser/sessions/session_restore_observer_browsertest.cc", "../browser/sessions/session_restore_observer_browsertest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment