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