diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/session_service.cc | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/session_service.cc')
-rw-r--r-- | chrome/browser/session_service.cc | 1113 |
1 files changed, 1113 insertions, 0 deletions
diff --git a/chrome/browser/session_service.cc b/chrome/browser/session_service.cc new file mode 100644 index 0000000..f657f91 --- /dev/null +++ b/chrome/browser/session_service.cc @@ -0,0 +1,1113 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <limits> +#include <Windows.h> + +#include "chrome/browser/session_service.h" + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/pickle.h" +#include "base/thread.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_frame.h" +#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/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" + +// 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 kCommandTabNavigationPathPruned = 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; + +// 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 { + +// 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 TabNavigationPathPrunedPayload; + +typedef IDAndIndexPayload SelectedNavigationIndexPayload; + +typedef IDAndIndexPayload SelectedTabInIndexPayload; + +typedef IDAndIndexPayload WindowTypePayload; + +} // 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), + has_open_tabbed_browsers_(false), + tabbed_browser_created_(false) { + DCHECK(profile); + // We should never be created when off the record. + DCHECK(!profile->IsOffTheRecord()); + Init(profile->GetPath()); +} + +SessionService::SessionService(const std::wstring& save_path) + : profile_(NULL), +#pragma warning(suppress: 4355) // Okay to pass "this" here. + save_factory_(this), + pending_reset_(false), + has_open_tabbed_browsers_(false), + tabbed_browser_created_(false) { + Init(save_path); +} + +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; +} + +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_.get(), &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, + BrowserType::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; + tabbed_browser_created_ = true; + + ScheduleCommand(CreateSetWindowTypeCommand(window_id, type)); +} + +void SessionService::TabNavigationPathPruned(const SessionID& window_id, + const SessionID& tab_id, + int index) { + if (!ShouldTrackChangesToWindow(window_id)) + return; + + TabNavigationPathPrunedPayload payload = { 0 }; + payload.id = tab_id.id(); + payload.index = index; + SessionCommand* command = + new SessionCommand(kCommandTabNavigationPathPruned, 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 (!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(tab_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::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)); + } +} + +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) { + 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. +} + +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) { + 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::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); + const GURL& url = entry.GetDisplayURL(); + const std::wstring& title = entry.GetTitle(); + const std::string& state = entry.GetContentState(); + static const SessionCommand::size_type max_state_size = + std::numeric_limits<SessionCommand::size_type>::max() - 1024; + if (url.spec().size() + title.size() + state.size() >= max_state_size) { + // 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. + if (url.spec().size() < max_state_size) { + pickle.WriteString(url.spec()); + pickle.WriteWString(std::wstring()); + pickle.WriteString(std::string()); + } else { + pickle.WriteString(std::string()); + pickle.WriteWString(std::wstring()); + pickle.WriteString(std::string()); + } + } else { + pickle.WriteString(url.spec()); + pickle.WriteWString(title); + pickle.WriteString(state); + } + pickle.WriteInt(entry.GetTransitionType()); + int type_mask = entry.HasPostData() ? TabNavigation::HAS_POST_DATA : 0; + pickle.WriteInt(type_mask); + // Adding more data? Be sure and update TabRestoreService too. + return new SessionCommand(kCommandUpdateTabNavigation, pickle); +} + +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, + BrowserType::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::OnGotSessionCommands( + Handle handle, + scoped_refptr<InternalSavedSessionRequest> 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()))); +} + +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 kCommandTabNavigationPathPruned: { + TabNavigationPathPrunedPayload 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 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)))) + return true; + // type_mask did not always exist in the written stream. As such, we + // don't fail if it can't be read. + pickle->ReadInt(&iterator, &(navigation.type_mask)); + 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) + *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<BrowserType::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 - kMaxNavigationCountToPersist); + const int max_index = std::min(current_index + kMaxNavigationCountToPersist, + 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); + commands->push_back( + CreateUpdateTabNavigationCommand(controller->session_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->frame()->GetNormalBounds(), + browser->frame()->IsMaximized())); + + commands->push_back(CreateSetWindowTypeCommand( + browser->session_id(), browser->GetType())); + + 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) { + if (should_track_changes_for_browser_type((*i)->GetType())) { + BuildCommandsForBrowser(*i, commands, tab_to_available_range, + windows_to_track); + } + } +} + +void SessionService::ScheduleReset() { + 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; + tabbed_browser_created_ = 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; + commands_since_reset_++; + pending_commands_.push_back(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(); + } + StartSaveTimer(); +} + +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(); +} + +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_) { + // 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)->GetType()) && + (*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->GetType()) && + 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(); +} + +// InternalSavedSessionRequest ------------------------------------------------ + +SessionService::InternalSavedSessionRequest::~InternalSavedSessionRequest() { + STLDeleteElements(&commands); +} |