Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sessions / persistent_tab_restore_service.cc
blob0aa9c3c03bad8809137a64184b657e1d584ecd9f
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
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/time/time.h"
19 #include "chrome/browser/common/cancelable_request.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/sessions/base_session_service.h"
22 #include "chrome/browser/sessions/session_command.h"
23 #include "chrome/browser/sessions/session_service.h"
24 #include "chrome/browser/sessions/session_service_factory.h"
25 #include "chrome/browser/sessions/tab_restore_service_factory.h"
26 #include "chrome/common/cancelable_task_tracker.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 SessionCommand::id_type kCommandUpdateTabNavigation = 1;
98 const SessionCommand::id_type kCommandRestoredEntry = 2;
99 const SessionCommand::id_type kCommandWindow = 3;
100 const SessionCommand::id_type kCommandSelectedNavigationInTab = 4;
101 const SessionCommand::id_type kCommandPinnedState = 5;
102 const SessionCommand::id_type kCommandSetExtensionAppID = 6;
103 const SessionCommand::id_type kCommandSetWindowAppName = 7;
104 const 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 // Implements the link between the tab restore service and the session backend.
117 class PersistentTabRestoreService::Delegate
118 : public BaseSessionService,
119 public TabRestoreServiceHelper::Observer {
120 public:
121 explicit Delegate(Profile* profile);
123 virtual ~Delegate();
125 // BaseSessionService:
126 virtual void Save() OVERRIDE;
128 // TabRestoreServiceHelper::Observer:
129 virtual void OnClearEntries() OVERRIDE;
130 virtual void OnRestoreEntryById(
131 SessionID::id_type id,
132 Entries::const_iterator entry_iterator) OVERRIDE;
133 virtual 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 bool IsLoaded() const;
144 // Creates and add entries to |entries| for each of the windows in |windows|.
145 static void CreateEntriesFromWindows(std::vector<SessionWindow*>* windows,
146 std::vector<Entry*>* entries);
148 void Shutdown();
150 // Schedules the commands for a window close.
151 void ScheduleCommandsForWindow(const Window& window);
153 // Schedules the commands for a tab close. |selected_index| gives the index of
154 // the selected navigation.
155 void ScheduleCommandsForTab(const Tab& tab, int selected_index);
157 // Creates a window close command.
158 static SessionCommand* CreateWindowCommand(SessionID::id_type id,
159 int selected_tab_index,
160 int num_tabs,
161 base::Time timestamp);
163 // Creates a tab close command.
164 static SessionCommand* CreateSelectedNavigationInTabCommand(
165 SessionID::id_type tab_id,
166 int32 index,
167 base::Time timestamp);
169 // Creates a restore command.
170 static SessionCommand* CreateRestoredEntryCommand(
171 SessionID::id_type entry_id);
173 // Returns the index to persist as the selected index. This is the same as
174 // |tab.current_navigation_index| unless the entry at
175 // |tab.current_navigation_index| shouldn't be persisted. Returns -1 if no
176 // valid navigation to persist.
177 int GetSelectedNavigationIndexToPersist(const Tab& tab);
179 // Invoked when we've loaded the session commands that identify the previously
180 // closed tabs. This creates entries, adds them to staging_entries_, and
181 // invokes LoadState.
182 void OnGotLastSessionCommands(ScopedVector<SessionCommand> commands);
184 // Populates |loaded_entries| with Entries from |commands|.
185 void CreateEntriesFromCommands(const std::vector<SessionCommand*>& commands,
186 std::vector<Entry*>* loaded_entries);
188 // Validates all entries in |entries|, deleting any with no navigations. This
189 // also deletes any entries beyond the max number of entries we can hold.
190 static void ValidateAndDeleteEmptyEntries(std::vector<Entry*>* entries);
192 // Callback from SessionService when we've received the windows from the
193 // previous session. This creates and add entries to |staging_entries_| and
194 // invokes LoadStateChanged. |ignored_active_window| is ignored because we
195 // don't need to restore activation.
196 void OnGotPreviousSession(ScopedVector<SessionWindow> windows,
197 SessionID::id_type ignored_active_window);
199 // Converts a SessionWindow into a Window, returning true on success. We use 0
200 // as the timestamp here since we do not know when the window/tab was closed.
201 static bool ConvertSessionWindowToWindow(SessionWindow* session_window,
202 Window* window);
204 // Invoked when previous tabs or session is loaded. If both have finished
205 // loading the entries in |staging_entries_| are added to entries and
206 // observers are notified.
207 void LoadStateChanged();
209 // If |id_to_entry| contains an entry for |id| the corresponding entry is
210 // deleted and removed from both |id_to_entry| and |entries|. This is used
211 // when creating entries from the backend file.
212 void RemoveEntryByID(SessionID::id_type id,
213 IDToEntry* id_to_entry,
214 std::vector<TabRestoreService::Entry*>* entries);
216 private:
217 TabRestoreServiceHelper* tab_restore_service_helper_;
219 // The number of entries to write.
220 int entries_to_write_;
222 // Number of entries we've written.
223 int entries_written_;
225 // Whether we've loaded the last session.
226 int load_state_;
228 // Results from previously closed tabs/sessions is first added here. When the
229 // results from both us and the session restore service have finished loading
230 // LoadStateChanged is invoked, which adds these entries to entries_.
231 ScopedVector<Entry> staging_entries_;
233 // Used when loading previous tabs/session and open tabs/session.
234 CancelableTaskTracker cancelable_task_tracker_;
236 DISALLOW_COPY_AND_ASSIGN(Delegate);
239 PersistentTabRestoreService::Delegate::Delegate(Profile* profile)
240 : BaseSessionService(BaseSessionService::TAB_RESTORE, profile,
241 base::FilePath()),
242 tab_restore_service_helper_(NULL),
243 entries_to_write_(0),
244 entries_written_(0),
245 load_state_(NOT_LOADED) {
248 PersistentTabRestoreService::Delegate::~Delegate() {}
250 void PersistentTabRestoreService::Delegate::Save() {
251 const Entries& entries = tab_restore_service_helper_->entries();
252 int to_write_count = std::min(entries_to_write_,
253 static_cast<int>(entries.size()));
254 entries_to_write_ = 0;
255 if (entries_written_ + to_write_count > kEntriesPerReset) {
256 to_write_count = entries.size();
257 set_pending_reset(true);
259 if (to_write_count) {
260 // Write the to_write_count most recently added entries out. The most
261 // recently added entry is at the front, so we use a reverse iterator to
262 // write in the order the entries were added.
263 Entries::const_reverse_iterator i = entries.rbegin();
264 DCHECK(static_cast<size_t>(to_write_count) <= entries.size());
265 std::advance(i, entries.size() - static_cast<int>(to_write_count));
266 for (; i != entries.rend(); ++i) {
267 Entry* entry = *i;
268 if (entry->type == TAB) {
269 Tab* tab = static_cast<Tab*>(entry);
270 int selected_index = GetSelectedNavigationIndexToPersist(*tab);
271 if (selected_index != -1)
272 ScheduleCommandsForTab(*tab, selected_index);
273 } else {
274 ScheduleCommandsForWindow(*static_cast<Window*>(entry));
276 entries_written_++;
279 if (pending_reset())
280 entries_written_ = 0;
281 BaseSessionService::Save();
284 void PersistentTabRestoreService::Delegate::OnClearEntries() {
285 // Mark all the tabs as closed so that we don't attempt to restore them.
286 const Entries& entries = tab_restore_service_helper_->entries();
287 for (Entries::const_iterator i = entries.begin(); i != entries.end(); ++i)
288 ScheduleCommand(CreateRestoredEntryCommand((*i)->id));
290 entries_to_write_ = 0;
292 // Schedule a pending reset so that we nuke the file on next write.
293 set_pending_reset(true);
295 // Schedule a command, otherwise if there are no pending commands Save does
296 // nothing.
297 ScheduleCommand(CreateRestoredEntryCommand(1));
300 void PersistentTabRestoreService::Delegate::OnRestoreEntryById(
301 SessionID::id_type id,
302 Entries::const_iterator entry_iterator) {
303 size_t index = 0;
304 const Entries& entries = tab_restore_service_helper_->entries();
305 for (Entries::const_iterator j = entries.begin();
306 j != entry_iterator && j != entries.end();
307 ++j, ++index) {}
308 if (static_cast<int>(index) < entries_to_write_)
309 entries_to_write_--;
311 ScheduleCommand(CreateRestoredEntryCommand(id));
314 void PersistentTabRestoreService::Delegate::OnAddEntry() {
315 // Start the save timer, when it fires we'll generate the commands.
316 StartSaveTimer();
317 entries_to_write_++;
320 void PersistentTabRestoreService::Delegate::LoadTabsFromLastSession() {
321 if (load_state_ != NOT_LOADED)
322 return;
324 if (tab_restore_service_helper_->entries().size() == kMaxEntries) {
325 // We already have the max number of entries we can take. There is no point
326 // in attempting to load since we'll just drop the results. Skip to loaded.
327 load_state_ = (LOADING | LOADED_LAST_SESSION | LOADED_LAST_TABS);
328 LoadStateChanged();
329 return;
332 #if !defined(ENABLE_SESSION_SERVICE)
333 // If sessions are not stored in the SessionService, default to
334 // |LOADED_LAST_SESSION| state.
335 load_state_ = LOADING | LOADED_LAST_SESSION;
336 #else
337 load_state_ = LOADING;
339 SessionService* session_service =
340 SessionServiceFactory::GetForProfile(profile());
341 Profile::ExitType exit_type = profile()->GetLastSessionExitType();
342 if (!profile()->restored_last_session() && session_service &&
343 (exit_type == Profile::EXIT_CRASHED ||
344 exit_type == Profile::EXIT_SESSION_ENDED)) {
345 // The previous session crashed and wasn't restored, or was a forced
346 // shutdown. Both of which won't have notified us of the browser close so
347 // that we need to load the windows from session service (which will have
348 // saved them).
349 session_service->GetLastSession(
350 base::Bind(&Delegate::OnGotPreviousSession, base::Unretained(this)),
351 &cancelable_task_tracker_);
352 } else {
353 load_state_ |= LOADED_LAST_SESSION;
355 #endif
357 // Request the tabs closed in the last session. If the last session crashed,
358 // this won't contain the tabs/window that were open at the point of the
359 // crash (the call to GetLastSession above requests those).
360 ScheduleGetLastSessionCommands(
361 base::Bind(&Delegate::OnGotLastSessionCommands, base::Unretained(this)),
362 &cancelable_task_tracker_);
365 bool PersistentTabRestoreService::Delegate::IsLoaded() const {
366 return !(load_state_ & (NOT_LOADED | LOADING));
369 // static
370 void PersistentTabRestoreService::Delegate::CreateEntriesFromWindows(
371 std::vector<SessionWindow*>* windows,
372 std::vector<Entry*>* entries) {
373 for (size_t i = 0; i < windows->size(); ++i) {
374 scoped_ptr<Window> window(new Window());
375 if (ConvertSessionWindowToWindow((*windows)[i], window.get()))
376 entries->push_back(window.release());
380 void PersistentTabRestoreService::Delegate::Shutdown() {
381 if (backend())
382 Save();
385 void PersistentTabRestoreService::Delegate::ScheduleCommandsForWindow(
386 const Window& window) {
387 DCHECK(!window.tabs.empty());
388 int selected_tab = window.selected_tab_index;
389 int valid_tab_count = 0;
390 int real_selected_tab = selected_tab;
391 for (size_t i = 0; i < window.tabs.size(); ++i) {
392 if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) {
393 valid_tab_count++;
394 } else if (static_cast<int>(i) < selected_tab) {
395 real_selected_tab--;
398 if (valid_tab_count == 0)
399 return; // No tabs to persist.
401 ScheduleCommand(
402 CreateWindowCommand(window.id,
403 std::min(real_selected_tab, valid_tab_count - 1),
404 valid_tab_count,
405 window.timestamp));
407 if (!window.app_name.empty()) {
408 ScheduleCommand(
409 CreateSetWindowAppNameCommand(kCommandSetWindowAppName,
410 window.id,
411 window.app_name));
414 for (size_t i = 0; i < window.tabs.size(); ++i) {
415 int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]);
416 if (selected_index != -1)
417 ScheduleCommandsForTab(window.tabs[i], selected_index);
421 void PersistentTabRestoreService::Delegate::ScheduleCommandsForTab(
422 const Tab& tab,
423 int selected_index) {
424 const std::vector<sessions::SerializedNavigationEntry>& navigations =
425 tab.navigations;
426 int max_index = static_cast<int>(navigations.size());
428 // Determine the first navigation we'll persist.
429 int valid_count_before_selected = 0;
430 int first_index_to_persist = selected_index;
431 for (int i = selected_index - 1; i >= 0 &&
432 valid_count_before_selected < max_persist_navigation_count; --i) {
433 if (ShouldTrackEntry(navigations[i].virtual_url())) {
434 first_index_to_persist = i;
435 valid_count_before_selected++;
439 // Write the command that identifies the selected tab.
440 ScheduleCommand(
441 CreateSelectedNavigationInTabCommand(tab.id,
442 valid_count_before_selected,
443 tab.timestamp));
445 if (tab.pinned) {
446 PinnedStatePayload payload = true;
447 SessionCommand* command =
448 new SessionCommand(kCommandPinnedState, sizeof(payload));
449 memcpy(command->contents(), &payload, sizeof(payload));
450 ScheduleCommand(command);
453 if (!tab.extension_app_id.empty()) {
454 ScheduleCommand(
455 CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id,
456 tab.extension_app_id));
459 if (!tab.user_agent_override.empty()) {
460 ScheduleCommand(
461 CreateSetTabUserAgentOverrideCommand(kCommandSetTabUserAgentOverride,
462 tab.id, tab.user_agent_override));
465 // Then write the navigations.
466 for (int i = first_index_to_persist, wrote_count = 0;
467 i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) {
468 if (ShouldTrackEntry(navigations[i].virtual_url())) {
469 ScheduleCommand(
470 CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id,
471 navigations[i]));
476 // static
477 SessionCommand* PersistentTabRestoreService::Delegate::CreateWindowCommand(
478 SessionID::id_type id,
479 int selected_tab_index,
480 int num_tabs,
481 base::Time timestamp) {
482 WindowPayload2 payload;
483 // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of
484 // uninitialized memory in the struct.
485 memset(&payload, 0, sizeof(payload));
486 payload.window_id = id;
487 payload.selected_tab_index = selected_tab_index;
488 payload.num_tabs = num_tabs;
489 payload.timestamp = timestamp.ToInternalValue();
491 SessionCommand* command =
492 new SessionCommand(kCommandWindow, sizeof(payload));
493 memcpy(command->contents(), &payload, sizeof(payload));
494 return command;
497 // static
498 SessionCommand*
499 PersistentTabRestoreService::Delegate::CreateSelectedNavigationInTabCommand(
500 SessionID::id_type tab_id,
501 int32 index,
502 base::Time timestamp) {
503 SelectedNavigationInTabPayload2 payload;
504 payload.id = tab_id;
505 payload.index = index;
506 payload.timestamp = timestamp.ToInternalValue();
507 SessionCommand* command =
508 new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload));
509 memcpy(command->contents(), &payload, sizeof(payload));
510 return command;
513 // static
514 SessionCommand*
515 PersistentTabRestoreService::Delegate::CreateRestoredEntryCommand(
516 SessionID::id_type entry_id) {
517 RestoredEntryPayload payload = entry_id;
518 SessionCommand* command =
519 new SessionCommand(kCommandRestoredEntry, sizeof(payload));
520 memcpy(command->contents(), &payload, sizeof(payload));
521 return command;
524 int PersistentTabRestoreService::Delegate::GetSelectedNavigationIndexToPersist(
525 const Tab& tab) {
526 const std::vector<sessions::SerializedNavigationEntry>& navigations =
527 tab.navigations;
528 int selected_index = tab.current_navigation_index;
529 int max_index = static_cast<int>(navigations.size());
531 // Find the first navigation to persist. We won't persist the selected
532 // navigation if ShouldTrackEntry returns false.
533 while (selected_index >= 0 &&
534 !ShouldTrackEntry(navigations[selected_index].virtual_url())) {
535 selected_index--;
538 if (selected_index != -1)
539 return selected_index;
541 // Couldn't find a navigation to persist going back, go forward.
542 selected_index = tab.current_navigation_index + 1;
543 while (selected_index < max_index &&
544 !ShouldTrackEntry(navigations[selected_index].virtual_url())) {
545 selected_index++;
548 return (selected_index == max_index) ? -1 : selected_index;
551 void PersistentTabRestoreService::Delegate::OnGotLastSessionCommands(
552 ScopedVector<SessionCommand> commands) {
553 std::vector<Entry*> entries;
554 CreateEntriesFromCommands(commands.get(), &entries);
555 // Closed tabs always go to the end.
556 staging_entries_.insert(staging_entries_.end(), entries.begin(),
557 entries.end());
558 load_state_ |= LOADED_LAST_TABS;
559 LoadStateChanged();
562 void PersistentTabRestoreService::Delegate::CreateEntriesFromCommands(
563 const std::vector<SessionCommand*>& commands,
564 std::vector<Entry*>* loaded_entries) {
565 if (tab_restore_service_helper_->entries().size() == kMaxEntries)
566 return;
568 // Iterate through the commands populating entries and id_to_entry.
569 ScopedVector<Entry> entries;
570 IDToEntry id_to_entry;
571 // If non-null we're processing the navigations of this tab.
572 Tab* current_tab = NULL;
573 // If non-null we're processing the tabs of this window.
574 Window* current_window = NULL;
575 // If > 0, we've gotten a window command but not all the tabs yet.
576 int pending_window_tabs = 0;
577 for (std::vector<SessionCommand*>::const_iterator i = commands.begin();
578 i != commands.end(); ++i) {
579 const SessionCommand& command = *(*i);
580 switch (command.id()) {
581 case kCommandRestoredEntry: {
582 if (pending_window_tabs > 0) {
583 // Should never receive a restored command while waiting for all the
584 // tabs in a window.
585 return;
588 current_tab = NULL;
589 current_window = NULL;
591 RestoredEntryPayload payload;
592 if (!command.GetPayload(&payload, sizeof(payload)))
593 return;
594 RemoveEntryByID(payload, &id_to_entry, &(entries.get()));
595 break;
598 case kCommandWindow: {
599 WindowPayload2 payload;
600 if (pending_window_tabs > 0) {
601 // Should never receive a window command while waiting for all the
602 // tabs in a window.
603 return;
606 // Try the new payload first
607 if (!command.GetPayload(&payload, sizeof(payload))) {
608 // then the old payload
609 WindowPayload old_payload;
610 if (!command.GetPayload(&old_payload, sizeof(old_payload)))
611 return;
613 // Copy the old payload data to the new payload.
614 payload.window_id = old_payload.window_id;
615 payload.selected_tab_index = old_payload.selected_tab_index;
616 payload.num_tabs = old_payload.num_tabs;
617 // Since we don't have a time use time 0 which is used to mark as an
618 // unknown timestamp.
619 payload.timestamp = 0;
622 pending_window_tabs = payload.num_tabs;
623 if (pending_window_tabs <= 0) {
624 // Should always have at least 1 tab. Likely indicates corruption.
625 return;
628 RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get()));
630 current_window = new Window();
631 current_window->selected_tab_index = payload.selected_tab_index;
632 current_window->timestamp =
633 base::Time::FromInternalValue(payload.timestamp);
634 entries.push_back(current_window);
635 id_to_entry[payload.window_id] = current_window;
636 break;
639 case kCommandSelectedNavigationInTab: {
640 SelectedNavigationInTabPayload2 payload;
641 if (!command.GetPayload(&payload, sizeof(payload))) {
642 SelectedNavigationInTabPayload old_payload;
643 if (!command.GetPayload(&old_payload, sizeof(old_payload)))
644 return;
645 payload.id = old_payload.id;
646 payload.index = old_payload.index;
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 if (pending_window_tabs > 0) {
653 if (!current_window) {
654 // We should have created a window already.
655 NOTREACHED();
656 return;
658 current_window->tabs.resize(current_window->tabs.size() + 1);
659 current_tab = &(current_window->tabs.back());
660 if (--pending_window_tabs == 0)
661 current_window = NULL;
662 } else {
663 RemoveEntryByID(payload.id, &id_to_entry, &(entries.get()));
664 current_tab = new Tab();
665 id_to_entry[payload.id] = current_tab;
666 current_tab->timestamp =
667 base::Time::FromInternalValue(payload.timestamp);
668 entries.push_back(current_tab);
670 current_tab->current_navigation_index = payload.index;
671 break;
674 case kCommandUpdateTabNavigation: {
675 if (!current_tab) {
676 // Should be in a tab when we get this.
677 return;
679 current_tab->navigations.resize(current_tab->navigations.size() + 1);
680 SessionID::id_type tab_id;
681 if (!RestoreUpdateTabNavigationCommand(
682 command, &current_tab->navigations.back(), &tab_id)) {
683 return;
685 break;
688 case kCommandPinnedState: {
689 if (!current_tab) {
690 // Should be in a tab when we get this.
691 return;
693 // NOTE: payload doesn't matter. kCommandPinnedState is only written if
694 // tab is pinned.
695 current_tab->pinned = true;
696 break;
699 case kCommandSetWindowAppName: {
700 if (!current_window) {
701 // We should have created a window already.
702 NOTREACHED();
703 return;
706 SessionID::id_type window_id;
707 std::string app_name;
708 if (!RestoreSetWindowAppNameCommand(command, &window_id, &app_name))
709 return;
711 current_window->app_name.swap(app_name);
712 break;
715 case kCommandSetExtensionAppID: {
716 if (!current_tab) {
717 // Should be in a tab when we get this.
718 return;
720 SessionID::id_type tab_id;
721 std::string extension_app_id;
722 if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id,
723 &extension_app_id)) {
724 return;
726 current_tab->extension_app_id.swap(extension_app_id);
727 break;
730 case kCommandSetTabUserAgentOverride: {
731 if (!current_tab) {
732 // Should be in a tab when we get this.
733 return;
735 SessionID::id_type tab_id;
736 std::string user_agent_override;
737 if (!RestoreSetTabUserAgentOverrideCommand(command, &tab_id,
738 &user_agent_override)) {
739 return;
741 current_tab->user_agent_override.swap(user_agent_override);
742 break;
745 default:
746 // Unknown type, usually indicates corruption of file. Ignore it.
747 return;
751 // If there was corruption some of the entries won't be valid.
752 ValidateAndDeleteEmptyEntries(&(entries.get()));
754 loaded_entries->swap(entries.get());
757 // static
758 void PersistentTabRestoreService::Delegate::ValidateAndDeleteEmptyEntries(
759 std::vector<Entry*>* entries) {
760 std::vector<Entry*> valid_entries;
761 std::vector<Entry*> invalid_entries;
763 // Iterate from the back so that we keep the most recently closed entries.
764 for (std::vector<Entry*>::reverse_iterator i = entries->rbegin();
765 i != entries->rend(); ++i) {
766 if (TabRestoreServiceHelper::ValidateEntry(*i))
767 valid_entries.push_back(*i);
768 else
769 invalid_entries.push_back(*i);
771 // NOTE: at this point the entries are ordered with newest at the front.
772 entries->swap(valid_entries);
774 // Delete the remaining entries.
775 STLDeleteElements(&invalid_entries);
778 void PersistentTabRestoreService::Delegate::OnGotPreviousSession(
779 ScopedVector<SessionWindow> windows,
780 SessionID::id_type ignored_active_window) {
781 std::vector<Entry*> entries;
782 CreateEntriesFromWindows(&windows.get(), &entries);
783 // Previous session tabs go first.
784 staging_entries_.insert(staging_entries_.begin(), entries.begin(),
785 entries.end());
786 load_state_ |= LOADED_LAST_SESSION;
787 LoadStateChanged();
790 bool PersistentTabRestoreService::Delegate::ConvertSessionWindowToWindow(
791 SessionWindow* session_window,
792 Window* window) {
793 for (size_t i = 0; i < session_window->tabs.size(); ++i) {
794 if (!session_window->tabs[i]->navigations.empty()) {
795 window->tabs.resize(window->tabs.size() + 1);
796 Tab& tab = window->tabs.back();
797 tab.pinned = session_window->tabs[i]->pinned;
798 tab.navigations.swap(session_window->tabs[i]->navigations);
799 tab.current_navigation_index =
800 session_window->tabs[i]->current_navigation_index;
801 tab.extension_app_id = session_window->tabs[i]->extension_app_id;
802 tab.timestamp = base::Time();
805 if (window->tabs.empty())
806 return false;
808 window->selected_tab_index =
809 std::min(session_window->selected_tab_index,
810 static_cast<int>(window->tabs.size() - 1));
811 window->timestamp = base::Time();
812 return true;
815 void PersistentTabRestoreService::Delegate::LoadStateChanged() {
816 if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) !=
817 (LOADED_LAST_TABS | LOADED_LAST_SESSION)) {
818 // Still waiting on previous session or previous tabs.
819 return;
822 // We're done loading.
823 load_state_ ^= LOADING;
825 const Entries& entries = tab_restore_service_helper_->entries();
826 if (staging_entries_.empty() || entries.size() >= kMaxEntries) {
827 staging_entries_.clear();
828 tab_restore_service_helper_->NotifyLoaded();
829 return;
832 if (staging_entries_.size() + entries.size() > kMaxEntries) {
833 // If we add all the staged entries we'll end up with more than
834 // kMaxEntries. Delete entries such that we only end up with at most
835 // kMaxEntries.
836 int surplus = kMaxEntries - entries.size();
837 CHECK_LE(0, surplus);
838 CHECK_GE(static_cast<int>(staging_entries_.size()), surplus);
839 staging_entries_.erase(
840 staging_entries_.begin() + (kMaxEntries - entries.size()),
841 staging_entries_.end());
844 // And add them.
845 for (size_t i = 0; i < staging_entries_.size(); ++i) {
846 staging_entries_[i]->from_last_session = true;
847 tab_restore_service_helper_->AddEntry(staging_entries_[i], false, false);
850 // AddEntry takes ownership of the entry, need to clear out entries so that
851 // it doesn't delete them.
852 staging_entries_.weak_clear();
854 // Make it so we rewrite all the tabs. We need to do this otherwise we won't
855 // correctly write out the entries when Save is invoked (Save starts from
856 // the front, not the end and we just added the entries to the end).
857 entries_to_write_ = staging_entries_.size();
859 tab_restore_service_helper_->PruneEntries();
860 tab_restore_service_helper_->NotifyTabsChanged();
862 tab_restore_service_helper_->NotifyLoaded();
865 void PersistentTabRestoreService::Delegate::RemoveEntryByID(
866 SessionID::id_type id,
867 IDToEntry* id_to_entry,
868 std::vector<TabRestoreService::Entry*>* entries) {
869 // Look for the entry in the map. If it is present, erase it from both
870 // collections and return.
871 IDToEntry::iterator i = id_to_entry->find(id);
872 if (i != id_to_entry->end()) {
873 entries->erase(std::find(entries->begin(), entries->end(), i->second));
874 delete i->second;
875 id_to_entry->erase(i);
876 return;
879 // Otherwise, loop over all items in the map and see if any of the Windows
880 // have Tabs with the |id|.
881 for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end();
882 ++i) {
883 if (i->second->type == TabRestoreService::WINDOW) {
884 TabRestoreService::Window* window =
885 static_cast<TabRestoreService::Window*>(i->second);
886 std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin();
887 for ( ; j != window->tabs.end(); ++j) {
888 // If the ID matches one of this window's tabs, remove it from the
889 // list.
890 if ((*j).id == id) {
891 window->tabs.erase(j);
892 return;
899 // PersistentTabRestoreService -------------------------------------------------
901 PersistentTabRestoreService::PersistentTabRestoreService(
902 Profile* profile,
903 TimeFactory* time_factory)
904 : delegate_(new Delegate(profile)),
905 helper_(this, delegate_.get(), profile, time_factory) {
906 delegate_->set_tab_restore_service_helper(&helper_);
909 PersistentTabRestoreService::~PersistentTabRestoreService() {}
911 void PersistentTabRestoreService::AddObserver(
912 TabRestoreServiceObserver* observer) {
913 helper_.AddObserver(observer);
916 void PersistentTabRestoreService::RemoveObserver(
917 TabRestoreServiceObserver* observer) {
918 helper_.RemoveObserver(observer);
921 void PersistentTabRestoreService::CreateHistoricalTab(
922 content::WebContents* contents,
923 int index) {
924 helper_.CreateHistoricalTab(contents, index);
927 void PersistentTabRestoreService::BrowserClosing(
928 TabRestoreServiceDelegate* delegate) {
929 helper_.BrowserClosing(delegate);
932 void PersistentTabRestoreService::BrowserClosed(
933 TabRestoreServiceDelegate* delegate) {
934 helper_.BrowserClosed(delegate);
937 void PersistentTabRestoreService::ClearEntries() {
938 helper_.ClearEntries();
941 const TabRestoreService::Entries& PersistentTabRestoreService::entries() const {
942 return helper_.entries();
945 std::vector<content::WebContents*>
946 PersistentTabRestoreService::RestoreMostRecentEntry(
947 TabRestoreServiceDelegate* delegate,
948 chrome::HostDesktopType host_desktop_type) {
949 return helper_.RestoreMostRecentEntry(delegate, host_desktop_type);
952 TabRestoreService::Tab* PersistentTabRestoreService::RemoveTabEntryById(
953 SessionID::id_type id) {
954 return helper_.RemoveTabEntryById(id);
957 std::vector<content::WebContents*>
958 PersistentTabRestoreService::RestoreEntryById(
959 TabRestoreServiceDelegate* delegate,
960 SessionID::id_type id,
961 chrome::HostDesktopType host_desktop_type,
962 WindowOpenDisposition disposition) {
963 return helper_.RestoreEntryById(delegate, id, host_desktop_type, disposition);
966 bool PersistentTabRestoreService::IsLoaded() const {
967 return delegate_->IsLoaded();
970 void PersistentTabRestoreService::DeleteLastSession() {
971 return delegate_->DeleteLastSession();
974 void PersistentTabRestoreService::Shutdown() {
975 return delegate_->Shutdown();
978 void PersistentTabRestoreService::LoadTabsFromLastSession() {
979 delegate_->LoadTabsFromLastSession();
982 TabRestoreService::Entries* PersistentTabRestoreService::mutable_entries() {
983 return &helper_.entries_;
986 void PersistentTabRestoreService::PruneEntries() {
987 helper_.PruneEntries();
990 BrowserContextKeyedService* TabRestoreServiceFactory::BuildServiceInstanceFor(
991 content::BrowserContext* profile) const {
992 return new PersistentTabRestoreService(static_cast<Profile*>(profile), NULL);