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, ¤t_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