// Copyright (c) 2010 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/session_types.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/tab_restore_service.h"
#include "chrome/test/render_view_test.h"
#include "chrome/test/testing_profile.h"
#include "content/browser/renderer_host/test_render_view_host.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/test_tab_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
// Create subclass that overrides TimeNow so that we can control the time used
// for closed tabs and windows.
class TabRestoreTimeFactory : public TabRestoreService::TimeFactory {
public:
TabRestoreTimeFactory() : time_(base::Time::Now()) {}
virtual ~TabRestoreTimeFactory() {}
virtual base::Time TimeNow() {
return time_;
}
private:
base::Time time_;
};
class TabRestoreServiceTest : public RenderViewHostTestHarness {
public:
TabRestoreServiceTest() {
url1_ = GURL("http://1");
url2_ = GURL("http://2");
url3_ = GURL("http://3");
}
~TabRestoreServiceTest() {
}
protected:
// testing::Test overrides
virtual void SetUp() {
RenderViewHostTestHarness::SetUp();
time_factory_ = new TabRestoreTimeFactory();
service_ = new TabRestoreService(profile(), time_factory_);
WebKit::initialize(&webkitclient_);
}
virtual void TearDown() {
service_ = NULL;
delete time_factory_;
RenderViewHostTestHarness::TearDown();
WebKit::shutdown();
}
void AddThreeNavigations() {
// Navigate to three URLs.
NavigateAndCommit(url1_);
NavigateAndCommit(url2_);
NavigateAndCommit(url3_);
}
void NavigateToIndex(int index) {
// Navigate back. We have to do this song and dance as NavigationController
// isn't happy if you navigate immediately while going back.
controller().GoToIndex(index);
contents()->CommitPendingNavigation();
}
void RecreateService() {
// Must set service to null first so that it is destroyed before the new
// one is created.
service_ = NULL;
service_ = new TabRestoreService(profile(), time_factory_);
service_->LoadTabsFromLastSession();
}
// Adds a window with one tab and url to the profile's session service.
// If |pinned| is true, the tab is marked as pinned in the session service.
void AddWindowWithOneTabToSessionService(bool pinned) {
SessionService* session_service = profile()->GetSessionService();
SessionID tab_id;
SessionID window_id;
session_service->SetWindowType(window_id, Browser::TYPE_NORMAL);
session_service->SetTabWindow(window_id, tab_id);
session_service->SetTabIndexInWindow(window_id, tab_id, 0);
session_service->SetSelectedTabInWindow(window_id, 0);
if (pinned)
session_service->SetPinnedState(window_id, tab_id, true);
NavigationEntry entry;
entry.set_url(url1_);
session_service->UpdateTabNavigation(window_id, tab_id, 0, entry);
}
// Creates a SessionService and assigns it to the Profile. The SessionService
// is configured with a single window with a single tab pointing at url1_ by
// way of AddWindowWithOneTabToSessionService. If |pinned| is true, the
// tab is marked as pinned in the session service.
void CreateSessionServiceWithOneWindow(bool pinned) {
// The profile takes ownership of this.
SessionService* session_service = new SessionService(profile());
profile()->set_session_service(session_service);
AddWindowWithOneTabToSessionService(pinned);
// Set this, otherwise previous session won't be loaded.
profile()->set_last_session_exited_cleanly(false);
}
GURL url1_;
GURL url2_;
GURL url3_;
scoped_refptr<TabRestoreService> service_;
TabRestoreTimeFactory* time_factory_;
RenderViewTest::RendererWebKitClientImplNoSandbox webkitclient_;
};
TEST_F(TabRestoreServiceTest, Basic) {
AddThreeNavigations();
// Have the service record the tab.
service_->CreateHistoricalTab(&controller(), -1);
// Make sure an entry was created.
ASSERT_EQ(1U, service_->entries().size());
// Make sure the entry matches.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, entry->type);
TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
EXPECT_FALSE(tab->pinned);
EXPECT_TRUE(tab->extension_app_id.empty());
ASSERT_EQ(3U, tab->navigations.size());
EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
EXPECT_EQ(2, tab->current_navigation_index);
EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
tab->timestamp.ToInternalValue());
NavigateToIndex(1);
// And check again.
service_->CreateHistoricalTab(&controller(), -1);
// There should be two entries now.
ASSERT_EQ(2U, service_->entries().size());
// Make sure the entry matches
entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, entry->type);
tab = static_cast<TabRestoreService::Tab*>(entry);
EXPECT_FALSE(tab->pinned);
ASSERT_EQ(3U, tab->navigations.size());
EXPECT_EQ(url1_, tab->navigations[0].virtual_url());
EXPECT_EQ(url2_, tab->navigations[1].virtual_url());
EXPECT_EQ(url3_, tab->navigations[2].virtual_url());
EXPECT_EQ(1, tab->current_navigation_index);
EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
tab->timestamp.ToInternalValue());
}
// Make sure TabRestoreService doesn't create an entry for a tab with no
// navigations.
TEST_F(TabRestoreServiceTest, DontCreateEmptyTab) {
service_->CreateHistoricalTab(&controller(), -1);
EXPECT_TRUE(service_->entries().empty());
}
// Tests restoring a single tab.
TEST_F(TabRestoreServiceTest, Restore) {
AddThreeNavigations();
// Have the service record the tab.
service_->CreateHistoricalTab(&controller(), -1);
// Recreate the service and have it load the tabs.
RecreateService();
// One entry should be created.
ASSERT_EQ(1U, service_->entries().size());
// And verify the entry.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, entry->type);
TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
EXPECT_FALSE(tab->pinned);
ASSERT_EQ(3U, tab->navigations.size());
EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
EXPECT_EQ(2, tab->current_navigation_index);
EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
tab->timestamp.ToInternalValue());
}
// Tests restoring a single pinned tab.
TEST_F(TabRestoreServiceTest, RestorePinnedAndApp) {
AddThreeNavigations();
// Have the service record the tab.
service_->CreateHistoricalTab(&controller(), -1);
// One entry should be created.
ASSERT_EQ(1U, service_->entries().size());
// We have to explicitly mark the tab as pinned as there is no browser for
// these tests.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, entry->type);
TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
tab->pinned = true;
const std::string extension_app_id("test");
tab->extension_app_id = extension_app_id;
// Recreate the service and have it load the tabs.
RecreateService();
// One entry should be created.
ASSERT_EQ(1U, service_->entries().size());
// And verify the entry.
entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, entry->type);
tab = static_cast<TabRestoreService::Tab*>(entry);
EXPECT_TRUE(tab->pinned);
ASSERT_EQ(3U, tab->navigations.size());
EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
EXPECT_EQ(2, tab->current_navigation_index);
EXPECT_TRUE(extension_app_id == tab->extension_app_id);
}
// Make sure we persist entries to disk that have post data.
TEST_F(TabRestoreServiceTest, DontPersistPostData) {
AddThreeNavigations();
controller().GetEntryAtIndex(0)->set_has_post_data(true);
controller().GetEntryAtIndex(1)->set_has_post_data(true);
controller().GetEntryAtIndex(2)->set_has_post_data(true);
// Have the service record the tab.
service_->CreateHistoricalTab(&controller(), -1);
ASSERT_EQ(1U, service_->entries().size());
// Recreate the service and have it load the tabs.
RecreateService();
// One entry should be created.
ASSERT_EQ(1U, service_->entries().size());
const TabRestoreService::Entry* restored_entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, restored_entry->type);
const TabRestoreService::Tab* restored_tab =
static_cast<const TabRestoreService::Tab*>(restored_entry);
// There should be 3 navs.
ASSERT_EQ(3U, restored_tab->navigations.size());
EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
restored_tab->timestamp.ToInternalValue());
}
// Make sure we don't persist entries to disk that have post data. This
// differs from DontPersistPostData1 in that all the navigations have post
// data, so that nothing should be persisted.
TEST_F(TabRestoreServiceTest, DontLoadTwice) {
AddThreeNavigations();
// Have the service record the tab.
service_->CreateHistoricalTab(&controller(), -1);
ASSERT_EQ(1U, service_->entries().size());
// Recreate the service and have it load the tabs.
RecreateService();
service_->LoadTabsFromLastSession();
// There should only be one entry.
ASSERT_EQ(1U, service_->entries().size());
}
// Makes sure we load the previous session as necessary.
TEST_F(TabRestoreServiceTest, LoadPreviousSession) {
CreateSessionServiceWithOneWindow(false);
profile()->GetSessionService()->MoveCurrentSessionToLastSession();
service_->LoadTabsFromLastSession();
// Make sure we get back one entry with one tab whose url is url1.
ASSERT_EQ(1U, service_->entries().size());
TabRestoreService::Entry* entry2 = service_->entries().front();
ASSERT_EQ(TabRestoreService::WINDOW, entry2->type);
TabRestoreService::Window* window =
static_cast<TabRestoreService::Window*>(entry2);
ASSERT_EQ(1U, window->tabs.size());
EXPECT_EQ(0, window->timestamp.ToInternalValue());
EXPECT_EQ(0, window->selected_tab_index);
ASSERT_EQ(1U, window->tabs[0].navigations.size());
EXPECT_EQ(0, window->tabs[0].current_navigation_index);
EXPECT_EQ(0, window->tabs[0].timestamp.ToInternalValue());
EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
}
// Makes sure we don't attempt to load previous sessions after a restore.
TEST_F(TabRestoreServiceTest, DontLoadAfterRestore) {
CreateSessionServiceWithOneWindow(false);
profile()->GetSessionService()->MoveCurrentSessionToLastSession();
profile()->set_restored_last_session(true);
service_->LoadTabsFromLastSession();
// Because we restored a session TabRestoreService shouldn't load the tabs.
ASSERT_EQ(0U, service_->entries().size());
}
// Makes sure we don't attempt to load previous sessions after a clean exit.
TEST_F(TabRestoreServiceTest, DontLoadAfterCleanExit) {
CreateSessionServiceWithOneWindow(false);
profile()->GetSessionService()->MoveCurrentSessionToLastSession();
profile()->set_last_session_exited_cleanly(true);
service_->LoadTabsFromLastSession();
ASSERT_EQ(0U, service_->entries().size());
}
TEST_F(TabRestoreServiceTest, LoadPreviousSessionAndTabs) {
CreateSessionServiceWithOneWindow(false);
profile()->GetSessionService()->MoveCurrentSessionToLastSession();
AddThreeNavigations();
service_->CreateHistoricalTab(&controller(), -1);
RecreateService();
// We should get back two entries, one from the previous session and one from
// the tab restore service. The previous session entry should be first.
ASSERT_EQ(2U, service_->entries().size());
// The first entry should come from the session service.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::WINDOW, entry->type);
TabRestoreService::Window* window =
static_cast<TabRestoreService::Window*>(entry);
ASSERT_EQ(1U, window->tabs.size());
EXPECT_EQ(0, window->selected_tab_index);
EXPECT_EQ(0, window->timestamp.ToInternalValue());
ASSERT_EQ(1U, window->tabs[0].navigations.size());
EXPECT_EQ(0, window->tabs[0].current_navigation_index);
EXPECT_EQ(0, window->tabs[0].timestamp.ToInternalValue());
EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
// Then the closed tab.
entry = *(++service_->entries().begin());
ASSERT_EQ(TabRestoreService::TAB, entry->type);
TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
ASSERT_FALSE(tab->pinned);
ASSERT_EQ(3U, tab->navigations.size());
EXPECT_EQ(2, tab->current_navigation_index);
EXPECT_EQ(time_factory_->TimeNow().ToInternalValue(),
tab->timestamp.ToInternalValue());
EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
}
// Make sure pinned state is correctly loaded from session service.
TEST_F(TabRestoreServiceTest, LoadPreviousSessionAndTabsPinned) {
CreateSessionServiceWithOneWindow(true);
profile()->GetSessionService()->MoveCurrentSessionToLastSession();
AddThreeNavigations();
service_->CreateHistoricalTab(&controller(), -1);
RecreateService();
// We should get back two entries, one from the previous session and one from
// the tab restore service. The previous session entry should be first.
ASSERT_EQ(2U, service_->entries().size());
// The first entry should come from the session service.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::WINDOW, entry->type);
TabRestoreService::Window* window =
static_cast<TabRestoreService::Window*>(entry);
ASSERT_EQ(1U, window->tabs.size());
EXPECT_EQ(0, window->selected_tab_index);
EXPECT_TRUE(window->tabs[0].pinned);
ASSERT_EQ(1U, window->tabs[0].navigations.size());
EXPECT_EQ(0, window->tabs[0].current_navigation_index);
EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
// Then the closed tab.
entry = *(++service_->entries().begin());
ASSERT_EQ(TabRestoreService::TAB, entry->type);
TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
ASSERT_FALSE(tab->pinned);
ASSERT_EQ(3U, tab->navigations.size());
EXPECT_EQ(2, tab->current_navigation_index);
EXPECT_TRUE(url1_ == tab->navigations[0].virtual_url());
EXPECT_TRUE(url2_ == tab->navigations[1].virtual_url());
EXPECT_TRUE(url3_ == tab->navigations[2].virtual_url());
}
// Creates TabRestoreService::kMaxEntries + 1 windows in the session service
// and makes sure we only get back TabRestoreService::kMaxEntries on restore.
TEST_F(TabRestoreServiceTest, ManyWindowsInSessionService) {
CreateSessionServiceWithOneWindow(false);
for (size_t i = 0; i < TabRestoreService::kMaxEntries; ++i)
AddWindowWithOneTabToSessionService(false);
profile()->GetSessionService()->MoveCurrentSessionToLastSession();
AddThreeNavigations();
service_->CreateHistoricalTab(&controller(), -1);
RecreateService();
// We should get back kMaxEntries entries. We added more, but
// TabRestoreService only allows up to kMaxEntries.
ASSERT_EQ(TabRestoreService::kMaxEntries, service_->entries().size());
// The first entry should come from the session service.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::WINDOW, entry->type);
TabRestoreService::Window* window =
static_cast<TabRestoreService::Window*>(entry);
ASSERT_EQ(1U, window->tabs.size());
EXPECT_EQ(0, window->selected_tab_index);
EXPECT_EQ(0, window->timestamp.ToInternalValue());
ASSERT_EQ(1U, window->tabs[0].navigations.size());
EXPECT_EQ(0, window->tabs[0].current_navigation_index);
EXPECT_EQ(0, window->tabs[0].timestamp.ToInternalValue());
EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].virtual_url());
}
// Makes sure we restore the time stamp correctly.
TEST_F(TabRestoreServiceTest, TimestampSurvivesRestore) {
base::Time tab_timestamp(base::Time::FromInternalValue(123456789));
AddThreeNavigations();
// Have the service record the tab.
service_->CreateHistoricalTab(&controller(), -1);
// Make sure an entry was created.
ASSERT_EQ(1U, service_->entries().size());
// Make sure the entry matches.
TabRestoreService::Entry* entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, entry->type);
TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
tab->timestamp = tab_timestamp;
// Set this, otherwise previous session won't be loaded.
profile()->set_last_session_exited_cleanly(false);
RecreateService();
// One entry should be created.
ASSERT_EQ(1U, service_->entries().size());
// And verify the entry.
TabRestoreService::Entry* restored_entry = service_->entries().front();
ASSERT_EQ(TabRestoreService::TAB, restored_entry->type);
TabRestoreService::Tab* restored_tab =
static_cast<TabRestoreService::Tab*>(restored_entry);
EXPECT_EQ(tab_timestamp.ToInternalValue(),
restored_tab->timestamp.ToInternalValue());
}