diff options
author | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-18 00:09:55 +0000 |
---|---|---|
committer | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-18 00:09:55 +0000 |
commit | ea6f76577b2fc1b31bb84fa03c5491609dc0bdf3 (patch) | |
tree | c46a94cdbe9e13a504b2b9eb0f63d3b4dae1bb46 /chrome/browser/sessions | |
parent | 5e324b7c50d5ec8bd2feb4ee6fe93bae82e82458 (diff) | |
download | chromium_src-ea6f76577b2fc1b31bb84fa03c5491609dc0bdf3.zip chromium_src-ea6f76577b2fc1b31bb84fa03c5491609dc0bdf3.tar.gz chromium_src-ea6f76577b2fc1b31bb84fa03c5491609dc0bdf3.tar.bz2 |
Makes the tab restore service contain any windows that were open at
the time of a crash.
BUG=5465
TEST=Bring up chrome, go to google.com. Wait a minute. Kill chrome via
the task manager. Launch chrome again. Make sure the new tab page
shows an entry named 'Window (g)' where g is Google's favicon.
Review URL: http://codereview.chromium.org/14172
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@7188 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sessions')
-rw-r--r-- | chrome/browser/sessions/session_restore.cc | 1 | ||||
-rw-r--r-- | chrome/browser/sessions/tab_restore_service.cc | 148 | ||||
-rw-r--r-- | chrome/browser/sessions/tab_restore_service.h | 67 | ||||
-rw-r--r-- | chrome/browser/sessions/tab_restore_service_unittest.cc | 144 |
4 files changed, 330 insertions, 30 deletions
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc index e9e0b1d1..8ffbac9 100644 --- a/chrome/browser/sessions/session_restore.cc +++ b/chrome/browser/sessions/session_restore.cc @@ -424,6 +424,7 @@ static void Restore(Profile* profile, NOTREACHED(); return; } + profile->set_restored_last_session(true); // SessionRestoreImpl takes care of deleting itself when done. SessionRestoreImpl* restorer = new SessionRestoreImpl(profile, browser, synchronous, diff --git a/chrome/browser/sessions/tab_restore_service.cc b/chrome/browser/sessions/tab_restore_service.cc index f12e6d9..13da68f 100644 --- a/chrome/browser/sessions/tab_restore_service.cc +++ b/chrome/browser/sessions/tab_restore_service.cc @@ -12,6 +12,7 @@ #include "chrome/browser/navigation_entry.h" #include "chrome/browser/profile.h" #include "chrome/browser/sessions/session_backend.h" +#include "chrome/browser/sessions/session_service.h" #include "chrome/common/scoped_vector.h" #include "chrome/common/stl_util-inl.h" @@ -28,8 +29,8 @@ TabRestoreService::Entry::Entry(Type type) : id(next_entry_id++), type(type) {} // TabRestoreService ---------------------------------------------------------- -// Max number of entries we'll keep around. -static const size_t kMaxEntries = 10; +// static +const size_t TabRestoreService::kMaxEntries = 10; // Identifier for commands written to file. // The ordering in the file is as follows: @@ -64,7 +65,7 @@ struct WindowPayload { int32 num_tabs; }; -// Paylowed used for the start of a tab close. +// Payload used for the start of a tab close. struct SelectedNavigationInTabPayload { SessionID::id_type id; int32 index; @@ -92,7 +93,7 @@ void RemoveEntryByID(SessionID::id_type id, TabRestoreService::TabRestoreService(Profile* profile) : BaseSessionService(BaseSessionService::TAB_RESTORE, profile, std::wstring()), - loaded_last_session_(false), + load_state_(NOT_LOADED), restoring_(false), reached_max_(false), entries_to_write_(0), @@ -105,6 +106,7 @@ TabRestoreService::~TabRestoreService() { FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceDestroyed(this)); STLDeleteElements(&entries_); + STLDeleteElements(&staging_entries_); } void TabRestoreService::AddObserver(Observer* observer) { @@ -242,15 +244,30 @@ void TabRestoreService::RestoreEntryById(Browser* browser, } void TabRestoreService::LoadTabsFromLastSession() { - if (loaded_last_session_ || reached_max_) + if (load_state_ != NOT_LOADED || reached_max_) return; - loaded_last_session_ = true; + load_state_ = LOADING; + if (!profile()->restored_last_session() && + !profile()->DidLastSessionExitCleanly() && + profile()->GetSessionService()) { + // The previous session crashed and wasn't restored. Load the tabs/windows + // that were open at the point of crash from the session service. + profile()->GetSessionService()->GetLastSession( + &load_consumer_, + NewCallback(this, &TabRestoreService::OnGotPreviousSession)); + } else { + load_state_ |= LOADED_LAST_SESSION; + } + + // Request the tabs closed in the last session. If the last session crashed, + // this won't contain the tabs/window that were open at the point of the + // crash (the call to GetLastSession above requests those). ScheduleGetLastSessionCommands( new InternalGetCommandsRequest( NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)), - &load_tabs_consumer_); + &load_consumer_); } void TabRestoreService::Save() { @@ -466,6 +483,18 @@ int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) { void TabRestoreService::OnGotLastSessionCommands( Handle handle, scoped_refptr<InternalGetCommandsRequest> request) { + std::vector<Entry*> entries; + CreateEntriesFromCommands(request, &entries); + // Closed tabs always go to the end. + staging_entries_.insert(staging_entries_.end(), entries.begin(), + entries.end()); + load_state_ |= LOADED_LAST_TABS; + LoadStateChanged(); +} + +void TabRestoreService::CreateEntriesFromCommands( + scoped_refptr<InternalGetCommandsRequest> request, + std::vector<Entry*>* loaded_entries) { if (request->canceled() || entries_.size() == kMaxEntries) return; @@ -575,23 +604,7 @@ void TabRestoreService::OnGotLastSessionCommands( // entries with no navigations. ValidateAndDeleteEmptyEntries(&(entries.get())); - if (entries->empty()) - return; - - // And add them. - for (size_t i = 0; i < entries->size(); ++i) - AddEntry(entries[i], false, false); - - // AddEntry takes ownership of the entry, need to clear out entries so that - // it doesn't delete them. - entries->clear(); - - // Make it so we rewrite all the tabs. We need to do this otherwise we won't - // correctly write out the entries when Save is invoked (Save starts from - // the front, not the end and we just added the entries to the end). - entries_to_write_ = entries_.size(); - - PruneAndNotify(); + loaded_entries->swap(entries.get()); } bool TabRestoreService::ValidateTab(Tab* tab) { @@ -647,3 +660,90 @@ void TabRestoreService::ValidateAndDeleteEmptyEntries( // Delete the remaining entries. STLDeleteElements(&invalid_entries); } + +void TabRestoreService::OnGotPreviousSession( + Handle handle, + std::vector<SessionWindow*>* windows) { + std::vector<Entry*> entries; + CreateEntriesFromWindows(windows, &entries); + // Previous session tabs go first. + staging_entries_.insert(staging_entries_.begin(), entries.begin(), + entries.end()); + load_state_ |= LOADED_LAST_SESSION; + LoadStateChanged(); +} + +void TabRestoreService::CreateEntriesFromWindows( + std::vector<SessionWindow*>* windows, + std::vector<Entry*>* entries) { + for (size_t i = 0; i < windows->size(); ++i) { + scoped_ptr<Window> window(new Window()); + if (ConvertSessionWindowToWindow((*windows)[i], window.get())) + entries->push_back(window.release()); + } +} + +bool TabRestoreService::ConvertSessionWindowToWindow( + SessionWindow* session_window, + Window* window) { + for (size_t i = 0; i < session_window->tabs.size(); ++i) { + if (!session_window->tabs[i]->navigations.empty()) { + window->tabs.resize(window->tabs.size() + 1); + Tab& tab = window->tabs.back(); + tab.navigations.swap(session_window->tabs[i]->navigations); + tab.current_navigation_index = + session_window->tabs[i]->current_navigation_index; + } + } + if (window->tabs.empty()) + return false; + + window->selected_tab_index = + std::min(session_window->selected_tab_index, + static_cast<int>(window->tabs.size() - 1)); + return true; +} + +void TabRestoreService::LoadStateChanged() { + if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) != + (LOADED_LAST_TABS | LOADED_LAST_SESSION)) { + // Still waiting on previous session or previous tabs. + return; + } + + // We're done loading. + load_state_ ^= LOADING; + + if (staging_entries_.empty() || reached_max_) { + STLDeleteElements(&staging_entries_); + return; + } + + if (staging_entries_.size() + entries_.size() > kMaxEntries) { + // If we add all the staged entries we'll end up with more than + // kMaxEntries. Delete entries such that we only end up with + // at most kMaxEntries. + DCHECK(entries_.size() < kMaxEntries); + STLDeleteContainerPointers( + staging_entries_.begin() + (kMaxEntries - entries_.size()), + staging_entries_.end()); + staging_entries_.erase( + staging_entries_.begin() + (kMaxEntries - entries_.size()), + staging_entries_.end()); + } + + // And add them. + for (size_t i = 0; i < staging_entries_.size(); ++i) + AddEntry(staging_entries_[i], false, false); + + // AddEntry takes ownership of the entry, need to clear out entries so that + // it doesn't delete them. + staging_entries_.clear(); + + // Make it so we rewrite all the tabs. We need to do this otherwise we won't + // correctly write out the entries when Save is invoked (Save starts from + // the front, not the end and we just added the entries to the end). + entries_to_write_ = staging_entries_.size(); + + PruneAndNotify(); +} diff --git a/chrome/browser/sessions/tab_restore_service.h b/chrome/browser/sessions/tab_restore_service.h index adf5df5..2962cf5 100644 --- a/chrome/browser/sessions/tab_restore_service.h +++ b/chrome/browser/sessions/tab_restore_service.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_SESSIONS_TAB_RESTORE_SERVICE_H_ #include <list> +#include <vector> #include "base/observer_list.h" #include "base/time.h" @@ -126,14 +127,35 @@ class TabRestoreService : public BaseSessionService { SessionID::id_type id, bool replace_existing_tab); - // Loads the tabs from the previous session. This does nothing if the tabs + // Loads the tabs and previous session. This does nothing if the tabs // from the previous session have already been loaded. void LoadTabsFromLastSession(); + // Max number of entries we'll keep around. + static const size_t kMaxEntries; + protected: virtual void Save(); private: + // Used to indicate what has loaded. + enum LoadState { + // Indicates we haven't loaded anything. + NOT_LOADED = 1 << 0, + + // Indicates we've asked for the last sessions and tabs but haven't gotten + // the result back yet. + LOADING = 1 << 2, + + // Indicates we finished loading the last tabs (but not necessarily the + // last session). + LOADED_LAST_TABS = 1 << 3, + + // Indicates we finished loading the last session (but not necessarily the + // last tabs). + LOADED_LAST_SESSION = 1 << 4 + }; + // Populates tabs->navigations from the NavigationController. void PopulateTabFromController(NavigationController* controller, Tab* tab); @@ -179,12 +201,18 @@ class TabRestoreService : public BaseSessionService { // no valid navigation to persist. int GetSelectedNavigationIndexToPersist(const Tab& tab); - // Invoked when we've loaded the session commands from the previous run. - // This creates entries and adds them to entries_, notifying the observer. + // Invoked when we've loaded the session commands that identify the + // previously closed tabs. This creates entries, adds them to + // staging_entries_, and invokes LoadState. void OnGotLastSessionCommands( Handle handle, scoped_refptr<InternalGetCommandsRequest> request); + // Populates |loaded_entries| with Entries from |request|. + void CreateEntriesFromCommands( + scoped_refptr<InternalGetCommandsRequest> request, + std::vector<Entry*>* loaded_entries); + // Returns true if |tab| has more than one navigation. If |tab| has more // than one navigation |tab->current_navigation_index| is constrained based // on the number of navigations. @@ -195,11 +223,32 @@ class TabRestoreService : public BaseSessionService { // hold. void ValidateAndDeleteEmptyEntries(std::vector<Entry*>* entries); + // Callback from SessionService when we've received the windows from the + // previous session. This creates and add entries to |staging_entries_| + // and invokes LoadStateChanged. + void OnGotPreviousSession(Handle handle, + std::vector<SessionWindow*>* windows); + + // Creates and add entries to |entries| for each of the windows in |windows|. + void CreateEntriesFromWindows( + std::vector<SessionWindow*>* windows, + std::vector<Entry*>* entries); + + // Converts a SessionWindow into a Window, returning true on success. + bool ConvertSessionWindowToWindow( + SessionWindow* session_window, + Window* window); + + // Invoked when previous tabs or session is loaded. If both have finished + // loading the entries in staging_entries_ are added to entries_ and + // observers are notified. + void LoadStateChanged(); + // Set of entries. Entries entries_; // Whether we've loaded the last session. - bool loaded_last_session_; + int load_state_; // Are we restoring a tab? If this is true we ignore requests to create a // historical tab. @@ -221,8 +270,14 @@ class TabRestoreService : public BaseSessionService { // avoid creating historical tabs for them. std::set<Browser*> closing_browsers_; - // Used when loading commands from the previous session. - CancelableRequestConsumer load_tabs_consumer_; + // Used when loading previous tabs/session. + CancelableRequestConsumer load_consumer_; + + // Results from previously closed tabs/sessions is first added here. When + // the results from both us and the session restore service have finished + // loading LoadStateChanged is invoked, which adds these entries to + // entries_. + std::vector<Entry*> staging_entries_; DISALLOW_COPY_AND_ASSIGN(TabRestoreService); }; diff --git a/chrome/browser/sessions/tab_restore_service_unittest.cc b/chrome/browser/sessions/tab_restore_service_unittest.cc index 99e65c3..2f7d4ba 100644 --- a/chrome/browser/sessions/tab_restore_service_unittest.cc +++ b/chrome/browser/sessions/tab_restore_service_unittest.cc @@ -53,6 +53,34 @@ class TabRestoreServiceTest : public testing::Test { service_->LoadTabsFromLastSession(); } + // Adds a window with one tab and url to the profile's session service. + void AddWindowWithOneTabToSessionService() { + 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); + NavigationEntry entry(tab_contents_factory_->type()); + 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. + void CreateSessionServiceWithOneWindow() { + // The profile takes ownership of this. + SessionService* session_service = new SessionService(profile_.get()); + profile_->set_session_service(session_service); + + AddWindowWithOneTabToSessionService(); + + // Set this, otherwise previous session won't be loaded. + profile_->set_last_session_exited_cleanly(false); + } + GURL url1_; GURL url2_; GURL url3_; @@ -244,3 +272,119 @@ TEST_F(TabRestoreServiceTest, DontLoadTwice) { // There should only be one entry. ASSERT_EQ(1, service_->entries().size()); } + +// Makes sure we load the previous session as necessary. +TEST_F(TabRestoreServiceTest, LoadPreviousSession) { + CreateSessionServiceWithOneWindow(); + + profile_->GetSessionService()->MoveCurrentSessionToLastSession(); + + service_->LoadTabsFromLastSession(); + + // Make sure we get back one entry with one tab whose url is url1. + ASSERT_EQ(1, 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(1, window->tabs.size()); + EXPECT_EQ(0, window->selected_tab_index); + ASSERT_EQ(1, window->tabs[0].navigations.size()); + EXPECT_EQ(0, window->tabs[0].current_navigation_index); + EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].url()); +} + +// Makes sure we don't attempt to load previous sessions after a restore. +TEST_F(TabRestoreServiceTest, DontLoadAfterRestore) { + CreateSessionServiceWithOneWindow(); + + profile_->GetSessionService()->MoveCurrentSessionToLastSession(); + + profile_->set_restored_last_session(true); + + service_->LoadTabsFromLastSession(); + + // Because we restored a session TabRestoreService shouldn't load the tabs. + ASSERT_EQ(0, service_->entries().size()); +} + +// Makes sure we don't attempt to load previous sessions after a clean exit. +TEST_F(TabRestoreServiceTest, DontLoadAfterCleanExit) { + CreateSessionServiceWithOneWindow(); + + profile_->GetSessionService()->MoveCurrentSessionToLastSession(); + + profile_->set_last_session_exited_cleanly(true); + + service_->LoadTabsFromLastSession(); + + ASSERT_EQ(0, service_->entries().size()); +} + +TEST_F(TabRestoreServiceTest, LoadPreviousSessionAndTabs) { + CreateSessionServiceWithOneWindow(); + + profile_->GetSessionService()->MoveCurrentSessionToLastSession(); + + AddThreeNavigations(); + + service_->CreateHistoricalTab(controller_); + + 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(2, 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(1, window->tabs.size()); + EXPECT_EQ(0, window->selected_tab_index); + ASSERT_EQ(1, window->tabs[0].navigations.size()); + EXPECT_EQ(0, window->tabs[0].current_navigation_index); + EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].url()); + + // Then the closed tab. + entry = *(++service_->entries().begin()); + ASSERT_EQ(TabRestoreService::TAB, entry->type); + TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry); + ASSERT_EQ(3, tab->navigations.size()); + EXPECT_EQ(2, tab->current_navigation_index); + EXPECT_TRUE(url1_ == tab->navigations[0].url()); + EXPECT_TRUE(url2_ == tab->navigations[1].url()); + EXPECT_TRUE(url3_ == tab->navigations[2].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(); + + for (size_t i = 0; i < TabRestoreService::kMaxEntries; ++i) + AddWindowWithOneTabToSessionService(); + + profile_->GetSessionService()->MoveCurrentSessionToLastSession(); + + AddThreeNavigations(); + + service_->CreateHistoricalTab(controller_); + + 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(1, window->tabs.size()); + EXPECT_EQ(0, window->selected_tab_index); + ASSERT_EQ(1, window->tabs[0].navigations.size()); + EXPECT_EQ(0, window->tabs[0].current_navigation_index); + EXPECT_TRUE(url1_ == window->tabs[0].navigations[0].url()); +} |