// Copyright (c) 2011 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/tab_restore_service.h"
#include <algorithm>
#include <iterator>
#include <map>
#include "base/callback.h"
#include "base/memory/scoped_vector.h"
#include "base/metrics/histogram.h"
#include "base/stl_util-inl.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_command.h"
#include "chrome/browser/sessions/session_types.h"
#include "chrome/browser/sessions/tab_restore_service_delegate.h"
#include "chrome/browser/sessions/tab_restore_service_observer.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents.h"
using base::Time;
// TimeFactory-----------------------------------------------------------------
TabRestoreService::TimeFactory::~TimeFactory() {}
// Entry ----------------------------------------------------------------------
// ID of the next Entry.
static SessionID::id_type next_entry_id = 1;
TabRestoreService::Entry::Entry()
: id(next_entry_id++),
type(TAB),
from_last_session(false) {}
TabRestoreService::Entry::Entry(Type type)
: id(next_entry_id++),
type(type),
from_last_session(false) {}
TabRestoreService::Entry::~Entry() {}
// TabRestoreService ----------------------------------------------------------
// static
const size_t TabRestoreService::kMaxEntries = 10;
// Identifier for commands written to file.
// The ordering in the file is as follows:
// . When the user closes a tab a command of type
// kCommandSelectedNavigationInTab is written identifying the tab and
// the selected index, then a kCommandPinnedState command if the tab was
// pinned and kCommandSetExtensionAppID if the tab has an app id. This is
// followed by any number of kCommandUpdateTabNavigation commands (1 per
// navigation entry).
// . When the user closes a window a kCommandSelectedNavigationInTab command
// is written out and followed by n tab closed sequences (as previoulsy
// described).
// . When the user restores an entry a command of type kCommandRestoredEntry
// is written.
static const SessionCommand::id_type kCommandUpdateTabNavigation = 1;
static const SessionCommand::id_type kCommandRestoredEntry = 2;
static const SessionCommand::id_type kCommandWindow = 3;
static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
static const SessionCommand::id_type kCommandPinnedState = 5;
static const SessionCommand::id_type kCommandSetExtensionAppID = 6;
// Number of entries (not commands) before we clobber the file and write
// everything.
static const int kEntriesPerReset = 40;
namespace {
// Payload structures.
typedef int32 RestoredEntryPayload;
// Payload used for the start of a window close. This is the old struct that is
// used for backwards compat when it comes to reading the session files. This
// struct must be POD, because we memset the contents.
struct WindowPayload {
SessionID::id_type window_id;
int32 selected_tab_index;
int32 num_tabs;
};
// Payload used for the start of a tab close. This is the old struct that is
// used for backwards compat when it comes to reading the session files.
struct SelectedNavigationInTabPayload {
SessionID::id_type id;
int32 index;
};
// Payload used for the start of a window close. This struct must be POD,
// because we memset the contents.
struct WindowPayload2 : WindowPayload {
int64 timestamp;
};
// Payload used for the start of a tab close.
struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload {
int64 timestamp;
};
// Only written if the tab is pinned.
typedef bool PinnedStatePayload;
typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry;
// If |id_to_entry| contains an entry for |id| the corresponding entry is
// deleted and removed from both |id_to_entry| and |entries|. This is used
// when creating entries from the backend file.
void RemoveEntryByID(SessionID::id_type id,
IDToEntry* id_to_entry,
std::vector<TabRestoreService::Entry*>* entries) {
// Look for the entry in the map. If it is present, erase it from both
// collections and return.
IDToEntry::iterator i = id_to_entry->find(id);
if (i != id_to_entry->end()) {
entries->erase(std::find(entries->begin(), entries->end(), i->second));
delete i->second;
id_to_entry->erase(i);
return;
}
// Otherwise, loop over all items in the map and see if any of the Windows
// have Tabs with the |id|.
for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end();
++i) {
if (i->second->type == TabRestoreService::WINDOW) {
TabRestoreService::Window* window =
static_cast<TabRestoreService::Window*>(i->second);
std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin();
for ( ; j != window->tabs.end(); ++j) {
// If the ID matches one of this window's tabs, remove it from the list.
if ((*j).id == id) {
window->tabs.erase(j);
return;
}
}
}
}
}
void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
DCHECK(profile->GetExtensionService());
if (!profile->GetExtensionService()->IsInstalledApp(url))
return;
UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
}
} // namespace
TabRestoreService::Tab::Tab()
: Entry(TAB),
current_navigation_index(-1),
browser_id(0),
tabstrip_index(-1),
pinned(false) {
}
TabRestoreService::Tab::~Tab() {
}
TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) {
}
TabRestoreService::Window::~Window() {
}
TabRestoreService::TabRestoreService(Profile* profile,
TabRestoreService::TimeFactory* time_factory)
: BaseSessionService(BaseSessionService::TAB_RESTORE, profile,
FilePath()),
load_state_(NOT_LOADED),
restoring_(false),
reached_max_(false),
entries_to_write_(0),
entries_written_(0),
time_factory_(time_factory) {
}
TabRestoreService::~TabRestoreService() {
if (backend())
Save();
FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
TabRestoreServiceDestroyed(this));
STLDeleteElements(&entries_);
STLDeleteElements(&staging_entries_);
time_factory_ = NULL;
}
void TabRestoreService::AddObserver(TabRestoreServiceObserver* observer) {
observer_list_.AddObserver(observer);
}
void TabRestoreService::RemoveObserver(TabRestoreServiceObserver* observer) {
observer_list_.RemoveObserver(observer);
}
void TabRestoreService::CreateHistoricalTab(NavigationController* tab,
int index) {
if (restoring_)
return;
TabRestoreServiceDelegate* delegate =
TabRestoreServiceDelegate::FindDelegateForController(tab, NULL);
if (closing_delegates_.find(delegate) != closing_delegates_.end())
return;
scoped_ptr<Tab> local_tab(new Tab());
PopulateTab(local_tab.get(), index, delegate, tab);
if (local_tab->navigations.empty())
return;
AddEntry(local_tab.release(), true, true);
}
void TabRestoreService::BrowserClosing(TabRestoreServiceDelegate* delegate) {
closing_delegates_.insert(delegate);
scoped_ptr<Window> window(new Window());
window->selected_tab_index = delegate->GetSelectedIndex();
window->timestamp = TimeNow();
// Don't use std::vector::resize() because it will push copies of an empty tab
// into the vector, which will give all tabs in a window the same ID.
for (int i = 0; i < delegate->GetTabCount(); ++i) {
window->tabs.push_back(Tab());
}
size_t entry_index = 0;
for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
PopulateTab(&(window->tabs[entry_index]),
tab_index,
delegate,
&delegate->GetTabContentsAt(tab_index)->controller());
if (window->tabs[entry_index].navigations.empty()) {
window->tabs.erase(window->tabs.begin() + entry_index);
} else {
window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
entry_index++;
}
}
if (window->tabs.size() == 1) {
// Short-circuit creating a Window if only 1 tab was present. This fixes
// http://crbug.com/56744. Copy the Tab because it's owned by an object on
// the stack.
AddEntry(new Tab(window->tabs[0]), true, true);
} else if (!window->tabs.empty()) {
window->selected_tab_index =
std::min(static_cast<int>(window->tabs.size() - 1),
window->selected_tab_index);
AddEntry(window.release(), true, true);
}
}
void TabRestoreService::BrowserClosed(TabRestoreServiceDelegate* delegate) {
closing_delegates_.erase(delegate);
}
void TabRestoreService::ClearEntries() {
// Mark all the tabs as closed so that we don't attempt to restore them.
for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i)
ScheduleCommand(CreateRestoredEntryCommand((*i)->id));
entries_to_write_ = 0;
// Schedule a pending reset so that we nuke the file on next write.
set_pending_reset(true);
// Schedule a command, otherwise if there are no pending commands Save does
// nothing.
ScheduleCommand(CreateRestoredEntryCommand(1));
STLDeleteElements(&entries_);
NotifyTabsChanged();
}
const TabRestoreService::Entries& TabRestoreService::entries() const {
return entries_;
}
void TabRestoreService::RestoreMostRecentEntry(
TabRestoreServiceDelegate* delegate) {
if (entries_.empty())
return;
RestoreEntryById(delegate, entries_.front()->id, false);
}
void TabRestoreService::RestoreEntryById(TabRestoreServiceDelegate* delegate,
SessionID::id_type id,
bool replace_existing_tab) {
Entries::iterator i = GetEntryIteratorById(id);
if (i == entries_.end()) {
// Don't hoark here, we allow an invalid id.
return;
}
size_t index = 0;
for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end();
++j, ++index) {}
if (static_cast<int>(index) < entries_to_write_)
entries_to_write_--;
ScheduleCommand(CreateRestoredEntryCommand(id));
restoring_ = true;
Entry* entry = *i;
// If the entry's ID does not match the ID that is being restored, then the
// entry is a window from which a single tab will be restored.
bool restoring_tab_in_window = entry->id != id;
if (!restoring_tab_in_window) {
entries_.erase(i);
i = entries_.end();
}
// |delegate| will be NULL in cases where one isn't already available (eg,
// when invoked on Mac OS X with no windows open). In this case, create a
// new browser into which we restore the tabs.
if (entry->type == TAB) {
Tab* tab = static_cast<Tab*>(entry);
delegate = RestoreTab(*tab, delegate, replace_existing_tab);
delegate->ShowBrowserWindow();
} else if (entry->type == WINDOW) {
TabRestoreServiceDelegate* current_delegate = delegate;
Window* window = static_cast<Window*>(entry);
// When restoring a window, either the entire window can be restored, or a
// single tab within it. If the entry's ID matches the one to restore, then
// the entire window will be restored.
if (!restoring_tab_in_window) {
delegate = TabRestoreServiceDelegate::Create(profile());
for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
const Tab& tab = window->tabs[tab_i];
TabContents* restored_tab =
delegate->AddRestoredTab(tab.navigations, delegate->GetTabCount(),
tab.current_navigation_index,
tab.extension_app_id,
(static_cast<int>(tab_i) ==
window->selected_tab_index),
tab.pinned, tab.from_last_session,
tab.session_storage_namespace);
if (restored_tab) {
restored_tab->controller().LoadIfNecessary();
RecordAppLaunch(profile(), tab);
}
}
// All the window's tabs had the same former browser_id.
if (window->tabs[0].has_browser()) {
UpdateTabBrowserIDs(window->tabs[0].browser_id,
delegate->GetSessionID().id());
}
} else {
// Restore a single tab from the window. Find the tab that matches the ID
// in the window and restore it.
for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
tab_i != window->tabs.end(); ++tab_i) {
const Tab& tab = *tab_i;
if (tab.id == id) {
delegate = RestoreTab(tab, delegate, replace_existing_tab);
window->tabs.erase(tab_i);
// If restoring the tab leaves the window with nothing else, delete it
// as well.
if (!window->tabs.size()) {
entries_.erase(i);
delete entry;
} else {
// Update the browser ID of the rest of the tabs in the window so if
// any one is restored, it goes into the same window as the tab
// being restored now.
UpdateTabBrowserIDs(tab.browser_id,
delegate->GetSessionID().id());
for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
tab_j != window->tabs.end(); ++tab_j) {
(*tab_j).browser_id = delegate->GetSessionID().id();
}
}
break;
}
}
}
delegate->ShowBrowserWindow();
if (replace_existing_tab && current_delegate &&
current_delegate->GetSelectedTabContents()) {
current_delegate->CloseTab();
}
} else {
NOTREACHED();
}
if (!restoring_tab_in_window) {
delete entry;
}
restoring_ = false;
NotifyTabsChanged();
}
void TabRestoreService::LoadTabsFromLastSession() {
if (load_state_ != NOT_LOADED || reached_max_)
return;
load_state_ = LOADING;
if (!profile()->restored_last_session() &&
!profile()->DidLastSessionExitCleanly() &&
profile()->GetSessionService()) {
// The previous session crashed and wasn't restored. Load the tabs/windows
// that were open at the point of crash from the session service.
profile()->GetSessionService()->GetLastSession(
&load_consumer_,
NewCallback(this, &TabRestoreService::OnGotPreviousSession));
} else {
load_state_ |= LOADED_LAST_SESSION;
}
// Request the tabs closed in the last session. If the last session crashed,
// this won't contain the tabs/window that were open at the point of the
// crash (the call to GetLastSession above requests those).
ScheduleGetLastSessionCommands(
new InternalGetCommandsRequest(
NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)),
&load_consumer_);
}
void TabRestoreService::Save() {
int to_write_count = std::min(entries_to_write_,
static_cast<int>(entries_.size()));
entries_to_write_ = 0;
if (entries_written_ + to_write_count > kEntriesPerReset) {
to_write_count = entries_.size();
set_pending_reset(true);
}
if (to_write_count) {
// Write the to_write_count most recently added entries out. The most
// recently added entry is at the front, so we use a reverse iterator to
// write in the order the entries were added.
Entries::reverse_iterator i = entries_.rbegin();
DCHECK(static_cast<size_t>(to_write_count) <= entries_.size());
std::advance(i, entries_.size() - static_cast<int>(to_write_count));
for (; i != entries_.rend(); ++i) {
Entry* entry = *i;
if (entry->type == TAB) {
Tab* tab = static_cast<Tab*>(entry);
int selected_index = GetSelectedNavigationIndexToPersist(*tab);
if (selected_index != -1)
ScheduleCommandsForTab(*tab, selected_index);
} else {
ScheduleCommandsForWindow(*static_cast<Window*>(entry));
}
entries_written_++;
}
}
if (pending_reset())
entries_written_ = 0;
BaseSessionService::Save();
}
void TabRestoreService::PopulateTab(Tab* tab,
int index,
TabRestoreServiceDelegate* delegate,
NavigationController* controller) {
const int pending_index = controller->pending_entry_index();
int entry_count = controller->entry_count();
if (entry_count == 0 && pending_index == 0)
entry_count++;
tab->navigations.resize(static_cast<int>(entry_count));
for (int i = 0; i < entry_count; ++i) {
NavigationEntry* entry = (i == pending_index) ?
controller->pending_entry() : controller->GetEntryAtIndex(i);
tab->navigations[i].SetFromNavigationEntry(*entry);
}
tab->timestamp = TimeNow();
tab->current_navigation_index = controller->GetCurrentEntryIndex();
if (tab->current_navigation_index == -1 && entry_count > 0)
tab->current_navigation_index = 0;
tab->tabstrip_index = index;
TabContentsWrapper* wrapper =
TabContentsWrapper::GetCurrentWrapperForContents(
controller->tab_contents());
// wrapper is NULL in some browser tests.
if (wrapper) {
const Extension* extension =
wrapper->extension_tab_helper()->extension_app();
if (extension)
tab->extension_app_id = extension->id();
}
tab->session_storage_namespace = controller->session_storage_namespace();
// Delegate may be NULL during unit tests.
if (delegate) {
tab->browser_id = delegate->GetSessionID().id();
tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
}
}
void TabRestoreService::NotifyTabsChanged() {
FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
TabRestoreServiceChanged(this));
}
void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) {
if (to_front)
entries_.push_front(entry);
else
entries_.push_back(entry);
if (notify)
PruneAndNotify();
// Start the save timer, when it fires we'll generate the commands.
StartSaveTimer();
entries_to_write_++;
}
void TabRestoreService::PruneAndNotify() {
while (entries_.size() > kMaxEntries) {
delete entries_.back();
entries_.pop_back();
reached_max_ = true;
}
NotifyTabsChanged();
}
TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById(
SessionID::id_type id) {
for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
if ((*i)->id == id)
return i;
// For Window entries, see if the ID matches a tab. If so, report the window
// as the Entry.
if ((*i)->type == WINDOW) {
std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
for (std::vector<Tab>::iterator j = tabs.begin();
j != tabs.end(); ++j) {
if ((*j).id == id) {
return i;
}
}
}
}
return entries_.end();
}
void TabRestoreService::ScheduleCommandsForWindow(const Window& window) {
DCHECK(!window.tabs.empty());
int selected_tab = window.selected_tab_index;
int valid_tab_count = 0;
int real_selected_tab = selected_tab;
for (size_t i = 0; i < window.tabs.size(); ++i) {
if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) {
valid_tab_count++;
} else if (static_cast<int>(i) < selected_tab) {
real_selected_tab--;
}
}
if (valid_tab_count == 0)
return; // No tabs to persist.
ScheduleCommand(
CreateWindowCommand(window.id,
std::min(real_selected_tab, valid_tab_count - 1),
valid_tab_count,
window.timestamp));
for (size_t i = 0; i < window.tabs.size(); ++i) {
int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]);
if (selected_index != -1)
ScheduleCommandsForTab(window.tabs[i], selected_index);
}
}
void TabRestoreService::ScheduleCommandsForTab(const Tab& tab,
int selected_index) {
const std::vector<TabNavigation>& navigations = tab.navigations;
int max_index = static_cast<int>(navigations.size());
// Determine the first navigation we'll persist.
int valid_count_before_selected = 0;
int first_index_to_persist = selected_index;
for (int i = selected_index - 1; i >= 0 &&
valid_count_before_selected < max_persist_navigation_count; --i) {
if (ShouldTrackEntry(navigations[i])) {
first_index_to_persist = i;
valid_count_before_selected++;
}
}
// Write the command that identifies the selected tab.
ScheduleCommand(
CreateSelectedNavigationInTabCommand(tab.id,
valid_count_before_selected,
tab.timestamp));
if (tab.pinned) {
PinnedStatePayload payload = true;
SessionCommand* command =
new SessionCommand(kCommandPinnedState, sizeof(payload));
memcpy(command->contents(), &payload, sizeof(payload));
ScheduleCommand(command);
}
if (!tab.extension_app_id.empty()) {
ScheduleCommand(
CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id,
tab.extension_app_id));
}
// Then write the navigations.
for (int i = first_index_to_persist, wrote_count = 0;
i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) {
if (ShouldTrackEntry(navigations[i])) {
// Creating a NavigationEntry isn't the most efficient way to go about
// this, but it simplifies the code and makes it less error prone as we
// add new data to NavigationEntry.
scoped_ptr<NavigationEntry> entry(
navigations[i].ToNavigationEntry(wrote_count, profile()));
ScheduleCommand(
CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id,
wrote_count++, *entry));
}
}
}
SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id,
int selected_tab_index,
int num_tabs,
Time timestamp) {
WindowPayload2 payload;
// |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of
// uninitialized memory in the struct.
memset(&payload, 0, sizeof(payload));
payload.window_id = id;
payload.selected_tab_index = selected_tab_index;
payload.num_tabs = num_tabs;
payload.timestamp = timestamp.ToInternalValue();
SessionCommand* command =
new SessionCommand(kCommandWindow, sizeof(payload));
memcpy(command->contents(), &payload, sizeof(payload));
return command;
}
SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand(
SessionID::id_type tab_id,
int32 index,
Time timestamp) {
SelectedNavigationInTabPayload2 payload;
payload.id = tab_id;
payload.index = index;
payload.timestamp = timestamp.ToInternalValue();
SessionCommand* command =
new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload));
memcpy(command->contents(), &payload, sizeof(payload));
return command;
}
SessionCommand* TabRestoreService::CreateRestoredEntryCommand(
SessionID::id_type entry_id) {
RestoredEntryPayload payload = entry_id;
SessionCommand* command =
new SessionCommand(kCommandRestoredEntry, sizeof(payload));
memcpy(command->contents(), &payload, sizeof(payload));
return command;
}
int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) {
const std::vector<TabNavigation>& navigations = tab.navigations;
int selected_index = tab.current_navigation_index;
int max_index = static_cast<int>(navigations.size());
// Find the first navigation to persist. We won't persist the selected
// navigation if ShouldTrackEntry returns false.
while (selected_index >= 0 &&
!ShouldTrackEntry(navigations[selected_index])) {
selected_index--;
}
if (selected_index != -1)
return selected_index;
// Couldn't find a navigation to persist going back, go forward.
selected_index = tab.current_navigation_index + 1;
while (selected_index < max_index &&
!ShouldTrackEntry(navigations[selected_index])) {
selected_index++;
}
return (selected_index == max_index) ? -1 : selected_index;
}
void TabRestoreService::OnGotLastSessionCommands(
Handle handle,
scoped_refptr<InternalGetCommandsRequest> request) {
std::vector<Entry*> entries;
CreateEntriesFromCommands(request, &entries);
// Closed tabs always go to the end.
staging_entries_.insert(staging_entries_.end(), entries.begin(),
entries.end());
load_state_ |= LOADED_LAST_TABS;
LoadStateChanged();
}
void TabRestoreService::CreateEntriesFromCommands(
scoped_refptr<InternalGetCommandsRequest> request,
std::vector<Entry*>* loaded_entries) {
if (request->canceled() || entries_.size() == kMaxEntries)
return;
std::vector<SessionCommand*>& commands = request->commands;
// Iterate through the commands populating entries and id_to_entry.
ScopedVector<Entry> entries;
IDToEntry id_to_entry;
// If non-null we're processing the navigations of this tab.
Tab* current_tab = NULL;
// If non-null we're processing the tabs of this window.
Window* current_window = NULL;
// If > 0, we've gotten a window command but not all the tabs yet.
int pending_window_tabs = 0;
for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
i != commands.end(); ++i) {
const SessionCommand& command = *(*i);
switch (command.id()) {
case kCommandRestoredEntry: {
if (pending_window_tabs > 0) {
// Should never receive a restored command while waiting for all the
// tabs in a window.
return;
}
current_tab = NULL;
current_window = NULL;
RestoredEntryPayload payload;
if (!command.GetPayload(&payload, sizeof(payload)))
return;
RemoveEntryByID(payload, &id_to_entry, &(entries.get()));
break;
}
case kCommandWindow: {
WindowPayload2 payload;
if (pending_window_tabs > 0) {
// Should never receive a window command while waiting for all the
// tabs in a window.
return;
}
// Try the new payload first
if (!command.GetPayload(&payload, sizeof(payload))) {
// then the old payload
WindowPayload old_payload;
if (!command.GetPayload(&old_payload, sizeof(old_payload)))
return;
// Copy the old payload data to the new payload.
payload.window_id = old_payload.window_id;
payload.selected_tab_index = old_payload.selected_tab_index;
payload.num_tabs = old_payload.num_tabs;
// Since we don't have a time use time 0 which is used to mark as an
// unknown timestamp.
payload.timestamp = 0;
}
pending_window_tabs = payload.num_tabs;
if (pending_window_tabs <= 0) {
// Should always have at least 1 tab. Likely indicates corruption.
return;
}
RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get()));
current_window = new Window();
current_window->selected_tab_index = payload.selected_tab_index;
current_window->timestamp = Time::FromInternalValue(payload.timestamp);
entries->push_back(current_window);
id_to_entry[payload.window_id] = current_window;
break;
}
case kCommandSelectedNavigationInTab: {
SelectedNavigationInTabPayload2 payload;
if (!command.GetPayload(&payload, sizeof(payload))) {
SelectedNavigationInTabPayload old_payload;
if (!command.GetPayload(&old_payload, sizeof(old_payload)))
return;
payload.id = old_payload.id;
payload.index = old_payload.index;
// Since we don't have a time use time 0 which is used to mark as an
// unknown timestamp.
payload.timestamp = 0;
}
if (pending_window_tabs > 0) {
if (!current_window) {
// We should have created a window already.
NOTREACHED();
return;
}
current_window->tabs.resize(current_window->tabs.size() + 1);
current_tab = &(current_window->tabs.back());
if (--pending_window_tabs == 0)
current_window = NULL;
} else {
RemoveEntryByID(payload.id, &id_to_entry, &(entries.get()));
current_tab = new Tab();
id_to_entry[payload.id] = current_tab;
current_tab->timestamp = Time::FromInternalValue(payload.timestamp);
entries->push_back(current_tab);
}
current_tab->current_navigation_index = payload.index;
break;
}
case kCommandUpdateTabNavigation: {
if (!current_tab) {
// Should be in a tab when we get this.
return;
}
current_tab->navigations.resize(current_tab->navigations.size() + 1);
SessionID::id_type tab_id;
if (!RestoreUpdateTabNavigationCommand(
command, ¤t_tab->navigations.back(), &tab_id)) {
return;
}
break;
}
case kCommandPinnedState: {
if (!current_tab) {
// Should be in a tab when we get this.
return;
}
// NOTE: payload doesn't matter. kCommandPinnedState is only written if
// tab is pinned.
current_tab->pinned = true;
break;
}
case kCommandSetExtensionAppID: {
if (!current_tab) {
// Should be in a tab when we get this.
return;
}
SessionID::id_type tab_id;
std::string extension_app_id;
if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id,
&extension_app_id)) {
return;
}
current_tab->extension_app_id.swap(extension_app_id);
break;
}
default:
// Unknown type, usually indicates corruption of file. Ignore it.
return;
}
}
// If there was corruption some of the entries won't be valid. Prune any
// entries with no navigations.
ValidateAndDeleteEmptyEntries(&(entries.get()));
loaded_entries->swap(entries.get());
}
TabRestoreServiceDelegate* TabRestoreService::RestoreTab(
const Tab& tab,
TabRestoreServiceDelegate* delegate,
bool replace_existing_tab) {
// |browser| will be NULL in cases where one isn't already available (eg,
// when invoked on Mac OS X with no windows open). In this case, create a
// new browser into which we restore the tabs.
if (replace_existing_tab && delegate) {
delegate->ReplaceRestoredTab(tab.navigations,
tab.current_navigation_index,
tab.from_last_session,
tab.extension_app_id,
tab.session_storage_namespace);
} else {
if (tab.has_browser())
delegate = TabRestoreServiceDelegate::FindDelegateWithID(tab.browser_id);
int tab_index = -1;
if (delegate) {
tab_index = tab.tabstrip_index;
} else {
delegate = TabRestoreServiceDelegate::Create(profile());
if (tab.has_browser()) {
UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
}
}
if (tab_index < 0 || tab_index > delegate->GetTabCount()) {
tab_index = delegate->GetTabCount();
}
delegate->AddRestoredTab(tab.navigations,
tab_index,
tab.current_navigation_index,
tab.extension_app_id,
true, tab.pinned, tab.from_last_session,
tab.session_storage_namespace);
}
RecordAppLaunch(profile(), tab);
return delegate;
}
bool TabRestoreService::ValidateTab(Tab* tab) {
if (tab->navigations.empty())
return false;
tab->current_navigation_index =
std::max(0, std::min(tab->current_navigation_index,
static_cast<int>(tab->navigations.size()) - 1));
return true;
}
void TabRestoreService::ValidateAndDeleteEmptyEntries(
std::vector<Entry*>* entries) {
std::vector<Entry*> valid_entries;
std::vector<Entry*> invalid_entries;
size_t max_valid = kMaxEntries - entries_.size();
// Iterate from the back so that we keep the most recently closed entries.
for (std::vector<Entry*>::reverse_iterator i = entries->rbegin();
i != entries->rend(); ++i) {
bool valid_entry = false;
if (valid_entries.size() != max_valid) {
if ((*i)->type == TAB) {
Tab* tab = static_cast<Tab*>(*i);
if (ValidateTab(tab))
valid_entry = true;
} else {
Window* window = static_cast<Window*>(*i);
for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
tab_i != window->tabs.end();) {
if (!ValidateTab(&(*tab_i)))
tab_i = window->tabs.erase(tab_i);
else
++tab_i;
}
if (!window->tabs.empty()) {
window->selected_tab_index =
std::max(0, std::min(window->selected_tab_index,
static_cast<int>(window->tabs.size() - 1)));
valid_entry = true;
}
}
}
if (valid_entry)
valid_entries.push_back(*i);
else
invalid_entries.push_back(*i);
}
// NOTE: at this point the entries are ordered with newest at the front.
entries->swap(valid_entries);
// Delete the remaining entries.
STLDeleteElements(&invalid_entries);
}
void TabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id,
SessionID::id_type new_id) {
for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
Entry* entry = *i;
if (entry->type == TAB) {
Tab* tab = static_cast<Tab*>(entry);
if (tab->browser_id == old_id)
tab->browser_id = new_id;
}
}
}
void TabRestoreService::OnGotPreviousSession(
Handle handle,
std::vector<SessionWindow*>* windows) {
std::vector<Entry*> entries;
CreateEntriesFromWindows(windows, &entries);
// Previous session tabs go first.
staging_entries_.insert(staging_entries_.begin(), entries.begin(),
entries.end());
load_state_ |= LOADED_LAST_SESSION;
LoadStateChanged();
}
void TabRestoreService::CreateEntriesFromWindows(
std::vector<SessionWindow*>* windows,
std::vector<Entry*>* entries) {
for (size_t i = 0; i < windows->size(); ++i) {
scoped_ptr<Window> window(new Window());
if (ConvertSessionWindowToWindow((*windows)[i], window.get()))
entries->push_back(window.release());
}
}
bool TabRestoreService::ConvertSessionWindowToWindow(
SessionWindow* session_window,
Window* window) {
for (size_t i = 0; i < session_window->tabs.size(); ++i) {
if (!session_window->tabs[i]->navigations.empty()) {
window->tabs.resize(window->tabs.size() + 1);
Tab& tab = window->tabs.back();
tab.pinned = session_window->tabs[i]->pinned;
tab.navigations.swap(session_window->tabs[i]->navigations);
tab.current_navigation_index =
session_window->tabs[i]->current_navigation_index;
tab.extension_app_id = session_window->tabs[i]->extension_app_id;
tab.timestamp = Time();
}
}
if (window->tabs.empty())
return false;
window->selected_tab_index =
std::min(session_window->selected_tab_index,
static_cast<int>(window->tabs.size() - 1));
window->timestamp = Time();
return true;
}
void TabRestoreService::LoadStateChanged() {
if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) !=
(LOADED_LAST_TABS | LOADED_LAST_SESSION)) {
// Still waiting on previous session or previous tabs.
return;
}
// We're done loading.
load_state_ ^= LOADING;
if (staging_entries_.empty() || reached_max_) {
STLDeleteElements(&staging_entries_);
return;
}
if (staging_entries_.size() + entries_.size() > kMaxEntries) {
// If we add all the staged entries we'll end up with more than
// kMaxEntries. Delete entries such that we only end up with
// at most kMaxEntries.
DCHECK(entries_.size() < kMaxEntries);
STLDeleteContainerPointers(
staging_entries_.begin() + (kMaxEntries - entries_.size()),
staging_entries_.end());
staging_entries_.erase(
staging_entries_.begin() + (kMaxEntries - entries_.size()),
staging_entries_.end());
}
// And add them.
for (size_t i = 0; i < staging_entries_.size(); ++i) {
staging_entries_[i]->from_last_session = true;
AddEntry(staging_entries_[i], false, false);
}
// AddEntry takes ownership of the entry, need to clear out entries so that
// it doesn't delete them.
staging_entries_.clear();
// Make it so we rewrite all the tabs. We need to do this otherwise we won't
// correctly write out the entries when Save is invoked (Save starts from
// the front, not the end and we just added the entries to the end).
entries_to_write_ = staging_entries_.size();
PruneAndNotify();
}
Time TabRestoreService::TimeNow() const {
return time_factory_ ? time_factory_->TimeNow() : Time::Now();
}