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
30using base::Time;
31
32// TimeFactory-----------------------------------------------------------------
33
34TabRestoreService::TimeFactory::~TimeFactory() {}
35
36// Entry ----------------------------------------------------------------------
37
38// ID of the next Entry.
39static SessionID::id_type next_entry_id = 1;
40
41TabRestoreService::Entry::Entry()
42    : id(next_entry_id++),
43      type(TAB),
44      from_last_session(false) {}
45
46TabRestoreService::Entry::Entry(Type type)
47    : id(next_entry_id++),
48      type(type),
49      from_last_session(false) {}
50
51TabRestoreService::Entry::~Entry() {}
52
53// TabRestoreService ----------------------------------------------------------
54
55// static
56const 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.
71static const SessionCommand::id_type kCommandUpdateTabNavigation = 1;
72static const SessionCommand::id_type kCommandRestoredEntry = 2;
73static const SessionCommand::id_type kCommandWindow = 3;
74static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
75static const SessionCommand::id_type kCommandPinnedState = 5;
76static const SessionCommand::id_type kCommandSetExtensionAppID = 6;
77
78// Number of entries (not commands) before we clobber the file and write
79// everything.
80static const int kEntriesPerReset = 40;
81
82namespace {
83
84// Payload structures.
85
86typedef 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.
91struct 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.
99struct 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.
106struct WindowPayload2 : WindowPayload {
107  int64 timestamp;
108};
109
110// Payload used for the start of a tab close.
111struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload {
112  int64 timestamp;
113};
114
115// Only written if the tab is pinned.
116typedef bool PinnedStatePayload;
117
118typedef 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.
123void 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
155void 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
168TabRestoreService::Tab::Tab()
169    : Entry(TAB),
170      current_navigation_index(-1),
171      browser_id(0),
172      tabstrip_index(-1),
173      pinned(false) {
174}
175
176TabRestoreService::Tab::~Tab() {
177}
178
179TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) {
180}
181
182TabRestoreService::Window::~Window() {
183}
184
185TabRestoreService::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
197TabRestoreService::~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
208void TabRestoreService::AddObserver(TabRestoreServiceObserver* observer) {
209  observer_list_.AddObserver(observer);
210}
211
212void TabRestoreService::RemoveObserver(TabRestoreServiceObserver* observer) {
213  observer_list_.RemoveObserver(observer);
214}
215
216void 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
234void 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
271void TabRestoreService::BrowserClosed(TabRestoreServiceDelegate* delegate) {
272  closing_delegates_.erase(delegate);
273}
274
275void 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
293const TabRestoreService::Entries& TabRestoreService::entries() const {
294  return entries_;
295}
296
297void TabRestoreService::RestoreMostRecentEntry(
298    TabRestoreServiceDelegate* delegate) {
299  if (entries_.empty())
300    return;
301
302  RestoreEntryById(delegate, entries_.front()->id, false);
303}
304
305void 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
417void 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
444void 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
477void 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
517void TabRestoreService::NotifyTabsChanged() {
518  FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
519                    TabRestoreServiceChanged(this));
520}
521
522void 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
534void 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
544TabRestoreService::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
565void 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
593void 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
645SessionCommand* 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
664SessionCommand* 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
678SessionCommand* 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
687int 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
712void 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
724void 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
888TabRestoreServiceDelegate* 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
931bool 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
941void 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
985void 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
997void 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
1009void 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
1019bool 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
1044void 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
1090Time TabRestoreService::TimeNow() const {
1091  return time_factory_ ? time_factory_->TimeNow() : Time::Now();
1092}
1093