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