1// Copyright (c) 2012 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/persistent_tab_restore_service.h"
6
7#include <cstring>  // memcpy
8#include <vector>
9
10#include "base/basictypes.h"
11#include "base/bind.h"
12#include "base/compiler_specific.h"
13#include "base/files/file_path.h"
14#include "base/logging.h"
15#include "base/memory/ref_counted.h"
16#include "base/memory/scoped_vector.h"
17#include "base/stl_util.h"
18#include "base/task/cancelable_task_tracker.h"
19#include "base/time/time.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/sessions/base_session_service.h"
22#include "chrome/browser/sessions/session_command.h"
23#include "chrome/browser/sessions/session_service.h"
24#include "chrome/browser/sessions/session_service_factory.h"
25#include "chrome/browser/sessions/tab_restore_service_factory.h"
26#include "content/public/browser/session_storage_namespace.h"
27
28namespace {
29
30// Only written if the tab is pinned.
31typedef bool PinnedStatePayload;
32
33typedef int32 RestoredEntryPayload;
34
35typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry;
36
37// Payload used for the start of a tab close. This is the old struct that is
38// used for backwards compat when it comes to reading the session files.
39struct SelectedNavigationInTabPayload {
40  SessionID::id_type id;
41  int32 index;
42};
43
44// Payload used for the start of a window close. This is the old struct that is
45// used for backwards compat when it comes to reading the session files. This
46// struct must be POD, because we memset the contents.
47struct WindowPayload {
48  SessionID::id_type window_id;
49  int32 selected_tab_index;
50  int32 num_tabs;
51};
52
53// Payload used for the start of a window close.  This struct must be POD,
54// because we memset the contents.
55struct WindowPayload2 : WindowPayload {
56  int64 timestamp;
57};
58
59// Payload used for the start of a tab close.
60struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload {
61  int64 timestamp;
62};
63
64// Used to indicate what has loaded.
65enum LoadState {
66  // Indicates we haven't loaded anything.
67  NOT_LOADED           = 1 << 0,
68
69  // Indicates we've asked for the last sessions and tabs but haven't gotten the
70  // result back yet.
71  LOADING              = 1 << 2,
72
73  // Indicates we finished loading the last tabs (but not necessarily the last
74  // session).
75  LOADED_LAST_TABS     = 1 << 3,
76
77  // Indicates we finished loading the last session (but not necessarily the
78  // last tabs).
79  LOADED_LAST_SESSION  = 1 << 4
80};
81
82// Identifier for commands written to file. The ordering in the file is as
83// follows:
84// . When the user closes a tab a command of type
85//   kCommandSelectedNavigationInTab is written identifying the tab and
86//   the selected index, then a kCommandPinnedState command if the tab was
87//   pinned and kCommandSetExtensionAppID if the tab has an app id and
88//   the user agent override if it was using one.  This is
89//   followed by any number of kCommandUpdateTabNavigation commands (1 per
90//   navigation entry).
91// . When the user closes a window a kCommandSelectedNavigationInTab command
92//   is written out and followed by n tab closed sequences (as previoulsy
93//   described).
94// . When the user restores an entry a command of type kCommandRestoredEntry
95//   is written.
96const SessionCommand::id_type kCommandUpdateTabNavigation = 1;
97const SessionCommand::id_type kCommandRestoredEntry = 2;
98const SessionCommand::id_type kCommandWindow = 3;
99const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
100const SessionCommand::id_type kCommandPinnedState = 5;
101const SessionCommand::id_type kCommandSetExtensionAppID = 6;
102const SessionCommand::id_type kCommandSetWindowAppName = 7;
103const SessionCommand::id_type kCommandSetTabUserAgentOverride = 8;
104
105// Number of entries (not commands) before we clobber the file and write
106// everything.
107const int kEntriesPerReset = 40;
108
109const size_t kMaxEntries = TabRestoreServiceHelper::kMaxEntries;
110
111}  // namespace
112
113// PersistentTabRestoreService::Delegate ---------------------------------------
114
115// Implements the link between the tab restore service and the session backend.
116class PersistentTabRestoreService::Delegate
117    : public BaseSessionService,
118      public TabRestoreServiceHelper::Observer {
119 public:
120  explicit Delegate(Profile* profile);
121
122  virtual ~Delegate();
123
124  // BaseSessionService:
125  virtual void Save() OVERRIDE;
126
127  // TabRestoreServiceHelper::Observer:
128  virtual void OnClearEntries() OVERRIDE;
129  virtual void OnRestoreEntryById(
130      SessionID::id_type id,
131      Entries::const_iterator entry_iterator) OVERRIDE;
132  virtual void OnAddEntry() OVERRIDE;
133
134  void set_tab_restore_service_helper(
135      TabRestoreServiceHelper* tab_restore_service_helper) {
136    tab_restore_service_helper_ = tab_restore_service_helper;
137  }
138
139  void LoadTabsFromLastSession();
140
141  bool IsLoaded() const;
142
143  // Creates and add entries to |entries| for each of the windows in |windows|.
144  static void CreateEntriesFromWindows(std::vector<SessionWindow*>* windows,
145                                       std::vector<Entry*>* entries);
146
147  void Shutdown();
148
149  // Schedules the commands for a window close.
150  void ScheduleCommandsForWindow(const Window& window);
151
152  // Schedules the commands for a tab close. |selected_index| gives the index of
153  // the selected navigation.
154  void ScheduleCommandsForTab(const Tab& tab, int selected_index);
155
156  // Creates a window close command.
157  static SessionCommand* CreateWindowCommand(SessionID::id_type id,
158                                             int selected_tab_index,
159                                             int num_tabs,
160                                             base::Time timestamp);
161
162  // Creates a tab close command.
163  static SessionCommand* CreateSelectedNavigationInTabCommand(
164      SessionID::id_type tab_id,
165      int32 index,
166      base::Time timestamp);
167
168  // Creates a restore command.
169  static SessionCommand* CreateRestoredEntryCommand(
170    SessionID::id_type entry_id);
171
172  // Returns the index to persist as the selected index. This is the same as
173  // |tab.current_navigation_index| unless the entry at
174  // |tab.current_navigation_index| shouldn't be persisted. Returns -1 if no
175  // valid navigation to persist.
176  int GetSelectedNavigationIndexToPersist(const Tab& tab);
177
178  // Invoked when we've loaded the session commands that identify the previously
179  // closed tabs. This creates entries, adds them to staging_entries_, and
180  // invokes LoadState.
181  void OnGotLastSessionCommands(ScopedVector<SessionCommand> commands);
182
183  // Populates |loaded_entries| with Entries from |commands|.
184  void CreateEntriesFromCommands(const std::vector<SessionCommand*>& commands,
185                                 std::vector<Entry*>* loaded_entries);
186
187  // Validates all entries in |entries|, deleting any with no navigations. This
188  // also deletes any entries beyond the max number of entries we can hold.
189  static void ValidateAndDeleteEmptyEntries(std::vector<Entry*>* entries);
190
191  // Callback from SessionService when we've received the windows from the
192  // previous session. This creates and add entries to |staging_entries_| and
193  // invokes LoadStateChanged. |ignored_active_window| is ignored because we
194  // don't need to restore activation.
195  void OnGotPreviousSession(ScopedVector<SessionWindow> windows,
196                            SessionID::id_type ignored_active_window);
197
198  // Converts a SessionWindow into a Window, returning true on success. We use 0
199  // as the timestamp here since we do not know when the window/tab was closed.
200  static bool ConvertSessionWindowToWindow(SessionWindow* session_window,
201                                           Window* window);
202
203  // Invoked when previous tabs or session is loaded. If both have finished
204  // loading the entries in |staging_entries_| are added to entries and
205  // observers are notified.
206  void LoadStateChanged();
207
208  // If |id_to_entry| contains an entry for |id| the corresponding entry is
209  // deleted and removed from both |id_to_entry| and |entries|. This is used
210  // when creating entries from the backend file.
211  void RemoveEntryByID(SessionID::id_type id,
212                       IDToEntry* id_to_entry,
213                       std::vector<TabRestoreService::Entry*>* entries);
214
215 private:
216  TabRestoreServiceHelper* tab_restore_service_helper_;
217
218  // The number of entries to write.
219  int entries_to_write_;
220
221  // Number of entries we've written.
222  int entries_written_;
223
224  // Whether we've loaded the last session.
225  int load_state_;
226
227  // Results from previously closed tabs/sessions is first added here. When the
228  // results from both us and the session restore service have finished loading
229  // LoadStateChanged is invoked, which adds these entries to entries_.
230  ScopedVector<Entry> staging_entries_;
231
232  // Used when loading previous tabs/session and open tabs/session.
233  base::CancelableTaskTracker cancelable_task_tracker_;
234
235  DISALLOW_COPY_AND_ASSIGN(Delegate);
236};
237
238PersistentTabRestoreService::Delegate::Delegate(Profile* profile)
239    : BaseSessionService(BaseSessionService::TAB_RESTORE, profile,
240                         base::FilePath()),
241      tab_restore_service_helper_(NULL),
242      entries_to_write_(0),
243      entries_written_(0),
244      load_state_(NOT_LOADED) {
245}
246
247PersistentTabRestoreService::Delegate::~Delegate() {}
248
249void PersistentTabRestoreService::Delegate::Save() {
250  const Entries& entries = tab_restore_service_helper_->entries();
251  int to_write_count = std::min(entries_to_write_,
252                                static_cast<int>(entries.size()));
253  entries_to_write_ = 0;
254  if (entries_written_ + to_write_count > kEntriesPerReset) {
255    to_write_count = entries.size();
256    set_pending_reset(true);
257  }
258  if (to_write_count) {
259    // Write the to_write_count most recently added entries out. The most
260    // recently added entry is at the front, so we use a reverse iterator to
261    // write in the order the entries were added.
262    Entries::const_reverse_iterator i = entries.rbegin();
263    DCHECK(static_cast<size_t>(to_write_count) <= entries.size());
264    std::advance(i, entries.size() - static_cast<int>(to_write_count));
265    for (; i != entries.rend(); ++i) {
266      Entry* entry = *i;
267      if (entry->type == TAB) {
268        Tab* tab = static_cast<Tab*>(entry);
269        int selected_index = GetSelectedNavigationIndexToPersist(*tab);
270        if (selected_index != -1)
271          ScheduleCommandsForTab(*tab, selected_index);
272      } else {
273        ScheduleCommandsForWindow(*static_cast<Window*>(entry));
274      }
275      entries_written_++;
276    }
277  }
278  if (pending_reset())
279    entries_written_ = 0;
280  BaseSessionService::Save();
281}
282
283void PersistentTabRestoreService::Delegate::OnClearEntries() {
284  // Mark all the tabs as closed so that we don't attempt to restore them.
285  const Entries& entries = tab_restore_service_helper_->entries();
286  for (Entries::const_iterator i = entries.begin(); i != entries.end(); ++i)
287    ScheduleCommand(CreateRestoredEntryCommand((*i)->id));
288
289  entries_to_write_ = 0;
290
291  // Schedule a pending reset so that we nuke the file on next write.
292  set_pending_reset(true);
293
294  // Schedule a command, otherwise if there are no pending commands Save does
295  // nothing.
296  ScheduleCommand(CreateRestoredEntryCommand(1));
297}
298
299void PersistentTabRestoreService::Delegate::OnRestoreEntryById(
300    SessionID::id_type id,
301    Entries::const_iterator entry_iterator) {
302  size_t index = 0;
303  const Entries& entries = tab_restore_service_helper_->entries();
304  for (Entries::const_iterator j = entries.begin();
305       j != entry_iterator && j != entries.end();
306       ++j, ++index) {}
307  if (static_cast<int>(index) < entries_to_write_)
308    entries_to_write_--;
309
310  ScheduleCommand(CreateRestoredEntryCommand(id));
311}
312
313void PersistentTabRestoreService::Delegate::OnAddEntry() {
314  // Start the save timer, when it fires we'll generate the commands.
315  StartSaveTimer();
316  entries_to_write_++;
317}
318
319void PersistentTabRestoreService::Delegate::LoadTabsFromLastSession() {
320  if (load_state_ != NOT_LOADED)
321    return;
322
323  if (tab_restore_service_helper_->entries().size() == kMaxEntries) {
324    // We already have the max number of entries we can take. There is no point
325    // in attempting to load since we'll just drop the results. Skip to loaded.
326    load_state_ = (LOADING | LOADED_LAST_SESSION | LOADED_LAST_TABS);
327    LoadStateChanged();
328    return;
329  }
330
331#if !defined(ENABLE_SESSION_SERVICE)
332  // If sessions are not stored in the SessionService, default to
333  // |LOADED_LAST_SESSION| state.
334  load_state_ = LOADING | LOADED_LAST_SESSION;
335#else
336  load_state_ = LOADING;
337
338  SessionService* session_service =
339      SessionServiceFactory::GetForProfile(profile());
340  Profile::ExitType exit_type = profile()->GetLastSessionExitType();
341  if (!profile()->restored_last_session() && session_service &&
342      (exit_type == Profile::EXIT_CRASHED ||
343       exit_type == Profile::EXIT_SESSION_ENDED)) {
344    // The previous session crashed and wasn't restored, or was a forced
345    // shutdown. Both of which won't have notified us of the browser close so
346    // that we need to load the windows from session service (which will have
347    // saved them).
348    session_service->GetLastSession(
349        base::Bind(&Delegate::OnGotPreviousSession, base::Unretained(this)),
350        &cancelable_task_tracker_);
351  } else {
352    load_state_ |= LOADED_LAST_SESSION;
353  }
354#endif
355
356  // Request the tabs closed in the last session. If the last session crashed,
357  // this won't contain the tabs/window that were open at the point of the
358  // crash (the call to GetLastSession above requests those).
359  ScheduleGetLastSessionCommands(
360      base::Bind(&Delegate::OnGotLastSessionCommands, base::Unretained(this)),
361      &cancelable_task_tracker_);
362}
363
364bool PersistentTabRestoreService::Delegate::IsLoaded() const {
365  return !(load_state_ & (NOT_LOADED | LOADING));
366}
367
368// static
369void PersistentTabRestoreService::Delegate::CreateEntriesFromWindows(
370    std::vector<SessionWindow*>* windows,
371    std::vector<Entry*>* entries) {
372  for (size_t i = 0; i < windows->size(); ++i) {
373    scoped_ptr<Window> window(new Window());
374    if (ConvertSessionWindowToWindow((*windows)[i], window.get()))
375      entries->push_back(window.release());
376  }
377}
378
379void PersistentTabRestoreService::Delegate::Shutdown() {
380  if (backend())
381    Save();
382}
383
384void PersistentTabRestoreService::Delegate::ScheduleCommandsForWindow(
385    const Window& window) {
386  DCHECK(!window.tabs.empty());
387  int selected_tab = window.selected_tab_index;
388  int valid_tab_count = 0;
389  int real_selected_tab = selected_tab;
390  for (size_t i = 0; i < window.tabs.size(); ++i) {
391    if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) {
392      valid_tab_count++;
393    } else if (static_cast<int>(i) < selected_tab) {
394      real_selected_tab--;
395    }
396  }
397  if (valid_tab_count == 0)
398    return;  // No tabs to persist.
399
400  ScheduleCommand(
401      CreateWindowCommand(window.id,
402                          std::min(real_selected_tab, valid_tab_count - 1),
403                          valid_tab_count,
404                          window.timestamp));
405
406  if (!window.app_name.empty()) {
407    ScheduleCommand(
408        CreateSetWindowAppNameCommand(kCommandSetWindowAppName,
409                                      window.id,
410                                      window.app_name));
411  }
412
413  for (size_t i = 0; i < window.tabs.size(); ++i) {
414    int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]);
415    if (selected_index != -1)
416      ScheduleCommandsForTab(window.tabs[i], selected_index);
417  }
418}
419
420void PersistentTabRestoreService::Delegate::ScheduleCommandsForTab(
421    const Tab& tab,
422    int selected_index) {
423  const std::vector<sessions::SerializedNavigationEntry>& navigations =
424      tab.navigations;
425  int max_index = static_cast<int>(navigations.size());
426
427  // Determine the first navigation we'll persist.
428  int valid_count_before_selected = 0;
429  int first_index_to_persist = selected_index;
430  for (int i = selected_index - 1; i >= 0 &&
431       valid_count_before_selected < max_persist_navigation_count; --i) {
432    if (ShouldTrackEntry(navigations[i].virtual_url())) {
433      first_index_to_persist = i;
434      valid_count_before_selected++;
435    }
436  }
437
438  // Write the command that identifies the selected tab.
439  ScheduleCommand(
440      CreateSelectedNavigationInTabCommand(tab.id,
441                                           valid_count_before_selected,
442                                           tab.timestamp));
443
444  if (tab.pinned) {
445    PinnedStatePayload payload = true;
446    SessionCommand* command =
447        new SessionCommand(kCommandPinnedState, sizeof(payload));
448    memcpy(command->contents(), &payload, sizeof(payload));
449    ScheduleCommand(command);
450  }
451
452  if (!tab.extension_app_id.empty()) {
453    ScheduleCommand(
454        CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id,
455                                          tab.extension_app_id));
456  }
457
458  if (!tab.user_agent_override.empty()) {
459    ScheduleCommand(
460        CreateSetTabUserAgentOverrideCommand(kCommandSetTabUserAgentOverride,
461                                             tab.id, tab.user_agent_override));
462  }
463
464  // Then write the navigations.
465  for (int i = first_index_to_persist, wrote_count = 0;
466       i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) {
467    if (ShouldTrackEntry(navigations[i].virtual_url())) {
468      ScheduleCommand(
469          CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id,
470                                           navigations[i]));
471    }
472  }
473}
474
475// static
476SessionCommand* PersistentTabRestoreService::Delegate::CreateWindowCommand(
477    SessionID::id_type id,
478    int selected_tab_index,
479    int num_tabs,
480    base::Time timestamp) {
481  WindowPayload2 payload;
482  // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of
483  // uninitialized memory in the struct.
484  memset(&payload, 0, sizeof(payload));
485  payload.window_id = id;
486  payload.selected_tab_index = selected_tab_index;
487  payload.num_tabs = num_tabs;
488  payload.timestamp = timestamp.ToInternalValue();
489
490  SessionCommand* command =
491      new SessionCommand(kCommandWindow, sizeof(payload));
492  memcpy(command->contents(), &payload, sizeof(payload));
493  return command;
494}
495
496// static
497SessionCommand*
498PersistentTabRestoreService::Delegate::CreateSelectedNavigationInTabCommand(
499    SessionID::id_type tab_id,
500    int32 index,
501    base::Time timestamp) {
502  SelectedNavigationInTabPayload2 payload;
503  payload.id = tab_id;
504  payload.index = index;
505  payload.timestamp = timestamp.ToInternalValue();
506  SessionCommand* command =
507      new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload));
508  memcpy(command->contents(), &payload, sizeof(payload));
509  return command;
510}
511
512// static
513SessionCommand*
514PersistentTabRestoreService::Delegate::CreateRestoredEntryCommand(
515    SessionID::id_type entry_id) {
516  RestoredEntryPayload payload = entry_id;
517  SessionCommand* command =
518      new SessionCommand(kCommandRestoredEntry, sizeof(payload));
519  memcpy(command->contents(), &payload, sizeof(payload));
520  return command;
521}
522
523int PersistentTabRestoreService::Delegate::GetSelectedNavigationIndexToPersist(
524    const Tab& tab) {
525  const std::vector<sessions::SerializedNavigationEntry>& navigations =
526      tab.navigations;
527  int selected_index = tab.current_navigation_index;
528  int max_index = static_cast<int>(navigations.size());
529
530  // Find the first navigation to persist. We won't persist the selected
531  // navigation if ShouldTrackEntry returns false.
532  while (selected_index >= 0 &&
533         !ShouldTrackEntry(navigations[selected_index].virtual_url())) {
534    selected_index--;
535  }
536
537  if (selected_index != -1)
538    return selected_index;
539
540  // Couldn't find a navigation to persist going back, go forward.
541  selected_index = tab.current_navigation_index + 1;
542  while (selected_index < max_index &&
543         !ShouldTrackEntry(navigations[selected_index].virtual_url())) {
544    selected_index++;
545  }
546
547  return (selected_index == max_index) ? -1 : selected_index;
548}
549
550void PersistentTabRestoreService::Delegate::OnGotLastSessionCommands(
551    ScopedVector<SessionCommand> commands) {
552  std::vector<Entry*> entries;
553  CreateEntriesFromCommands(commands.get(), &entries);
554  // Closed tabs always go to the end.
555  staging_entries_.insert(staging_entries_.end(), entries.begin(),
556                          entries.end());
557  load_state_ |= LOADED_LAST_TABS;
558  LoadStateChanged();
559}
560
561void PersistentTabRestoreService::Delegate::CreateEntriesFromCommands(
562    const std::vector<SessionCommand*>& commands,
563    std::vector<Entry*>* loaded_entries) {
564  if (tab_restore_service_helper_->entries().size() == kMaxEntries)
565    return;
566
567  // Iterate through the commands populating entries and id_to_entry.
568  ScopedVector<Entry> entries;
569  IDToEntry id_to_entry;
570  // If non-null we're processing the navigations of this tab.
571  Tab* current_tab = NULL;
572  // If non-null we're processing the tabs of this window.
573  Window* current_window = NULL;
574  // If > 0, we've gotten a window command but not all the tabs yet.
575  int pending_window_tabs = 0;
576  for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
577       i != commands.end(); ++i) {
578    const SessionCommand& command = *(*i);
579    switch (command.id()) {
580      case kCommandRestoredEntry: {
581        if (pending_window_tabs > 0) {
582          // Should never receive a restored command while waiting for all the
583          // tabs in a window.
584          return;
585        }
586
587        current_tab = NULL;
588        current_window = NULL;
589
590        RestoredEntryPayload payload;
591        if (!command.GetPayload(&payload, sizeof(payload)))
592          return;
593        RemoveEntryByID(payload, &id_to_entry, &(entries.get()));
594        break;
595      }
596
597      case kCommandWindow: {
598        WindowPayload2 payload;
599        if (pending_window_tabs > 0) {
600          // Should never receive a window command while waiting for all the
601          // tabs in a window.
602          return;
603        }
604
605        // Try the new payload first
606        if (!command.GetPayload(&payload, sizeof(payload))) {
607          // then the old payload
608          WindowPayload old_payload;
609          if (!command.GetPayload(&old_payload, sizeof(old_payload)))
610            return;
611
612          // Copy the old payload data to the new payload.
613          payload.window_id = old_payload.window_id;
614          payload.selected_tab_index = old_payload.selected_tab_index;
615          payload.num_tabs = old_payload.num_tabs;
616          // Since we don't have a time use time 0 which is used to mark as an
617          // unknown timestamp.
618          payload.timestamp = 0;
619        }
620
621        pending_window_tabs = payload.num_tabs;
622        if (pending_window_tabs <= 0) {
623          // Should always have at least 1 tab. Likely indicates corruption.
624          return;
625        }
626
627        RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get()));
628
629        current_window = new Window();
630        current_window->selected_tab_index = payload.selected_tab_index;
631        current_window->timestamp =
632            base::Time::FromInternalValue(payload.timestamp);
633        entries.push_back(current_window);
634        id_to_entry[payload.window_id] = current_window;
635        break;
636      }
637
638      case kCommandSelectedNavigationInTab: {
639        SelectedNavigationInTabPayload2 payload;
640        if (!command.GetPayload(&payload, sizeof(payload))) {
641          SelectedNavigationInTabPayload old_payload;
642          if (!command.GetPayload(&old_payload, sizeof(old_payload)))
643            return;
644          payload.id = old_payload.id;
645          payload.index = old_payload.index;
646          // Since we don't have a time use time 0 which is used to mark as an
647          // unknown timestamp.
648          payload.timestamp = 0;
649        }
650
651        if (pending_window_tabs > 0) {
652          if (!current_window) {
653            // We should have created a window already.
654            NOTREACHED();
655            return;
656          }
657          current_window->tabs.resize(current_window->tabs.size() + 1);
658          current_tab = &(current_window->tabs.back());
659          if (--pending_window_tabs == 0)
660            current_window = NULL;
661        } else {
662          RemoveEntryByID(payload.id, &id_to_entry, &(entries.get()));
663          current_tab = new Tab();
664          id_to_entry[payload.id] = current_tab;
665          current_tab->timestamp =
666              base::Time::FromInternalValue(payload.timestamp);
667          entries.push_back(current_tab);
668        }
669        current_tab->current_navigation_index = payload.index;
670        break;
671      }
672
673      case kCommandUpdateTabNavigation: {
674        if (!current_tab) {
675          // Should be in a tab when we get this.
676          return;
677        }
678        current_tab->navigations.resize(current_tab->navigations.size() + 1);
679        SessionID::id_type tab_id;
680        if (!RestoreUpdateTabNavigationCommand(
681            command, &current_tab->navigations.back(), &tab_id)) {
682          return;
683        }
684        break;
685      }
686
687      case kCommandPinnedState: {
688        if (!current_tab) {
689          // Should be in a tab when we get this.
690          return;
691        }
692        // NOTE: payload doesn't matter. kCommandPinnedState is only written if
693        // tab is pinned.
694        current_tab->pinned = true;
695        break;
696      }
697
698      case kCommandSetWindowAppName: {
699        if (!current_window) {
700          // We should have created a window already.
701          NOTREACHED();
702          return;
703        }
704
705        SessionID::id_type window_id;
706        std::string app_name;
707        if (!RestoreSetWindowAppNameCommand(command, &window_id, &app_name))
708          return;
709
710        current_window->app_name.swap(app_name);
711        break;
712      }
713
714      case kCommandSetExtensionAppID: {
715        if (!current_tab) {
716          // Should be in a tab when we get this.
717          return;
718        }
719        SessionID::id_type tab_id;
720        std::string extension_app_id;
721        if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id,
722                                                &extension_app_id)) {
723          return;
724        }
725        current_tab->extension_app_id.swap(extension_app_id);
726        break;
727      }
728
729      case kCommandSetTabUserAgentOverride: {
730        if (!current_tab) {
731          // Should be in a tab when we get this.
732          return;
733        }
734        SessionID::id_type tab_id;
735        std::string user_agent_override;
736        if (!RestoreSetTabUserAgentOverrideCommand(command, &tab_id,
737                                                   &user_agent_override)) {
738          return;
739        }
740        current_tab->user_agent_override.swap(user_agent_override);
741        break;
742      }
743
744      default:
745        // Unknown type, usually indicates corruption of file. Ignore it.
746        return;
747    }
748  }
749
750  // If there was corruption some of the entries won't be valid.
751  ValidateAndDeleteEmptyEntries(&(entries.get()));
752
753  loaded_entries->swap(entries.get());
754}
755
756// static
757void PersistentTabRestoreService::Delegate::ValidateAndDeleteEmptyEntries(
758    std::vector<Entry*>* entries) {
759  std::vector<Entry*> valid_entries;
760  std::vector<Entry*> invalid_entries;
761
762  // Iterate from the back so that we keep the most recently closed entries.
763  for (std::vector<Entry*>::reverse_iterator i = entries->rbegin();
764       i != entries->rend(); ++i) {
765    if (TabRestoreServiceHelper::ValidateEntry(*i))
766      valid_entries.push_back(*i);
767    else
768      invalid_entries.push_back(*i);
769  }
770  // NOTE: at this point the entries are ordered with newest at the front.
771  entries->swap(valid_entries);
772
773  // Delete the remaining entries.
774  STLDeleteElements(&invalid_entries);
775}
776
777void PersistentTabRestoreService::Delegate::OnGotPreviousSession(
778    ScopedVector<SessionWindow> windows,
779    SessionID::id_type ignored_active_window) {
780  std::vector<Entry*> entries;
781  CreateEntriesFromWindows(&windows.get(), &entries);
782  // Previous session tabs go first.
783  staging_entries_.insert(staging_entries_.begin(), entries.begin(),
784                          entries.end());
785  load_state_ |= LOADED_LAST_SESSION;
786  LoadStateChanged();
787}
788
789bool PersistentTabRestoreService::Delegate::ConvertSessionWindowToWindow(
790    SessionWindow* session_window,
791    Window* window) {
792  for (size_t i = 0; i < session_window->tabs.size(); ++i) {
793    if (!session_window->tabs[i]->navigations.empty()) {
794      window->tabs.resize(window->tabs.size() + 1);
795      Tab& tab = window->tabs.back();
796      tab.pinned = session_window->tabs[i]->pinned;
797      tab.navigations.swap(session_window->tabs[i]->navigations);
798      tab.current_navigation_index =
799          session_window->tabs[i]->current_navigation_index;
800      tab.extension_app_id = session_window->tabs[i]->extension_app_id;
801      tab.timestamp = base::Time();
802    }
803  }
804  if (window->tabs.empty())
805    return false;
806
807  window->selected_tab_index =
808      std::min(session_window->selected_tab_index,
809               static_cast<int>(window->tabs.size() - 1));
810  window->timestamp = base::Time();
811  return true;
812}
813
814void PersistentTabRestoreService::Delegate::LoadStateChanged() {
815  if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) !=
816      (LOADED_LAST_TABS | LOADED_LAST_SESSION)) {
817    // Still waiting on previous session or previous tabs.
818    return;
819  }
820
821  // We're done loading.
822  load_state_ ^= LOADING;
823
824  const Entries& entries = tab_restore_service_helper_->entries();
825  if (staging_entries_.empty() || entries.size() >= kMaxEntries) {
826    staging_entries_.clear();
827    tab_restore_service_helper_->NotifyLoaded();
828    return;
829  }
830
831  if (staging_entries_.size() + entries.size() > kMaxEntries) {
832    // If we add all the staged entries we'll end up with more than
833    // kMaxEntries. Delete entries such that we only end up with at most
834    // kMaxEntries.
835    int surplus = kMaxEntries - entries.size();
836    CHECK_LE(0, surplus);
837    CHECK_GE(static_cast<int>(staging_entries_.size()), surplus);
838    staging_entries_.erase(
839        staging_entries_.begin() + (kMaxEntries - entries.size()),
840        staging_entries_.end());
841  }
842
843  // And add them.
844  for (size_t i = 0; i < staging_entries_.size(); ++i) {
845    staging_entries_[i]->from_last_session = true;
846    tab_restore_service_helper_->AddEntry(staging_entries_[i], false, false);
847  }
848
849  // AddEntry takes ownership of the entry, need to clear out entries so that
850  // it doesn't delete them.
851  staging_entries_.weak_clear();
852
853  // Make it so we rewrite all the tabs. We need to do this otherwise we won't
854  // correctly write out the entries when Save is invoked (Save starts from
855  // the front, not the end and we just added the entries to the end).
856  entries_to_write_ = staging_entries_.size();
857
858  tab_restore_service_helper_->PruneEntries();
859  tab_restore_service_helper_->NotifyTabsChanged();
860
861  tab_restore_service_helper_->NotifyLoaded();
862}
863
864void PersistentTabRestoreService::Delegate::RemoveEntryByID(
865    SessionID::id_type id,
866    IDToEntry* id_to_entry,
867    std::vector<TabRestoreService::Entry*>* entries) {
868  // Look for the entry in the map. If it is present, erase it from both
869  // collections and return.
870  IDToEntry::iterator i = id_to_entry->find(id);
871  if (i != id_to_entry->end()) {
872    entries->erase(std::find(entries->begin(), entries->end(), i->second));
873    delete i->second;
874    id_to_entry->erase(i);
875    return;
876  }
877
878  // Otherwise, loop over all items in the map and see if any of the Windows
879  // have Tabs with the |id|.
880  for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end();
881       ++i) {
882    if (i->second->type == TabRestoreService::WINDOW) {
883      TabRestoreService::Window* window =
884          static_cast<TabRestoreService::Window*>(i->second);
885      std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin();
886      for ( ; j != window->tabs.end(); ++j) {
887        // If the ID matches one of this window's tabs, remove it from the
888        // list.
889        if ((*j).id == id) {
890          window->tabs.erase(j);
891          return;
892        }
893      }
894    }
895  }
896}
897
898// PersistentTabRestoreService -------------------------------------------------
899
900PersistentTabRestoreService::PersistentTabRestoreService(
901    Profile* profile,
902    TimeFactory* time_factory)
903    : delegate_(new Delegate(profile)),
904      helper_(this, delegate_.get(), profile, time_factory) {
905  delegate_->set_tab_restore_service_helper(&helper_);
906}
907
908PersistentTabRestoreService::~PersistentTabRestoreService() {}
909
910void PersistentTabRestoreService::AddObserver(
911    TabRestoreServiceObserver* observer) {
912  helper_.AddObserver(observer);
913}
914
915void PersistentTabRestoreService::RemoveObserver(
916    TabRestoreServiceObserver* observer) {
917  helper_.RemoveObserver(observer);
918}
919
920void PersistentTabRestoreService::CreateHistoricalTab(
921    content::WebContents* contents,
922    int index) {
923  helper_.CreateHistoricalTab(contents, index);
924}
925
926void PersistentTabRestoreService::BrowserClosing(
927    TabRestoreServiceDelegate* delegate) {
928  helper_.BrowserClosing(delegate);
929}
930
931void PersistentTabRestoreService::BrowserClosed(
932    TabRestoreServiceDelegate* delegate) {
933  helper_.BrowserClosed(delegate);
934}
935
936void PersistentTabRestoreService::ClearEntries() {
937  helper_.ClearEntries();
938}
939
940const TabRestoreService::Entries& PersistentTabRestoreService::entries() const {
941  return helper_.entries();
942}
943
944std::vector<content::WebContents*>
945PersistentTabRestoreService::RestoreMostRecentEntry(
946    TabRestoreServiceDelegate* delegate,
947    chrome::HostDesktopType host_desktop_type) {
948  return helper_.RestoreMostRecentEntry(delegate, host_desktop_type);
949}
950
951TabRestoreService::Tab* PersistentTabRestoreService::RemoveTabEntryById(
952    SessionID::id_type id) {
953  return helper_.RemoveTabEntryById(id);
954}
955
956std::vector<content::WebContents*>
957    PersistentTabRestoreService::RestoreEntryById(
958      TabRestoreServiceDelegate* delegate,
959      SessionID::id_type id,
960      chrome::HostDesktopType host_desktop_type,
961      WindowOpenDisposition disposition) {
962  return helper_.RestoreEntryById(delegate, id, host_desktop_type, disposition);
963}
964
965bool PersistentTabRestoreService::IsLoaded() const {
966  return delegate_->IsLoaded();
967}
968
969void PersistentTabRestoreService::DeleteLastSession() {
970  return delegate_->DeleteLastSession();
971}
972
973void PersistentTabRestoreService::Shutdown() {
974  return delegate_->Shutdown();
975}
976
977void PersistentTabRestoreService::LoadTabsFromLastSession() {
978  delegate_->LoadTabsFromLastSession();
979}
980
981TabRestoreService::Entries* PersistentTabRestoreService::mutable_entries() {
982  return &helper_.entries_;
983}
984
985void PersistentTabRestoreService::PruneEntries() {
986  helper_.PruneEntries();
987}
988
989KeyedService* TabRestoreServiceFactory::BuildServiceInstanceFor(
990    content::BrowserContext* profile) const {
991  return new PersistentTabRestoreService(static_cast<Profile*>(profile), NULL);
992}
993