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_service.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_service.cc')
-rw-r--r-- | chrome/browser/sessions/session_service.cc | 1120 |
1 files changed, 1120 insertions, 0 deletions
diff --git a/chrome/browser/sessions/session_service.cc b/chrome/browser/sessions/session_service.cc new file mode 100644 index 0000000..6016651 --- /dev/null +++ b/chrome/browser/sessions/session_service.cc @@ -0,0 +1,1120 @@ +// 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_service.h" + +#include <limits> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/pickle.h" +#include "base/thread.h" +#include "chrome/browser/browser_init.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/navigation_controller.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/profile.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" +#include "chrome/common/notification_types.h" +#include "chrome/common/scoped_vector.h" +#include "chrome/common/win_util.h" + +using base::Time; + +// Identifier for commands written to file. +static const SessionCommand::id_type kCommandSetTabWindow = 0; +// kCommandSetWindowBounds is no longer used (it's superseded by +// kCommandSetWindowBounds2). I leave it here to document what it was. +// static const SessionCommand::id_type kCommandSetWindowBounds = 1; +static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2; +static const SessionCommand::id_type kCommandTabClosed = 3; +static const SessionCommand::id_type kCommandWindowClosed = 4; +static const SessionCommand::id_type + kCommandTabNavigationPathPrunedFromBack = 5; +static const SessionCommand::id_type kCommandUpdateTabNavigation = 6; +static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7; +static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8; +static const SessionCommand::id_type kCommandSetWindowType = 9; +static const SessionCommand::id_type kCommandSetWindowBounds2 = 10; +static const SessionCommand::id_type + kCommandTabNavigationPathPrunedFromFront = 11; + +// 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; + int64 close_time; +}; + +struct WindowBoundsPayload2 { + SessionID::id_type window_id; + int32 x; + int32 y; + int32 w; + int32 h; + bool is_maximized; +}; + +struct IDAndIndexPayload { + SessionID::id_type id; + int32 index; +}; + +typedef IDAndIndexPayload TabIndexInWindowPayload; + +typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload; + +typedef IDAndIndexPayload SelectedNavigationIndexPayload; + +typedef IDAndIndexPayload SelectedTabInIndexPayload; + +typedef IDAndIndexPayload WindowTypePayload; + +typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; + +} // namespace + +// SessionService ------------------------------------------------------------- + +SessionService::SessionService(Profile* profile) + : BaseSessionService(SESSION_RESTORE, profile, std::wstring()), + has_open_tabbed_browsers_(false), + move_on_new_browser_(false) { + Init(); +} + +SessionService::SessionService(const std::wstring& save_path) + : BaseSessionService(SESSION_RESTORE, NULL, save_path), + has_open_tabbed_browsers_(false), + move_on_new_browser_(false) { + Init(); +} + +SessionService::~SessionService() { + Save(); + + // Unregister our notifications. + NotificationService::current()->RemoveObserver( + this, NOTIFY_TAB_PARENTED, NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, NOTIFY_TAB_CLOSED, NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, NOTIFY_NAV_LIST_PRUNED, NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, NOTIFY_NAV_ENTRY_CHANGED, NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, NOTIFY_NAV_ENTRY_COMMITTED, NotificationService::AllSources()); + NotificationService::current()->RemoveObserver( + this, NOTIFY_BROWSER_OPENED, NotificationService::AllSources()); +} + +void SessionService::ResetFromCurrentBrowsers() { + ScheduleReset(); +} + +void SessionService::MoveCurrentSessionToLastSession() { + pending_tab_close_ids_.clear(); + window_closing_ids_.clear(); + pending_window_close_ids_.clear(); + + Save(); + + if (!backend_thread()) { + backend()->MoveCurrentSessionToLastSession(); + } else { + backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + backend(), &SessionBackend::MoveCurrentSessionToLastSession)); + } +} + +void SessionService::SetTabWindow(const SessionID& window_id, + const SessionID& tab_id) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id)); +} + +void SessionService::SetWindowBounds(const SessionID& window_id, + const gfx::Rect& bounds, + bool is_maximized) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, + is_maximized)); +} + +void SessionService::SetTabIndexInWindow(const SessionID& window_id, + const SessionID& tab_id, + int new_index) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index)); +} + +void SessionService::TabClosed(const SessionID& window_id, + const SessionID& tab_id) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + IdToRange::iterator i = tab_to_available_range_.find(tab_id.id()); + if (i != tab_to_available_range_.end()) + tab_to_available_range_.erase(i); + + if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(), + window_id.id()) != pending_window_close_ids_.end()) { + // Tab is in last window. Don't commit it immediately, instead add it to the + // list of tabs to close. If the user creates another window, the close is + // committed. + pending_tab_close_ids_.insert(tab_id.id()); + } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(), + window_id.id()) != window_closing_ids_.end() || + !IsOnlyOneTabLeft()) { + // Tab closure is the result of a window close (and it isn't the last + // window), or closing a tab and there are other windows/tabs open. Mark the + // tab as closed. + ScheduleCommand(CreateTabClosedCommand(tab_id.id())); + } else { + // User closed the last tab in the last tabbed browser. Don't mark the + // tab closed. + pending_tab_close_ids_.insert(tab_id.id()); + has_open_tabbed_browsers_ = false; + } +} + +void SessionService::WindowClosing(const SessionID& window_id) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + // The window is about to close. If there are other tabbed browsers with the + // same original profile commit the close immediately. + // + // NOTE: if the user chooses the exit menu item session service is destroyed + // and this code isn't hit. + if (has_open_tabbed_browsers_) { + // Closing a window can never make has_open_tabbed_browsers_ go from false + // to true, so only update it if already true. + has_open_tabbed_browsers_ = HasOpenTabbedBrowsers(window_id); + } + if (!has_open_tabbed_browsers_) + pending_window_close_ids_.insert(window_id.id()); + else + window_closing_ids_.insert(window_id.id()); +} + +void SessionService::WindowClosed(const SessionID& window_id) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + windows_tracking_.erase(window_id.id()); + + if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) { + window_closing_ids_.erase(window_id.id()); + ScheduleCommand(CreateWindowClosedCommand(window_id.id())); + } else if (pending_window_close_ids_.find(window_id.id()) == + pending_window_close_ids_.end()) { + // We'll hit this if user closed the last tab in a window. + has_open_tabbed_browsers_ = HasOpenTabbedBrowsers(window_id); + if (!has_open_tabbed_browsers_) + pending_window_close_ids_.insert(window_id.id()); + else + ScheduleCommand(CreateWindowClosedCommand(window_id.id())); + } +} + +void SessionService::SetWindowType(const SessionID& window_id, + Browser::Type type) { + if (!should_track_changes_for_browser_type(type)) + return; + + windows_tracking_.insert(window_id.id()); + + // The user created a new tabbed browser with our profile. Commit any + // pending closes. + CommitPendingCloses(); + + has_open_tabbed_browsers_ = true; + move_on_new_browser_ = true; + + ScheduleCommand(CreateSetWindowTypeCommand(window_id, type)); +} + +void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id, + const SessionID& tab_id, + int count) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + TabNavigationPathPrunedFromBackPayload payload = { 0 }; + payload.id = tab_id.id(); + payload.index = count; + SessionCommand* command = + new SessionCommand(kCommandTabNavigationPathPrunedFromBack, + sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + ScheduleCommand(command); +} + +void SessionService::TabNavigationPathPrunedFromFront( + const SessionID& window_id, + const SessionID& tab_id, + int count) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + // Update the range of indices. + if (tab_to_available_range_.find(tab_id.id()) != + tab_to_available_range_.end()) { + std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; + range.first = std::max(0, range.first - count); + range.second = std::max(0, range.second - count); + } + + TabNavigationPathPrunedFromFrontPayload payload = { 0 }; + payload.id = tab_id.id(); + payload.index = count; + SessionCommand* command = + new SessionCommand(kCommandTabNavigationPathPrunedFromFront, + sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + ScheduleCommand(command); +} + +void SessionService::UpdateTabNavigation(const SessionID& window_id, + const SessionID& tab_id, + int index, + const NavigationEntry& entry) { + if (!ShouldTrackEntry(entry) || !ShouldTrackChangesToWindow(window_id)) + return; + + if (tab_to_available_range_.find(tab_id.id()) != + tab_to_available_range_.end()) { + std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; + range.first = std::min(index, range.first); + range.second = std::max(index, range.second); + } + ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, + tab_id.id(), index, entry)); +} + +void SessionService::TabRestored(NavigationController* controller) { + if (!ShouldTrackChangesToWindow(controller->window_id())) + return; + + BuildCommandsForTab(controller->window_id(), controller, -1, + &pending_commands(), NULL); + StartSaveTimer(); +} + +void SessionService::SetSelectedNavigationIndex(const SessionID& window_id, + const SessionID& tab_id, + int index) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + if (tab_to_available_range_.find(tab_id.id()) != + tab_to_available_range_.end()) { + if (index < tab_to_available_range_[tab_id.id()].first || + index > tab_to_available_range_[tab_id.id()].second) { + // The new index is outside the range of what we've archived, schedule + // a reset. + ResetFromCurrentBrowsers(); + return; + } + } + ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index)); +} + +void SessionService::SetSelectedTabInWindow(const SessionID& window_id, + int index) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index)); +} + +SessionService::Handle SessionService::GetLastSession( + CancelableRequestConsumerBase* consumer, + LastSessionCallback* callback) { + return ScheduleGetLastSessionCommands( + new InternalLastSessionRequest( + NewCallback(this, &SessionService::OnGotLastSessionCommands), + callback), consumer); +} + +void SessionService::Init() { + // Register for the notifications we're interested in. + NotificationService::current()->AddObserver( + this, NOTIFY_TAB_PARENTED, NotificationService::AllSources()); + NotificationService::current()->AddObserver( + this, NOTIFY_TAB_CLOSED, NotificationService::AllSources()); + NotificationService::current()->AddObserver( + this, NOTIFY_NAV_LIST_PRUNED, NotificationService::AllSources()); + NotificationService::current()->AddObserver( + this, NOTIFY_NAV_ENTRY_CHANGED, NotificationService::AllSources()); + NotificationService::current()->AddObserver( + this, NOTIFY_NAV_ENTRY_COMMITTED, NotificationService::AllSources()); + NotificationService::current()->AddObserver( + this, NOTIFY_BROWSER_OPENED, NotificationService::AllSources()); +} + +void SessionService::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // All of our messages have the NavigationController as the source. + switch (type) { + case NOTIFY_BROWSER_OPENED: { + Browser* browser = Source<Browser>(source).ptr(); + if (browser->profile() != profile() || + !should_track_changes_for_browser_type(browser->type())) { + return; + } + + if (!has_open_tabbed_browsers_ && !BrowserInit::InProcessStartup()) { + // We're going from no tabbed browsers to a tabbed browser (and not in + // process startup), restore the last session. + if (move_on_new_browser_) { + // Make the current session the last. + MoveCurrentSessionToLastSession(); + move_on_new_browser_ = false; + } + SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile()); + if (pref.type == SessionStartupPref::LAST) { + SessionRestore::RestoreSession( + profile(), browser, false, false, std::vector<GURL>()); + } + } + SetWindowType(browser->session_id(), browser->type()); + break; + } + + case NOTIFY_TAB_PARENTED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + SetTabWindow(controller->window_id(), controller->session_id()); + break; + } + + case NOTIFY_TAB_CLOSED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + TabClosed(controller->window_id(), controller->session_id()); + break; + } + + case NOTIFY_NAV_LIST_PRUNED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + Details<NavigationController::PrunedDetails> pruned_details(details); + if (pruned_details->from_front) { + TabNavigationPathPrunedFromFront(controller->window_id(), + controller->session_id(), + pruned_details->count); + } else { + TabNavigationPathPrunedFromBack(controller->window_id(), + controller->session_id(), + controller->GetEntryCount()); + } + break; + } + + case NOTIFY_NAV_ENTRY_CHANGED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + Details<NavigationController::EntryChangedDetails> changed(details); + UpdateTabNavigation(controller->window_id(), controller->session_id(), + changed->index, *changed->changed_entry); + break; + } + + case NOTIFY_NAV_ENTRY_COMMITTED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + int current_entry_index = controller->GetCurrentEntryIndex(); + SetSelectedNavigationIndex(controller->window_id(), + controller->session_id(), + current_entry_index); + UpdateTabNavigation(controller->window_id(), controller->session_id(), + current_entry_index, + *controller->GetEntryAtIndex(current_entry_index)); + break; + } + + default: + NOTREACHED(); + } +} + +SessionCommand* SessionService::CreateSetSelectedTabInWindow( + const SessionID& window_id, + int index) { + SelectedTabInIndexPayload payload = { 0 }; + payload.id = window_id.id(); + payload.index = index; + SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex, + sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateSetTabWindowCommand( + const SessionID& window_id, + const SessionID& tab_id) { + SessionID::id_type payload[] = { window_id.id(), tab_id.id() }; + SessionCommand* command = + new SessionCommand(kCommandSetTabWindow, sizeof(payload)); + memcpy(command->contents(), payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateSetWindowBoundsCommand( + const SessionID& window_id, + const gfx::Rect& bounds, + bool is_maximized) { + WindowBoundsPayload2 payload = { 0 }; + payload.window_id = window_id.id(); + payload.x = bounds.x(); + payload.y = bounds.y(); + payload.w = bounds.width(); + payload.h = bounds.height(); + payload.is_maximized = is_maximized; + SessionCommand* command = new SessionCommand(kCommandSetWindowBounds2, + sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateSetTabIndexInWindowCommand( + const SessionID& tab_id, + int new_index) { + TabIndexInWindowPayload payload = { 0 }; + payload.id = tab_id.id(); + payload.index = new_index; + SessionCommand* command = + new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateTabClosedCommand( + const SessionID::id_type tab_id) { + ClosedPayload payload; + // Because of what appears to be a compiler bug setting payload to {0} doesn't + // set the padding to 0, resulting in Purify reporting an UMR when we write + // the structure to disk. To avoid this we explicitly memset the struct. + memset(&payload, 0, sizeof(payload)); + payload.id = tab_id; + payload.close_time = Time::Now().ToInternalValue(); + SessionCommand* command = + new SessionCommand(kCommandTabClosed, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateWindowClosedCommand( + const SessionID::id_type window_id) { + ClosedPayload payload; + // See comment in CreateTabClosedCommand as to why we do this. + memset(&payload, 0, sizeof(payload)); + payload.id = window_id; + payload.close_time = Time::Now().ToInternalValue(); + SessionCommand* command = + new SessionCommand(kCommandWindowClosed, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand( + const SessionID& tab_id, + int index) { + SelectedNavigationIndexPayload payload = { 0 }; + payload.id = tab_id.id(); + payload.index = index; + SessionCommand* command = new SessionCommand( + kCommandSetSelectedNavigationIndex, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +SessionCommand* SessionService::CreateSetWindowTypeCommand( + const SessionID& window_id, + Browser::Type type) { + WindowTypePayload payload = { 0 }; + payload.id = window_id.id(); + payload.index = static_cast<int32>(type); + SessionCommand* command = new SessionCommand( + kCommandSetWindowType, sizeof(payload)); + memcpy(command->contents(), &payload, sizeof(payload)); + return command; +} + +void SessionService::OnGotLastSessionCommands( + Handle handle, + scoped_refptr<InternalGetCommandsRequest> request) { + if (request->canceled()) + return; + ScopedVector<SessionWindow> valid_windows; + RestoreSessionFromCommands( + request->commands, &(valid_windows.get())); + static_cast<InternalLastSessionRequest*>(request.get())-> + real_callback->RunWithParams( + LastSessionCallback::TupleType(request->handle(), + &(valid_windows.get()))); +} + +void SessionService::RestoreSessionFromCommands( + const std::vector<SessionCommand*>& commands, + std::vector<SessionWindow*>* valid_windows) { + std::map<int, SessionTab*> tabs; + std::map<int, SessionWindow*> windows; + + if (CreateTabsAndWindows(commands, &tabs, &windows)) { + AddTabsToWindows(&tabs, &windows); + SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows); + UpdateSelectedTabIndex(valid_windows); + } + STLDeleteValues(&tabs); + // Don't delete conents of windows, that is done by the caller as all + // valid windows are added to valid_windows. +} + +void SessionService::UpdateSelectedTabIndex( + std::vector<SessionWindow*>* windows) { + for (std::vector<SessionWindow*>::const_iterator i = windows->begin(); + i != windows->end(); ++i) { + // See note in SessionWindow as to why we do this. + int new_index = 0; + for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin(); + j != (*i)->tabs.end(); ++j) { + if ((*j)->tab_visual_index == (*i)->selected_tab_index) { + new_index = static_cast<int>(j - (*i)->tabs.begin()); + break; + } + } + (*i)->selected_tab_index = new_index; + } +} + +SessionWindow* SessionService::GetWindow( + SessionID::id_type window_id, + IdToSessionWindow* windows) { + std::map<int, SessionWindow*>::iterator i = windows->find(window_id); + if (i == windows->end()) { + SessionWindow* window = new SessionWindow(); + window->window_id.set_id(window_id); + (*windows)[window_id] = window; + return window; + } + return i->second; +} + +SessionTab* SessionService::GetTab( + SessionID::id_type tab_id, + IdToSessionTab* tabs) { + DCHECK(tabs); + std::map<int, SessionTab*>::iterator i = tabs->find(tab_id); + if (i == tabs->end()) { + SessionTab* tab = new SessionTab(); + tab->tab_id.set_id(tab_id); + (*tabs)[tab_id] = tab; + return tab; + } + return i->second; +} + +std::vector<TabNavigation>::iterator + SessionService::FindClosestNavigationWithIndex( + std::vector<TabNavigation>* navigations, + int index) { + DCHECK(navigations); + for (std::vector<TabNavigation>::iterator i = navigations->begin(); + i != navigations->end(); ++i) { + if (i->index() >= index) + return i; + } + return navigations->end(); +} + +// Function used in sorting windows. Sorting is done based on window id. As +// window ids increment for each new window, this effectively sorts by creation +// time. +static bool WindowOrderSortFunction(const SessionWindow* w1, + const SessionWindow* w2) { + return w1->window_id.id() < w2->window_id.id(); +} + +// Compares the two tabs based on visual index. +static bool TabVisualIndexSortFunction(const SessionTab* t1, + const SessionTab* t2) { + const int delta = t1->tab_visual_index - t2->tab_visual_index; + return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0); +} + +void SessionService::SortTabsBasedOnVisualOrderAndPrune( + std::map<int, SessionWindow*>* windows, + std::vector<SessionWindow*>* valid_windows) { + std::map<int, SessionWindow*>::iterator i = windows->begin(); + while (i != windows->end()) { + if (i->second->tabs.empty() || i->second->is_constrained || + !should_track_changes_for_browser_type(i->second->type)) { + delete i->second; + i = windows->erase(i); + } else { + // Valid window; sort the tabs and add it to the list of valid windows. + std::sort(i->second->tabs.begin(), i->second->tabs.end(), + &TabVisualIndexSortFunction); + // Add the window such that older windows appear first. + if (valid_windows->empty()) { + valid_windows->push_back(i->second); + } else { + valid_windows->insert( + std::upper_bound(valid_windows->begin(), valid_windows->end(), + i->second, &WindowOrderSortFunction), + i->second); + } + ++i; + } + } +} + +void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs, + std::map<int, SessionWindow*>* windows) { + std::map<int, SessionTab*>::iterator i = tabs->begin(); + while (i != tabs->end()) { + SessionTab* tab = i->second; + if (tab->window_id.id() && !tab->navigations.empty()) { + SessionWindow* window = GetWindow(tab->window_id.id(), windows); + window->tabs.push_back(tab); + i = tabs->erase(i); + + // See note in SessionTab as to why we do this. + std::vector<TabNavigation>::iterator j = + FindClosestNavigationWithIndex(&(tab->navigations), + tab->current_navigation_index); + if (j == tab->navigations.end()) { + tab->current_navigation_index = + static_cast<int>(tab->navigations.size() - 1); + } else { + tab->current_navigation_index = + static_cast<int>(j - tab->navigations.begin()); + } + } else { + // Never got a set tab index in window, or tabs are empty, nothing + // to do. + ++i; + } + } +} + +bool SessionService::CreateTabsAndWindows( + const std::vector<SessionCommand*>& data, + std::map<int, SessionTab*>* tabs, + std::map<int, SessionWindow*>* windows) { + // If the file is corrupt (command with wrong size, or unknown command), we + // still return true and attempt to restore what we we can. + + for (std::vector<SessionCommand*>::const_iterator i = data.begin(); + i != data.end(); ++i) { + const SessionCommand* command = *i; + + switch (command->id()) { + case kCommandSetTabWindow: { + SessionID::id_type payload[2]; + if (!command->GetPayload(payload, sizeof(payload))) + return true; + GetTab(payload[1], tabs)->window_id.set_id(payload[0]); + break; + } + + case kCommandSetWindowBounds2: { + WindowBoundsPayload2 payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x, + payload.y, + payload.w, + payload.h); + GetWindow(payload.window_id, windows)->is_maximized = + payload.is_maximized; + break; + } + + case kCommandSetTabIndexInWindow: { + TabIndexInWindowPayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + GetTab(payload.id, tabs)->tab_visual_index = payload.index; + break; + } + + case kCommandTabClosed: + case kCommandWindowClosed: { + ClosedPayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + if (command->id() == kCommandTabClosed) { + delete GetTab(payload.id, tabs); + tabs->erase(payload.id); + } else { + delete GetWindow(payload.id, windows); + windows->erase(payload.id); + } + break; + } + + case kCommandTabNavigationPathPrunedFromBack: { + TabNavigationPathPrunedFromBackPayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + SessionTab* tab = GetTab(payload.id, tabs); + tab->navigations.erase( + FindClosestNavigationWithIndex(&(tab->navigations), payload.index), + tab->navigations.end()); + break; + } + + case kCommandTabNavigationPathPrunedFromFront: { + TabNavigationPathPrunedFromFrontPayload payload; + if (!command->GetPayload(&payload, sizeof(payload)) || + payload.index <= 0) { + return true; + } + SessionTab* tab = GetTab(payload.id, tabs); + + // Update the selected navigation index. + tab->current_navigation_index = + std::max(-1, tab->current_navigation_index - payload.index); + + // And update the index of existing navigations. + for (std::vector<TabNavigation>::iterator i = tab->navigations.begin(); + i != tab->navigations.end();) { + i->set_index(i->index() - payload.index); + if (i->index() < 0) + i = tab->navigations.erase(i); + else + ++i; + } + break; + } + + case kCommandUpdateTabNavigation: { + TabNavigation navigation; + SessionID::id_type tab_id; + if (!RestoreUpdateTabNavigationCommand(*command, &navigation, &tab_id)) + return true; + + 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()) + *i = navigation; + else + tab->navigations.insert(i, navigation); + break; + } + + case kCommandSetSelectedNavigationIndex: { + SelectedNavigationIndexPayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + GetTab(payload.id, tabs)->current_navigation_index = payload.index; + break; + } + + case kCommandSetSelectedTabInIndex: { + SelectedTabInIndexPayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + GetWindow(payload.id, windows)->selected_tab_index = payload.index; + break; + } + + case kCommandSetWindowType: { + WindowTypePayload payload; + if (!command->GetPayload(&payload, sizeof(payload))) + return true; + GetWindow(payload.id, windows)->is_constrained = false; + GetWindow(payload.id, windows)->type = + static_cast<Browser::Type>(payload.index); + break; + } + + default: + return true; + } + } + return true; +} + +void SessionService::BuildCommandsForTab( + const SessionID& window_id, + NavigationController* controller, + int index_in_window, + std::vector<SessionCommand*>* commands, + IdToRange* tab_to_available_range) { + DCHECK(controller && commands && window_id.id()); + commands->push_back( + CreateSetTabWindowCommand(window_id, controller->session_id())); + const int current_index = controller->GetCurrentEntryIndex(); + const int min_index = std::max(0, + 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) { + (*tab_to_available_range)[controller->session_id().id()] = + std::pair<int, int>(min_index, max_index); + } + for (int i = min_index; i < max_index; ++i) { + const NavigationEntry* entry = (i == pending_index) ? + controller->GetPendingEntry() : controller->GetEntryAtIndex(i); + DCHECK(entry); + if (ShouldTrackEntry(*entry)) { + commands->push_back( + CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, + controller->session_id().id(), + i, + *entry)); + } + } + commands->push_back( + CreateSetSelectedNavigationIndexCommand(controller->session_id(), + current_index)); + + if (index_in_window != -1) { + commands->push_back( + CreateSetTabIndexInWindowCommand(controller->session_id(), + index_in_window)); + } +} + +void SessionService::BuildCommandsForBrowser( + Browser* browser, + std::vector<SessionCommand*>* commands, + IdToRange* tab_to_available_range, + std::set<SessionID::id_type>* windows_to_track) { + DCHECK(browser && commands); + DCHECK(browser->session_id().id()); + + commands->push_back( + CreateSetWindowBoundsCommand(browser->session_id(), + browser->window()->GetNormalBounds(), + browser->window()->IsMaximized())); + + commands->push_back(CreateSetWindowTypeCommand( + browser->session_id(), browser->type())); + + bool added_to_windows_to_track = false; + for (int i = 0; i < browser->tab_count(); ++i) { + TabContents* tab = browser->GetTabContentsAt(i); + DCHECK(tab); + 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) { + windows_to_track->insert(browser->session_id().id()); + added_to_windows_to_track = true; + } + } + } + commands->push_back( + CreateSetSelectedTabInWindow(browser->session_id(), + browser->selected_index())); +} + +void SessionService::BuildCommandsFromBrowsers( + std::vector<SessionCommand*>* commands, + IdToRange* tab_to_available_range, + std::set<SessionID::id_type>* windows_to_track) { + DCHECK(commands); + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end(); ++i) { + // Make sure the browser has tabs and a window. Browsers destructor + // removes itself from the BrowserList. When a browser is closed the + // destructor is not necessarily run immediately. This means its possible + // for us to get a handle to a browser that is about to be removed. If + // the tab count is 0 or the window is NULL, the browser is about to be + // deleted, so we ignore it. + if (should_track_changes_for_browser_type((*i)->type()) && + (*i)->tab_count() && (*i)->window()) { + BuildCommandsForBrowser(*i, commands, tab_to_available_range, + windows_to_track); + } + } +} + +void SessionService::ScheduleReset() { + set_pending_reset(true); + STLDeleteElements(&pending_commands()); + tab_to_available_range_.clear(); + windows_tracking_.clear(); + 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 + // SetWindowType messages. Set these here to make sure our state is correct. + has_open_tabbed_browsers_ = true; + move_on_new_browser_ = true; + } + StartSaveTimer(); +} + +bool SessionService::ReplacePendingCommand(SessionCommand* command) { + // We only optimize page navigations, which can happen quite frequently and + // are expensive. If necessary, other commands could be searched for as + // well. + if (command->id() != kCommandUpdateTabNavigation) + return false; + void* iterator = NULL; + scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle()); + SessionID::id_type command_tab_id; + int command_nav_index; + if (!command_pickle->ReadInt(&iterator, &command_tab_id) || + !command_pickle->ReadInt(&iterator, &command_nav_index)) { + return false; + } + for (std::vector<SessionCommand*>::reverse_iterator i = + pending_commands().rbegin(); i != pending_commands().rend(); ++i) { + SessionCommand* existing_command = *i; + if (existing_command->id() == kCommandUpdateTabNavigation) { + SessionID::id_type existing_tab_id; + int existing_nav_index; + scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle()); + iterator = NULL; + if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) || + !existing_pickle->ReadInt(&iterator, &existing_nav_index)) { + return false; + } + if (existing_tab_id == command_tab_id && + existing_nav_index == command_nav_index) { + // existing_command is an update for the same tab/index pair. Replace + // 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); + return true; + } + return false; + } + } + return false; +} + +void SessionService::ScheduleCommand(SessionCommand* command) { + DCHECK(command); + if (ReplacePendingCommand(command)) + return; + 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 && + (command->id() != kCommandTabClosed && + command->id() != kCommandWindowClosed)) { + ScheduleReset(); + } +} + +void SessionService::CommitPendingCloses() { + for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin(); + i != pending_tab_close_ids_.end(); ++i) { + ScheduleCommand(CreateTabClosedCommand(*i)); + } + pending_tab_close_ids_.clear(); + + for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin(); + i != pending_window_close_ids_.end(); ++i) { + ScheduleCommand(CreateWindowClosedCommand(*i)); + } + pending_window_close_ids_.clear(); +} + +bool SessionService::IsOnlyOneTabLeft() { + if (!profile()) { + // We're testing, always return false. + return false; + } + + // NOTE: This uses the original profile so that closing the last non-off the + // record window while there are open off the record window resets state). + int window_count = 0; + for (BrowserList::const_iterator i = BrowserList::begin(); + 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() && + window_closing_ids_.find(window_id) == window_closing_ids_.end()) { + if (++window_count > 1) + return false; + // By the time this is invoked the tab has been removed. As such, we use + // > 0 here rather than > 1. + if ((*i)->tab_count() > 0) + return false; + } + } + return true; +} + +bool SessionService::HasOpenTabbedBrowsers(const SessionID& window_id) { + if (!profile()) { + // We're testing, always return false. + return true; + } + + // NOTE: This uses the original profile so that closing the last non-off the + // record window while there are open off the record window resets state). + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end(); ++i) { + Browser* browser = *i; + const SessionID::id_type browser_id = browser->session_id().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()) { + return true; + } + } + return false; +} + +bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) { + return windows_tracking_.find(window_id.id()) != windows_tracking_.end(); +} |