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