diff options
author | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-06 19:30:19 +0000 |
---|---|---|
committer | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-06 19:30:19 +0000 |
commit | 169627b81ce036a7014476c366b060e050b5ff70 (patch) | |
tree | 6231d8cfaa065513b4742d99204b25da4178037a /chrome | |
parent | ee824a436efbdeed4ca78efc4dd2aa4976ba43a9 (diff) | |
download | chromium_src-169627b81ce036a7014476c366b060e050b5ff70.zip chromium_src-169627b81ce036a7014476c366b060e050b5ff70.tar.gz chromium_src-169627b81ce036a7014476c366b060e050b5ff70.tar.bz2 |
Makes the tab restore service persist closed tabs/windows to disk and
reload them when asked.
Sorry for largish looking change. It's made big by refactoring common
code between TabRestoreService and SessionService into a common
superclass. At the same time I removed some dead code and shuffled the
session related classes into a single directory for easier perusal.
BUG=384
TEST=close the browser, start the browser and make sure the new tab
page shows closed windows/tabs from the previous session.
Review URL: http://codereview.chromium.org/13152
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6490 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
42 files changed, 2192 insertions, 1213 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index ad297be..4d96433 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -33,6 +33,7 @@ #include "chrome/browser/plugin_process_host.h" #include "chrome/browser/plugin_service.h" #include "chrome/browser/profile.h" +#include "chrome/browser/sessions/session_service.h" #include "chrome/browser/ssl_error_info.h" #include "chrome/browser/site_instance.h" #include "chrome/browser/task_manager.h" @@ -2234,11 +2235,6 @@ NavigationController* Browser::BuildRestoredNavigationController( if (!navigations.empty()) { DCHECK(selected_navigation >= 0 && selected_navigation < static_cast<int>(navigations.size())); - // We should have a valid URL, if we don't fall back to the default. - GURL url = navigations[selected_navigation].url; - if (url.is_empty()) - url = GetHomePage(); - // Create a NavigationController. This constructor creates the appropriate // set of TabContents. return new NavigationController(profile_, navigations, selected_navigation); diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h index 9e8de5e..a704199 100644 --- a/chrome/browser/browser.h +++ b/chrome/browser/browser.h @@ -11,7 +11,7 @@ #include "chrome/browser/controller.h" #include "chrome/browser/shell_dialogs.h" #include "chrome/browser/browser_window.h" -#include "chrome/browser/session_id.h" +#include "chrome/browser/sessions/session_id.h" #include "chrome/browser/tab_contents.h" #include "chrome/browser/tab_contents_delegate.h" #include "chrome/browser/tabs/tab_strip_model.h" @@ -27,7 +27,7 @@ class LocationBarView; class PrefService; class Profile; class StatusBubble; -struct TabNavigation; +class TabNavigation; class WebApp; class Browser : public TabStripModelDelegate, @@ -539,7 +539,7 @@ class Browser : public TabStripModelDelegate, // Unique identifier of this browser for session restore. This id is only // unique within the current session, and is not guaranteed to be unique // across sessions. - SessionID session_id_; + const SessionID session_id_; // TODO(beng): should be combined with ToolbarModel now that this is the only // implementation. diff --git a/chrome/browser/browser.scons b/chrome/browser/browser.scons index 564b09c..3ac082e 100644 --- a/chrome/browser/browser.scons +++ b/chrome/browser/browser.scons @@ -251,9 +251,14 @@ if env['PLATFORM'] == 'win32': 'safe_browsing/protocol_manager.cc', 'safe_browsing/safe_browsing_blocking_page.cc', 'sandbox_policy.cc', - 'session_backend.cc', - 'session_restore.cc', - 'session_service.cc', + 'sessions/base_session_service.cc', + 'sessions/session_backend.cc', + 'sessions/session_command.cc', + 'sessions/session_id.cc', + 'sessions/session_restore.cc', + 'sessions/session_service.cc', + 'sessions/session_types.cc', + 'sessions/tab_restore_service.cc', 'shell_integration.cc', 'site_instance.cc', 'spellcheck_worditerator.cc', @@ -266,7 +271,6 @@ if env['PLATFORM'] == 'win32': 'suspend_controller.cc', 'tab_contents.cc', 'tab_contents_factory.cc', - 'tab_restore_service.cc', 'tab_util.cc', 'tabs/tab_strip_model.cc', 'tabs/tab_strip_model_order_controller.cc', diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj index fdfcfc7..9383b95 100644 --- a/chrome/browser/browser.vcproj +++ b/chrome/browser/browser.vcproj @@ -562,14 +562,6 @@ >
</File>
<File
- RelativePath=".\session_restore.cc"
- >
- </File>
- <File
- RelativePath=".\session_restore.h"
- >
- </File>
- <File
RelativePath=".\session_startup_pref.cc"
>
</File>
@@ -1478,31 +1470,67 @@ Name="Sessions and Tab Restore"
>
<File
- RelativePath=".\session_backend.cc"
+ RelativePath=".\sessions\base_session_service.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\base_session_service.h"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_backend.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_backend.h"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_id.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_id.h"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_command.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_command.h"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_restore.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\sessions\session_restore.h"
>
</File>
<File
- RelativePath=".\session_backend.h"
+ RelativePath=".\sessions\session_service.cc"
>
</File>
<File
- RelativePath=".\session_id.h"
+ RelativePath=".\sessions\session_service.h"
>
</File>
<File
- RelativePath=".\session_service.cc"
+ RelativePath=".\sessions\session_types.cc"
>
</File>
<File
- RelativePath=".\session_service.h"
+ RelativePath=".\sessions\session_types.h"
>
</File>
<File
- RelativePath=".\tab_restore_service.cc"
+ RelativePath=".\sessions\tab_restore_service.cc"
>
</File>
<File
- RelativePath=".\tab_restore_service.h"
+ RelativePath=".\sessions\tab_restore_service.h"
>
</File>
</Filter>
diff --git a/chrome/browser/browser_init.cc b/chrome/browser/browser_init.cc index b56c0b38..6084a53 100644 --- a/chrome/browser/browser_init.cc +++ b/chrome/browser/browser_init.cc @@ -27,8 +27,8 @@ #include "chrome/browser/infobar_delegate.h" #include "chrome/browser/navigation_controller.h" #include "chrome/browser/net/dns_global.h" -#include "chrome/browser/session_restore.h" #include "chrome/browser/session_startup_pref.h" +#include "chrome/browser/sessions/session_restore.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/url_fixer_upper.h" #include "chrome/browser/web_app_launcher.h" @@ -76,7 +76,7 @@ class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate { } virtual bool Accept() { // Restore the session. - SessionRestore::RestoreSession(profile_, NULL, false, true, false, + SessionRestore::RestoreSession(profile_, NULL, true, false, std::vector<GURL>()); return true; } @@ -507,8 +507,7 @@ bool BrowserInit::LaunchWithProfile::OpenStartupURLs( // infobar. return false; } - SessionRestore::RestoreSessionSynchronously(profile_, false, - urls_to_open); + SessionRestore::RestoreSessionSynchronously(profile_, urls_to_open); return true; case SessionStartupPref::URLS: diff --git a/chrome/browser/browsing_data_remover.cc b/chrome/browser/browsing_data_remover.cc index e0cdc68..86f8e98 100644 --- a/chrome/browser/browsing_data_remover.cc +++ b/chrome/browser/browsing_data_remover.cc @@ -8,8 +8,8 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/profile.h" -#include "chrome/browser/session_service.h" -#include "chrome/browser/tab_restore_service.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/tab_restore_service.h" #include "chrome/browser/template_url_model.h" #include "chrome/browser/user_metrics.h" #include "chrome/browser/webdata/web_data_service.h" @@ -77,13 +77,15 @@ void BrowsingDataRemover::Remove(int remove_mask) { // We also delete the list of recently closed tabs. Since these expire, // they can't be more than a day old, so we can simply clear them all. TabRestoreService* tab_service = profile_->GetTabRestoreService(); - if (tab_service) + if (tab_service) { tab_service->ClearEntries(); + tab_service->DeleteLastSession(); + } // We also delete the last session when we delete the history. SessionService* session_service = profile_->GetSessionService(); if (session_service) - session_service->DeleteSession(false); // Last session. + session_service->DeleteLastSession(); } if (remove_mask & REMOVE_DOWNLOADS) { diff --git a/chrome/browser/dom_ui/new_tab_ui.cc b/chrome/browser/dom_ui/new_tab_ui.cc index cb1fe1b..1775e1e 100644 --- a/chrome/browser/dom_ui/new_tab_ui.cc +++ b/chrome/browser/dom_ui/new_tab_ui.cc @@ -15,6 +15,7 @@ #include "chrome/browser/navigation_entry.h" #include "chrome/browser/profile.h" #include "chrome/browser/render_view_host.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/browser/template_url.h" #include "chrome/browser/user_data_manager.h" #include "chrome/browser/user_metrics.h" @@ -677,8 +678,13 @@ void RecentlyClosedTabsHandler::HandleGetRecentlyClosedTabs( // GetTabRestoreService() can return NULL (i.e., when in Off the // Record mode) - if (tab_restore_service_) + if (tab_restore_service_) { + // This does nothing if the tabs have already been loaded or they + // shouldn't be loaded. + tab_restore_service_->LoadTabsFromLastSession(); + tab_restore_service_->AddObserver(this); + } } if (tab_restore_service_) @@ -726,10 +732,11 @@ bool RecentlyClosedTabsHandler::TabToValue( const TabNavigation& current_navigation = tab.navigations.at(tab.current_navigation_index); - if (current_navigation.url == NewTabUIURL()) + if (current_navigation.url() == NewTabUIURL()) return false; - SetURLAndTitle(dictionary, current_navigation.title, current_navigation.url); + SetURLAndTitle(dictionary, current_navigation.title(), + current_navigation.url()); dictionary->SetString(L"type", L"tab"); return true; } diff --git a/chrome/browser/dom_ui/new_tab_ui.h b/chrome/browser/dom_ui/new_tab_ui.h index 814e73a..4995b4d 100644 --- a/chrome/browser/dom_ui/new_tab_ui.h +++ b/chrome/browser/dom_ui/new_tab_ui.h @@ -9,7 +9,7 @@ #include "chrome/browser/dom_ui/dom_ui_host.h" #include "chrome/browser/dom_ui/chrome_url_data_manager.h" #include "chrome/browser/history/history.h" -#include "chrome/browser/tab_restore_service.h" +#include "chrome/browser/sessions/tab_restore_service.h" #include "chrome/browser/template_url_model.h" class GURL; diff --git a/chrome/browser/navigation_controller.cc b/chrome/browser/navigation_controller.cc index 18e270a..2b3f96e 100644 --- a/chrome/browser/navigation_controller.cc +++ b/chrome/browser/navigation_controller.cc @@ -13,7 +13,7 @@ #include "chrome/browser/navigation_entry.h" #include "chrome/browser/profile.h" #include "chrome/browser/repost_form_warning_dialog.h" -#include "chrome/browser/session_service.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/browser/site_instance.h" #include "chrome/browser/tab_contents.h" #include "chrome/browser/tab_contents_delegate.h" @@ -137,30 +137,11 @@ static void CreateNavigationEntriesFromTabNavigations( const std::vector<TabNavigation>& navigations, std::vector<linked_ptr<NavigationEntry> >* entries) { // Create a NavigationEntry for each of the navigations. + int page_id = 0; for (std::vector<TabNavigation>::const_iterator i = - navigations.begin(); i != navigations.end(); ++i) { - const TabNavigation& navigation = *i; - - GURL real_url = navigation.url; - TabContentsType type = TabContents::TypeForURL(&real_url); - DCHECK(type != TAB_CONTENTS_UNKNOWN_TYPE); - - NavigationEntry* entry = new NavigationEntry( - type, - NULL, // The site instance for restored tabs is sent on navigation - // (WebContents::GetSiteInstanceForEntry). - static_cast<int>(i - navigations.begin()), - real_url, - navigation.referrer, - navigation.title, - // Use a transition type of reload so that we don't incorrectly - // increase the typed count. - PageTransition::RELOAD); - entry->set_display_url(navigation.url); - entry->set_content_state(navigation.state); - entry->set_has_post_data( - navigation.type_mask & TabNavigation::HAS_POST_DATA); - entries->push_back(linked_ptr<NavigationEntry>(entry)); + navigations.begin(); i != navigations.end(); ++i, ++page_id) { + entries->push_back( + linked_ptr<NavigationEntry>(i->ToNavigationEntry(page_id))); } } diff --git a/chrome/browser/navigation_controller.h b/chrome/browser/navigation_controller.h index 74e3049..bd1d2aa 100644 --- a/chrome/browser/navigation_controller.h +++ b/chrome/browser/navigation_controller.h @@ -9,7 +9,7 @@ #include "base/linked_ptr.h" #include "base/ref_counted.h" -#include "chrome/browser/session_id.h" +#include "chrome/browser/sessions/session_id.h" #include "chrome/browser/site_instance.h" #include "chrome/browser/ssl_manager.h" #include "chrome/browser/tab_contents_type.h" @@ -20,7 +20,7 @@ class Profile; class TabContents; class WebContents; class TabContentsCollector; -struct TabNavigation; +class TabNavigation; struct ViewHostMsg_FrameNavigate_Params; // A NavigationController maintains the back-forward list for a single tab and @@ -538,7 +538,7 @@ class NavigationController { // Unique identifier of this controller for session restore. This id is only // unique within the current session, and is not guaranteed to be unique // across sessions. - SessionID session_id_; + const SessionID session_id_; // Unique identifier of the window we're in. Used by session restore. SessionID window_id_; diff --git a/chrome/browser/navigation_controller_unittest.cc b/chrome/browser/navigation_controller_unittest.cc index 98e9ad7..08b43a0 100644 --- a/chrome/browser/navigation_controller_unittest.cc +++ b/chrome/browser/navigation_controller_unittest.cc @@ -9,8 +9,9 @@ #include "chrome/browser/navigation_entry.h" #include "chrome/browser/profile_manager.h" #include "chrome/browser/history/history.h" -#include "chrome/browser/session_service.h" -#include "chrome/browser/session_service_test_helper.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/session_service_test_helper.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/browser/tab_contents.h" #include "chrome/browser/tab_contents_delegate.h" #include "chrome/browser/tab_contents_factory.h" @@ -1529,9 +1530,9 @@ TEST_F(NavigationControllerHistoryTest, NavigationThenBack) { TabNavigation nav(0, url0, GURL(), std::wstring(), std::string(), PageTransition::LINK); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[0]); - nav.url = url1; + nav.set_url(url1); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[1]); - nav.url = url2; + nav.set_url(url2); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[2]); } @@ -1559,6 +1560,6 @@ TEST_F(NavigationControllerHistoryTest, NavigationPruning) { TabNavigation nav(0, url0, GURL(), std::wstring(), std::string(), PageTransition::LINK); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[0]); - nav.url = url2; + nav.set_url(url2); helper_.AssertNavigationEquals(nav, windows_[0]->tabs[0]->navigations[1]); } diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index 76f2f16..3430a3d 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -23,8 +23,9 @@ #include "chrome/browser/navigation_controller.h" #include "chrome/browser/profile_manager.h" #include "chrome/browser/render_process_host.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/tab_restore_service.h" #include "chrome/browser/spellchecker.h" -#include "chrome/browser/tab_restore_service.h" #include "chrome/browser/template_url_fetcher.h" #include "chrome/browser/template_url_model.h" #include "chrome/browser/visitedlink_master.h" @@ -574,7 +575,7 @@ ProfileImpl::ProfileImpl(const std::wstring& path) } ProfileImpl::~ProfileImpl() { - tab_restore_service_.reset(); + tab_restore_service_ = NULL; StopCreateSessionServiceTimer(); // TemplateURLModel schedules a task on the WebDataService from its @@ -878,12 +879,12 @@ Time ProfileImpl::GetStartTime() const { TabRestoreService* ProfileImpl::GetTabRestoreService() { if (!tab_restore_service_.get()) - tab_restore_service_.reset(new TabRestoreService(this)); + tab_restore_service_ = new TabRestoreService(this); return tab_restore_service_.get(); } void ProfileImpl::ResetTabRestoreService() { - tab_restore_service_.reset(NULL); + tab_restore_service_ = NULL; } // To be run in the IO thread to notify all resource message filters that the diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index 51a5c32..44cdf7f 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -339,7 +339,7 @@ class ProfileImpl : public Profile, // See GetStartTime for details. base::Time start_time_; - scoped_ptr<TabRestoreService> tab_restore_service_; + scoped_refptr<TabRestoreService> tab_restore_service_; // This can not be a scoped_refptr because we must release it on the I/O // thread. diff --git a/chrome/browser/session_backend.h b/chrome/browser/session_backend.h deleted file mode 100644 index 011ea95..0000000 --- a/chrome/browser/session_backend.h +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) 2006-2008 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_SESSION_BACKEND_H__ -#define CHROME_BROWSER_SESSION_BACKEND_H__ - -#include <vector> - -#include "base/ref_counted.h" -#include "base/scoped_handle.h" -#include "chrome/browser/session_service.h" - -class Pickle; - -// SessionCommand ------------------------------------------------------------- - -// SessionCommand contains a command id and arbitrary amount chunk of memory. -// -// SessionBackend reads and writes SessionCommands. -// -// A SessionCommand may be created directly from a Pickle, which is useful -// for types of arbitrary length. -class SessionCommand { - public: - // These get written to disk, so we define types for them. - // Type for the identifier. - typedef uint8 id_type; - // Type for writing the size. - typedef uint16 size_type; - - // Creates a session command with the specified id. This allocates a buffer - // of size |size| that must be filled via contents(). - SessionCommand(id_type id, size_type size); - - // Convenience constructor that creates a session command with the specified - // id whose contents is populated from the contents of pickle. - SessionCommand(id_type id, const Pickle& pickle); - - // The contents of the command. - char* contents() { return &(contents_[0]); } - const char* contents() const { return &(contents_[0]); } - - // Identifier for the command. - id_type id() const { return id_; } - - // Size of data. - size_type size() const { return static_cast<size_type>(contents_.size()); } - - // Convenience for extracting the data to a target. Returns false if - // count is not equal to the size of data this command contains. - bool GetPayload(void* dest, size_t count) const; - - // Returns the contents as a pickle. It is up to the caller to delete the - // returned Pickle. The returned Pickle references the underlying data of - // this SessionCommand. If you need it to outlive the command, copy the - // pickle. - Pickle* PayloadAsPickle() const; - - private: - const id_type id_; - std::string contents_; - - DISALLOW_EVIL_CONSTRUCTORS(SessionCommand); -}; - -// SessionBackend ------------------------------------------------------------- - -// SessionBackend is the backend used by SessionService. It is responsible -// for maintaining up to 3 files: -// . The current file, which is the file commands passed to AppendCommands -// get written to. -// . The last file. When created the current file is moved to the last -// file. -// . A save file, which is created with arbitrary commands. -// -// Each file contains an arbitrary set of commands supplied from -// SessionService. - -class SessionBackend : public base::RefCountedThreadSafe<SessionBackend> { - public: - typedef SessionCommand::id_type id_type; - typedef SessionCommand::size_type size_type; - - // Initial size of the buffer used in reading the file. This is exposed - // for testing. - static const int kFileReadBufferSize; - - // Creates a SessionBackend. This method is invoked on the MAIN thread, - // and does NO IO. The real work is done from Init, which is invoked on - // the file thread. - // - // The supplied path is the directory the files are writen to. - explicit SessionBackend(const std::wstring& path_to_dir); - - // Moves the current file to the last file, and recreates the current file. - // - // NOTE: this is invoked before every command, and does nothing if we've - // already Init'ed. - void Init(); - - // Recreates the save file with the specified commands. - // - // This deletes the SessionCommands passed to it. - void SaveSession(const std::vector<SessionCommand*>& commands); - - // Appends the specified commands to the current file. If reset_first is - // true the the current file is recreated. - // - // NOTE: this deletes SessionCommands in commands as well as the supplied - // vector. - void AppendCommands(std::vector<SessionCommand*>* commands, - bool reset_first); - - // Invoked from the service, invokes ReadSessionImpl to do the work. - void ReadSession( - scoped_refptr<SessionService::InternalSavedSessionRequest> request); - - // Reads the commands from the last file, or save file if - // use_save_file is true. - // - // On success, the read commands are added to commands. It is up to the - // caller to delete the commands. - bool ReadSessionImpl(bool use_save_file, - std::vector<SessionCommand*>* commands); - - // If saved_session is true, deletes the saved session, otherwise deletes - // the last file. - void DeleteSession(bool saved_session); - - // Copies the contents of the last session file to the saved session file. - void CopyLastSessionToSavedSession(); - - // Moves the current session to the last and resets the current. This is - // called during startup and if the user launchs the app and no tabbed - // browsers are running. - void MoveCurrentSessionToLastSession(); - - private: - // Recreates the current file such that it only contains the header and - // NO commands. - void ResetFile(); - - // Opens the current file and writes the header. On success a handle to - // the file is returned. - HANDLE OpenAndWriteHeader(const std::wstring& path); - - // Appends the specified commands to the specified file. - bool AppendCommandsToFile(HANDLE handle, - const std::vector<SessionCommand*>& commands); - - // Returns the path to the last file. - std::wstring GetLastSessionPath(); - - // Returns the path to the save file. - std::wstring GetSavedSessionPath(); - - // Returns the path to the current file. - std::wstring GetCurrentSessionPath(); - - // Directory files are relative to. - const std::wstring path_to_dir_; - - // Whether the previous target file is valid. - bool last_session_valid_; - - // Handle to the target file. - ScopedHandle current_session_handle_; - - // Whether we've inited. Remember, the constructor is run on the - // Main thread, all others on the IO thread, hence lazy initialization. - bool inited_; - - // If true, the file is empty (no commands have been added to it). - bool empty_file_; - - DISALLOW_EVIL_CONSTRUCTORS(SessionBackend); -}; - -#endif // #define CHROME_BROWSER_SESSION_BACKEND_H__ - diff --git a/chrome/browser/sessions/base_session_service.cc b/chrome/browser/sessions/base_session_service.cc new file mode 100644 index 0000000..b26f7e0 --- /dev/null +++ b/chrome/browser/sessions/base_session_service.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2006-2008 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/base_session_service.h" + +#include "base/pickle.h" +#include "base/thread.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sessions/session_backend.h" +#include "chrome/browser/sessions/session_types.h" +#include "chrome/browser/tab_contents.h" +#include "chrome/common/stl_util-inl.h" + +// InternalGetCommandsRequest ------------------------------------------------- + +BaseSessionService::InternalGetCommandsRequest::~InternalGetCommandsRequest() { + STLDeleteElements(&commands); +} + +// BaseSessionService --------------------------------------------------------- + +namespace { + +// Helper used by CreateUpdateTabNavigationCommand(). It writes |str| to +// |pickle|, if and only if |str| fits within (|max_bytes| - |*bytes_written|). +// |bytes_written| is incremented to reflect the data written. +void WriteStringToPickle(Pickle& pickle, int* bytes_written, int max_bytes, + const std::string& str) { + int num_bytes = str.size() * sizeof(char); + if (*bytes_written + num_bytes < max_bytes) { + *bytes_written += num_bytes; + pickle.WriteString(str); + } else { + pickle.WriteString(std::string()); + } +} + +// Wide version of WriteStringToPickle. +void WriteWStringToPickle(Pickle& pickle, int* bytes_written, int max_bytes, + const std::wstring& str) { + int num_bytes = str.size() * sizeof(wchar_t); + if (*bytes_written + num_bytes < max_bytes) { + *bytes_written += num_bytes; + pickle.WriteWString(str); + } else { + pickle.WriteWString(std::wstring()); + } +} + +} // namespace + +// Delay between when a command is received, and when we save it to the +// backend. +static const int kSaveDelayMS = 2500; + +// static +const int BaseSessionService::max_persist_navigation_count = 6; + +BaseSessionService::BaseSessionService(SessionType type, + Profile* profile, + const std::wstring& path) + : profile_(profile), + path_(path), + backend_thread_(NULL), +#pragma warning(suppress: 4355) // Okay to pass "this" here. + save_factory_(this), + pending_reset_(false), + commands_since_reset_(0) { + if (profile) { + // We should never be created when off the record. + DCHECK(!profile->IsOffTheRecord()); + } + backend_ = new SessionBackend(type, profile_ ? profile_->GetPath() : path_); + DCHECK(backend_.get()); + backend_thread_ = g_browser_process->file_thread(); + if (!backend_thread_) + backend_->Init(); + // If backend_thread is non-null, backend will init itself as appropriate. +} + +BaseSessionService::~BaseSessionService() { +} + +void BaseSessionService::DeleteLastSession() { + if (!backend_thread()) { + backend()->DeleteLastSession(); + } else { + backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + backend(), &SessionBackend::DeleteLastSession)); + } +} + +void BaseSessionService::ScheduleCommand(SessionCommand* command) { + DCHECK(command); + commands_since_reset_++; + pending_commands_.push_back(command); + StartSaveTimer(); +} + +void BaseSessionService::StartSaveTimer() { + // Don't start a timer when testing (profile == NULL or + // MessageLoop::current() is NULL). + if (MessageLoop::current() && profile() && save_factory_.empty()) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + save_factory_.NewRunnableMethod(&BaseSessionService::Save), + kSaveDelayMS); + } +} + +void BaseSessionService::Save() { + DCHECK(backend()); + + if (pending_commands_.empty()) + return; + + if (!backend_thread()) { + backend()->AppendCommands( + new std::vector<SessionCommand*>(pending_commands_), pending_reset_); + } else { + backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + backend(), &SessionBackend::AppendCommands, + new std::vector<SessionCommand*>(pending_commands_), + pending_reset_)); + } + // Backend took ownership of commands. + pending_commands_.clear(); + + if (pending_reset_) { + commands_since_reset_ = 0; + pending_reset_ = false; + } +} + +SessionCommand* BaseSessionService::CreateUpdateTabNavigationCommand( + SessionID::id_type command_id, + SessionID::id_type tab_id, + int index, + const NavigationEntry& entry) { + // Use pickle to handle marshalling. + Pickle pickle; + pickle.WriteInt(tab_id); + pickle.WriteInt(index); + + // We only allow navigations up to 63k (which should be completely + // reasonable). On the off chance we get one that is too big, try to + // keep the url. + + // Bound the string data (which is variable length) to + // |max_state_size bytes| bytes. + static const SessionCommand::size_type max_state_size = + std::numeric_limits<SessionCommand::size_type>::max() - 1024; + + int bytes_written = 0; + + WriteStringToPickle(pickle, &bytes_written, max_state_size, + entry.display_url().spec()); + + WriteWStringToPickle(pickle, &bytes_written, max_state_size, + entry.title()); + + WriteStringToPickle(pickle, &bytes_written, max_state_size, + entry.content_state()); + + pickle.WriteInt(entry.transition_type()); + int type_mask = entry.has_post_data() ? TabNavigation::HAS_POST_DATA : 0; + pickle.WriteInt(type_mask); + + WriteStringToPickle(pickle, &bytes_written, max_state_size, + entry.referrer().is_valid() ? entry.referrer().spec() : std::string()); + + // Adding more data? Be sure and update TabRestoreService too. + return new SessionCommand(command_id, pickle); +} + +bool BaseSessionService::RestoreUpdateTabNavigationCommand( + const SessionCommand& command, + TabNavigation* navigation, + SessionID::id_type* tab_id) { + scoped_ptr<Pickle> pickle(command.PayloadAsPickle()); + if (!pickle.get()) + return false; + void* iterator = NULL; + std::string url_spec; + if (!pickle->ReadInt(&iterator, tab_id) || + !pickle->ReadInt(&iterator, &(navigation->index_)) || + !pickle->ReadString(&iterator, &url_spec) || + !pickle->ReadWString(&iterator, &(navigation->title_)) || + !pickle->ReadString(&iterator, &(navigation->state_)) || + !pickle->ReadInt(&iterator, + reinterpret_cast<int*>(&(navigation->transition_)))) + return false; + // type_mask did not always exist in the written stream. As such, we + // don't fail if it can't be read. + bool has_type_mask = pickle->ReadInt(&iterator, &(navigation->type_mask_)); + + if (has_type_mask) { + // the "referrer" property was added after type_mask to the written + // stream. As such, we don't fail if it can't be read. + std::string referrer_spec; + pickle->ReadString(&iterator, &referrer_spec); + if (!referrer_spec.empty()) + navigation->referrer_ = GURL(referrer_spec); + } + + navigation->url_ = GURL(url_spec); + return true; +} + +bool BaseSessionService::ShouldTrackEntry(const NavigationEntry& entry) { + // Don't track entries that have post data. Post data may contain passwords + // and other sensitive data users don't want stored to disk. + return entry.display_url().is_valid() && !entry.has_post_data(); +} + +bool BaseSessionService::ShouldTrackEntry(const TabNavigation& navigation) { + // Don't track entries that have post data. Post data may contain passwords + // and other sensitive data users don't want stored to disk. + return navigation.url().is_valid() && + (navigation.type_mask() & TabNavigation::HAS_POST_DATA) == 0; +} + +BaseSessionService::Handle BaseSessionService::ScheduleGetLastSessionCommands( + InternalGetCommandsRequest* request, + CancelableRequestConsumerBase* consumer) { + scoped_refptr<InternalGetCommandsRequest> request_wrapper(request); + AddRequest(request, consumer); + if (backend_thread()) { + backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + backend(), &SessionBackend::ReadLastSessionCommands, request)); + } else { + backend()->ReadLastSessionCommands(request); + } + return request->handle(); +} diff --git a/chrome/browser/sessions/base_session_service.h b/chrome/browser/sessions/base_session_service.h new file mode 100644 index 0000000..49c70c4 --- /dev/null +++ b/chrome/browser/sessions/base_session_service.h @@ -0,0 +1,168 @@ +// Copyright (c) 2006-2008 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_BASE_SESSION_SERVICE_H_ +#define CHROME_BROWSER_SESSIONS_BASE_SESSION_SERVICE_H_ + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/sessions/session_id.h" + +class NavigationEntry; +class Profile; +class SessionBackend; +class SessionCommand; +class SessionService; +class TabNavigation; + +namespace base { +class Thread; +} + +// BaseSessionService is the super class of both tab restore service and +// session service. It contains commonality needed by both, in particular +// it manages a set of SessionCommands that are periodically sent to a +// SessionBackend. +class BaseSessionService : public CancelableRequestProvider, + public base::RefCountedThreadSafe<BaseSessionService> { + public: + // Identifies the type of session service this is. This is used by the + // backend to determine the name of the files. + enum SessionType { + SESSION_RESTORE, + TAB_RESTORE + }; + + // Creates a new BaseSessionService. After creation you need to invoke + // Init. + // |type| gives the type of session service, |profile| the profile and + // |path| the path to save files to. If |profile| is non-NULL, |path| is + // ignored and instead the path comes from the profile. + BaseSessionService(SessionType type, + Profile* profile, + const std::wstring& path); + + virtual ~BaseSessionService(); + + Profile* profile() const { return profile_; } + + // Deletes the last session. + void DeleteLastSession(); + + class InternalGetCommandsRequest; + + typedef Callback2<Handle, scoped_refptr<InternalGetCommandsRequest> >::Type + InternalGetCommandsCallback; + + // Callback used when fetching the last session. The last session consists + // of a vector of SessionCommands. + class InternalGetCommandsRequest : + public CancelableRequest<InternalGetCommandsCallback> { + public: + explicit InternalGetCommandsRequest(CallbackType* callback) + : CancelableRequest(callback) { + } + virtual ~InternalGetCommandsRequest(); + + // The commands. The backend fills this in for us. + std::vector<SessionCommand*> commands; + + private: + DISALLOW_COPY_AND_ASSIGN(InternalGetCommandsRequest); + }; + + protected: + // Returns the backend. + SessionBackend* backend() const { return backend_; } + + // Returns the thread the backend runs on. This returns NULL during testing. + base::Thread* backend_thread() const { return backend_thread_; } + + // Returns the set of commands that needed to be scheduled. The commands + // in the vector are owned by BaseSessionService, until they are scheduled + // on the backend at which point the backend owns the commands. + std::vector<SessionCommand*>& pending_commands() { + return pending_commands_; + } + + // Whether the next save resets the file before writing to it. + void set_pending_reset(bool value) { pending_reset_ = value; } + bool pending_reset() const { return pending_reset_; } + + // Returns the number of commands sent down since the last reset. + int commands_since_reset() const { return commands_since_reset_; } + + // Schedules a command. This adds |command| to pending_commands_ and + // invokes StartSaveTimer to start a timer that invokes Save at a later + // time. + virtual void ScheduleCommand(SessionCommand* command); + + // Starts the timer that invokes Save (if timer isn't already running). + void StartSaveTimer(); + + // Saves pending commands to the backend. This is invoked from the timer + // scheduled by StartSaveTimer. + virtual void Save(); + + // Creates a SessionCommand that represents a navigation. + SessionCommand* CreateUpdateTabNavigationCommand( + SessionID::id_type command_id, + SessionID::id_type tab_id, + int index, + const NavigationEntry& entry); + + // Converts a SessionCommand previously created by + // CreateUpdateTabNavigationCommand into a TabNavigation. Returns true + // on success. If successful |tab_id| is set to the id of the restored tab. + bool RestoreUpdateTabNavigationCommand(const SessionCommand& command, + TabNavigation* navigation, + SessionID::id_type* tab_id); + + // Returns true if the NavigationEntry should be written to disk. + bool ShouldTrackEntry(const NavigationEntry& entry); + + // Returns true if the TabNavigationshould be written to disk. + bool ShouldTrackEntry(const TabNavigation& navigation); + + // Invokes ReadLastSessionCommands with request on the backend thread. + // If testing, ReadLastSessionCommands is invoked directly. + Handle ScheduleGetLastSessionCommands( + InternalGetCommandsRequest* request, + CancelableRequestConsumerBase* consumer); + + // Max number of navigation entries in each direction we'll persist. + static const int max_persist_navigation_count; + + private: + // The profile. This may be null during testing. + Profile* profile_; + + // Path to read from. This is only used if profile_ is NULL. + const std::wstring& path_; + + // The backend. + scoped_refptr<SessionBackend> backend_; + + // Thread backend tasks are run on, is NULL during testing. + base::Thread* backend_thread_; + + // Used to invoke Save. + ScopedRunnableMethodFactory<BaseSessionService> save_factory_; + + // Commands we need to send over to the backend. + std::vector<SessionCommand*> pending_commands_; + + // Whether the backend file should be recreated the next time we send + // over the commands. + bool pending_reset_; + + // The number of commands sent to the backend before doing a reset. + int commands_since_reset_; + + DISALLOW_COPY_AND_ASSIGN(BaseSessionService); +}; + +#endif // CHROME_BROWSER_SESSIONS_BASE_SESSION_SERVICE_H_ diff --git a/chrome/browser/session_backend.cc b/chrome/browser/sessions/session_backend.cc index 0f1f863..dc259d1 100644 --- a/chrome/browser/session_backend.cc +++ b/chrome/browser/sessions/session_backend.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/session_backend.h" +#include "chrome/browser/sessions/session_backend.h" #include <limits> @@ -43,13 +43,14 @@ class SessionFileReader { // Reads the contents of the file specified in the constructor, returning // true on success. It is up to the caller to free all SessionCommands // added to commands. - bool Read(std::vector<SessionCommand*>* commands); + bool Read(BaseSessionService::SessionType type, + std::vector<SessionCommand*>* commands); private: // Reads a single command, returning it. A return value of NULL indicates // either there are no commands, or there was an error. Use errored_ to // distinguish the two. If NULL is returned, and there is no error, it means - // the end of file was sucessfully reached. + // the end of file was successfully reached. SessionCommand* ReadCommand(); // Shifts the unused portion of buffer_ to the beginning and fills the @@ -76,7 +77,8 @@ class SessionFileReader { DISALLOW_EVIL_CONSTRUCTORS(SessionFileReader); }; -bool SessionFileReader::Read(std::vector<SessionCommand*>* commands) { +bool SessionFileReader::Read(BaseSessionService::SessionType type, + std::vector<SessionCommand*>* commands) { if (!handle_.IsValid()) return false; int32 header[2]; @@ -93,8 +95,13 @@ bool SessionFileReader::Read(std::vector<SessionCommand*>* commands) { read_commands->push_back(command); if (!errored_) read_commands->swap(*commands); - UMA_HISTOGRAM_TIMES(L"SessionRestore.read_session_file_time", - TimeTicks::Now() - start_time); + if (type == BaseSessionService::TAB_RESTORE) { + UMA_HISTOGRAM_TIMES(L"TabRestore.read_session_file_time", + TimeTicks::Now() - start_time); + } else { + UMA_HISTOGRAM_TIMES(L"SessionRestore.read_session_file_time", + TimeTicks::Now() - start_time); + } return !errored_; } @@ -166,47 +173,23 @@ bool SessionFileReader::FillBuffer() { } // namespace -// SessionCommand ------------------------------------------------------------- - -SessionCommand::SessionCommand(id_type id, size_type size) - : id_(id), - contents_(size, 0) { -} - -SessionCommand::SessionCommand(id_type id, const Pickle& pickle) - : id_(id), - contents_(pickle.size(), 0) { - DCHECK(pickle.size() < std::numeric_limits<size_type>::max()); - memcpy(contents(), pickle.data(), pickle.size()); -} - -bool SessionCommand::GetPayload(void* dest, size_t count) const { - if (size() != count) - return false; - memcpy(dest, &(contents_[0]), count); - return true; -} - -Pickle* SessionCommand::PayloadAsPickle() const { - return new Pickle(contents(), static_cast<int>(size())); -} - // SessionBackend ------------------------------------------------------------- -// Target file name. -static const wchar_t* const kCurrentSessionFileName = L"Current Session"; +// File names (current and previous) for a type of TAB. +static const wchar_t* const kCurrentTabSessionFileName = L"Current Tabs"; +static const wchar_t* const kLastTabSessionFileName = L"Last Tabs"; -// Previous target file. +// File names (current and previous) for a type of SESSION. +static const wchar_t* const kCurrentSessionFileName = L"Current Session"; static const wchar_t* const kLastSessionFileName = L"Last Session"; -// Saved session file name. -static const wchar_t* const kSavedSessionFileName = L"Saved Session"; - // static const int SessionBackend::kFileReadBufferSize = 1024; -SessionBackend::SessionBackend(const std::wstring& path_to_dir) - : path_to_dir_(path_to_dir), +SessionBackend::SessionBackend(BaseSessionService::SessionType type, + const std::wstring& path_to_dir) + : type_(type), + path_to_dir_(path_to_dir), last_session_valid_(false), inited_(false), empty_file_(true) { @@ -225,15 +208,6 @@ void SessionBackend::Init() { MoveCurrentSessionToLastSession(); } -void SessionBackend::SaveSession( - const std::vector<SessionCommand*>& commands) { - Init(); - ScopedHandle handle(OpenAndWriteHeader(GetSavedSessionPath())); - if (handle.IsValid()) - AppendCommandsToFile(handle, commands); - STLDeleteContainerPointers(commands.begin(), commands.end()); -} - void SessionBackend::AppendCommands( std::vector<SessionCommand*>* commands, bool reset_first) { @@ -249,36 +223,27 @@ void SessionBackend::AppendCommands( delete commands; } -void SessionBackend::ReadSession( - scoped_refptr<SessionService::InternalSavedSessionRequest> request) { +void SessionBackend::ReadLastSessionCommands( + scoped_refptr<BaseSessionService::InternalGetCommandsRequest> request) { if (request->canceled()) return; Init(); - ReadSessionImpl(request->is_saved_session, &(request->commands)); + ReadLastSessionCommandsImpl(&(request->commands)); request->ForwardResult( - SessionService::InternalSavedSessionRequest::TupleType(request->handle(), - request)); + BaseSessionService::InternalGetCommandsRequest::TupleType( + request->handle(), request)); } -bool SessionBackend::ReadSessionImpl(bool use_save_file, - std::vector<SessionCommand*>* commands) { +bool SessionBackend::ReadLastSessionCommandsImpl( + std::vector<SessionCommand*>* commands) { Init(); - const std::wstring path = - use_save_file ? GetSavedSessionPath() : GetLastSessionPath(); - SessionFileReader file_reader(path); - return file_reader.Read(commands); + SessionFileReader file_reader(GetLastSessionPath()); + return file_reader.Read(type_, commands); } -void SessionBackend::DeleteSession(bool saved_session) { +void SessionBackend::DeleteLastSession() { Init(); - const std::wstring path = - saved_session ? GetSavedSessionPath() : GetLastSessionPath(); - file_util::Delete(path, false); -} - -void SessionBackend::CopyLastSessionToSavedSession() { - Init(); - file_util::CopyFile(GetLastSessionPath(), GetSavedSessionPath()); + file_util::Delete(GetLastSessionPath(), false); } void SessionBackend::MoveCurrentSessionToLastSession() { @@ -292,8 +257,13 @@ void SessionBackend::MoveCurrentSessionToLastSession() { if (file_util::PathExists(current_session_path)) { int64 file_size; if (file_util::GetFileSize(current_session_path, &file_size)) { - UMA_HISTOGRAM_COUNTS(L"SessionRestore.last_session_file_size", - static_cast<int>(file_size / 1024)); + if (type_ == BaseSessionService::TAB_RESTORE) { + UMA_HISTOGRAM_COUNTS(L"TabRestore.last_session_file_size", + static_cast<int>(file_size / 1024)); + } else { + UMA_HISTOGRAM_COUNTS(L"SessionRestore.last_session_file_size", + static_cast<int>(file_size / 1024)); + } } last_session_valid_ = file_util::Move(current_session_path, last_session_path); @@ -314,7 +284,10 @@ bool SessionBackend::AppendCommandsToFile( DWORD wrote; const size_type content_size = static_cast<size_type>((*i)->size()); const size_type total_size = content_size + sizeof(id_type); - UMA_HISTOGRAM_COUNTS(L"SessionRestore.command_size", total_size); + if (type_ == BaseSessionService::TAB_RESTORE) + UMA_HISTOGRAM_COUNTS(L"TabRestore.command_size", total_size); + else + UMA_HISTOGRAM_COUNTS(L"SessionRestore.command_size", total_size); if (!WriteFile(handle, &total_size, sizeof(total_size), &wrote, NULL) || wrote != sizeof(total_size)) { NOTREACHED() << "error writing"; @@ -362,18 +335,18 @@ HANDLE SessionBackend::OpenAndWriteHeader(const std::wstring& path) { std::wstring SessionBackend::GetLastSessionPath() { std::wstring path = path_to_dir_; - file_util::AppendToPath(&path, kLastSessionFileName); - return path; -} - -std::wstring SessionBackend::GetSavedSessionPath() { - std::wstring path = path_to_dir_; - file_util::AppendToPath(&path, kSavedSessionFileName); + if (type_ == BaseSessionService::TAB_RESTORE) + file_util::AppendToPath(&path, kLastTabSessionFileName); + else + file_util::AppendToPath(&path, kLastSessionFileName); return path; } std::wstring SessionBackend::GetCurrentSessionPath() { std::wstring path = path_to_dir_; - file_util::AppendToPath(&path, kCurrentSessionFileName); + if (type_ == BaseSessionService::TAB_RESTORE) + file_util::AppendToPath(&path, kCurrentTabSessionFileName); + else + file_util::AppendToPath(&path, kCurrentSessionFileName); return path; } diff --git a/chrome/browser/sessions/session_backend.h b/chrome/browser/sessions/session_backend.h new file mode 100644 index 0000000..1519d3f --- /dev/null +++ b/chrome/browser/sessions/session_backend.h @@ -0,0 +1,122 @@ +// Copyright (c) 2006-2008 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_SESSION_BACKEND_H_ +#define CHROME_BROWSER_SESSIONS_SESSION_BACKEND_H_ + +#include <vector> + +#include "base/ref_counted.h" +#include "base/scoped_handle.h" +#include "chrome/browser/sessions/base_session_service.h" +#include "chrome/browser/sessions/session_command.h" + +class Pickle; + +// SessionBackend ------------------------------------------------------------- + +// SessionBackend is the backend used by BaseSessionService. It is responsible +// for maintaining two files: +// . The current file, which is the file commands passed to AppendCommands +// get written to. +// . The last file. When created the current file is moved to the last +// file. +// +// Each file contains an arbitrary set of commands supplied from +// BaseSessionService. A command consists of a unique id and a stream of bytes. +// SessionBackend does not use the id in anyway, that is used by +// BaseSessionService. +class SessionBackend : public base::RefCountedThreadSafe<SessionBackend> { + public: + typedef SessionCommand::id_type id_type; + typedef SessionCommand::size_type size_type; + + // Initial size of the buffer used in reading the file. This is exposed + // for testing. + static const int kFileReadBufferSize; + + // Creates a SessionBackend. This method is invoked on the MAIN thread, + // and does no IO. The real work is done from Init, which is invoked on + // the file thread. + // + // |path_to_dir| gives the path the files are written two, and |type| + // indicates which service is using this backend. |type| is used to determine + // the name of the files to use as well as for logging. + SessionBackend(BaseSessionService::SessionType type, + const std::wstring& path_to_dir); + + // Moves the current file to the last file, and recreates the current file. + // + // NOTE: this is invoked before every command, and does nothing if we've + // already Init'ed. + void Init(); + + // Appends the specified commands to the current file. If reset_first is + // true the the current file is recreated. + // + // NOTE: this deletes SessionCommands in commands as well as the supplied + // vector. + void AppendCommands(std::vector<SessionCommand*>* commands, + bool reset_first); + + // Invoked from the service to read the commands that make up the last + // session, invokes ReadSessionImpl to do the work. + void ReadLastSessionCommands( + scoped_refptr<BaseSessionService::InternalGetCommandsRequest> request); + + // Reads the commands from the last file. + // + // On success, the read commands are added to commands. It is up to the + // caller to delete the commands. + bool ReadLastSessionCommandsImpl(std::vector<SessionCommand*>* commands); + + // Deletes the file containing the commands for the last session. + void DeleteLastSession(); + + // Moves the current session to the last and resets the current. This is + // called during startup and if the user launchs the app and no tabbed + // browsers are running. + void MoveCurrentSessionToLastSession(); + + private: + // Recreates the current file such that it only contains the header and + // NO commands. + void ResetFile(); + + // Opens the current file and writes the header. On success a handle to + // the file is returned. + HANDLE OpenAndWriteHeader(const std::wstring& path); + + // Appends the specified commands to the specified file. + bool AppendCommandsToFile(HANDLE handle, + const std::vector<SessionCommand*>& commands); + + const BaseSessionService::SessionType type_; + + // Returns the path to the last file. + std::wstring GetLastSessionPath(); + + // Returns the path to the current file. + std::wstring GetCurrentSessionPath(); + + // Directory files are relative to. + const std::wstring path_to_dir_; + + // Whether the previous target file is valid. + bool last_session_valid_; + + // Handle to the target file. + ScopedHandle current_session_handle_; + + // Whether we've inited. Remember, the constructor is run on the + // Main thread, all others on the IO thread, hence lazy initialization. + bool inited_; + + // If true, the file is empty (no commands have been added to it). + bool empty_file_; + + DISALLOW_COPY_AND_ASSIGN(SessionBackend); +}; + +#endif // #define CHROME_BROWSER_SESSIONS_SESSION_BACKEND_H_ diff --git a/chrome/browser/session_backend_unittest.cc b/chrome/browser/sessions/session_backend_unittest.cc index 8023648..b7bfd92 100644 --- a/chrome/browser/session_backend_unittest.cc +++ b/chrome/browser/sessions/session_backend_unittest.cc @@ -5,7 +5,8 @@ #include "base/file_util.h" #include "base/path_service.h" #include "base/time.h" -#include "chrome/browser/session_backend.h" +#include "chrome/browser/sessions/session_backend.h" +#include "chrome/common/stl_util-inl.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -57,7 +58,8 @@ class SessionBackendTest : public testing::Test { }; TEST_F(SessionBackendTest, SimpleReadWrite) { - scoped_refptr<SessionBackend> backend(new SessionBackend(path_)); + scoped_refptr<SessionBackend> backend( + new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); struct TestData data = { 1, "a" }; std::vector<SessionCommand*> commands; commands.push_back(CreateCommandFromData(data)); @@ -66,8 +68,8 @@ TEST_F(SessionBackendTest, SimpleReadWrite) { // Read it back in. backend = NULL; - backend = new SessionBackend(path_); - backend->ReadSessionImpl(false, &commands); + backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); + backend->ReadLastSessionCommandsImpl(&commands); ASSERT_EQ(1, commands.size()); AssertCommandEqualsData(data, commands[0]); @@ -75,14 +77,14 @@ TEST_F(SessionBackendTest, SimpleReadWrite) { STLDeleteElements(&commands); backend = NULL; - backend = new SessionBackend(path_); - backend->ReadSessionImpl(false, &commands); + backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); + backend->ReadLastSessionCommandsImpl(&commands); ASSERT_EQ(0, commands.size()); // Make sure we can delete. - backend->DeleteSession(false); - backend->ReadSessionImpl(false, &commands); + backend->DeleteLastSession(); + backend->ReadLastSessionCommandsImpl(&commands); ASSERT_EQ(0, commands.size()); } @@ -104,11 +106,12 @@ TEST_F(SessionBackendTest, RandomData) { }; for (int i = 0; i < arraysize(data); ++i) { - scoped_refptr<SessionBackend> backend(new SessionBackend(path_)); + scoped_refptr<SessionBackend> backend( + new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); std::vector<SessionCommand*> commands; if (i != 0) { // Read previous data. - backend->ReadSessionImpl(false, &commands); + backend->ReadLastSessionCommandsImpl(&commands); ASSERT_EQ(i, commands.size()); for (std::vector<SessionCommand*>::iterator j = commands.begin(); j != commands.end(); ++j) { @@ -128,7 +131,8 @@ TEST_F(SessionBackendTest, BigData) { { 2, "ab" }, }; - scoped_refptr<SessionBackend> backend(new SessionBackend(path_)); + scoped_refptr<SessionBackend> backend( + new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); std::vector<SessionCommand*> commands; commands.push_back(CreateCommandFromData(data[0])); const SessionCommand::size_type big_size = SessionBackend::kFileReadBufferSize + 100; @@ -142,9 +146,9 @@ TEST_F(SessionBackendTest, BigData) { commands.clear(); backend = NULL; - backend = new SessionBackend(path_); + backend = new SessionBackend(BaseSessionService::SESSION_RESTORE, path_); commands.clear(); - backend->ReadSessionImpl(false, &commands); + backend->ReadLastSessionCommandsImpl(&commands); ASSERT_EQ(3, commands.size()); AssertCommandEqualsData(data[0], commands[0]); AssertCommandEqualsData(data[1], commands[2]); @@ -157,41 +161,20 @@ TEST_F(SessionBackendTest, BigData) { STLDeleteElements(&commands); } -TEST_F(SessionBackendTest, SaveSession) { - struct TestData data[] = { - { 1, "a" }, - { 2, "ab" }, - }; - - scoped_refptr<SessionBackend> backend(new SessionBackend(path_)); - std::vector<SessionCommand*> commands; - for (int i = 0; i < arraysize(data); ++i) { - commands.push_back(CreateCommandFromData(data[i])); - } - backend->SaveSession(commands); - - commands.clear(); - - backend->ReadSessionImpl(true, &commands); - ASSERT_EQ(arraysize(data), commands.size()); - for (int i = 0; i < arraysize(data); ++i) - AssertCommandEqualsData(data[i], commands[i]); - STLDeleteElements(&commands); -} - TEST_F(SessionBackendTest, EmptyCommand) { TestData empty_command; empty_command.command_id = 1; - scoped_refptr<SessionBackend> backend(new SessionBackend(path_)); - std::vector<SessionCommand*> commands; - commands.push_back(CreateCommandFromData(empty_command)); - backend->SaveSession(commands); - - commands.clear(); + scoped_refptr<SessionBackend> backend( + new SessionBackend(BaseSessionService::SESSION_RESTORE, path_)); + std::vector<SessionCommand*>* empty_commands = + new std::vector<SessionCommand*>(); + empty_commands->push_back(CreateCommandFromData(empty_command)); + backend->AppendCommands(empty_commands, true); + backend->MoveCurrentSessionToLastSession(); - backend->ReadSessionImpl(true, &commands); + std::vector<SessionCommand*> commands; + backend->ReadLastSessionCommandsImpl(&commands); ASSERT_EQ(1, commands.size()); AssertCommandEqualsData(empty_command, commands[0]); STLDeleteElements(&commands); } - diff --git a/chrome/browser/sessions/session_command.cc b/chrome/browser/sessions/session_command.cc new file mode 100644 index 0000000..e5ca2f5 --- /dev/null +++ b/chrome/browser/sessions/session_command.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2006-2008 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 <limits> + +#include "chrome/browser/sessions/session_command.h" + +#include "base/pickle.h" + +SessionCommand::SessionCommand(id_type id, size_type size) + : id_(id), + contents_(size, 0) { +} + +SessionCommand::SessionCommand(id_type id, const Pickle& pickle) + : id_(id), + contents_(pickle.size(), 0) { + DCHECK(pickle.size() < std::numeric_limits<size_type>::max()); + memcpy(contents(), pickle.data(), pickle.size()); +} + +bool SessionCommand::GetPayload(void* dest, size_t count) const { + if (size() != count) + return false; + memcpy(dest, &(contents_[0]), count); + return true; +} + +Pickle* SessionCommand::PayloadAsPickle() const { + return new Pickle(contents(), static_cast<int>(size())); +} diff --git a/chrome/browser/sessions/session_command.h b/chrome/browser/sessions/session_command.h new file mode 100644 index 0000000..beb7d85 --- /dev/null +++ b/chrome/browser/sessions/session_command.h @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 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_SESSION_COMMAND_H_ +#define CHROME_BROWSER_SESSIONS_SESSION_COMMAND_H_ + +#include <string> + +#include "base/basictypes.h" + +class Pickle; + +// SessionCommand contains a command id and arbitrary chunk of data. The id +// and chunk of data are specific to the service creating them. +// +// Both TabRestoreService and SessionService use SessionCommands to represent +// state on disk. +// +// There are two ways to create a SessionCommand: +// . Specificy the size of the data block to create. This is useful for +// commands that have a fixed size. +// . From a pickle, this is useful for commands whose length varies. +class SessionCommand { + public: + // These get written to disk, so we define types for them. + // Type for the identifier. + typedef uint8 id_type; + // Type for writing the size. + typedef uint16 size_type; + + // Creates a session command with the specified id. This allocates a buffer + // of size |size| that must be filled via contents(). + SessionCommand(id_type id, size_type size); + + // Convenience constructor that creates a session command with the specified + // id whose contents is populated from the contents of pickle. + SessionCommand(id_type id, const Pickle& pickle); + + // The contents of the command. + char* contents() { return &(contents_[0]); } + const char* contents() const { return &(contents_[0]); } + + // Identifier for the command. + id_type id() const { return id_; } + + // Size of data. + size_type size() const { return static_cast<size_type>(contents_.size()); } + + // Convenience for extracting the data to a target. Returns false if + // count is not equal to the size of data this command contains. + bool GetPayload(void* dest, size_t count) const; + + // Returns the contents as a pickle. It is up to the caller to delete the + // returned Pickle. The returned Pickle references the underlying data of + // this SessionCommand. If you need it to outlive the command, copy the + // pickle. + Pickle* PayloadAsPickle() const; + + private: + const id_type id_; + std::string contents_; + + DISALLOW_COPY_AND_ASSIGN(SessionCommand); +}; + +#endif // CHROME_BROWSER_SESSIONS_SESSION_COMMAND_H_ diff --git a/chrome/browser/sessions/session_id.cc b/chrome/browser/sessions/session_id.cc new file mode 100644 index 0000000..fc7e249 --- /dev/null +++ b/chrome/browser/sessions/session_id.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2006-2008 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_id.h" + +static SessionID::id_type next_id = 1; + +SessionID::SessionID() { + id_ = next_id++; +} diff --git a/chrome/browser/session_id.h b/chrome/browser/sessions/session_id.h index 8ecf84e..bab33c85 100644 --- a/chrome/browser/session_id.h +++ b/chrome/browser/sessions/session_id.h @@ -2,15 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SESSION_ID_H_ -#define CHROME_BROWSER_SESSION_ID_H_ +#ifndef CHROME_BROWSER_SESSIONS_SESSION_ID_H_ +#define CHROME_BROWSER_SESSIONS_SESSION_ID_H_ -// SessionID ------------------------------------------------------------------ +#include "base/basictypes.h" -// Uniquely identifies a session, tab or window. +class SessionService; +// Uniquely identifies a tab or window for the duration of a session. class SessionID { - friend class SessionService; public: typedef int32 id_type; @@ -20,10 +20,9 @@ class SessionID { // Returns the underlying id. id_type id() const { return id_; } - // Returns true if the two commands are equal. - bool Equals(const SessionID& other) const; - private: + friend class SessionService; + explicit SessionID(id_type id) : id_(id) {} // Resets the id. This is used when restoring a session @@ -32,5 +31,4 @@ class SessionID { id_type id_; }; -#endif // CHROME_BROWSER_SESSION_ID_H_ - +#endif // CHROME_BROWSER_SESSIONS_SESSION_ID_H_ diff --git a/chrome/browser/session_restore.cc b/chrome/browser/sessions/session_restore.cc index 258ed7c..74038ce 100644 --- a/chrome/browser/session_restore.cc +++ b/chrome/browser/sessions/session_restore.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/session_restore.h" +#include "chrome/browser/sessions/session_restore.h" #include <vector> @@ -12,7 +12,8 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/navigation_controller.h" #include "chrome/browser/profile.h" -#include "chrome/browser/session_service.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/browser/tab_contents.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" @@ -182,14 +183,12 @@ class SessionRestoreImpl : public NotificationObserver { public: SessionRestoreImpl(Profile* profile, Browser* browser, - bool use_saved_session, bool synchronous, bool clobber_existing_window, bool always_create_tabbed_browser, const std::vector<GURL>& urls_to_open) : profile_(profile), browser_(browser), - use_saved_session_(use_saved_session), synchronous_(synchronous), clobber_existing_window_(clobber_existing_window), always_create_tabbed_browser_(always_create_tabbed_browser), @@ -199,12 +198,9 @@ class SessionRestoreImpl : public NotificationObserver { void SessionRestoreImpl::Restore() { SessionService* session_service = profile_->GetSessionService(); DCHECK(session_service); - SessionService::SavedSessionCallback* callback = + SessionService::LastSessionCallback* callback = NewCallback(this, &SessionRestoreImpl::OnGotSession); - if (use_saved_session_) - session_service->GetSavedSession(&request_consumer_, callback); - else - session_service->GetLastSession(&request_consumer_, callback); + session_service->GetLastSession(&request_consumer_, callback); if (synchronous_) { MessageLoop::current()->Run(); @@ -384,10 +380,6 @@ class SessionRestoreImpl : public NotificationObserver { // The first browser to restore to, may be null. Browser* browser_; - // Whether we're restoring the saved session (true) or the last session - // (false). - const bool use_saved_session_; - // Whether or not restore is synchronous. const bool synchronous_; @@ -420,7 +412,6 @@ size_t SessionRestore::num_tabs_to_load_ = 0; static void Restore(Profile* profile, Browser* browser, - bool use_saved_session, bool synchronous, bool clobber_existing_window, bool always_create_tabbed_browser, @@ -430,28 +421,25 @@ static void Restore(Profile* profile, return; // SessionRestoreImpl takes care of deleting itself when done. SessionRestoreImpl* restorer = - new SessionRestoreImpl(profile, browser, use_saved_session, - synchronous, clobber_existing_window, - always_create_tabbed_browser, - urls_to_open); + new SessionRestoreImpl(profile, browser, synchronous, + clobber_existing_window, + always_create_tabbed_browser, urls_to_open); restorer->Restore(); } // static void SessionRestore::RestoreSession(Profile* profile, Browser* browser, - bool use_saved_session, bool clobber_existing_window, bool always_create_tabbed_browser, const std::vector<GURL>& urls_to_open) { - Restore(profile, browser, use_saved_session, false, clobber_existing_window, + Restore(profile, browser, false, clobber_existing_window, always_create_tabbed_browser, urls_to_open); } // static void SessionRestore::RestoreSessionSynchronously( Profile* profile, - bool use_saved_session, const std::vector<GURL>& urls_to_open) { - Restore(profile, NULL, use_saved_session, true, false, true, urls_to_open); + Restore(profile, NULL, true, false, true, urls_to_open); } diff --git a/chrome/browser/session_restore.h b/chrome/browser/sessions/session_restore.h index 9ebe7d9..eb44d9a 100644 --- a/chrome/browser/session_restore.h +++ b/chrome/browser/sessions/session_restore.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SESSION_RESTORE_H__ -#define CHROME_BROWSER_SESSION_RESTORE_H__ +#ifndef CHROME_BROWSER_SESSIONS_SESSION_RESTORE_H_ +#define CHROME_BROWSER_SESSIONS_SESSION_RESTORE_H_ #include <vector> @@ -30,7 +30,6 @@ class SessionRestore { // If urls_to_open is non-empty, a tab is added for each of the URLs. static void RestoreSession(Profile* profile, Browser* browser, - bool use_saved_session, bool clobber_existing_window, bool always_create_tabbed_browser, const std::vector<GURL>& urls_to_open); @@ -41,7 +40,6 @@ class SessionRestore { // If urls_to_open is non-empty, a tab is added for each of the URLs. static void RestoreSessionSynchronously( Profile* profile, - bool use_saved_session, const std::vector<GURL>& urls_to_open); // The max number of non-selected tabs SessionRestore loads when restoring @@ -51,8 +49,7 @@ class SessionRestore { private: SessionRestore(); - DISALLOW_EVIL_CONSTRUCTORS(SessionRestore); + DISALLOW_COPY_AND_ASSIGN(SessionRestore); }; -#endif // CHROME_BROWSER_SESSION_RESTORE_H__ - +#endif // CHROME_BROWSER_SESSIONS_SESSION_RESTORE_H_ diff --git a/chrome/browser/session_restore_uitest.cc b/chrome/browser/sessions/session_restore_uitest.cc index f93098a..f93098a 100644 --- a/chrome/browser/session_restore_uitest.cc +++ b/chrome/browser/sessions/session_restore_uitest.cc diff --git a/chrome/browser/session_service.cc b/chrome/browser/sessions/session_service.cc index fa11168..6016651 100644 --- a/chrome/browser/session_service.cc +++ b/chrome/browser/sessions/session_service.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/session_service.h" +#include "chrome/browser/sessions/session_service.h" #include <limits> @@ -17,9 +17,10 @@ #include "chrome/browser/navigation_controller.h" #include "chrome/browser/navigation_entry.h" #include "chrome/browser/profile.h" -#include "chrome/browser/session_backend.h" -#include "chrome/browser/session_restore.h" #include "chrome/browser/session_startup_pref.h" +#include "chrome/browser/sessions/session_backend.h" +#include "chrome/browser/sessions/session_restore.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/browser/tab_contents.h" #include "chrome/common/notification_details.h" #include "chrome/common/notification_service.h" @@ -47,18 +48,32 @@ static const SessionCommand::id_type kCommandSetWindowBounds2 = 10; static const SessionCommand::id_type kCommandTabNavigationPathPrunedFromFront = 11; -// Max number of navigation entries in each direction we'll persist. -static const int kMaxNavigationCountToPersist = 6; - -// Delay between when a command is received, and when we save it to the -// backend. -static const int kSaveDelayMS = 2500; - // Every kWritesPerReset commands triggers recreating the file. static const int kWritesPerReset = 250; namespace { +// The callback from GetLastSession is internally routed to SessionService +// first and then the caller. This is done so that the SessionWindows can be +// recreated from the SessionCommands and the SessionWindows passed to the +// caller. The following class is used for this. +class InternalLastSessionRequest : + public BaseSessionService::InternalGetCommandsRequest { + public: + InternalLastSessionRequest( + CallbackType* callback, + SessionService::LastSessionCallback* real_callback) + : BaseSessionService::InternalGetCommandsRequest(callback), + real_callback(real_callback) { + } + + // The callback supplied to GetLastSession. + scoped_ptr<SessionService::LastSessionCallback> real_callback; + + private: + DISALLOW_COPY_AND_ASSIGN(InternalLastSessionRequest); +}; + // Various payload structures. struct ClosedPayload { SessionID::id_type id; @@ -91,75 +106,26 @@ typedef IDAndIndexPayload WindowTypePayload; typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; -// Helper used by CreateUpdateTabNavigationCommand(). It writes |str| to -// |pickle|, if and only if |str| fits within (|max_bytes| - |*bytes_written|). -// |bytes_written| is incremented to reflect the data written. -void WriteStringToPickle(Pickle& pickle, int* bytes_written, int max_bytes, - const std::string& str) { - int num_bytes = str.size() * sizeof(char); - if (*bytes_written + num_bytes < max_bytes) { - *bytes_written += num_bytes; - pickle.WriteString(str); - } else { - pickle.WriteString(std::string()); - } -} - -// Wide version of WriteStringToPickle. -void WriteWStringToPickle(Pickle& pickle, int* bytes_written, int max_bytes, - const std::wstring& str) { - int num_bytes = str.size() * sizeof(wchar_t); - if (*bytes_written + num_bytes < max_bytes) { - *bytes_written += num_bytes; - pickle.WriteWString(str); - } else { - pickle.WriteWString(std::wstring()); - } -} - } // namespace -// SessionID ------------------------------------------------------------------ - -static SessionID::id_type next_id = 1; - -SessionID::SessionID() { - id_ = next_id++; -} - // SessionService ------------------------------------------------------------- SessionService::SessionService(Profile* profile) - : profile_(profile), -#pragma warning(suppress: 4355) // Okay to pass "this" here. - save_factory_(this), - pending_reset_(false), + : BaseSessionService(SESSION_RESTORE, profile, std::wstring()), has_open_tabbed_browsers_(false), move_on_new_browser_(false) { - DCHECK(profile); - // We should never be created when off the record. - DCHECK(!profile->IsOffTheRecord()); - Init(profile->GetPath()); + Init(); } SessionService::SessionService(const std::wstring& save_path) - : profile_(NULL), -#pragma warning(suppress: 4355) // Okay to pass "this" here. - save_factory_(this), - pending_reset_(false), + : BaseSessionService(SESSION_RESTORE, NULL, save_path), has_open_tabbed_browsers_(false), move_on_new_browser_(false) { - Init(save_path); + Init(); } SessionService::~SessionService() { - if (!backend_.get()) - return; Save(); - // If no pending requests, then the backend closes immediately and is - // deleted. Otherwise the backend is deleted after all pending requests on - // the file thread complete, which is done before the process exits. - backend_ = NULL; // Unregister our notifications. NotificationService::current()->RemoveObserver( @@ -187,11 +153,11 @@ void SessionService::MoveCurrentSessionToLastSession() { Save(); - if (!backend_thread_) { - backend_->MoveCurrentSessionToLastSession(); + if (!backend_thread()) { + backend()->MoveCurrentSessionToLastSession(); } else { - backend_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - backend_.get(), &SessionBackend::MoveCurrentSessionToLastSession)); + backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + backend(), &SessionBackend::MoveCurrentSessionToLastSession)); } } @@ -363,7 +329,8 @@ void SessionService::UpdateTabNavigation(const SessionID& window_id, range.first = std::min(index, range.first); range.second = std::max(index, range.second); } - ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, index, entry)); + ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, + tab_id.id(), index, entry)); } void SessionService::TabRestored(NavigationController* controller) { @@ -371,7 +338,7 @@ void SessionService::TabRestored(NavigationController* controller) { return; BuildCommandsForTab(controller->window_id(), controller, -1, - &pending_commands_, NULL); + &pending_commands(), NULL); StartSaveTimer(); } @@ -402,49 +369,16 @@ void SessionService::SetSelectedTabInWindow(const SessionID& window_id, ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index)); } -SessionService::Handle SessionService::GetSavedSession( - CancelableRequestConsumerBase* consumer, - SavedSessionCallback* callback) { - return GetSessionImpl(consumer, callback, true); -} - SessionService::Handle SessionService::GetLastSession( CancelableRequestConsumerBase* consumer, - SavedSessionCallback* callback) { - return GetSessionImpl(consumer, callback, false); -} - -void SessionService::CreateSavedSession() { - std::vector<SessionCommand*> commands; - // Commands are freed by backend. - BuildCommandsFromBrowsers(&commands, NULL, NULL); - if (!backend_thread_) { - backend_->SaveSession(commands); - } else { - backend_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - backend_.get(), &SessionBackend::SaveSession, commands)); - } -} - -void SessionService::DeleteSession(bool saved_session) { - if (!backend_thread_) { - backend_->DeleteSession(saved_session); - } else { - backend_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - backend_.get(), &SessionBackend::DeleteSession, saved_session)); - } + LastSessionCallback* callback) { + return ScheduleGetLastSessionCommands( + new InternalLastSessionRequest( + NewCallback(this, &SessionService::OnGotLastSessionCommands), + callback), consumer); } -void SessionService::CopyLastSessionToSavedSession() { - if (!backend_thread_) { - backend_->CopyLastSessionToSavedSession(); - } else { - backend_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - backend_.get(), &SessionBackend::CopyLastSessionToSavedSession)); - } -} - -void SessionService::Init(const std::wstring& path) { +void SessionService::Init() { // Register for the notifications we're interested in. NotificationService::current()->AddObserver( this, NOTIFY_TAB_PARENTED, NotificationService::AllSources()); @@ -458,14 +392,6 @@ void SessionService::Init(const std::wstring& path) { this, NOTIFY_NAV_ENTRY_COMMITTED, NotificationService::AllSources()); NotificationService::current()->AddObserver( this, NOTIFY_BROWSER_OPENED, NotificationService::AllSources()); - - DCHECK(!path.empty()); - commands_since_reset_ = 0; - backend_ = new SessionBackend(path); - backend_thread_ = g_browser_process->file_thread(); - if (!backend_thread_) - backend_->Init(); - // If backend_thread, backend will init itself as appropriate. } void SessionService::Observe(NotificationType type, @@ -475,7 +401,7 @@ void SessionService::Observe(NotificationType type, switch (type) { case NOTIFY_BROWSER_OPENED: { Browser* browser = Source<Browser>(source).ptr(); - if (browser->profile() != profile_ || + if (browser->profile() != profile() || !should_track_changes_for_browser_type(browser->type())) { return; } @@ -488,10 +414,10 @@ void SessionService::Observe(NotificationType type, MoveCurrentSessionToLastSession(); move_on_new_browser_ = false; } - SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile_); + SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile()); if (pref.type == SessionStartupPref::LAST) { SessionRestore::RestoreSession( - profile_, browser, false, false, false, std::vector<GURL>()); + profile(), browser, false, false, std::vector<GURL>()); } } SetWindowType(browser->session_id(), browser->type()); @@ -555,21 +481,6 @@ void SessionService::Observe(NotificationType type, } } -SessionService::Handle SessionService::GetSessionImpl( - CancelableRequestConsumerBase* consumer, - SavedSessionCallback* callback, - bool is_saved_session) { - scoped_refptr<InternalSavedSessionRequest> request( - new InternalSavedSessionRequest( - NewCallback(this, &SessionService::OnGotSessionCommands), - callback, - is_saved_session)); - AddRequest(request.get(), consumer); - backend_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - backend_.get(), &SessionBackend::ReadSession, request)); - return request->handle(); -} - SessionCommand* SessionService::CreateSetSelectedTabInWindow( const SessionID& window_id, int index) { @@ -649,46 +560,6 @@ SessionCommand* SessionService::CreateWindowClosedCommand( return command; } -SessionCommand* SessionService::CreateUpdateTabNavigationCommand( - const SessionID& tab_id, - int index, - const NavigationEntry& entry) { - // Use pickle to handle marshalling. - Pickle pickle; - pickle.WriteInt(tab_id.id()); - pickle.WriteInt(index); - - // We only allow navigations up to 63k (which should be completely - // reasonable). On the off chance we get one that is too big, try to - // keep the url. - - // Bound the string data (which is variable length) to - // |max_state_size bytes| bytes. - static const SessionCommand::size_type max_state_size = - std::numeric_limits<SessionCommand::size_type>::max() - 1024; - - int bytes_written = 0; - - WriteStringToPickle(pickle, &bytes_written, max_state_size, - entry.display_url().spec()); - - WriteWStringToPickle(pickle, &bytes_written, max_state_size, - entry.title()); - - WriteStringToPickle(pickle, &bytes_written, max_state_size, - entry.content_state()); - - pickle.WriteInt(entry.transition_type()); - int type_mask = entry.has_post_data() ? TabNavigation::HAS_POST_DATA : 0; - pickle.WriteInt(type_mask); - - WriteStringToPickle(pickle, &bytes_written, max_state_size, - entry.referrer().is_valid() ? entry.referrer().spec() : std::string()); - - // Adding more data? Be sure and update TabRestoreService too. - return new SessionCommand(kCommandUpdateTabNavigation, pickle); -} - SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand( const SessionID& tab_id, int index) { @@ -713,17 +584,18 @@ SessionCommand* SessionService::CreateSetWindowTypeCommand( return command; } -void SessionService::OnGotSessionCommands( +void SessionService::OnGotLastSessionCommands( Handle handle, - scoped_refptr<InternalSavedSessionRequest> request) { + scoped_refptr<InternalGetCommandsRequest> request) { if (request->canceled()) return; ScopedVector<SessionWindow> valid_windows; RestoreSessionFromCommands( request->commands, &(valid_windows.get())); - request->real_callback->RunWithParams( - SavedSessionCallback::TupleType(request->handle(), - &(valid_windows.get()))); + static_cast<InternalLastSessionRequest*>(request.get())-> + real_callback->RunWithParams( + LastSessionCallback::TupleType(request->handle(), + &(valid_windows.get()))); } void SessionService::RestoreSessionFromCommands( @@ -793,7 +665,7 @@ std::vector<TabNavigation>::iterator DCHECK(navigations); for (std::vector<TabNavigation>::iterator i = navigations->begin(); i != navigations->end(); ++i) { - if (i->index >= index) + if (i->index() >= index) return i; } return navigations->end(); @@ -952,8 +824,8 @@ bool SessionService::CreateTabsAndWindows( // And update the index of existing navigations. for (std::vector<TabNavigation>::iterator i = tab->navigations.begin(); i != tab->navigations.end();) { - i->index -= payload.index; - if (i->index < 0) + i->set_index(i->index() - payload.index); + if (i->index() < 0) i = tab->navigations.erase(i); else ++i; @@ -962,42 +834,16 @@ bool SessionService::CreateTabsAndWindows( } case kCommandUpdateTabNavigation: { - scoped_ptr<Pickle> pickle(command->PayloadAsPickle()); - if (!pickle.get()) - return true; TabNavigation navigation; SessionID::id_type tab_id; - void* iterator = NULL; - std::string url_spec; - if (!pickle->ReadInt(&iterator, &tab_id) || - !pickle->ReadInt(&iterator, &(navigation.index)) || - !pickle->ReadString(&iterator, &url_spec) || - !pickle->ReadWString(&iterator, &(navigation.title)) || - !pickle->ReadString(&iterator, &(navigation.state)) || - !pickle->ReadInt(&iterator, - reinterpret_cast<int*>(&(navigation.transition)))) + if (!RestoreUpdateTabNavigationCommand(*command, &navigation, &tab_id)) return true; - // type_mask did not always exist in the written stream. As such, we - // don't fail if it can't be read. - bool has_type_mask = - pickle->ReadInt(&iterator, &(navigation.type_mask)); - - if (has_type_mask) { - // the "referrer" property was added after type_mask to the written - // stream. As such, we don't fail if it can't be read. - std::string referrer_spec; - pickle->ReadString(&iterator, &referrer_spec); - if (!referrer_spec.empty()) { - navigation.referrer = GURL(referrer_spec); - } - } - navigation.url = GURL(url_spec); SessionTab* tab = GetTab(tab_id, tabs); std::vector<TabNavigation>::iterator i = FindClosestNavigationWithIndex(&(tab->navigations), - navigation.index); - if (i != tab->navigations.end() && i->index == navigation.index) + navigation.index()); + if (i != tab->navigations.end() && i->index() == navigation.index()) *i = navigation; else tab->navigations.insert(i, navigation); @@ -1048,8 +894,8 @@ void SessionService::BuildCommandsForTab( CreateSetTabWindowCommand(window_id, controller->session_id())); const int current_index = controller->GetCurrentEntryIndex(); const int min_index = std::max(0, - current_index - kMaxNavigationCountToPersist); - const int max_index = std::min(current_index + kMaxNavigationCountToPersist, + current_index - max_persist_navigation_count); + const int max_index = std::min(current_index + max_persist_navigation_count, controller->GetEntryCount()); const int pending_index = controller->GetPendingEntryIndex(); if (tab_to_available_range) { @@ -1062,7 +908,8 @@ void SessionService::BuildCommandsForTab( DCHECK(entry); if (ShouldTrackEntry(*entry)) { commands->push_back( - CreateUpdateTabNavigationCommand(controller->session_id(), + CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, + controller->session_id().id(), i, *entry)); } @@ -1098,7 +945,7 @@ void SessionService::BuildCommandsForBrowser( for (int i = 0; i < browser->tab_count(); ++i) { TabContents* tab = browser->GetTabContentsAt(i); DCHECK(tab); - if (tab->profile() == profile_) { + if (tab->profile() == profile()) { BuildCommandsForTab(browser->session_id(), tab->controller(), i, commands, tab_to_available_range); if (windows_to_track && !added_to_windows_to_track) { @@ -1134,11 +981,11 @@ void SessionService::BuildCommandsFromBrowsers( } void SessionService::ScheduleReset() { - pending_reset_ = true; - STLDeleteElements(&pending_commands_); + set_pending_reset(true); + STLDeleteElements(&pending_commands()); tab_to_available_range_.clear(); windows_tracking_.clear(); - BuildCommandsFromBrowsers(&pending_commands_, &tab_to_available_range_, + BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_, &windows_tracking_); if (!windows_tracking_.empty()) { // We're lazily created on startup and won't get an initial batch of @@ -1164,7 +1011,7 @@ bool SessionService::ReplacePendingCommand(SessionCommand* command) { return false; } for (std::vector<SessionCommand*>::reverse_iterator i = - pending_commands_.rbegin(); i != pending_commands_.rend(); ++i) { + pending_commands().rbegin(); i != pending_commands().rend(); ++i) { SessionCommand* existing_command = *i; if (existing_command->id() == kCommandUpdateTabNavigation) { SessionID::id_type existing_tab_id; @@ -1181,8 +1028,8 @@ bool SessionService::ReplacePendingCommand(SessionCommand* command) { // it with the new one. We need to add to the end of the list just in // case there is a prune command after the update command. delete existing_command; - pending_commands_.erase(i.base() - 1); - pending_commands_.push_back(command); + pending_commands().erase(i.base() - 1); + pending_commands().push_back(command); return true; } return false; @@ -1195,17 +1042,15 @@ void SessionService::ScheduleCommand(SessionCommand* command) { DCHECK(command); if (ReplacePendingCommand(command)) return; - commands_since_reset_++; - pending_commands_.push_back(command); + BaseSessionService::ScheduleCommand(command); // Don't schedule a reset on tab closed/window closed. Otherwise we may // lose tabs/windows we want to restore from if we exit right after this. - if (!pending_reset_ && pending_window_close_ids_.empty() && - commands_since_reset_ >= kWritesPerReset && + if (!pending_reset() && pending_window_close_ids_.empty() && + commands_since_reset() >= kWritesPerReset && (command->id() != kCommandTabClosed && command->id() != kCommandWindowClosed)) { ScheduleReset(); } - StartSaveTimer(); } void SessionService::CommitPendingCloses() { @@ -1222,39 +1067,8 @@ void SessionService::CommitPendingCloses() { pending_window_close_ids_.clear(); } -void SessionService::Save() { - DCHECK(backend_.get()); - - if (pending_commands_.empty()) - return; - - if (!backend_thread_) { - backend_->AppendCommands( - new std::vector<SessionCommand*>(pending_commands_), pending_reset_); - } else { - backend_thread_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - backend_.get(), &SessionBackend::AppendCommands, - new std::vector<SessionCommand*>(pending_commands_), - pending_reset_)); - } - if (pending_reset_) { - commands_since_reset_ = 0; - pending_reset_ = false; - } - // Backend took ownership of commands. - pending_commands_.clear(); -} - -void SessionService::StartSaveTimer() { - // Don't start a timer when testing (profile == NULL). - if (profile_ && save_factory_.empty()) { - MessageLoop::current()->PostDelayedTask(FROM_HERE, - save_factory_.NewRunnableMethod(&SessionService::Save), kSaveDelayMS); - } -} - bool SessionService::IsOnlyOneTabLeft() { - if (!profile_) { + if (!profile()) { // We're testing, always return false. return false; } @@ -1266,7 +1080,7 @@ bool SessionService::IsOnlyOneTabLeft() { i != BrowserList::end(); ++i) { const SessionID::id_type window_id = (*i)->session_id().id(); if (should_track_changes_for_browser_type((*i)->type()) && - (*i)->profile()->GetOriginalProfile() == profile_ && + (*i)->profile()->GetOriginalProfile() == profile() && window_closing_ids_.find(window_id) == window_closing_ids_.end()) { if (++window_count > 1) return false; @@ -1280,7 +1094,7 @@ bool SessionService::IsOnlyOneTabLeft() { } bool SessionService::HasOpenTabbedBrowsers(const SessionID& window_id) { - if (!profile_) { + if (!profile()) { // We're testing, always return false. return true; } @@ -1294,7 +1108,7 @@ bool SessionService::HasOpenTabbedBrowsers(const SessionID& window_id) { if (browser_id != window_id.id() && window_closing_ids_.find(browser_id) == window_closing_ids_.end() && should_track_changes_for_browser_type(browser->type()) && - browser->profile()->GetOriginalProfile() == profile_) { + browser->profile()->GetOriginalProfile() == profile()) { return true; } } @@ -1304,15 +1118,3 @@ bool SessionService::HasOpenTabbedBrowsers(const SessionID& window_id) { bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) { return windows_tracking_.find(window_id.id()) != windows_tracking_.end(); } - -bool SessionService::ShouldTrackEntry(const NavigationEntry& entry) { - // Don't track entries that have post data. Post data may contain passwords - // and other sensitive data users don't want stored to disk. - return entry.display_url().is_valid() && !entry.has_post_data(); -} - -// InternalSavedSessionRequest ------------------------------------------------ - -SessionService::InternalSavedSessionRequest::~InternalSavedSessionRequest() { - STLDeleteElements(&commands); -} diff --git a/chrome/browser/session_service.h b/chrome/browser/sessions/session_service.h index 2ff8608..1c3402d3 100644 --- a/chrome/browser/session_service.h +++ b/chrome/browser/sessions/session_service.h @@ -2,165 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SESSION_SERVICE_H__ -#define CHROME_BROWSER_SESSION_SERVICE_H__ +#ifndef CHROME_BROWSER_SESSIONS_SESSION_SERVICE_H_ +#define CHROME_BROWSER_SESSIONS_SESSION_SERVICE_H_ #include <map> #include "base/basictypes.h" -#include "base/gfx/rect.h" -#include "base/task.h" -#include "base/time.h" #include "chrome/browser/browser.h" -#include "chrome/browser/cancelable_request.h" -#include "chrome/browser/session_id.h" +#include "chrome/browser/sessions/base_session_service.h" +#include "chrome/browser/sessions/session_id.h" #include "chrome/common/notification_service.h" -#include "chrome/common/page_transition_types.h" -#include "chrome/common/stl_util-inl.h" -#include "googleurl/src/gurl.h" class Browser; class NavigationController; class NavigationEntry; class Profile; -class TabContents; -class SessionBackend; class SessionCommand; - -namespace base { -class Thread; -} - -// TabNavigation ------------------------------------------------------------ - -// TabNavigation corresponds to a NavigationEntry. - -struct TabNavigation { - friend class SessionService; - - enum TypeMask { - HAS_POST_DATA = 1 - }; - - TabNavigation() : transition(PageTransition::TYPED), type_mask(0), index(-1) { - } - TabNavigation(int index, - const GURL& url, - const GURL& referrer, - const std::wstring& title, - const std::string& state, - PageTransition::Type transition) - : url(url), - referrer(referrer), - title(title), - state(state), - transition(transition), - type_mask(0), - index(index) {} - - - GURL url; - GURL referrer; - - // The title of the page. - std::wstring title; - std::string state; - PageTransition::Type transition; - - // A mask used for arbitrary boolean values needed to represent a - // NavigationEntry. Currently only contains HAS_POST_DATA or 0. - int type_mask; - - private: - // The index in the NavigationController. If this is -1, it means this - // TabNavigation is bogus. - // - // This is used when determining the selected TabNavigation and only useful - // by SessionService. - int index; -}; - -// SessionTab ---------------------------------------------------------------- - -// SessionTab corresponds to a NavigationController. - -struct SessionTab { - SessionTab() : tab_visual_index(-1), current_navigation_index(-1) { } - - // Unique id of the window. - SessionID window_id; - - // Unique if of the tab. - SessionID tab_id; - - // Visual index of the tab within its window. There may be gaps in these - // values. - // - // NOTE: this is really only useful for the SessionService during - // restore, others can likely ignore this and use the order of the - // tabs in SessionWindow.tabs. - int tab_visual_index; - - // Identifies the index of the current navigation in navigations. For - // example, if this is 2 it means the current navigation is navigations[2]. - // - // NOTE: when the service is creating SessionTabs, initially this - // corresponds to TabNavigation.index, not the index in navigations. When done - // creating though, this is set to the index in navigations. - int current_navigation_index; - - std::vector<TabNavigation> navigations; - - private: - DISALLOW_EVIL_CONSTRUCTORS(SessionTab); -}; - -// SessionWindow ------------------------------------------------------------- - -// Describes a saved window. - -struct SessionWindow { - SessionWindow() - : selected_tab_index(-1), - type(Browser::TYPE_NORMAL), - is_constrained(true), - is_maximized(false) {} - ~SessionWindow() { STLDeleteElements(&tabs); } - - // Identifier of the window. - SessionID window_id; - - // Bounds of the window. - gfx::Rect bounds; - - // Index of the selected tab in tabs; -1 if no tab is selected. After restore - // this value is guaranteed to be a valid index into tabs. - // - // NOTE: when the service is creating SessionWindows, initially this - // corresponds to SessionTab.tab_visual_index, not the index in - // tabs. When done creating though, this is set to the index in - // tabs. - int selected_tab_index; - - // Type of the browser. Currently we only store browsers of type - // TYPE_NORMAL and TYPE_POPUP. - Browser::Type type; - - // If true, the window is constrained. - // - // Currently SessionService prunes all constrained windows so that session - // restore does not attempt to restore them. - bool is_constrained; - - // The tabs, ordered by visual order. - std::vector<SessionTab*> tabs; - - // Is the window maximized? - bool is_maximized; - - private: - DISALLOW_EVIL_CONSTRUCTORS(SessionWindow); -}; +struct SessionTab; +struct SessionWindow; // SessionService ------------------------------------------------------------ @@ -168,19 +27,12 @@ struct SessionWindow { // and tabs so that they can be restored at a later date. The state of the // currently open browsers is referred to as the current session. // -// SessionService supports restoring from two distinct points (or sessions): -// . The previous or last session. The previous session typically corresponds -// to the last run of the browser, but not always. For example, if the user -// has a tabbed browser and app window running, closes the tabbed browser, -// then creates a new tabbed browser the current session is made the last -// session and the current session reset. This is done to provide the -// illusion that app windows run in separate processes. -// . A user defined point. That is, any time CreateSavedSession is invoked -// the save session is reset from the current state of the browser. -// -// Additionally the current session can be made the 'last' session at any point -// by way of MoveCurrentSessionToLastSession. This may be done at certain points -// during the browser that are viewed as changing the +// SessionService supports restoring from the previous or last session. The +// previous session typically corresponds to the last run of the browser, but +// not always. For example, if the user has a tabbed browser and app window +// running, closes the tabbed browser, then creates a new tabbed browser the +// current session is made the last session and the current session reset. This +// is done to provide the illusion that app windows run in separate processes. // // SessionService itself maintains a set of SessionCommands that allow // SessionService to rebuild the open state of the browser (as @@ -188,10 +40,8 @@ struct SessionWindow { // flushed to SessionBackend and written to a file. Every so often // SessionService rebuilds the contents of the file from the open state // of the browser. - -class SessionService : public CancelableRequestProvider, - public NotificationObserver, - public base::RefCountedThreadSafe<SessionService> { +class SessionService : public BaseSessionService, + public NotificationObserver { friend class SessionServiceTestHelper; public: // Creates a SessionService for the specified profile. @@ -199,7 +49,7 @@ class SessionService : public CancelableRequestProvider, // For testing. explicit SessionService(const std::wstring& save_path); - ~SessionService(); + virtual ~SessionService(); // Resets the contents of the file from the current state of all open // browsers whose profile matches our profile. @@ -279,84 +129,29 @@ class SessionService : public CancelableRequestProvider, // // The time gives the time the session was closed. typedef Callback2<Handle, std::vector<SessionWindow*>*>::Type - SavedSessionCallback; - - // Fetches the contents of the save session, notifying the callback when - // done. If the callback is supplied an empty vector of SessionWindows - // it means the session could not be restored. - Handle GetSavedSession(CancelableRequestConsumerBase* consumer, - SavedSessionCallback* callback); + LastSessionCallback; // Fetches the contents of the last session, notifying the callback when // done. If the callback is supplied an empty vector of SessionWindows // it means the session could not be restored. + // + // The created request does NOT directly invoke the callback, rather the + // callback invokes OnGotSessionCommands from which we map the + // SessionCommands to browser state, then notify the callback. Handle GetLastSession(CancelableRequestConsumerBase* consumer, - SavedSessionCallback* callback); - - // Creates a save session from the current state of the browser. - void CreateSavedSession(); - - // Deletes the saved session if saved session is true, or the last session - // if saved_session is false. - void DeleteSession(bool saved_session); - - // Creates a saved session from the contents of the last session. - void CopyLastSessionToSavedSession(); - - // The callback from Get*Session is internally routed to SessionService - // first. This is done so that the SessionWindows can be recreated from - // the SessionCommands. The following types are used for this. - class InternalSavedSessionRequest; - - typedef Callback2<Handle, scoped_refptr<InternalSavedSessionRequest> >::Type - InternalSavedSessionCallback; - - // Request class used from Get*Session. - class InternalSavedSessionRequest : - public CancelableRequest<InternalSavedSessionCallback> { - public: - InternalSavedSessionRequest(CallbackType* callback, - SavedSessionCallback* real_callback, - bool is_saved_session) - : CancelableRequest(callback), - real_callback(real_callback), - is_saved_session(is_saved_session) { - } - virtual ~InternalSavedSessionRequest(); - - // The callback supplied to Get*Session. - scoped_ptr<SavedSessionCallback> real_callback; - - // Whether the request is for a saved session, or the last session. - bool is_saved_session; - - // The commands. The backend fills this in for us. - std::vector<SessionCommand*> commands; - - private: - DISALLOW_EVIL_CONSTRUCTORS(InternalSavedSessionRequest); - }; + LastSessionCallback* callback); private: typedef std::map<SessionID::id_type,std::pair<int,int> > IdToRange; typedef std::map<SessionID::id_type,SessionTab*> IdToSessionTab; typedef std::map<SessionID::id_type,SessionWindow*> IdToSessionWindow; - // Various initialization; called from the constructor. - void Init(const std::wstring& path); + void Init(); virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details); - // Get*Session call into this to schedule the request. The request - // does NOT directly invoke the callback, rather the callback invokes - // OnGotSessionCommands from which we map the SessionCommands to browser - // state, then notify the callback. - Handle GetSessionImpl(CancelableRequestConsumerBase* consumer, - SavedSessionCallback* callback, - bool is_saved_session); - // Methods to create the various commands. It is up to the caller to delete // the returned the SessionCommand* object. SessionCommand* CreateSetSelectedTabInWindow(const SessionID& window_id, @@ -376,11 +171,6 @@ class SessionService : public CancelableRequestProvider, SessionCommand* CreateWindowClosedCommand(SessionID::id_type tab_id); - SessionCommand* CreateUpdateTabNavigationCommand( - const SessionID& tab_id, - int index, - const NavigationEntry& entry); - SessionCommand* CreateSetSelectedNavigationIndexCommand( const SessionID& tab_id, int index); @@ -391,9 +181,9 @@ class SessionService : public CancelableRequestProvider, // Callback form the backend for getting the commands from the previous // or save file. Converts the commands in SessionWindows and notifies // the real callback. - void OnGotSessionCommands( + void OnGotLastSessionCommands( Handle handle, - scoped_refptr<InternalSavedSessionRequest> request); + scoped_refptr<InternalGetCommandsRequest> request); // Converts the commands into SessionWindows. On return any valid // windows are added to valid_windows. It is up to the caller to delete @@ -499,12 +289,6 @@ class SessionService : public CancelableRequestProvider, // Converts all pending tab/window closes to commands and schedules them. void CommitPendingCloses(); - // Saves pending commands to the backend. - void Save(); - - // Starts the save timer (if it isn't running already). - void StartSaveTimer(); - // Returns true if there is only one window open with a single tab that shares // our profile. bool IsOnlyOneTabLeft(); @@ -516,21 +300,11 @@ class SessionService : public CancelableRequestProvider, // Returns true if changes to tabs in the specified window should be tracked. bool ShouldTrackChangesToWindow(const SessionID& window_id); - // Should we track the specified entry? - bool SessionService::ShouldTrackEntry(const NavigationEntry& entry); - // Returns true if we track changes to the specified browser type. static bool should_track_changes_for_browser_type(Browser::Type type) { return type == Browser::TYPE_NORMAL; } - // The profile used to determine where to save, as well as what tabs - // to persist. - Profile* profile_; - - // The number of commands sent to the backend before doing a reset. - int commands_since_reset_; - // Maps from session tab id to the range of navigation entries that has // been written to disk. // @@ -538,16 +312,6 @@ class SessionService : public CancelableRequestProvider, // written. IdToRange tab_to_available_range_; - // Commands we need to send over to the backend. - std::vector<SessionCommand*> pending_commands_; - - // Whether the backend file should be recreated the next time we send - // over the commands. - bool pending_reset_; - - // Used to invoke Save. - ScopedRunnableMethodFactory<SessionService> save_factory_; - // When the user closes the last window, where the last window is the // last tabbed browser and no more tabbed browsers are open with the same // profile, the window ID is added here. These IDs are only committed (which @@ -570,13 +334,6 @@ class SessionService : public CancelableRequestProvider, typedef std::set<SessionID::id_type> WindowsTracking; WindowsTracking windows_tracking_; - // The backend. - scoped_refptr<SessionBackend> backend_; - - // Thread backend tasks are run on. This comes from the profile, and is - // null during testing. - base::Thread* backend_thread_; - // Are there any open open tabbed browsers? bool has_open_tabbed_browsers_; @@ -585,7 +342,8 @@ class SessionService : public CancelableRequestProvider, // is made the previous session. See description above class for details on // current/previou session. bool move_on_new_browser_; -}; -#endif // CHROME_BROWSER_SESSION_SERVICE_H__ + DISALLOW_COPY_AND_ASSIGN(SessionService); +}; +#endif // CHROME_BROWSER_SESSIONS_SESSION_SERVICE_H_ diff --git a/chrome/browser/session_service_test_helper.cc b/chrome/browser/sessions/session_service_test_helper.cc index 4155d71..401c59c 100644 --- a/chrome/browser/session_service_test_helper.cc +++ b/chrome/browser/sessions/session_service_test_helper.cc @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/session_service_test_helper.h" +#include "chrome/browser/sessions/session_service_test_helper.h" -#include "chrome/browser/session_backend.h" -#include "chrome/browser/session_service.h" +#include "chrome/browser/sessions/session_backend.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/common/scoped_vector.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,7 +34,7 @@ void SessionServiceTestHelper::ReadWindows( std::vector<SessionWindow*>* windows) { Time last_time; ScopedVector<SessionCommand> read_commands; - backend()->ReadSessionImpl(false, &(read_commands.get())); + backend()->ReadLastSessionCommandsImpl(&(read_commands.get())); RestoreSessionFromCommands(read_commands.get(), windows); } @@ -60,12 +62,12 @@ void SessionServiceTestHelper::AssertTabEquals( void SessionServiceTestHelper::AssertNavigationEquals( const TabNavigation& expected, const TabNavigation& actual) { - EXPECT_TRUE(expected.url == actual.url); - EXPECT_EQ(expected.referrer, actual.referrer); - EXPECT_EQ(expected.title, actual.title); - EXPECT_EQ(expected.state, actual.state); - EXPECT_EQ(expected.transition, actual.transition); - EXPECT_EQ(expected.type_mask, actual.type_mask); + EXPECT_TRUE(expected.url() == actual.url()); + EXPECT_EQ(expected.referrer(), actual.referrer()); + EXPECT_EQ(expected.title(), actual.title()); + EXPECT_EQ(expected.state(), actual.state()); + EXPECT_EQ(expected.transition(), actual.transition()); + EXPECT_EQ(expected.type_mask(), actual.type_mask()); } void SessionServiceTestHelper::AssertSingleWindowWithSingleTab( @@ -77,6 +79,6 @@ void SessionServiceTestHelper::AssertSingleWindowWithSingleTab( } SessionBackend* SessionServiceTestHelper::backend() { - return service_->backend_.get(); + return service_->backend(); } diff --git a/chrome/browser/session_service_test_helper.h b/chrome/browser/sessions/session_service_test_helper.h index 80b9bb4..92a6db6 100644 --- a/chrome/browser/session_service_test_helper.h +++ b/chrome/browser/sessions/session_service_test_helper.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SESSION_SERVICE_TEST_HELPER_H__ -#define CHROME_BROWSER_SESSION_SERVICE_TEST_HELPER_H__ +#ifndef CHROME_BROWSER_SESSIONS_SERVICE_TEST_HELPER_H_ +#define CHROME_BROWSER_SESSIONS_SERVICE_TEST_HELPER_H_ #include <vector> @@ -15,7 +15,7 @@ class SessionID; class SessionService; struct SessionTab; struct SessionWindow; -struct TabNavigation; +class TabNavigation; // A simple class that makes writing SessionService related tests easier. @@ -64,8 +64,7 @@ class SessionServiceTestHelper { private: scoped_refptr<SessionService> service_; - DISALLOW_EVIL_CONSTRUCTORS(SessionServiceTestHelper); + DISALLOW_COPY_AND_ASSIGN(SessionServiceTestHelper); }; -#endif // CHROME_BROWSER_SESSION_SERVICE_TEST_HELPER_H__ - +#endif // CHROME_BROWSER_SESSIONS_SERVICE_TEST_HELPER_H_ diff --git a/chrome/browser/session_service_unittest.cc b/chrome/browser/sessions/session_service_unittest.cc index fc2a2d7..1939c7e 100644 --- a/chrome/browser/session_service_unittest.cc +++ b/chrome/browser/sessions/session_service_unittest.cc @@ -7,9 +7,10 @@ #include "base/file_util.h" #include "base/path_service.h" #include "chrome/browser/navigation_entry.h" -#include "chrome/browser/session_backend.h" -#include "chrome/browser/session_service.h" -#include "chrome/browser/session_service_test_helper.h" +#include "chrome/browser/sessions/session_backend.h" +#include "chrome/browser/sessions/session_service.h" +#include "chrome/browser/sessions/session_service_test_helper.h" +#include "chrome/browser/sessions/session_types.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/scoped_vector.h" #include "chrome/common/stl_util-inl.h" @@ -29,7 +30,8 @@ class SessionServiceTest : public testing::Test { file_util::CreateDirectory(path_); file_util::AppendToPath(&path_, b); - helper_.set_service(new SessionService(path_)); + SessionService* session_service = new SessionService(path_); + helper_.set_service(session_service); service()->SetWindowType(window_id, Browser::TYPE_NORMAL); service()->SetWindowBounds(window_id, window_bounds, false); @@ -46,13 +48,13 @@ class SessionServiceTest : public testing::Test { int index, bool select) { NavigationEntry entry(TAB_CONTENTS_UNKNOWN_TYPE); - entry.set_url(navigation.url); - entry.set_referrer(navigation.referrer); - entry.set_title(navigation.title); - entry.set_content_state(navigation.state); - entry.set_transition_type(navigation.transition); + entry.set_url(navigation.url()); + entry.set_referrer(navigation.referrer()); + entry.set_title(navigation.title()); + entry.set_content_state(navigation.state()); + entry.set_transition_type(navigation.transition()); entry.set_has_post_data( - navigation.type_mask & TabNavigation::HAS_POST_DATA); + navigation.type_mask() & TabNavigation::HAS_POST_DATA); service()->UpdateTabNavigation(window_id, tab_id, index, entry); if (select) service()->SetSelectedNavigationIndex(window_id, tab_id, index); @@ -62,7 +64,8 @@ class SessionServiceTest : public testing::Test { // Forces closing the file. helper_.set_service(NULL); - helper_.set_service(new SessionService(path_)); + SessionService* session_service = new SessionService(path_); + helper_.set_service(session_service); helper_.ReadWindows(windows); } @@ -115,7 +118,7 @@ TEST_F(SessionServiceTest, PrunePostData1) { TabNavigation nav1(0, GURL("http://google.com"), GURL(), L"abc", "def", PageTransition::QUALIFIER_MASK); - nav1.type_mask = TabNavigation::HAS_POST_DATA; + nav1.set_type_mask(TabNavigation::HAS_POST_DATA); helper_.PrepareTabInWindow(window_id, tab_id, 0, true); UpdateNavigation(window_id, tab_id, nav1, 0, true); @@ -135,7 +138,7 @@ TEST_F(SessionServiceTest, PrunePostData2) { TabNavigation nav1(0, GURL("http://google.com"), GURL("http://www.referrer.com"), L"abc", "def", PageTransition::QUALIFIER_MASK); - nav1.type_mask = TabNavigation::HAS_POST_DATA; + nav1.set_type_mask(TabNavigation::HAS_POST_DATA); TabNavigation nav2(0, GURL("http://google2.com"), GURL(), L"abc", "def", PageTransition::QUALIFIER_MASK); @@ -436,9 +439,9 @@ TEST_F(SessionServiceTest, PruneFromFront) { SessionTab* tab = windows[0]->tabs[0]; ASSERT_EQ(1, tab->current_navigation_index); EXPECT_EQ(3U, tab->navigations.size()); - EXPECT_TRUE(GURL(base_url + IntToString(2)) == tab->navigations[0].url); - EXPECT_TRUE(GURL(base_url + IntToString(3)) == tab->navigations[1].url); - EXPECT_TRUE(GURL(base_url + IntToString(4)) == tab->navigations[2].url); + EXPECT_TRUE(GURL(base_url + IntToString(2)) == tab->navigations[0].url()); + EXPECT_TRUE(GURL(base_url + IntToString(3)) == tab->navigations[1].url()); + EXPECT_TRUE(GURL(base_url + IntToString(4)) == tab->navigations[2].url()); } // Prunes from front so that we have no entries. diff --git a/chrome/browser/sessions/session_types.cc b/chrome/browser/sessions/session_types.cc new file mode 100644 index 0000000..5c17454 --- /dev/null +++ b/chrome/browser/sessions/session_types.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2008 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/navigation_entry.h" + +// TabNavigation -------------------------------------------------------------- + +// static +NavigationEntry* TabNavigation::ToNavigationEntry(int page_id) const { + GURL real_url = url_; + TabContentsType type = TabContents::TypeForURL(&real_url); + DCHECK(type != TAB_CONTENTS_UNKNOWN_TYPE); + + NavigationEntry* entry = new NavigationEntry( + type, + NULL, // The site instance for restored tabs is sent on navigation + // (WebContents::GetSiteInstanceForEntry). + page_id, + real_url, + referrer_, + title_, + // Use a transition type of reload so that we don't incorrectly + // increase the typed count. + PageTransition::RELOAD); + entry->set_display_url(url_); + entry->set_content_state(state_); + entry->set_has_post_data(type_mask_ & TabNavigation::HAS_POST_DATA); + return entry; +} + +void TabNavigation::SetFromNavigationEntry(const NavigationEntry& entry) { + url_ = entry.display_url(); + referrer_ = entry.referrer(); + title_ = entry.title(); + state_ = entry.content_state(); + transition_ = entry.transition_type(); + type_mask_ = entry.has_post_data() ? TabNavigation::HAS_POST_DATA : 0; +} diff --git a/chrome/browser/sessions/session_types.h b/chrome/browser/sessions/session_types.h new file mode 100644 index 0000000..deabead --- /dev/null +++ b/chrome/browser/sessions/session_types.h @@ -0,0 +1,183 @@ +// Copyright (c) 2006-2008 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_SESSION_TYPES_H_ +#define CHROME_BROWSER_SESSIONS_SESSION_TYPES_H_ + +#include <string> +#include <vector> + +#include "base/gfx/rect.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/common/page_transition_types.h" +#include "googleurl/src/gurl.h" + +class NavigationEntry; + +// TabNavigation ------------------------------------------------------------- + +// TabNavigation corresponds to the parts of NavigationEntry needed to restore +// the NavigationEntry during session restore and tab restore. +// +// TabNavigation is cheap and supports copy semantics. +class TabNavigation { + public: + enum TypeMask { + HAS_POST_DATA = 1 + }; + + TabNavigation() + : transition_(PageTransition::TYPED), + type_mask_(0), + index_(-1) { + } + + TabNavigation(int index, + const GURL& url, + const GURL& referrer, + const std::wstring& title, + const std::string& state, + PageTransition::Type transition) + : url_(url), + referrer_(referrer), + title_(title), + state_(state), + transition_(transition), + type_mask_(0), + index_(index) {} + + // Converts this TabNavigation into a NavigationEntry with a page id of + // |page_id|. The caller owns the returned NavigationEntry. + NavigationEntry* ToNavigationEntry(int page_id) const; + + // Resets this TabNavigation from |entry|. + void SetFromNavigationEntry(const NavigationEntry& entry); + + // URL of the page. + void set_url(const GURL& url) { url_ = url; } + const GURL& url() const { return url_; } + + // The referrer. + const GURL& referrer() const { return referrer_; } + + // The title of the page. + const std::wstring& title() const { return title_; } + + // State bits. + const std::string& state() const { return state_; } + + // Transition type. + void set_transition(PageTransition::Type transition) { + transition_ = transition; + } + PageTransition::Type transition() const { return transition_; } + + // A mask used for arbitrary boolean values needed to represent a + // NavigationEntry. Currently only contains HAS_POST_DATA or 0. + void set_type_mask(int type_mask) { type_mask_ = type_mask; } + int type_mask() const { return type_mask_; } + + // The index in the NavigationController. If this is -1, it means this + // TabNavigation is bogus. + // + // This is used when determining the selected TabNavigation and only useful + // by BaseSessionService and SessionService. + void set_index(int index) { index_ = index; } + int index() { return index_; } + + private: + friend class BaseSessionService; + + GURL url_; + GURL referrer_; + std::wstring title_; + std::string state_; + PageTransition::Type transition_; + int type_mask_; + + int index_; +}; + +// SessionTab ---------------------------------------------------------------- + +// SessionTab corresponds to a NavigationController. +struct SessionTab { + SessionTab() : tab_visual_index(-1), current_navigation_index(-1) { } + + // Unique id of the window. + SessionID window_id; + + // Unique if of the tab. + SessionID tab_id; + + // Visual index of the tab within its window. There may be gaps in these + // values. + // + // NOTE: this is really only useful for the SessionService during + // restore, others can likely ignore this and use the order of the + // tabs in SessionWindow.tabs. + int tab_visual_index; + + // Identifies the index of the current navigation in navigations. For + // example, if this is 2 it means the current navigation is navigations[2]. + // + // NOTE: when the service is creating SessionTabs, initially this + // corresponds to TabNavigation.index, not the index in navigations. When done + // creating though, this is set to the index in navigations. + int current_navigation_index; + + std::vector<TabNavigation> navigations; + + private: + DISALLOW_COPY_AND_ASSIGN(SessionTab); +}; + +// SessionWindow ------------------------------------------------------------- + +// Describes a saved window. +struct SessionWindow { + SessionWindow() + : selected_tab_index(-1), + type(Browser::TYPE_NORMAL), + is_constrained(true), + is_maximized(false) {} + ~SessionWindow() { STLDeleteElements(&tabs); } + + // Identifier of the window. + SessionID window_id; + + // Bounds of the window. + gfx::Rect bounds; + + // Index of the selected tab in tabs; -1 if no tab is selected. After restore + // this value is guaranteed to be a valid index into tabs. + // + // NOTE: when the service is creating SessionWindows, initially this + // corresponds to SessionTab.tab_visual_index, not the index in + // tabs. When done creating though, this is set to the index in + // tabs. + int selected_tab_index; + + // Type of the browser. Currently we only store browsers of type + // TYPE_NORMAL and TYPE_POPUP. + Browser::Type type; + + // If true, the window is constrained. + // + // Currently SessionService prunes all constrained windows so that session + // restore does not attempt to restore them. + bool is_constrained; + + // The tabs, ordered by visual order. + std::vector<SessionTab*> tabs; + + // Is the window maximized? + bool is_maximized; + + private: + DISALLOW_COPY_AND_ASSIGN(SessionWindow); +}; + +#endif // CHROME_BROWSER_SESSIONS_SESSION_TYPES_H_ diff --git a/chrome/browser/sessions/tab_restore_service.cc b/chrome/browser/sessions/tab_restore_service.cc new file mode 100644 index 0000000..f12e6d9 --- /dev/null +++ b/chrome/browser/sessions/tab_restore_service.cc @@ -0,0 +1,649 @@ +// Copyright (c) 2006-2008 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 <algorithm> +#include <iterator> + +#include "chrome/browser/sessions/tab_restore_service.h" + +#include "chrome/browser/browser_list.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sessions/session_backend.h" +#include "chrome/common/scoped_vector.h" +#include "chrome/common/stl_util-inl.h" + +using base::Time; + +// Entry ---------------------------------------------------------------------- + +// ID of the next Entry. +static SessionID::id_type next_entry_id = 1; + +TabRestoreService::Entry::Entry() : id(next_entry_id++), type(TAB) {} + +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; + +// Identifier for commands written to file. +// The ordering in the file is as follows: +// . When the user closes a tab a command of type +// kCommandSelectedNavigationInTab is written identifying the tab and +// the selected index. This is followed by any number of +// kCommandUpdateTabNavigation commands (1 per navigation entry). +// . When the user closes a window a kCommandSelectedNavigationInTab command +// is written out and followed by n tab closed sequences (as previoulsy +// described). +// . When the user restores an entry a command of type kCommandRestoredEntry +// is written. +static const SessionCommand::id_type kCommandUpdateTabNavigation = 1; +static const SessionCommand::id_type kCommandRestoredEntry = 2; +static const SessionCommand::id_type kCommandWindow = 3; +static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4; + +// Number of entries (not commands) before we clobber the file and write +// everything. +static const int kEntriesPerReset = 40; + +namespace { + +// Payload structures. + +typedef int32 RestoredEntryPayload; + +// Payload used for the start of a window close. +struct WindowPayload { + SessionID::id_type window_id; + int32 selected_tab_index; + int32 num_tabs; +}; + +// Paylowed used for the start of a tab close. +struct SelectedNavigationInTabPayload { + SessionID::id_type id; + int32 index; +}; + +typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry; + +// If |id_to_entry| contains an entry for |id| the corresponding entry is +// deleted and removed from both |id_to_entry| and |entries|. This is used +// when creating entries from the backend file. +void RemoveEntryByID(SessionID::id_type id, + IDToEntry* id_to_entry, + std::vector<TabRestoreService::Entry*>* entries) { + IDToEntry::iterator i = id_to_entry->find(id); + if (i == id_to_entry->end()) + return; + + entries->erase(std::find(entries->begin(), entries->end(), i->second)); + delete i->second; + id_to_entry->erase(i); +} + +} // namespace + +TabRestoreService::TabRestoreService(Profile* profile) + : BaseSessionService(BaseSessionService::TAB_RESTORE, profile, + std::wstring()), + loaded_last_session_(false), + restoring_(false), + reached_max_(false), + entries_to_write_(0), + entries_written_(0) { +} + +TabRestoreService::~TabRestoreService() { + if (backend()) + Save(); + + FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceDestroyed(this)); + STLDeleteElements(&entries_); +} + +void TabRestoreService::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} + +void TabRestoreService::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +void TabRestoreService::CreateHistoricalTab(NavigationController* tab) { + if (restoring_) + return; + + Browser* browser = Browser::GetBrowserForController(tab, NULL); + if (closing_browsers_.find(browser) != closing_browsers_.end()) + return; + + scoped_ptr<Tab> local_tab(new Tab()); + PopulateTabFromController(tab, local_tab.get()); + if (local_tab->navigations.empty()) + return; + + AddEntry(local_tab.release(), true, true); +} + +void TabRestoreService::BrowserClosing(Browser* browser) { + if (browser->type() != Browser::TYPE_NORMAL || + browser->tab_count() == 0) + return; + + closing_browsers_.insert(browser); + + Window* window = new Window(); + window->selected_tab_index = browser->selected_index(); + window->tabs.resize(browser->tab_count()); + size_t entry_index = 0; + for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) { + PopulateTabFromController( + browser->GetTabContentsAt(tab_index)->controller(), + &(window->tabs[entry_index])); + if (window->tabs[entry_index].navigations.empty()) + window->tabs.erase(window->tabs.begin() + entry_index); + else + entry_index++; + } + if (window->tabs.empty()) { + delete window; + window = NULL; + } else { + AddEntry(window, true, true); + } +} + +void TabRestoreService::BrowserClosed(Browser* browser) { + closing_browsers_.erase(browser); +} + +void TabRestoreService::ClearEntries() { + // Mark all the tabs as closed so that we don't attempt to restore them. + for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) + ScheduleCommand(CreateRestoredEntryCommand((*i)->id)); + + entries_to_write_ = 0; + + // Schedule a pending reset so that we nuke the file on next write. + set_pending_reset(true); + + // Schedule a command, otherwise if there are no pending commands Save does + // nothing. + ScheduleCommand(CreateRestoredEntryCommand(1)); + + STLDeleteElements(&entries_); + NotifyTabsChanged(); +} + +void TabRestoreService::RestoreMostRecentEntry(Browser* browser) { + if (entries_.empty()) + return; + + RestoreEntryById(browser, entries_.front()->id, false); +} + +void TabRestoreService::RestoreEntryById(Browser* browser, + SessionID::id_type id, + bool replace_existing_tab) { + Entries::iterator i = GetEntryIteratorById(id); + if (i == entries_.end()) { + // Don't hoark here, we allow an invalid id. + return; + } + + size_t index = 0; + for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end(); + ++j, ++index); + if (static_cast<int>(index) < entries_to_write_) + entries_to_write_--; + + ScheduleCommand(CreateRestoredEntryCommand(id)); + + restoring_ = true; + Entry* entry = *i; + entries_.erase(i); + i = entries_.end(); + if (browser) { // Browser is null during testing. + if (entry->type == TAB) { + Tab* tab = static_cast<Tab*>(entry); + if (replace_existing_tab) { + browser->ReplaceRestoredTab(tab->navigations, + tab->current_navigation_index); + } else { + browser->AddRestoredTab(tab->navigations, browser->tab_count(), + tab->current_navigation_index, true); + } + } else if (entry->type == WINDOW) { + const Window* window = static_cast<Window*>(entry); + Browser* browser = Browser::Create(profile()); + for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) { + const Tab& tab = window->tabs[tab_i]; + NavigationController* restored_controller = + browser->AddRestoredTab(tab.navigations, browser->tab_count(), + tab.current_navigation_index, + (tab_i == window->selected_tab_index)); + if (restored_controller) + restored_controller->LoadIfNecessary(); + } + browser->window()->Show(); + } else { + NOTREACHED(); + } + } + delete entry; + restoring_ = false; + NotifyTabsChanged(); +} + +void TabRestoreService::LoadTabsFromLastSession() { + if (loaded_last_session_ || reached_max_) + return; + + loaded_last_session_ = true; + + ScheduleGetLastSessionCommands( + new InternalGetCommandsRequest( + NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)), + &load_tabs_consumer_); +} + +void TabRestoreService::Save() { + int to_write_count = std::min(entries_to_write_, + static_cast<int>(entries_.size())); + entries_to_write_ = 0; + if (entries_written_ + to_write_count > kEntriesPerReset) { + to_write_count = entries_.size(); + set_pending_reset(true); + } + if (to_write_count) { + // Write the to_write_count most recently added entries out. The most + // recently added entry is at the front, so we use a reverse iterator to + // write in the order the entries were added. + Entries::reverse_iterator i = entries_.rbegin(); + DCHECK(static_cast<size_t>(to_write_count) <= entries_.size()); + std::advance(i, entries_.size() - static_cast<int>(to_write_count)); + for (; i != entries_.rend(); ++i) { + Entry* entry = *i; + if (entry->type == TAB) { + Tab* tab = static_cast<Tab*>(entry); + int selected_index = GetSelectedNavigationIndexToPersist(*tab); + if (selected_index != -1) + ScheduleCommandsForTab(*tab, selected_index); + } else { + ScheduleCommandsForWindow(*static_cast<Window*>(entry)); + } + entries_written_++; + } + } + if (pending_reset()) + entries_written_ = 0; + BaseSessionService::Save(); +} + +void TabRestoreService::PopulateTabFromController( + NavigationController* controller, + Tab* tab) { + const int pending_index = controller->GetPendingEntryIndex(); + int entry_count = controller->GetEntryCount(); + if (entry_count == 0 && pending_index == 0) + entry_count++; + tab->navigations.resize(static_cast<int>(entry_count)); + for (int i = 0; i < entry_count; ++i) { + NavigationEntry* entry = (i == pending_index) ? + controller->GetPendingEntry() : controller->GetEntryAtIndex(i); + tab->navigations[i].SetFromNavigationEntry(*entry); + } + tab->current_navigation_index = controller->GetCurrentEntryIndex(); + if (tab->current_navigation_index == -1 && entry_count > 0) + tab->current_navigation_index = 0; +} + +void TabRestoreService::NotifyTabsChanged() { + FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceChanged(this)); +} + +void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) { + if (to_front) + entries_.push_front(entry); + else + entries_.push_back(entry); + if (notify) + PruneAndNotify(); + // Start the save timer, when it fires we'll generate the commands. + StartSaveTimer(); + entries_to_write_++; +} + +void TabRestoreService::PruneAndNotify() { + while (entries_.size() > kMaxEntries) { + delete entries_.back(); + entries_.pop_back(); + reached_max_ = true; + } + + NotifyTabsChanged(); +} + +TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById( + SessionID::id_type id) { + for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { + if ((*i)->id == id) + return i; + } + return entries_.end(); +} + +void TabRestoreService::ScheduleCommandsForWindow(const Window& window) { + DCHECK(!window.tabs.empty()); + int selected_tab = window.selected_tab_index; + int valid_tab_count = 0; + int real_selected_tab = selected_tab; + for (size_t i = 0; i < window.tabs.size(); ++i) { + if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) { + valid_tab_count++; + } else if (static_cast<int>(i) < selected_tab) { + real_selected_tab--; + } + } + if (valid_tab_count == 0) + return; // No tabs to persist. + + ScheduleCommand( + CreateWindowCommand(window.id, + std::min(real_selected_tab, valid_tab_count - 1), + valid_tab_count)); + + for (size_t i = 0; i < window.tabs.size(); ++i) { + int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]); + if (selected_index != -1) + ScheduleCommandsForTab(window.tabs[i], selected_index); + } +} + +void TabRestoreService::ScheduleCommandsForTab(const Tab& tab, + int selected_index) { + const std::vector<TabNavigation>& navigations = tab.navigations; + int max_index = static_cast<int>(navigations.size()); + + // Determine the first navigation we'll persist. + int valid_count_before_selected = 0; + int first_index_to_persist = selected_index; + for (int i = selected_index - 1; i >= 0 && + valid_count_before_selected < max_persist_navigation_count; --i) { + if (ShouldTrackEntry(navigations[i])) { + first_index_to_persist = i; + valid_count_before_selected++; + } + } + + // Write the command that identifies the selected tab. + ScheduleCommand( + CreateSelectedNavigationInTabCommand(tab.id, + valid_count_before_selected)); + + // Then write the navigations. + for (int i = first_index_to_persist, wrote_count = 0; + i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) { + if (ShouldTrackEntry(navigations[i])) { + // Creating a NavigationEntry isn't the most efficient way to go about + // this, but it simplifies the code and makes it less error prone as we + // add new data to NavigationEntry. + scoped_ptr<NavigationEntry> entry( + navigations[i].ToNavigationEntry(wrote_count)); + ScheduleCommand( + CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id, + wrote_count++, *entry)); + } + } +} + +SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id, + int selected_tab_index, + int num_tabs) { + WindowPayload payload = { 0 }; + payload.window_id = id; + payload.selected_tab_index = selected_tab_index; + payload.num_tabs = num_tabs; + + SessionCommand* command = + new SessionCommand(kCommandWindow, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand( + SessionID::id_type tab_id, + int32 index) { + SelectedNavigationInTabPayload payload = { 0 }; + payload.id = tab_id; + payload.index = index; + SessionCommand* command = + new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* TabRestoreService::CreateRestoredEntryCommand( + SessionID::id_type entry_id) { + RestoredEntryPayload payload = entry_id; + SessionCommand* command = + new SessionCommand(kCommandRestoredEntry, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) { + const std::vector<TabNavigation>& navigations = tab.navigations; + int selected_index = tab.current_navigation_index; + int max_index = static_cast<int>(navigations.size()); + + // Find the first navigation to persist. We won't persist the selected + // navigation if ShouldTrackEntry returns false. + while (selected_index >= 0 && + !ShouldTrackEntry(navigations[selected_index])) { + selected_index--; + } + + if (selected_index != -1) + return selected_index; + + // Couldn't find a navigation to persist going back, go forward. + selected_index = tab.current_navigation_index + 1; + while (selected_index < max_index && + !ShouldTrackEntry(navigations[selected_index])) { + selected_index++; + } + + return (selected_index == max_index) ? -1 : selected_index; +} + +void TabRestoreService::OnGotLastSessionCommands( + Handle handle, + scoped_refptr<InternalGetCommandsRequest> request) { + if (request->canceled() || entries_.size() == kMaxEntries) + return; + + std::vector<SessionCommand*>& commands = request->commands; + // Iterate through the commands populating entries and id_to_entry. + ScopedVector<Entry> entries; + IDToEntry id_to_entry; + // If non-null we're processing the navigations of this tab. + Tab* current_tab = NULL; + // If non-null we're processing the tabs of this window. + Window* current_window = NULL; + // If > 0, we've gotten a window command but not all the tabs yet. + int pending_window_tabs = 0; + for (std::vector<SessionCommand*>::const_iterator i = commands.begin(); + i != commands.end(); ++i) { + const SessionCommand& command = *(*i); + switch (command.id()) { + case kCommandRestoredEntry: { + if (pending_window_tabs > 0) { + // Should never receive a restored command while waiting for all the + // tabs in a window. + return; + } + + current_tab = NULL; + current_window = NULL; + + RestoredEntryPayload payload; + if (!command.GetPayload(&payload, sizeof(payload))) + return; + RemoveEntryByID(payload, &id_to_entry, &(entries.get())); + break; + } + + case kCommandWindow: { + WindowPayload payload; + if (pending_window_tabs > 0) { + // Should never receive a window command while waiting for all the + // tabs in a window. + return; + } + + if (!command.GetPayload(&payload, sizeof(payload))) + return; + + pending_window_tabs = payload.num_tabs; + if (pending_window_tabs <= 0) { + // Should always have at least 1 tab. Likely indicates corruption. + return; + } + + RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get())); + + current_window = new Window(); + current_window->selected_tab_index = payload.selected_tab_index; + entries->push_back(current_window); + id_to_entry[payload.window_id] = current_window; + break; + } + + case kCommandSelectedNavigationInTab: { + SelectedNavigationInTabPayload payload; + if (!command.GetPayload(&payload, sizeof(payload))) + return; + + if (pending_window_tabs > 0) { + if (!current_window) { + // We should have created a window already. + NOTREACHED(); + return; + } + current_window->tabs.resize(current_window->tabs.size() + 1); + current_tab = &(current_window->tabs.back()); + if (--pending_window_tabs == 0) + current_window = NULL; + } else { + RemoveEntryByID(payload.id, &id_to_entry, &(entries.get())); + current_tab = new Tab(); + id_to_entry[payload.id] = current_tab; + entries->push_back(current_tab); + } + current_tab->current_navigation_index = payload.index; + break; + } + + case kCommandUpdateTabNavigation: { + if (!current_tab) { + // Should be in a tab when we get this. + return; + } + current_tab->navigations.resize(current_tab->navigations.size() + 1); + SessionID::id_type tab_id; + if (!RestoreUpdateTabNavigationCommand( + command, ¤t_tab->navigations.back(), &tab_id)) { + return; + } + break; + } + + default: + // Unknown type, usually indicates corruption of file. Ignore it. + return; + } + } + + // If there was corruption some of the entries won't be valid. Prune any + // 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(); +} + +bool TabRestoreService::ValidateTab(Tab* tab) { + if (tab->navigations.empty()) + return false; + + tab->current_navigation_index = + std::max(0, std::min(tab->current_navigation_index, + static_cast<int>(tab->navigations.size()) - 1)); + return true; +} + +void TabRestoreService::ValidateAndDeleteEmptyEntries( + std::vector<Entry*>* entries) { + std::vector<Entry*> valid_entries; + std::vector<Entry*> invalid_entries; + + size_t max_valid = kMaxEntries - entries_.size(); + // Iterate from the back so that we keep the most recently closed entries. + for (std::vector<Entry*>::reverse_iterator i = entries->rbegin(); + i != entries->rend(); ++i) { + bool valid_entry = false; + if (valid_entries.size() != max_valid) { + if ((*i)->type == TAB) { + Tab* tab = static_cast<Tab*>(*i); + if (ValidateTab(tab)) + valid_entry = true; + } else { + Window* window = static_cast<Window*>(*i); + for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); + tab_i != window->tabs.end();) { + if (!ValidateTab(&(*tab_i))) + tab_i = window->tabs.erase(tab_i); + else + ++tab_i; + } + if (!window->tabs.empty()) { + window->selected_tab_index = + std::max(0, std::min(window->selected_tab_index, + static_cast<int>(window->tabs.size() - 1))); + valid_entry = true; + } + } + } + if (valid_entry) + valid_entries.push_back(*i); + else + invalid_entries.push_back(*i); + } + // NOTE: at this point the entries are ordered with newest at the front. + entries->swap(valid_entries); + + // Delete the remaining entries. + STLDeleteElements(&invalid_entries); +} diff --git a/chrome/browser/tab_restore_service.h b/chrome/browser/sessions/tab_restore_service.h index 5d1460e..adf5df5 100644 --- a/chrome/browser/tab_restore_service.h +++ b/chrome/browser/sessions/tab_restore_service.h @@ -2,15 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_TAB_RESTORE_SERVICE_H_ -#define CHROME_BROWSER_TAB_RESTORE_SERVICE_H_ +#ifndef CHROME_BROWSER_SESSIONS_TAB_RESTORE_SERVICE_H_ +#define CHROME_BROWSER_SESSIONS_TAB_RESTORE_SERVICE_H_ #include <list> #include "base/observer_list.h" #include "base/time.h" -#include "chrome/browser/session_service.h" +#include "chrome/browser/sessions/base_session_service.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/browser/sessions/session_types.h" +class Browser; class NavigationController; class Profile; @@ -25,7 +28,7 @@ class Profile; // // To listen for changes to the set of entries managed by the TabRestoreService // add an observer. -class TabRestoreService { +class TabRestoreService : public BaseSessionService { public: // Observer is notified when the set of entries managed by TabRestoreService // changes in some way. @@ -52,7 +55,7 @@ class TabRestoreService { // Unique id for this entry. The id is guaranteed to be unique for a // session. - int id; + SessionID::id_type id; // The type of the entry. Type type; @@ -63,7 +66,6 @@ class TabRestoreService { Tab() : Entry(TAB), current_navigation_index(-1) {} // The navigations. - // WARNING: navigations may be empty. std::vector<TabNavigation> navigations; // Index of the selected navigation in navigations. @@ -85,7 +87,7 @@ class TabRestoreService { // Creates a new TabRestoreService. explicit TabRestoreService(Profile* profile); - ~TabRestoreService(); + virtual ~TabRestoreService(); // Adds/removes an observer. TabRestoreService does not take ownership of // the observer. @@ -120,7 +122,16 @@ class TabRestoreService { // Restores an entry by id. If there is no entry with an id matching |id|, // this does nothing. If |replace_existing_tab| is true and id identifies a // tab, the newly created tab replaces the selected tab in |browser|. - void RestoreEntryById(Browser* browser, int id, bool replace_existing_tab); + void RestoreEntryById(Browser* browser, + SessionID::id_type id, + bool replace_existing_tab); + + // Loads the tabs from the previous session. This does nothing if the tabs + // from the previous session have already been loaded. + void LoadTabsFromLastSession(); + + protected: + virtual void Save(); private: // Populates tabs->navigations from the NavigationController. @@ -130,24 +141,79 @@ class TabRestoreService { // Notifies observers the tabs have changed. void NotifyTabsChanged(); + // Adds |entry| to the list of entries. If |prune| is true |PruneAndNotify| + // is invoked. If |to_front| is true the entry is added to the front, + // otherwise the back. Normal closes go to the front, but tab/window closes + // from the previous session are added to the back. + void AddEntry(Entry* entry, bool prune, bool to_front); + // Prunes entries_ to contain only kMaxEntries and invokes NotifyTabsChanged. void PruneAndNotify(); // Returns an iterator into entries_ whose id matches |id|. - Entries::iterator GetEntryIteratorById(int id); + Entries::iterator GetEntryIteratorById(SessionID::id_type id); + + // Schedules the commands for a window close. + void ScheduleCommandsForWindow(const Window& window); + + // Schedules the commands for a tab close. |selected_index| gives the + // index of the selected navigation. + void ScheduleCommandsForTab(const Tab& tab, int selected_index); + + // Creates a window close command. + SessionCommand* CreateWindowCommand(SessionID::id_type id, + int selected_tab_index, + int num_tabs); + + // Creates a tab close command. + SessionCommand* CreateSelectedNavigationInTabCommand( + SessionID::id_type tab_id, + int32 index); + + // Creates a restore command. + SessionCommand* CreateRestoredEntryCommand(SessionID::id_type entry_id); + + // Returns the index to persist as the selected index. This is the same + // as |tab.current_navigation_index| unless the entry at + // |tab.current_navigation_index| shouldn't be persisted. Returns -1 if + // 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. + void OnGotLastSessionCommands( + Handle handle, + scoped_refptr<InternalGetCommandsRequest> request); + + // 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. + bool ValidateTab(Tab* tab); + + // Validates all entries in |entries|, deleting any with no navigations. + // This also deletes any entries beyond the max number of entries we can + // hold. + void ValidateAndDeleteEmptyEntries(std::vector<Entry*>* entries); - Profile* profile_; + // Set of entries. + Entries entries_; // Whether we've loaded the last session. bool loaded_last_session_; - // Set of entries. - Entries entries_; - // Are we restoring a tab? If this is true we ignore requests to create a // historical tab. bool restoring_; + // Have the max number of entries ever been created? + bool reached_max_; + + // The number of entries to write. + int entries_to_write_; + + // Number of entries we've written. + int entries_written_; + ObserverList<Observer> observer_list_; // Set of tabs that we've received a BrowserClosing method for but no @@ -155,7 +221,10 @@ class TabRestoreService { // avoid creating historical tabs for them. std::set<Browser*> closing_browsers_; + // Used when loading commands from the previous session. + CancelableRequestConsumer load_tabs_consumer_; + DISALLOW_COPY_AND_ASSIGN(TabRestoreService); }; -#endif // CHROME_BROWSER_TAB_RESTORE_SERVICE_H_ +#endif // CHROME_BROWSER_SESSIONS_TAB_RESTORE_SERVICE_H_ diff --git a/chrome/browser/sessions/tab_restore_service_unittest.cc b/chrome/browser/sessions/tab_restore_service_unittest.cc new file mode 100644 index 0000000..99e65c3 --- /dev/null +++ b/chrome/browser/sessions/tab_restore_service_unittest.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2006-2008 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/navigation_entry.h" +#include "chrome/browser/sessions/session_types.h" +#include "chrome/browser/sessions/tab_restore_service.h" +#include "chrome/test/test_tab_contents.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TabRestoreServiceTest : public testing::Test { + public: + TabRestoreServiceTest() + : tab_contents_factory_( + TestTabContentsFactory::CreateAndRegisterFactory()), + profile_(new TestingProfile()), + service_(new TabRestoreService(profile_.get())) { + test_contents_ = tab_contents_factory_->CreateInstanceImpl(); + test_contents_->set_commit_on_navigate(true); + controller_ = new NavigationController(test_contents_, profile_.get()); + url1_ = GURL(tab_contents_factory_->scheme() + "://1"); + url2_ = GURL(tab_contents_factory_->scheme() + "://2"); + url3_ = GURL(tab_contents_factory_->scheme() + "://3"); + } + + ~TabRestoreServiceTest() { + controller_->Destroy(); + } + + protected: + void AddThreeNavigations() { + // Navigate to three URLs. + controller_->LoadURL(url1_, GURL(), PageTransition::RELOAD); + controller_->LoadURL(url2_, GURL(), PageTransition::RELOAD); + controller_->LoadURL(url3_, GURL(), PageTransition::RELOAD); + } + + 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. + test_contents_->set_commit_on_navigate(false); + controller_->GoToIndex(index); + test_contents_->CompleteNavigationAsRenderer( + controller_->GetPendingEntry()->page_id(), + controller_->GetPendingEntry()->url()); + } + + void RecreateService() { + // Must set service to null first so that it is destroyed. + service_ = NULL; + service_ = new TabRestoreService(profile_.get()); + service_->LoadTabsFromLastSession(); + } + + GURL url1_; + GURL url2_; + GURL url3_; + scoped_ptr<TestTabContentsFactory> tab_contents_factory_; + scoped_ptr<TestingProfile> profile_; + scoped_refptr<TabRestoreService> service_; + NavigationController* controller_; + TestTabContents* test_contents_; +}; + +TEST_F(TabRestoreServiceTest, Basic) { + AddThreeNavigations(); + + // Have the service record the tab. + service_->CreateHistoricalTab(controller_); + + // Make sure an entry was created. + ASSERT_EQ(1, 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); + ASSERT_EQ(3, tab->navigations.size()); + EXPECT_TRUE(url1_ == tab->navigations[0].url()); + EXPECT_TRUE(url2_ == tab->navigations[1].url()); + EXPECT_TRUE(url3_ == tab->navigations[2].url()); + EXPECT_EQ(2, tab->current_navigation_index); + + NavigateToIndex(1); + + // And check again. + service_->CreateHistoricalTab(controller_); + + // There should be two entries now. + ASSERT_EQ(2, service_->entries().size()); + + // Make sure the entry matches + entry = service_->entries().front(); + ASSERT_EQ(TabRestoreService::TAB, entry->type); + tab = static_cast<TabRestoreService::Tab*>(entry); + ASSERT_EQ(3, tab->navigations.size()); + EXPECT_TRUE(url1_ == tab->navigations[0].url()); + EXPECT_TRUE(url2_ == tab->navigations[1].url()); + EXPECT_TRUE(url3_ == tab->navigations[2].url()); + EXPECT_EQ(1, tab->current_navigation_index); +} + +// Make sure TabRestoreService doesn't create an entry for a tab with no +// navigations. +TEST_F(TabRestoreServiceTest, DontCreateEmptyTab) { + service_->CreateHistoricalTab(controller_); + EXPECT_TRUE(service_->entries().empty()); +} + +// Tests restoring a single tab. +TEST_F(TabRestoreServiceTest, Restore) { + AddThreeNavigations(); + + // Have the service record the tab. + service_->CreateHistoricalTab(controller_); + + // Recreate the service and have it load the tabs. + RecreateService(); + + // One entry should be created. + ASSERT_EQ(1, 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); + ASSERT_EQ(3, tab->navigations.size()); + EXPECT_TRUE(url1_ == tab->navigations[0].url()); + EXPECT_TRUE(url2_ == tab->navigations[1].url()); + EXPECT_TRUE(url3_ == tab->navigations[2].url()); + EXPECT_EQ(2, tab->current_navigation_index); +} + +// Make sure a tab that is restored doesn't come back. +TEST_F(TabRestoreServiceTest, DontLoadRestoredTab) { + AddThreeNavigations(); + + // Have the service record the tab. + service_->CreateHistoricalTab(controller_); + ASSERT_EQ(1, service_->entries().size()); + + // Restore the tab. + service_->RestoreEntryById(NULL, service_->entries().front()->id, true); + ASSERT_TRUE(service_->entries().empty()); + + // Recreate the service and have it load the tabs. + RecreateService(); + + // There should be no entries. + ASSERT_EQ(0, service_->entries().size()); +} + +// Make sure we don't persist entries to disk that have post data. +TEST_F(TabRestoreServiceTest, DontPersistPostData1) { + AddThreeNavigations(); + controller_->GetEntryAtIndex(2)->set_has_post_data(true); + + // Have the service record the tab. + service_->CreateHistoricalTab(controller_); + ASSERT_EQ(1, service_->entries().size()); + + // Recreate the service and have it load the tabs. + RecreateService(); + + // One entry should be created. + ASSERT_EQ(1, service_->entries().size()); + + // And verify the entry, the last navigation (url3_) should not have + // been written to disk as it contained post data. + TabRestoreService::Entry* entry = service_->entries().front(); + ASSERT_EQ(TabRestoreService::TAB, entry->type); + TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry); + ASSERT_EQ(2, tab->navigations.size()); + EXPECT_TRUE(url1_ == tab->navigations[0].url()); + EXPECT_TRUE(url2_ == tab->navigations[1].url()); + EXPECT_EQ(1, tab->current_navigation_index); +} + +// Make sure we don't persist entries to disk that have post data. This +// differs from DontPersistPostData1 in that all navigations before the +// current index have post data. +TEST_F(TabRestoreServiceTest, DontPersistPostData2) { + AddThreeNavigations(); + NavigateToIndex(1); + controller_->GetEntryAtIndex(0)->set_has_post_data(true); + controller_->GetEntryAtIndex(1)->set_has_post_data(true); + + // Have the service record the tab. + service_->CreateHistoricalTab(controller_); + ASSERT_EQ(1, service_->entries().size()); + + // Recreate the service and have it load the tabs. + RecreateService(); + + // One entry should be created. + ASSERT_EQ(1, service_->entries().size()); + + // And verify the entry, the last navigation (url3_) should not have + // been written to disk as it contained post data. + TabRestoreService::Entry* entry = service_->entries().front(); + ASSERT_EQ(TabRestoreService::TAB, entry->type); + TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry); + ASSERT_EQ(1, tab->navigations.size()); + EXPECT_TRUE(url3_ == tab->navigations[0].url()); + EXPECT_EQ(0, tab->current_navigation_index); +} + +// 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, DontPersistPostData3) { + 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_); + ASSERT_EQ(1, service_->entries().size()); + + // Recreate the service and have it load the tabs. + RecreateService(); + + // One entry should be created. + ASSERT_TRUE(service_->entries().empty()); +} + +// 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_); + ASSERT_EQ(1, service_->entries().size()); + + // Recreate the service and have it load the tabs. + RecreateService(); + + service_->LoadTabsFromLastSession(); + + // There should only be one entry. + ASSERT_EQ(1, service_->entries().size()); +} diff --git a/chrome/browser/tab_restore_service.cc b/chrome/browser/tab_restore_service.cc deleted file mode 100644 index bc8b9c7..0000000 --- a/chrome/browser/tab_restore_service.cc +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) 2006-2008 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/tab_restore_service.h" - -#include "chrome/browser/browser_list.h" -#include "chrome/browser/navigation_controller.h" -#include "chrome/browser/navigation_entry.h" -#include "chrome/browser/profile.h" -#include "chrome/common/stl_util-inl.h" - -using base::Time; - -// Entry ---------------------------------------------------------------------- - -// ID of the next Entry. -static int next_entry_id = 1; - -TabRestoreService::Entry::Entry() : id(next_entry_id++), type(TAB) {} - -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; - -TabRestoreService::TabRestoreService(Profile* profile) - : profile_(profile), - loaded_last_session_(false), - restoring_(false) { -} - -TabRestoreService::~TabRestoreService() { - FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceDestroyed(this)); - STLDeleteElements(&entries_); -} - -void TabRestoreService::AddObserver(Observer* observer) { - observer_list_.AddObserver(observer); -} - -void TabRestoreService::RemoveObserver(Observer* observer) { - observer_list_.RemoveObserver(observer); -} - -void TabRestoreService::CreateHistoricalTab(NavigationController* tab) { - if (restoring_) - return; - - Browser* browser = Browser::GetBrowserForController(tab, NULL); - if (closing_browsers_.find(browser) != closing_browsers_.end()) - return; - - Tab* local_tab = new Tab(); - PopulateTabFromController(tab, local_tab); - entries_.push_front(local_tab); - - PruneAndNotify(); -} - -void TabRestoreService::BrowserClosing(Browser* browser) { - if (browser->type() != Browser::TYPE_NORMAL || - browser->tab_count() == 0) - return; - - closing_browsers_.insert(browser); - - Window* window = new Window(); - window->selected_tab_index = browser->selected_index(); - window->tabs.resize(browser->tab_count()); - size_t entry_index = 0; - for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) { - PopulateTabFromController( - browser->GetTabContentsAt(tab_index)->controller(), - &(window->tabs[entry_index])); - if (window->tabs[entry_index].navigations.empty()) - window->tabs.erase(window->tabs.begin() + entry_index); - else - entry_index++; - } - if (window->tabs.empty()) { - delete window; - window = NULL; - } else { - entries_.push_front(window); - PruneAndNotify(); - } -} - -void TabRestoreService::BrowserClosed(Browser* browser) { - closing_browsers_.erase(browser); -} - -void TabRestoreService::ClearEntries() { - STLDeleteElements(&entries_); - NotifyTabsChanged(); -} - -void TabRestoreService::RestoreMostRecentEntry(Browser* browser) { - if (entries_.empty()) - return; - - RestoreEntryById(browser, entries_.front()->id, false); -} - -void TabRestoreService::RestoreEntryById(Browser* browser, - int id, - bool replace_existing_tab) { - Entries::iterator i = GetEntryIteratorById(id); - if (i == entries_.end()) { - // Don't hoark here, we allow an invalid id. - return; - } - - restoring_ = true; - Entry* entry = *i; - entries_.erase(i); - i = entries_.end(); - if (entry->type == TAB) { - Tab* tab = static_cast<Tab*>(entry); - if (replace_existing_tab) { - browser->ReplaceRestoredTab(tab->navigations, - tab->current_navigation_index); - } else { - browser->AddRestoredTab(tab->navigations, browser->tab_count(), - tab->current_navigation_index, true); - } - } else if (entry->type == WINDOW) { - const Window* window = static_cast<Window*>(entry); - Browser* browser = Browser::Create(profile_); - for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) { - const Tab& tab = window->tabs[tab_i]; - NavigationController* restored_controller = - browser->AddRestoredTab(tab.navigations, browser->tab_count(), - tab.current_navigation_index, - (tab_i == window->selected_tab_index)); - if (restored_controller) - restored_controller->LoadIfNecessary(); - } - browser->window()->Show(); - } else { - NOTREACHED(); - } - delete entry; - restoring_ = false; - NotifyTabsChanged(); -} - -void TabRestoreService::PopulateTabFromController( - NavigationController* controller, - Tab* tab) { - const int pending_index = controller->GetPendingEntryIndex(); - int entry_count = controller->GetEntryCount(); - if (entry_count == 0 && pending_index == 0) - entry_count++; - tab->navigations.resize(static_cast<int>(entry_count)); - for (int i = 0; i < entry_count; ++i) { - NavigationEntry* entry = (i == pending_index) ? - controller->GetPendingEntry() : controller->GetEntryAtIndex(i); - TabNavigation& tab_nav = tab->navigations[i]; - tab_nav.url = entry->display_url(); - tab_nav.referrer = entry->referrer(); - tab_nav.title = entry->title(); - tab_nav.state = entry->content_state(); - tab_nav.transition = entry->transition_type(); - tab_nav.type_mask = entry->has_post_data() ? - TabNavigation::HAS_POST_DATA : 0; - } - tab->current_navigation_index = controller->GetCurrentEntryIndex(); - if (tab->current_navigation_index == -1 && entry_count > 0) - tab->current_navigation_index = 0; -} - -void TabRestoreService::NotifyTabsChanged() { - FOR_EACH_OBSERVER(Observer, observer_list_, TabRestoreServiceChanged(this)); -} - -void TabRestoreService::PruneAndNotify() { - while (entries_.size() > kMaxEntries) { - delete entries_.back(); - entries_.pop_back(); - } - - NotifyTabsChanged(); -} - -TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById( - int id) { - for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { - if ((*i)->id == id) - return i; - } - return entries_.end(); -} diff --git a/chrome/browser/tabs/tab_strip_model.cc b/chrome/browser/tabs/tab_strip_model.cc index 21ba9b5..3658ef5 100644 --- a/chrome/browser/tabs/tab_strip_model.cc +++ b/chrome/browser/tabs/tab_strip_model.cc @@ -13,8 +13,8 @@ #include "chrome/browser/navigation_controller.h" #include "chrome/browser/navigation_entry.h" #include "chrome/browser/render_view_host.h" +#include "chrome/browser/sessions/tab_restore_service.h" #include "chrome/browser/tab_contents_factory.h" -#include "chrome/browser/tab_restore_service.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/tabs/tab_strip_model_order_controller.h" #include "chrome/browser/user_metrics.h" diff --git a/chrome/test/ui/ui_tests.scons b/chrome/test/ui/ui_tests.scons index 597e438..202ce96 100644 --- a/chrome/test/ui/ui_tests.scons +++ b/chrome/test/ui/ui_tests.scons @@ -104,7 +104,7 @@ ui_test_files = [ '$CHROME_DIR/browser/resource_dispatcher_host_uitest.cc', '$CHROME_DIR/browser/sanity_uitest.cc', '$CHROME_DIR/browser/session_history_uitest.cc', - '$CHROME_DIR/browser/session_restore_uitest.cc', + '$CHROME_DIR/browser/sessions/session_restore_uitest.cc', '$CHROME_DIR/browser/ssl_uitest.cc', '$CHROME_DIR/browser/tab_restore_uitest.cc', '$CHROME_DIR/browser/view_source_uitest.cc', diff --git a/chrome/test/ui/ui_tests.vcproj b/chrome/test/ui/ui_tests.vcproj index 7173351..fbfe834 100644 --- a/chrome/test/ui/ui_tests.vcproj +++ b/chrome/test/ui/ui_tests.vcproj @@ -306,7 +306,7 @@ Name="TestSessionRestore"
>
<File
- RelativePath="..\..\browser\session_restore_uitest.cc"
+ RelativePath="..\..\browser\sessions\session_restore_uitest.cc"
>
</File>
</Filter>
diff --git a/chrome/test/unit/unit_tests.scons b/chrome/test/unit/unit_tests.scons index df884b1..e870062 100644 --- a/chrome/test/unit/unit_tests.scons +++ b/chrome/test/unit/unit_tests.scons @@ -184,9 +184,10 @@ if env['PLATFORM'] == 'win32': '$CHROME_DIR/browser/resource_dispatcher_host_unittest.cc', '$CHROME_DIR/browser/rlz/rlz_unittest.cc', '$CHROME_DIR/browser/safe_browsing/protocol_manager_unittest.cc', - '$CHROME_DIR/browser/session_backend_unittest.cc', - '$CHROME_DIR/browser/session_service_test_helper.cc', - '$CHROME_DIR/browser/session_service_unittest.cc', + '$CHROME_DIR/browser/sessions/session_backend_unittest.cc', + '$CHROME_DIR/browser/sessions/session_service_test_helper.cc', + '$CHROME_DIR/browser/sessions/session_service_unittest.cc', + '$CHROME_DIR/browser/sessions/tab_restore_service_unittest.cc', '$CHROME_DIR/browser/site_instance_unittest.cc', '$CHROME_DIR/browser/spellcheck_unittest.cc', '$CHROME_DIR/browser/tabs/tab_strip_model_unittest.cc', diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index ab3eda5..11bf464 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -474,19 +474,27 @@ Name="TestSessionService"
>
<File
- RelativePath="..\..\browser\session_backend_unittest.cc"
+ RelativePath="..\..\browser\sessions\session_backend_unittest.cc"
>
</File>
<File
- RelativePath="..\..\browser\session_service_test_helper.cc"
+ RelativePath="..\..\browser\sessions\session_service_test_helper.cc"
>
</File>
<File
- RelativePath="..\..\browser\session_service_test_helper.h"
+ RelativePath="..\..\browser\sessions\session_service_test_helper.h"
>
</File>
<File
- RelativePath="..\..\browser\session_service_unittest.cc"
+ RelativePath="..\..\browser\sessions\session_service_unittest.cc"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="TestTabRestoreService"
+ >
+ <File
+ RelativePath="..\..\browser\sessions\tab_restore_service_unittest.cc"
>
</File>
</Filter>
|