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