Home | History | Annotate | Download | only in sessions
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/sessions/tab_restore_service.h"
      6 
      7 #include <algorithm>
      8 #include <iterator>
      9 #include <map>
     10 
     11 #include "base/callback.h"
     12 #include "base/memory/scoped_vector.h"
     13 #include "base/metrics/histogram.h"
     14 #include "base/stl_util-inl.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/extensions/extension_tab_helper.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/sessions/session_service.h"
     19 #include "chrome/browser/sessions/session_command.h"
     20 #include "chrome/browser/sessions/session_types.h"
     21 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
     22 #include "chrome/browser/sessions/tab_restore_service_observer.h"
     23 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     24 #include "chrome/common/extensions/extension.h"
     25 #include "chrome/common/extensions/extension_constants.h"
     26 #include "content/browser/tab_contents/navigation_controller.h"
     27 #include "content/browser/tab_contents/navigation_entry.h"
     28 #include "content/browser/tab_contents/tab_contents.h"
     29 
     30 using base::Time;
     31 
     32 // TimeFactory-----------------------------------------------------------------
     33 
     34 TabRestoreService::TimeFactory::~TimeFactory() {}
     35 
     36 // Entry ----------------------------------------------------------------------
     37 
     38 // ID of the next Entry.
     39 static SessionID::id_type next_entry_id = 1;
     40 
     41 TabRestoreService::Entry::Entry()
     42     : id(next_entry_id++),
     43       type(TAB),
     44       from_last_session(false) {}
     45 
     46 TabRestoreService::Entry::Entry(Type type)
     47     : id(next_entry_id++),
     48       type(type),
     49       from_last_session(false) {}
     50 
     51 TabRestoreService::Entry::~Entry() {}
     52 
     53 // TabRestoreService ----------------------------------------------------------
     54 
     55 // static
     56 const size_t TabRestoreService::kMaxEntries = 10;
     57 
     58 // Identifier for commands written to file.
     59 // The ordering in the file is as follows:
     60 // . When the user closes a tab a command of type
     61 //   kCommandSelectedNavigationInTab is written identifying the tab and
     62 //   the selected index, then a kCommandPinnedState command if the tab was
     63 //   pinned and kCommandSetExtensionAppID if the tab has an app id. This is
     64 //   followed by any number of kCommandUpdateTabNavigation commands (1 per
     65 //   navigation entry).
     66 // . When the user closes a window a kCommandSelectedNavigationInTab command
     67 //   is written out and followed by n tab closed sequences (as previoulsy
     68 //   described).
     69 // . When the user restores an entry a command of type kCommandRestoredEntry
     70 //   is written.
     71 static const SessionCommand::id_type kCommandUpdateTabNavigation = 1;
     72 static const SessionCommand::id_type kCommandRestoredEntry = 2;
     73 static const SessionCommand::id_type kCommandWindow = 3;
     74 static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
     75 static const SessionCommand::id_type kCommandPinnedState = 5;
     76 static const SessionCommand::id_type kCommandSetExtensionAppID = 6;
     77 
     78 // Number of entries (not commands) before we clobber the file and write
     79 // everything.
     80 static const int kEntriesPerReset = 40;
     81 
     82 namespace {
     83 
     84 // Payload structures.
     85 
     86 typedef int32 RestoredEntryPayload;
     87 
     88 // Payload used for the start of a window close. This is the old struct that is
     89 // used for backwards compat when it comes to reading the session files. This
     90 // struct must be POD, because we memset the contents.
     91 struct WindowPayload {
     92   SessionID::id_type window_id;
     93   int32 selected_tab_index;
     94   int32 num_tabs;
     95 };
     96 
     97 // Payload used for the start of a tab close. This is the old struct that is
     98 // used for backwards compat when it comes to reading the session files.
     99 struct SelectedNavigationInTabPayload {
    100   SessionID::id_type id;
    101   int32 index;
    102 };
    103 
    104 // Payload used for the start of a window close.  This struct must be POD,
    105 // because we memset the contents.
    106 struct WindowPayload2 : WindowPayload {
    107   int64 timestamp;
    108 };
    109 
    110 // Payload used for the start of a tab close.
    111 struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload {
    112   int64 timestamp;
    113 };
    114 
    115 // Only written if the tab is pinned.
    116 typedef bool PinnedStatePayload;
    117 
    118 typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry;
    119 
    120 // If |id_to_entry| contains an entry for |id| the corresponding entry is
    121 // deleted and removed from both |id_to_entry| and |entries|. This is used
    122 // when creating entries from the backend file.
    123 void RemoveEntryByID(SessionID::id_type id,
    124                      IDToEntry* id_to_entry,
    125                      std::vector<TabRestoreService::Entry*>* entries) {
    126   // Look for the entry in the map. If it is present, erase it from both
    127   // collections and return.
    128   IDToEntry::iterator i = id_to_entry->find(id);
    129   if (i != id_to_entry->end()) {
    130     entries->erase(std::find(entries->begin(), entries->end(), i->second));
    131     delete i->second;
    132     id_to_entry->erase(i);
    133     return;
    134   }
    135 
    136   // Otherwise, loop over all items in the map and see if any of the Windows
    137   // have Tabs with the |id|.
    138   for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end();
    139        ++i) {
    140     if (i->second->type == TabRestoreService::WINDOW) {
    141       TabRestoreService::Window* window =
    142           static_cast<TabRestoreService::Window*>(i->second);
    143       std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin();
    144       for ( ; j != window->tabs.end(); ++j) {
    145         // If the ID matches one of this window's tabs, remove it from the list.
    146         if ((*j).id == id) {
    147           window->tabs.erase(j);
    148           return;
    149         }
    150       }
    151     }
    152   }
    153 }
    154 
    155 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
    156   GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
    157   DCHECK(profile->GetExtensionService());
    158   if (!profile->GetExtensionService()->IsInstalledApp(url))
    159     return;
    160 
    161   UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
    162                             extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
    163                             extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
    164 }
    165 
    166 }  // namespace
    167 
    168 TabRestoreService::Tab::Tab()
    169     : Entry(TAB),
    170       current_navigation_index(-1),
    171       browser_id(0),
    172       tabstrip_index(-1),
    173       pinned(false) {
    174 }
    175 
    176 TabRestoreService::Tab::~Tab() {
    177 }
    178 
    179 TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) {
    180 }
    181 
    182 TabRestoreService::Window::~Window() {
    183 }
    184 
    185 TabRestoreService::TabRestoreService(Profile* profile,
    186     TabRestoreService::TimeFactory* time_factory)
    187     : BaseSessionService(BaseSessionService::TAB_RESTORE, profile,
    188                          FilePath()),
    189       load_state_(NOT_LOADED),
    190       restoring_(false),
    191       reached_max_(false),
    192       entries_to_write_(0),
    193       entries_written_(0),
    194       time_factory_(time_factory) {
    195 }
    196 
    197 TabRestoreService::~TabRestoreService() {
    198   if (backend())
    199     Save();
    200 
    201   FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
    202                     TabRestoreServiceDestroyed(this));
    203   STLDeleteElements(&entries_);
    204   STLDeleteElements(&staging_entries_);
    205   time_factory_ = NULL;
    206 }
    207 
    208 void TabRestoreService::AddObserver(TabRestoreServiceObserver* observer) {
    209   observer_list_.AddObserver(observer);
    210 }
    211 
    212 void TabRestoreService::RemoveObserver(TabRestoreServiceObserver* observer) {
    213   observer_list_.RemoveObserver(observer);
    214 }
    215 
    216 void TabRestoreService::CreateHistoricalTab(NavigationController* tab,
    217                                             int index) {
    218   if (restoring_)
    219     return;
    220 
    221   TabRestoreServiceDelegate* delegate =
    222       TabRestoreServiceDelegate::FindDelegateForController(tab, NULL);
    223   if (closing_delegates_.find(delegate) != closing_delegates_.end())
    224     return;
    225 
    226   scoped_ptr<Tab> local_tab(new Tab());
    227   PopulateTab(local_tab.get(), index, delegate, tab);
    228   if (local_tab->navigations.empty())
    229     return;
    230 
    231   AddEntry(local_tab.release(), true, true);
    232 }
    233 
    234 void TabRestoreService::BrowserClosing(TabRestoreServiceDelegate* delegate) {
    235   closing_delegates_.insert(delegate);
    236 
    237   scoped_ptr<Window> window(new Window());
    238   window->selected_tab_index = delegate->GetSelectedIndex();
    239   window->timestamp = TimeNow();
    240   // Don't use std::vector::resize() because it will push copies of an empty tab
    241   // into the vector, which will give all tabs in a window the same ID.
    242   for (int i = 0; i < delegate->GetTabCount(); ++i) {
    243     window->tabs.push_back(Tab());
    244   }
    245   size_t entry_index = 0;
    246   for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
    247     PopulateTab(&(window->tabs[entry_index]),
    248                 tab_index,
    249                 delegate,
    250                 &delegate->GetTabContentsAt(tab_index)->controller());
    251     if (window->tabs[entry_index].navigations.empty()) {
    252       window->tabs.erase(window->tabs.begin() + entry_index);
    253     } else {
    254       window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
    255       entry_index++;
    256     }
    257   }
    258   if (window->tabs.size() == 1) {
    259     // Short-circuit creating a Window if only 1 tab was present. This fixes
    260     // http://crbug.com/56744. Copy the Tab because it's owned by an object on
    261     // the stack.
    262     AddEntry(new Tab(window->tabs[0]), true, true);
    263   } else if (!window->tabs.empty()) {
    264     window->selected_tab_index =
    265         std::min(static_cast<int>(window->tabs.size() - 1),
    266                  window->selected_tab_index);
    267     AddEntry(window.release(), true, true);
    268   }
    269 }
    270 
    271 void TabRestoreService::BrowserClosed(TabRestoreServiceDelegate* delegate) {
    272   closing_delegates_.erase(delegate);
    273 }
    274 
    275 void TabRestoreService::ClearEntries() {
    276   // Mark all the tabs as closed so that we don't attempt to restore them.
    277   for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i)
    278     ScheduleCommand(CreateRestoredEntryCommand((*i)->id));
    279 
    280   entries_to_write_ = 0;
    281 
    282   // Schedule a pending reset so that we nuke the file on next write.
    283   set_pending_reset(true);
    284 
    285   // Schedule a command, otherwise if there are no pending commands Save does
    286   // nothing.
    287   ScheduleCommand(CreateRestoredEntryCommand(1));
    288 
    289   STLDeleteElements(&entries_);
    290   NotifyTabsChanged();
    291 }
    292 
    293 const TabRestoreService::Entries& TabRestoreService::entries() const {
    294   return entries_;
    295 }
    296 
    297 void TabRestoreService::RestoreMostRecentEntry(
    298     TabRestoreServiceDelegate* delegate) {
    299   if (entries_.empty())
    300     return;
    301 
    302   RestoreEntryById(delegate, entries_.front()->id, false);
    303 }
    304 
    305 void TabRestoreService::RestoreEntryById(TabRestoreServiceDelegate* delegate,
    306                                          SessionID::id_type id,
    307                                          bool replace_existing_tab) {
    308   Entries::iterator i = GetEntryIteratorById(id);
    309   if (i == entries_.end()) {
    310     // Don't hoark here, we allow an invalid id.
    311     return;
    312   }
    313 
    314   size_t index = 0;
    315   for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end();
    316        ++j, ++index) {}
    317   if (static_cast<int>(index) < entries_to_write_)
    318     entries_to_write_--;
    319 
    320   ScheduleCommand(CreateRestoredEntryCommand(id));
    321 
    322   restoring_ = true;
    323   Entry* entry = *i;
    324 
    325   // If the entry's ID does not match the ID that is being restored, then the
    326   // entry is a window from which a single tab will be restored.
    327   bool restoring_tab_in_window = entry->id != id;
    328 
    329   if (!restoring_tab_in_window) {
    330     entries_.erase(i);
    331     i = entries_.end();
    332   }
    333 
    334   // |delegate| will be NULL in cases where one isn't already available (eg,
    335   // when invoked on Mac OS X with no windows open). In this case, create a
    336   // new browser into which we restore the tabs.
    337   if (entry->type == TAB) {
    338     Tab* tab = static_cast<Tab*>(entry);
    339     delegate = RestoreTab(*tab, delegate, replace_existing_tab);
    340     delegate->ShowBrowserWindow();
    341   } else if (entry->type == WINDOW) {
    342     TabRestoreServiceDelegate* current_delegate = delegate;
    343     Window* window = static_cast<Window*>(entry);
    344 
    345     // When restoring a window, either the entire window can be restored, or a
    346     // single tab within it. If the entry's ID matches the one to restore, then
    347     // the entire window will be restored.
    348     if (!restoring_tab_in_window) {
    349       delegate = TabRestoreServiceDelegate::Create(profile());
    350       for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
    351         const Tab& tab = window->tabs[tab_i];
    352         TabContents* restored_tab =
    353             delegate->AddRestoredTab(tab.navigations, delegate->GetTabCount(),
    354                                      tab.current_navigation_index,
    355                                      tab.extension_app_id,
    356                                      (static_cast<int>(tab_i) ==
    357                                          window->selected_tab_index),
    358                                      tab.pinned, tab.from_last_session,
    359                                      tab.session_storage_namespace);
    360         if (restored_tab) {
    361           restored_tab->controller().LoadIfNecessary();
    362           RecordAppLaunch(profile(), tab);
    363         }
    364       }
    365       // All the window's tabs had the same former browser_id.
    366       if (window->tabs[0].has_browser()) {
    367         UpdateTabBrowserIDs(window->tabs[0].browser_id,
    368                             delegate->GetSessionID().id());
    369       }
    370     } else {
    371       // Restore a single tab from the window. Find the tab that matches the ID
    372       // in the window and restore it.
    373       for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
    374            tab_i != window->tabs.end(); ++tab_i) {
    375         const Tab& tab = *tab_i;
    376         if (tab.id == id) {
    377           delegate = RestoreTab(tab, delegate, replace_existing_tab);
    378           window->tabs.erase(tab_i);
    379           // If restoring the tab leaves the window with nothing else, delete it
    380           // as well.
    381           if (!window->tabs.size()) {
    382             entries_.erase(i);
    383             delete entry;
    384           } else {
    385             // Update the browser ID of the rest of the tabs in the window so if
    386             // any one is restored, it goes into the same window as the tab
    387             // being restored now.
    388             UpdateTabBrowserIDs(tab.browser_id,
    389                                 delegate->GetSessionID().id());
    390             for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
    391                  tab_j != window->tabs.end(); ++tab_j) {
    392               (*tab_j).browser_id = delegate->GetSessionID().id();
    393             }
    394           }
    395           break;
    396         }
    397       }
    398     }
    399     delegate->ShowBrowserWindow();
    400 
    401     if (replace_existing_tab && current_delegate &&
    402         current_delegate->GetSelectedTabContents()) {
    403       current_delegate->CloseTab();
    404     }
    405   } else {
    406     NOTREACHED();
    407   }
    408 
    409   if (!restoring_tab_in_window) {
    410     delete entry;
    411   }
    412 
    413   restoring_ = false;
    414   NotifyTabsChanged();
    415 }
    416 
    417 void TabRestoreService::LoadTabsFromLastSession() {
    418   if (load_state_ != NOT_LOADED || reached_max_)
    419     return;
    420 
    421   load_state_ = LOADING;
    422 
    423   if (!profile()->restored_last_session() &&
    424       !profile()->DidLastSessionExitCleanly() &&
    425       profile()->GetSessionService()) {
    426     // The previous session crashed and wasn't restored. Load the tabs/windows
    427     // that were open at the point of crash from the session service.
    428     profile()->GetSessionService()->GetLastSession(
    429         &load_consumer_,
    430         NewCallback(this, &TabRestoreService::OnGotPreviousSession));
    431   } else {
    432     load_state_ |= LOADED_LAST_SESSION;
    433   }
    434 
    435   // Request the tabs closed in the last session. If the last session crashed,
    436   // this won't contain the tabs/window that were open at the point of the
    437   // crash (the call to GetLastSession above requests those).
    438   ScheduleGetLastSessionCommands(
    439       new InternalGetCommandsRequest(
    440           NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)),
    441       &load_consumer_);
    442 }
    443 
    444 void TabRestoreService::Save() {
    445   int to_write_count = std::min(entries_to_write_,
    446                                 static_cast<int>(entries_.size()));
    447   entries_to_write_ = 0;
    448   if (entries_written_ + to_write_count > kEntriesPerReset) {
    449     to_write_count = entries_.size();
    450     set_pending_reset(true);
    451   }
    452   if (to_write_count) {
    453     // Write the to_write_count most recently added entries out. The most
    454     // recently added entry is at the front, so we use a reverse iterator to
    455     // write in the order the entries were added.
    456     Entries::reverse_iterator i = entries_.rbegin();
    457     DCHECK(static_cast<size_t>(to_write_count) <= entries_.size());
    458     std::advance(i, entries_.size() - static_cast<int>(to_write_count));
    459     for (; i != entries_.rend(); ++i) {
    460       Entry* entry = *i;
    461       if (entry->type == TAB) {
    462         Tab* tab = static_cast<Tab*>(entry);
    463         int selected_index = GetSelectedNavigationIndexToPersist(*tab);
    464         if (selected_index != -1)
    465           ScheduleCommandsForTab(*tab, selected_index);
    466       } else {
    467         ScheduleCommandsForWindow(*static_cast<Window*>(entry));
    468       }
    469       entries_written_++;
    470     }
    471   }
    472   if (pending_reset())
    473     entries_written_ = 0;
    474   BaseSessionService::Save();
    475 }
    476 
    477 void TabRestoreService::PopulateTab(Tab* tab,
    478                                     int index,
    479                                     TabRestoreServiceDelegate* delegate,
    480                                     NavigationController* controller) {
    481   const int pending_index = controller->pending_entry_index();
    482   int entry_count = controller->entry_count();
    483   if (entry_count == 0 && pending_index == 0)
    484     entry_count++;
    485   tab->navigations.resize(static_cast<int>(entry_count));
    486   for (int i = 0; i < entry_count; ++i) {
    487     NavigationEntry* entry = (i == pending_index) ?
    488         controller->pending_entry() : controller->GetEntryAtIndex(i);
    489     tab->navigations[i].SetFromNavigationEntry(*entry);
    490   }
    491   tab->timestamp = TimeNow();
    492   tab->current_navigation_index = controller->GetCurrentEntryIndex();
    493   if (tab->current_navigation_index == -1 && entry_count > 0)
    494     tab->current_navigation_index = 0;
    495   tab->tabstrip_index = index;
    496 
    497   TabContentsWrapper* wrapper =
    498       TabContentsWrapper::GetCurrentWrapperForContents(
    499           controller->tab_contents());
    500   // wrapper is NULL in some browser tests.
    501   if (wrapper) {
    502     const Extension* extension =
    503         wrapper->extension_tab_helper()->extension_app();
    504     if (extension)
    505       tab->extension_app_id = extension->id();
    506   }
    507 
    508   tab->session_storage_namespace = controller->session_storage_namespace();
    509 
    510   // Delegate may be NULL during unit tests.
    511   if (delegate) {
    512     tab->browser_id = delegate->GetSessionID().id();
    513     tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
    514   }
    515 }
    516 
    517 void TabRestoreService::NotifyTabsChanged() {
    518   FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
    519                     TabRestoreServiceChanged(this));
    520 }
    521 
    522 void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) {
    523   if (to_front)
    524     entries_.push_front(entry);
    525   else
    526     entries_.push_back(entry);
    527   if (notify)
    528     PruneAndNotify();
    529   // Start the save timer, when it fires we'll generate the commands.
    530   StartSaveTimer();
    531   entries_to_write_++;
    532 }
    533 
    534 void TabRestoreService::PruneAndNotify() {
    535   while (entries_.size() > kMaxEntries) {
    536     delete entries_.back();
    537     entries_.pop_back();
    538     reached_max_ = true;
    539   }
    540 
    541   NotifyTabsChanged();
    542 }
    543 
    544 TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById(
    545     SessionID::id_type id) {
    546   for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
    547     if ((*i)->id == id)
    548       return i;
    549 
    550     // For Window entries, see if the ID matches a tab. If so, report the window
    551     // as the Entry.
    552     if ((*i)->type == WINDOW) {
    553       std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
    554       for (std::vector<Tab>::iterator j = tabs.begin();
    555            j != tabs.end(); ++j) {
    556         if ((*j).id == id) {
    557           return i;
    558         }
    559       }
    560     }
    561   }
    562   return entries_.end();
    563 }
    564 
    565 void TabRestoreService::ScheduleCommandsForWindow(const Window& window) {
    566   DCHECK(!window.tabs.empty());
    567   int selected_tab = window.selected_tab_index;
    568   int valid_tab_count = 0;
    569   int real_selected_tab = selected_tab;
    570   for (size_t i = 0; i < window.tabs.size(); ++i) {
    571     if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) {
    572       valid_tab_count++;
    573     } else if (static_cast<int>(i) < selected_tab) {
    574       real_selected_tab--;
    575     }
    576   }
    577   if (valid_tab_count == 0)
    578     return;  // No tabs to persist.
    579 
    580   ScheduleCommand(
    581       CreateWindowCommand(window.id,
    582                           std::min(real_selected_tab, valid_tab_count - 1),
    583                           valid_tab_count,
    584                           window.timestamp));
    585 
    586   for (size_t i = 0; i < window.tabs.size(); ++i) {
    587     int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]);
    588     if (selected_index != -1)
    589       ScheduleCommandsForTab(window.tabs[i], selected_index);
    590   }
    591 }
    592 
    593 void TabRestoreService::ScheduleCommandsForTab(const Tab& tab,
    594                                                int selected_index) {
    595   const std::vector<TabNavigation>& navigations = tab.navigations;
    596   int max_index = static_cast<int>(navigations.size());
    597 
    598   // Determine the first navigation we'll persist.
    599   int valid_count_before_selected = 0;
    600   int first_index_to_persist = selected_index;
    601   for (int i = selected_index - 1; i >= 0 &&
    602        valid_count_before_selected < max_persist_navigation_count; --i) {
    603     if (ShouldTrackEntry(navigations[i])) {
    604       first_index_to_persist = i;
    605       valid_count_before_selected++;
    606     }
    607   }
    608 
    609   // Write the command that identifies the selected tab.
    610   ScheduleCommand(
    611       CreateSelectedNavigationInTabCommand(tab.id,
    612                                            valid_count_before_selected,
    613                                            tab.timestamp));
    614 
    615   if (tab.pinned) {
    616     PinnedStatePayload payload = true;
    617     SessionCommand* command =
    618         new SessionCommand(kCommandPinnedState, sizeof(payload));
    619     memcpy(command->contents(), &payload, sizeof(payload));
    620     ScheduleCommand(command);
    621   }
    622 
    623   if (!tab.extension_app_id.empty()) {
    624     ScheduleCommand(
    625         CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id,
    626                                           tab.extension_app_id));
    627   }
    628 
    629   // Then write the navigations.
    630   for (int i = first_index_to_persist, wrote_count = 0;
    631        i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) {
    632     if (ShouldTrackEntry(navigations[i])) {
    633       // Creating a NavigationEntry isn't the most efficient way to go about
    634       // this, but it simplifies the code and makes it less error prone as we
    635       // add new data to NavigationEntry.
    636       scoped_ptr<NavigationEntry> entry(
    637           navigations[i].ToNavigationEntry(wrote_count, profile()));
    638       ScheduleCommand(
    639           CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id,
    640                                            wrote_count++, *entry));
    641     }
    642   }
    643 }
    644 
    645 SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id,
    646                                                        int selected_tab_index,
    647                                                        int num_tabs,
    648                                                        Time timestamp) {
    649   WindowPayload2 payload;
    650   // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of
    651   // uninitialized memory in the struct.
    652   memset(&payload, 0, sizeof(payload));
    653   payload.window_id = id;
    654   payload.selected_tab_index = selected_tab_index;
    655   payload.num_tabs = num_tabs;
    656   payload.timestamp = timestamp.ToInternalValue();
    657 
    658   SessionCommand* command =
    659       new SessionCommand(kCommandWindow, sizeof(payload));
    660   memcpy(command->contents(), &payload, sizeof(payload));
    661   return command;
    662 }
    663 
    664 SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand(
    665     SessionID::id_type tab_id,
    666     int32 index,
    667     Time timestamp) {
    668   SelectedNavigationInTabPayload2 payload;
    669   payload.id = tab_id;
    670   payload.index = index;
    671   payload.timestamp = timestamp.ToInternalValue();
    672   SessionCommand* command =
    673       new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload));
    674   memcpy(command->contents(), &payload, sizeof(payload));
    675   return command;
    676 }
    677 
    678 SessionCommand* TabRestoreService::CreateRestoredEntryCommand(
    679     SessionID::id_type entry_id) {
    680   RestoredEntryPayload payload = entry_id;
    681   SessionCommand* command =
    682       new SessionCommand(kCommandRestoredEntry, sizeof(payload));
    683   memcpy(command->contents(), &payload, sizeof(payload));
    684   return command;
    685 }
    686 
    687 int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) {
    688   const std::vector<TabNavigation>& navigations = tab.navigations;
    689   int selected_index = tab.current_navigation_index;
    690   int max_index = static_cast<int>(navigations.size());
    691 
    692   // Find the first navigation to persist. We won't persist the selected
    693   // navigation if ShouldTrackEntry returns false.
    694   while (selected_index >= 0 &&
    695          !ShouldTrackEntry(navigations[selected_index])) {
    696     selected_index--;
    697   }
    698 
    699   if (selected_index != -1)
    700     return selected_index;
    701 
    702   // Couldn't find a navigation to persist going back, go forward.
    703   selected_index = tab.current_navigation_index + 1;
    704   while (selected_index < max_index &&
    705          !ShouldTrackEntry(navigations[selected_index])) {
    706     selected_index++;
    707   }
    708 
    709   return (selected_index == max_index) ? -1 : selected_index;
    710 }
    711 
    712 void TabRestoreService::OnGotLastSessionCommands(
    713     Handle handle,
    714     scoped_refptr<InternalGetCommandsRequest> request) {
    715   std::vector<Entry*> entries;
    716   CreateEntriesFromCommands(request, &entries);
    717   // Closed tabs always go to the end.
    718   staging_entries_.insert(staging_entries_.end(), entries.begin(),
    719                           entries.end());
    720   load_state_ |= LOADED_LAST_TABS;
    721   LoadStateChanged();
    722 }
    723 
    724 void TabRestoreService::CreateEntriesFromCommands(
    725     scoped_refptr<InternalGetCommandsRequest> request,
    726     std::vector<Entry*>* loaded_entries) {
    727   if (request->canceled() || entries_.size() == kMaxEntries)
    728     return;
    729 
    730   std::vector<SessionCommand*>& commands = request->commands;
    731   // Iterate through the commands populating entries and id_to_entry.
    732   ScopedVector<Entry> entries;
    733   IDToEntry id_to_entry;
    734   // If non-null we're processing the navigations of this tab.
    735   Tab* current_tab = NULL;
    736   // If non-null we're processing the tabs of this window.
    737   Window* current_window = NULL;
    738   // If > 0, we've gotten a window command but not all the tabs yet.
    739   int pending_window_tabs = 0;
    740   for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
    741        i != commands.end(); ++i) {
    742     const SessionCommand& command = *(*i);
    743     switch (command.id()) {
    744       case kCommandRestoredEntry: {
    745         if (pending_window_tabs > 0) {
    746           // Should never receive a restored command while waiting for all the
    747           // tabs in a window.
    748           return;
    749         }
    750 
    751         current_tab = NULL;
    752         current_window = NULL;
    753 
    754         RestoredEntryPayload payload;
    755         if (!command.GetPayload(&payload, sizeof(payload)))
    756           return;
    757         RemoveEntryByID(payload, &id_to_entry, &(entries.get()));
    758         break;
    759       }
    760 
    761       case kCommandWindow: {
    762         WindowPayload2 payload;
    763         if (pending_window_tabs > 0) {
    764           // Should never receive a window command while waiting for all the
    765           // tabs in a window.
    766           return;
    767         }
    768 
    769         // Try the new payload first
    770         if (!command.GetPayload(&payload, sizeof(payload))) {
    771           // then the old payload
    772           WindowPayload old_payload;
    773           if (!command.GetPayload(&old_payload, sizeof(old_payload)))
    774             return;
    775 
    776           // Copy the old payload data to the new payload.
    777           payload.window_id = old_payload.window_id;
    778           payload.selected_tab_index = old_payload.selected_tab_index;
    779           payload.num_tabs = old_payload.num_tabs;
    780           // Since we don't have a time use time 0 which is used to mark as an
    781           // unknown timestamp.
    782           payload.timestamp = 0;
    783         }
    784 
    785         pending_window_tabs = payload.num_tabs;
    786         if (pending_window_tabs <= 0) {
    787           // Should always have at least 1 tab. Likely indicates corruption.
    788           return;
    789         }
    790 
    791         RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get()));
    792 
    793         current_window = new Window();
    794         current_window->selected_tab_index = payload.selected_tab_index;
    795         current_window->timestamp = Time::FromInternalValue(payload.timestamp);
    796         entries->push_back(current_window);
    797         id_to_entry[payload.window_id] = current_window;
    798         break;
    799       }
    800 
    801       case kCommandSelectedNavigationInTab: {
    802         SelectedNavigationInTabPayload2 payload;
    803         if (!command.GetPayload(&payload, sizeof(payload))) {
    804           SelectedNavigationInTabPayload old_payload;
    805           if (!command.GetPayload(&old_payload, sizeof(old_payload)))
    806             return;
    807           payload.id = old_payload.id;
    808           payload.index = old_payload.index;
    809           // Since we don't have a time use time 0 which is used to mark as an
    810           // unknown timestamp.
    811           payload.timestamp = 0;
    812         }
    813 
    814         if (pending_window_tabs > 0) {
    815           if (!current_window) {
    816             // We should have created a window already.
    817             NOTREACHED();
    818             return;
    819           }
    820           current_window->tabs.resize(current_window->tabs.size() + 1);
    821           current_tab = &(current_window->tabs.back());
    822           if (--pending_window_tabs == 0)
    823             current_window = NULL;
    824         } else {
    825           RemoveEntryByID(payload.id, &id_to_entry, &(entries.get()));
    826           current_tab = new Tab();
    827           id_to_entry[payload.id] = current_tab;
    828           current_tab->timestamp = Time::FromInternalValue(payload.timestamp);
    829           entries->push_back(current_tab);
    830         }
    831         current_tab->current_navigation_index = payload.index;
    832         break;
    833       }
    834 
    835       case kCommandUpdateTabNavigation: {
    836         if (!current_tab) {
    837           // Should be in a tab when we get this.
    838           return;
    839         }
    840         current_tab->navigations.resize(current_tab->navigations.size() + 1);
    841         SessionID::id_type tab_id;
    842         if (!RestoreUpdateTabNavigationCommand(
    843             command, &current_tab->navigations.back(), &tab_id)) {
    844           return;
    845         }
    846         break;
    847       }
    848 
    849       case kCommandPinnedState: {
    850         if (!current_tab) {
    851           // Should be in a tab when we get this.
    852           return;
    853         }
    854         // NOTE: payload doesn't matter. kCommandPinnedState is only written if
    855         // tab is pinned.
    856         current_tab->pinned = true;
    857         break;
    858       }
    859 
    860       case kCommandSetExtensionAppID: {
    861         if (!current_tab) {
    862           // Should be in a tab when we get this.
    863           return;
    864         }
    865         SessionID::id_type tab_id;
    866         std::string extension_app_id;
    867         if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id,
    868                                                 &extension_app_id)) {
    869           return;
    870         }
    871         current_tab->extension_app_id.swap(extension_app_id);
    872         break;
    873       }
    874 
    875       default:
    876         // Unknown type, usually indicates corruption of file. Ignore it.
    877         return;
    878     }
    879   }
    880 
    881   // If there was corruption some of the entries won't be valid. Prune any
    882   // entries with no navigations.
    883   ValidateAndDeleteEmptyEntries(&(entries.get()));
    884 
    885   loaded_entries->swap(entries.get());
    886 }
    887 
    888 TabRestoreServiceDelegate* TabRestoreService::RestoreTab(
    889     const Tab& tab,
    890     TabRestoreServiceDelegate* delegate,
    891     bool replace_existing_tab) {
    892   // |browser| will be NULL in cases where one isn't already available (eg,
    893   // when invoked on Mac OS X with no windows open). In this case, create a
    894   // new browser into which we restore the tabs.
    895   if (replace_existing_tab && delegate) {
    896     delegate->ReplaceRestoredTab(tab.navigations,
    897                                  tab.current_navigation_index,
    898                                  tab.from_last_session,
    899                                  tab.extension_app_id,
    900                                  tab.session_storage_namespace);
    901   } else {
    902     if (tab.has_browser())
    903       delegate = TabRestoreServiceDelegate::FindDelegateWithID(tab.browser_id);
    904 
    905     int tab_index = -1;
    906     if (delegate) {
    907       tab_index = tab.tabstrip_index;
    908     } else {
    909       delegate = TabRestoreServiceDelegate::Create(profile());
    910       if (tab.has_browser()) {
    911         UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
    912       }
    913     }
    914 
    915     if (tab_index < 0 || tab_index > delegate->GetTabCount()) {
    916       tab_index = delegate->GetTabCount();
    917     }
    918 
    919     delegate->AddRestoredTab(tab.navigations,
    920                              tab_index,
    921                              tab.current_navigation_index,
    922                              tab.extension_app_id,
    923                              true, tab.pinned, tab.from_last_session,
    924                              tab.session_storage_namespace);
    925   }
    926   RecordAppLaunch(profile(), tab);
    927   return delegate;
    928 }
    929 
    930 
    931 bool TabRestoreService::ValidateTab(Tab* tab) {
    932   if (tab->navigations.empty())
    933     return false;
    934 
    935   tab->current_navigation_index =
    936       std::max(0, std::min(tab->current_navigation_index,
    937                            static_cast<int>(tab->navigations.size()) - 1));
    938   return true;
    939 }
    940 
    941 void TabRestoreService::ValidateAndDeleteEmptyEntries(
    942     std::vector<Entry*>* entries) {
    943   std::vector<Entry*> valid_entries;
    944   std::vector<Entry*> invalid_entries;
    945 
    946   size_t max_valid = kMaxEntries - entries_.size();
    947   // Iterate from the back so that we keep the most recently closed entries.
    948   for (std::vector<Entry*>::reverse_iterator i = entries->rbegin();
    949        i != entries->rend(); ++i) {
    950     bool valid_entry = false;
    951     if (valid_entries.size() != max_valid) {
    952       if ((*i)->type == TAB) {
    953         Tab* tab = static_cast<Tab*>(*i);
    954         if (ValidateTab(tab))
    955           valid_entry = true;
    956       } else {
    957         Window* window = static_cast<Window*>(*i);
    958         for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
    959              tab_i != window->tabs.end();) {
    960           if (!ValidateTab(&(*tab_i)))
    961             tab_i = window->tabs.erase(tab_i);
    962           else
    963             ++tab_i;
    964         }
    965         if (!window->tabs.empty()) {
    966           window->selected_tab_index =
    967               std::max(0, std::min(window->selected_tab_index,
    968                                    static_cast<int>(window->tabs.size() - 1)));
    969           valid_entry = true;
    970         }
    971       }
    972     }
    973     if (valid_entry)
    974       valid_entries.push_back(*i);
    975     else
    976       invalid_entries.push_back(*i);
    977   }
    978   // NOTE: at this point the entries are ordered with newest at the front.
    979   entries->swap(valid_entries);
    980 
    981   // Delete the remaining entries.
    982   STLDeleteElements(&invalid_entries);
    983 }
    984 
    985 void TabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id,
    986                                             SessionID::id_type new_id) {
    987   for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
    988     Entry* entry = *i;
    989     if (entry->type == TAB) {
    990       Tab* tab = static_cast<Tab*>(entry);
    991       if (tab->browser_id == old_id)
    992         tab->browser_id = new_id;
    993     }
    994   }
    995 }
    996 
    997 void TabRestoreService::OnGotPreviousSession(
    998     Handle handle,
    999     std::vector<SessionWindow*>* windows) {
   1000   std::vector<Entry*> entries;
   1001   CreateEntriesFromWindows(windows, &entries);
   1002   // Previous session tabs go first.
   1003   staging_entries_.insert(staging_entries_.begin(), entries.begin(),
   1004                           entries.end());
   1005   load_state_ |= LOADED_LAST_SESSION;
   1006   LoadStateChanged();
   1007 }
   1008 
   1009 void TabRestoreService::CreateEntriesFromWindows(
   1010     std::vector<SessionWindow*>* windows,
   1011     std::vector<Entry*>* entries) {
   1012   for (size_t i = 0; i < windows->size(); ++i) {
   1013     scoped_ptr<Window> window(new Window());
   1014     if (ConvertSessionWindowToWindow((*windows)[i], window.get()))
   1015       entries->push_back(window.release());
   1016   }
   1017 }
   1018 
   1019 bool TabRestoreService::ConvertSessionWindowToWindow(
   1020     SessionWindow* session_window,
   1021     Window* window) {
   1022   for (size_t i = 0; i < session_window->tabs.size(); ++i) {
   1023     if (!session_window->tabs[i]->navigations.empty()) {
   1024       window->tabs.resize(window->tabs.size() + 1);
   1025       Tab& tab = window->tabs.back();
   1026       tab.pinned = session_window->tabs[i]->pinned;
   1027       tab.navigations.swap(session_window->tabs[i]->navigations);
   1028       tab.current_navigation_index =
   1029           session_window->tabs[i]->current_navigation_index;
   1030       tab.extension_app_id = session_window->tabs[i]->extension_app_id;
   1031       tab.timestamp = Time();
   1032     }
   1033   }
   1034   if (window->tabs.empty())
   1035     return false;
   1036 
   1037   window->selected_tab_index =
   1038       std::min(session_window->selected_tab_index,
   1039                static_cast<int>(window->tabs.size() - 1));
   1040   window->timestamp = Time();
   1041   return true;
   1042 }
   1043 
   1044 void TabRestoreService::LoadStateChanged() {
   1045   if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) !=
   1046       (LOADED_LAST_TABS | LOADED_LAST_SESSION)) {
   1047     // Still waiting on previous session or previous tabs.
   1048     return;
   1049   }
   1050 
   1051   // We're done loading.
   1052   load_state_ ^= LOADING;
   1053 
   1054   if (staging_entries_.empty() || reached_max_) {
   1055     STLDeleteElements(&staging_entries_);
   1056     return;
   1057   }
   1058 
   1059   if (staging_entries_.size() + entries_.size() > kMaxEntries) {
   1060     // If we add all the staged entries we'll end up with more than
   1061     // kMaxEntries. Delete entries such that we only end up with
   1062     // at most kMaxEntries.
   1063     DCHECK(entries_.size() < kMaxEntries);
   1064     STLDeleteContainerPointers(
   1065         staging_entries_.begin() + (kMaxEntries - entries_.size()),
   1066         staging_entries_.end());
   1067     staging_entries_.erase(
   1068         staging_entries_.begin() + (kMaxEntries - entries_.size()),
   1069         staging_entries_.end());
   1070   }
   1071 
   1072   // And add them.
   1073   for (size_t i = 0; i < staging_entries_.size(); ++i) {
   1074     staging_entries_[i]->from_last_session = true;
   1075     AddEntry(staging_entries_[i], false, false);
   1076   }
   1077 
   1078   // AddEntry takes ownership of the entry, need to clear out entries so that
   1079   // it doesn't delete them.
   1080   staging_entries_.clear();
   1081 
   1082   // Make it so we rewrite all the tabs. We need to do this otherwise we won't
   1083   // correctly write out the entries when Save is invoked (Save starts from
   1084   // the front, not the end and we just added the entries to the end).
   1085   entries_to_write_ = staging_entries_.size();
   1086 
   1087   PruneAndNotify();
   1088 }
   1089 
   1090 Time TabRestoreService::TimeNow() const {
   1091   return time_factory_ ? time_factory_->TimeNow() : Time::Now();
   1092 }
   1093