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/browser/sessions/session_restore.cc | |
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/browser/sessions/session_restore.cc')
-rw-r--r-- | chrome/browser/sessions/session_restore.cc | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc new file mode 100644 index 0000000..74038ce --- /dev/null +++ b/chrome/browser/sessions/session_restore.cc @@ -0,0 +1,445 @@ +// 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_restore.h" + +#include <vector> + +#include "base/scoped_ptr.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/profile.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" + +namespace { + +// TabLoader ------------------------------------------------------------------ + +// TabLoader is responsible for ensuring after session restore we have +// at least SessionRestore::num_tabs_to_load_ loading. As tabs finish loading +// new tabs are loaded. When all tabs are loading TabLoader deletes itself. +// +// This is not part of SessionRestoreImpl so that synchronous destruction +// of SessionRestoreImpl doesn't have timing problems. + +class TabLoader : public NotificationObserver { + public: + typedef std::list<NavigationController*> TabsToLoad; + + TabLoader(); + ~TabLoader(); + + // Adds a tab to load. + void AddTab(NavigationController* controller); + + // Loads the next batch of tabs until SessionRestore::num_tabs_to_load_ tabs + // are loading, or all tabs are loading. If there are no more tabs to load, + // this deletes the TabLoader. + // + // This must be invoked once to start loading. + void LoadTabs(); + + private: + // NotificationObserver method. Removes the specified tab and loads the next + // tab. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Removes the listeners from the specified tab and removes the tab from + // the set of tabs to load and list of tabs we're waiting to get a load + // from. + void RemoveTab(NavigationController* tab); + + // Adds the necessary listeners for the specified tab. + void AddListeners(NavigationController* controller); + + // Removes the necessary listeners for the specified tab. + void RemoveListeners(NavigationController* controller); + + // Has Load been invoked? + bool loading_; + + // The set of tabs we've initiated loading on. This does NOT include the + // selected tabs. + typedef std::set<NavigationController*> TabsLoading; + TabsLoading tabs_loading_; + + // The tabs we need to load. + TabsToLoad tabs_to_load_; +}; + +TabLoader::TabLoader() + : loading_(false) { +} + +TabLoader::~TabLoader() { + DCHECK(tabs_to_load_.empty() && tabs_loading_.empty()); +} + +void TabLoader::AddTab(NavigationController* controller) { + if (controller) { + DCHECK(find(tabs_to_load_.begin(), tabs_to_load_.end(), controller) == + tabs_to_load_.end()); + tabs_to_load_.push_back(controller); + AddListeners(controller); + } else { + // Should never get a NULL tab. + NOTREACHED(); + } +} + +void TabLoader::LoadTabs() { + loading_ = true; + while (!tabs_to_load_.empty() && + (SessionRestore::num_tabs_to_load_ == 0 || + tabs_loading_.size() < SessionRestore::num_tabs_to_load_)) { + NavigationController* tab = tabs_to_load_.front(); + tabs_loading_.insert(tab); + tabs_to_load_.pop_front(); + tab->LoadIfNecessary(); + if (tab && tab->active_contents()) { + int tab_index; + Browser* browser = Browser::GetBrowserForController(tab, &tab_index); + if (browser && browser->selected_index() != tab_index) { + // By default tabs are marked as visible. As only the selected tab is + // visible we need to explicitly tell non-selected tabs they are hidden. + // Without this call non-selected tabs are not marked as backgrounded. + // + // NOTE: We need to do this here rather than when the tab is added to + // the Browser as at that time not everything has been created, so that + // the call would do nothing. + tab->active_contents()->WasHidden(); + } + } + } + + if (tabs_to_load_.empty()) { + for (TabsLoading::iterator i = tabs_loading_.begin(); + i != tabs_loading_.end(); ++i) { + RemoveListeners(*i); + } + tabs_loading_.clear(); + delete this; + } +} + +void TabLoader::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NOTIFY_TAB_CLOSED || type == NOTIFY_LOAD_STOP); + NavigationController* tab = Source<NavigationController>(source).ptr(); + RemoveTab(tab); + if (loading_) { + LoadTabs(); + // WARNING: if there are no more tabs to load, we have been deleted. + } +} + +void TabLoader::RemoveTab(NavigationController* tab) { + RemoveListeners(tab); + + TabsLoading::iterator i = tabs_loading_.find(tab); + if (i != tabs_loading_.end()) + tabs_loading_.erase(i); + + TabsToLoad::iterator j = + find(tabs_to_load_.begin(), tabs_to_load_.end(), tab); + if (j != tabs_to_load_.end()) + tabs_to_load_.erase(j); +} + +void TabLoader::AddListeners(NavigationController* controller) { + NotificationService::current()->AddObserver( + this, NOTIFY_TAB_CLOSED, + Source<NavigationController>(controller)); + NotificationService::current()->AddObserver( + this, NOTIFY_LOAD_STOP, + Source<NavigationController>(controller)); +} + +void TabLoader::RemoveListeners(NavigationController* controller) { + NotificationService::current()->RemoveObserver( + this, NOTIFY_TAB_CLOSED, + Source<NavigationController>(controller)); + NotificationService::current()->RemoveObserver( + this, NOTIFY_LOAD_STOP, + Source<NavigationController>(controller)); +} + +// SessionRestoreImpl --------------------------------------------------------- + +// SessionRestoreImpl is responsible for fetching the set of tabs to create +// from SessionService. SessionRestoreImpl deletes itself when done. + +class SessionRestoreImpl : public NotificationObserver { + public: + SessionRestoreImpl(Profile* profile, + Browser* browser, + bool synchronous, + bool clobber_existing_window, + bool always_create_tabbed_browser, + const std::vector<GURL>& urls_to_open) + : profile_(profile), + browser_(browser), + synchronous_(synchronous), + clobber_existing_window_(clobber_existing_window), + always_create_tabbed_browser_(always_create_tabbed_browser), + urls_to_open_(urls_to_open) { + } + + void SessionRestoreImpl::Restore() { + SessionService* session_service = profile_->GetSessionService(); + DCHECK(session_service); + SessionService::LastSessionCallback* callback = + NewCallback(this, &SessionRestoreImpl::OnGotSession); + session_service->GetLastSession(&request_consumer_, callback); + + if (synchronous_) { + MessageLoop::current()->Run(); + delete this; + return; + } + + if (browser_) + registrar_.Add(this, NOTIFY_BROWSER_CLOSED, Source<Browser>(browser_)); + } + + ~SessionRestoreImpl() { + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type != NOTIFY_BROWSER_CLOSED) { + NOTREACHED(); + return; + } + delete this; + } + + private: + // Invoked when done with creating all the tabs/browsers. + // + // |created_tabbed_browser| indicates whether a tabbed browser was created, + // or we used an existing tabbed browser. + // + // If successful, this begins loading tabs and deletes itself when all tabs + // have been loaded. + void FinishedTabCreation(bool succeeded, bool created_tabbed_browser) { + if (!created_tabbed_browser && always_create_tabbed_browser_) { + Browser* browser = Browser::Create(profile_); + if (urls_to_open_.empty()) { + // No tab browsers were created and no URLs were supplied on the command + // line. Add an empty URL, which is treated as opening the users home + // page. + urls_to_open_.push_back(GURL()); + } + AppendURLsToBrowser(browser, urls_to_open_); + browser->window()->Show(); + } + + if (synchronous_) + MessageLoop::current()->Quit(); + + if (succeeded) { + DCHECK(tab_loader_.get()); + // TabLoader delets itself when done loading. + tab_loader_.release()->LoadTabs(); + } + + if (!synchronous_) { + // If we're not synchronous we need to delete ourself. + // NOTE: we must use DeleteLater here as most likely we're in a callback + // from the history service which doesn't deal well with deleting the + // object it is notifying. + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } + } + + void OnGotSession(SessionService::Handle handle, + std::vector<SessionWindow*>* windows) { + if (windows->empty()) { + // Restore was unsuccessful. + FinishedTabCreation(false, false); + return; + } + + tab_loader_.reset(new TabLoader()); + + Browser* current_browser = + browser_ ? browser_ : BrowserList::GetLastActive(); + // After the for loop this contains the last TABBED_BROWSER. Is null if no + // tabbed browsers exist. + Browser* last_browser = NULL; + bool has_tabbed_browser = false; + for (std::vector<SessionWindow*>::iterator i = windows->begin(); + i != windows->end(); ++i) { + Browser* browser = NULL; + if (!has_tabbed_browser && (*i)->type == Browser::TYPE_NORMAL) + has_tabbed_browser = true; + if (i == windows->begin() && (*i)->type == Browser::TYPE_NORMAL && + !clobber_existing_window_) { + // If there is an open tabbed browser window, use it. Otherwise fall + // through and create a new one. + browser = current_browser; + if (browser && (browser->type() != Browser::TYPE_NORMAL || + browser->profile()->IsOffTheRecord())) { + browser = NULL; + } + } + if (!browser) { + browser = new Browser((*i)->type, profile_); + browser->set_override_bounds((*i)->bounds); + browser->set_override_maximized((*i)->is_maximized); + browser->CreateBrowserWindow(); + } + if ((*i)->type == Browser::TYPE_NORMAL) + last_browser = browser; + const int initial_tab_count = browser->tab_count(); + RestoreTabsToBrowser(*(*i), browser); + ShowBrowser(browser, initial_tab_count, (*i)->selected_tab_index); + NotifySessionServiceOfRestoredTabs(browser, initial_tab_count); + } + + // If we're restoring a session as the result of a crash and the session + // included at least one tabbed browser, then close the browser window + // that was opened when the user clicked to restore the session. + if (clobber_existing_window_ && current_browser && has_tabbed_browser && + current_browser->type() == Browser::TYPE_NORMAL) { + current_browser->CloseAllTabs(); + } + if (last_browser && !urls_to_open_.empty()) + AppendURLsToBrowser(last_browser, urls_to_open_); + // If last_browser is NULL and urls_to_open_ is non-empty, + // FinishedTabCreation will create a new TabbedBrowser and add the urls to + // it. + FinishedTabCreation(true, has_tabbed_browser); + } + + void RestoreTabsToBrowser(const SessionWindow& window, Browser* browser) { + DCHECK(!window.tabs.empty()); + for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin(); + i != window.tabs.end(); ++i) { + const SessionTab& tab = *(*i); + DCHECK(!tab.navigations.empty()); + int selected_index = tab.current_navigation_index; + selected_index = std::max( + 0, + std::min(selected_index, + static_cast<int>(tab.navigations.size() - 1))); + tab_loader_->AddTab( + browser->AddRestoredTab(tab.navigations, + static_cast<int>(i - window.tabs.begin()), + selected_index, + false)); + } + } + + void ShowBrowser(Browser* browser, + int initial_tab_count, + int selected_session_index) { + if (browser_ == browser) { + browser->SelectTabContentsAt(browser->tab_count() - 1, true); + return; + } + + DCHECK(browser); + DCHECK(browser->tab_count()); + browser->SelectTabContentsAt( + std::min(initial_tab_count + std::max(0, selected_session_index), + browser->tab_count() - 1), true); + browser->window()->Show(); + } + + void AppendURLsToBrowser(Browser* browser, const std::vector<GURL>& urls) { + for (size_t i = 0; i < urls.size(); ++i) { + browser->AddTabWithURL(urls[i], GURL(), PageTransition::START_PAGE, + (i == 0), NULL); + } + } + + // Invokes TabRestored on the SessionService for all tabs in browser after + // initial_count. + void NotifySessionServiceOfRestoredTabs(Browser* browser, int initial_count) { + SessionService* session_service = profile_->GetSessionService(); + for (int i = initial_count; i < browser->tab_count(); ++i) + session_service->TabRestored(browser->GetTabContentsAt(i)->controller()); + } + + // The profile to create the sessions for. + Profile* profile_; + + // The first browser to restore to, may be null. + Browser* browser_; + + // Whether or not restore is synchronous. + const bool synchronous_; + + // See description in RestoreSession (in .h). + const bool clobber_existing_window_; + + // If true and there is an error or there are no windows to restore, we + // create a tabbed browser anyway. This is used on startup to make sure at + // at least one window is created. + const bool always_create_tabbed_browser_; + + // Set of URLs to open in addition to those restored from the session. + std::vector<GURL> urls_to_open_; + + // Used to get the session. + CancelableRequestConsumer request_consumer_; + + // Responsible for loading the tabs. + scoped_ptr<TabLoader> tab_loader_; + + NotificationRegistrar registrar_; +}; + +} // namespace + +// SessionRestore ------------------------------------------------------------- + +// static +size_t SessionRestore::num_tabs_to_load_ = 0; + +static void Restore(Profile* profile, + Browser* browser, + bool synchronous, + bool clobber_existing_window, + bool always_create_tabbed_browser, + const std::vector<GURL>& urls_to_open) { + DCHECK(profile); + if (!profile->GetSessionService()) + return; + // SessionRestoreImpl takes care of deleting itself when done. + SessionRestoreImpl* restorer = + 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 clobber_existing_window, + bool always_create_tabbed_browser, + const std::vector<GURL>& urls_to_open) { + Restore(profile, browser, false, clobber_existing_window, + always_create_tabbed_browser, urls_to_open); +} + +// static +void SessionRestore::RestoreSessionSynchronously( + Profile* profile, + const std::vector<GURL>& urls_to_open) { + Restore(profile, NULL, true, false, true, urls_to_open); +} |