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. 4 5#include "chrome/browser/sessions/persistent_tab_restore_service.h" 6 7#include <cstring> // memcpy 8#include <vector> 9 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" 27 28namespace { 29 30// Only written if the tab is pinned. 31typedef bool PinnedStatePayload; 32 33typedef int32 RestoredEntryPayload; 34 35typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry; 36 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. 39struct SelectedNavigationInTabPayload { 40 SessionID::id_type id; 41 int32 index; 42}; 43 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. 47struct WindowPayload { 48 SessionID::id_type window_id; 49 int32 selected_tab_index; 50 int32 num_tabs; 51}; 52 53// Payload used for the start of a window close. This struct must be POD, 54// because we memset the contents. 55struct WindowPayload2 : WindowPayload { 56 int64 timestamp; 57}; 58 59// Payload used for the start of a tab close. 60struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload { 61 int64 timestamp; 62}; 63 64// Used to indicate what has loaded. 65enum LoadState { 66 // Indicates we haven't loaded anything. 67 NOT_LOADED = 1 << 0, 68 69 // Indicates we've asked for the last sessions and tabs but haven't gotten the 70 // result back yet. 71 LOADING = 1 << 2, 72 73 // Indicates we finished loading the last tabs (but not necessarily the last 74 // session). 75 LOADED_LAST_TABS = 1 << 3, 76 77 // Indicates we finished loading the last session (but not necessarily the 78 // last tabs). 79 LOADED_LAST_SESSION = 1 << 4 80}; 81 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. 96const SessionCommand::id_type kCommandUpdateTabNavigation = 1; 97const SessionCommand::id_type kCommandRestoredEntry = 2; 98const SessionCommand::id_type kCommandWindow = 3; 99const SessionCommand::id_type kCommandSelectedNavigationInTab = 4; 100const SessionCommand::id_type kCommandPinnedState = 5; 101const SessionCommand::id_type kCommandSetExtensionAppID = 6; 102const SessionCommand::id_type kCommandSetWindowAppName = 7; 103const SessionCommand::id_type kCommandSetTabUserAgentOverride = 8; 104 105// Number of entries (not commands) before we clobber the file and write 106// everything. 107const int kEntriesPerReset = 40; 108 109const size_t kMaxEntries = TabRestoreServiceHelper::kMaxEntries; 110 111} // namespace 112 113// PersistentTabRestoreService::Delegate --------------------------------------- 114 115// Implements the link between the tab restore service and the session backend. 116class PersistentTabRestoreService::Delegate 117 : public BaseSessionService, 118 public TabRestoreServiceHelper::Observer { 119 public: 120 explicit Delegate(Profile* profile); 121 122 virtual ~Delegate(); 123 124 // BaseSessionService: 125 virtual void Save() OVERRIDE; 126 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; 133 134 void set_tab_restore_service_helper( 135 TabRestoreServiceHelper* tab_restore_service_helper) { 136 tab_restore_service_helper_ = tab_restore_service_helper; 137 } 138 139 void LoadTabsFromLastSession(); 140 141 bool IsLoaded() const; 142 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); 146 147 void Shutdown(); 148 149 // Schedules the commands for a window close. 150 void ScheduleCommandsForWindow(const Window& window); 151 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); 155 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); 161 162 // Creates a tab close command. 163 static SessionCommand* CreateSelectedNavigationInTabCommand( 164 SessionID::id_type tab_id, 165 int32 index, 166 base::Time timestamp); 167 168 // Creates a restore command. 169 static SessionCommand* CreateRestoredEntryCommand( 170 SessionID::id_type entry_id); 171 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); 177 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); 182 183 // Populates |loaded_entries| with Entries from |commands|. 184 void CreateEntriesFromCommands(const std::vector<SessionCommand*>& commands, 185 std::vector<Entry*>* loaded_entries); 186 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); 190 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); 197 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); 202 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(); 207 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); 214 215 private: 216 TabRestoreServiceHelper* tab_restore_service_helper_; 217 218 // The number of entries to write. 219 int entries_to_write_; 220 221 // Number of entries we've written. 222 int entries_written_; 223 224 // Whether we've loaded the last session. 225 int load_state_; 226 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_; 231 232 // Used when loading previous tabs/session and open tabs/session. 233 base::CancelableTaskTracker cancelable_task_tracker_; 234 235 DISALLOW_COPY_AND_ASSIGN(Delegate); 236}; 237 238PersistentTabRestoreService::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) { 245} 246 247PersistentTabRestoreService::Delegate::~Delegate() {} 248 249void 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); 257 } 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)); 274 } 275 entries_written_++; 276 } 277 } 278 if (pending_reset()) 279 entries_written_ = 0; 280 BaseSessionService::Save(); 281} 282 283void 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)); 288 289 entries_to_write_ = 0; 290 291 // Schedule a pending reset so that we nuke the file on next write. 292 set_pending_reset(true); 293 294 // Schedule a command, otherwise if there are no pending commands Save does 295 // nothing. 296 ScheduleCommand(CreateRestoredEntryCommand(1)); 297} 298 299void 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_--; 309 310 ScheduleCommand(CreateRestoredEntryCommand(id)); 311} 312 313void PersistentTabRestoreService::Delegate::OnAddEntry() { 314 // Start the save timer, when it fires we'll generate the commands. 315 StartSaveTimer(); 316 entries_to_write_++; 317} 318 319void PersistentTabRestoreService::Delegate::LoadTabsFromLastSession() { 320 if (load_state_ != NOT_LOADED) 321 return; 322 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; 329 } 330 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; 337 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; 353 } 354#endif 355 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_); 362} 363 364bool PersistentTabRestoreService::Delegate::IsLoaded() const { 365 return !(load_state_ & (NOT_LOADED | LOADING)); 366} 367 368// static 369void 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()); 376 } 377} 378 379void PersistentTabRestoreService::Delegate::Shutdown() { 380 if (backend()) 381 Save(); 382} 383 384void 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--; 395 } 396 } 397 if (valid_tab_count == 0) 398 return; // No tabs to persist. 399 400 ScheduleCommand( 401 CreateWindowCommand(window.id, 402 std::min(real_selected_tab, valid_tab_count - 1), 403 valid_tab_count, 404 window.timestamp)); 405 406 if (!window.app_name.empty()) { 407 ScheduleCommand( 408 CreateSetWindowAppNameCommand(kCommandSetWindowAppName, 409 window.id, 410 window.app_name)); 411 } 412 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); 417 } 418} 419 420void 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()); 426 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++; 435 } 436 } 437 438 // Write the command that identifies the selected tab. 439 ScheduleCommand( 440 CreateSelectedNavigationInTabCommand(tab.id, 441 valid_count_before_selected, 442 tab.timestamp)); 443 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); 450 } 451 452 if (!tab.extension_app_id.empty()) { 453 ScheduleCommand( 454 CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id, 455 tab.extension_app_id)); 456 } 457 458 if (!tab.user_agent_override.empty()) { 459 ScheduleCommand( 460 CreateSetTabUserAgentOverrideCommand(kCommandSetTabUserAgentOverride, 461 tab.id, tab.user_agent_override)); 462 } 463 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])); 471 } 472 } 473} 474 475// static 476SessionCommand* 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(); 489 490 SessionCommand* command = 491 new SessionCommand(kCommandWindow, sizeof(payload)); 492 memcpy(command->contents(), &payload, sizeof(payload)); 493 return command; 494} 495 496// static 497SessionCommand* 498PersistentTabRestoreService::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; 510} 511 512// static 513SessionCommand* 514PersistentTabRestoreService::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; 521} 522 523int 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()); 529 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--; 535 } 536 537 if (selected_index != -1) 538 return selected_index; 539 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++; 545 } 546 547 return (selected_index == max_index) ? -1 : selected_index; 548} 549 550void 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(); 559} 560 561void 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; 566 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; 585 } 586 587 current_tab = NULL; 588 current_window = NULL; 589 590 RestoredEntryPayload payload; 591 if (!command.GetPayload(&payload, sizeof(payload))) 592 return; 593 RemoveEntryByID(payload, &id_to_entry, &(entries.get())); 594 break; 595 } 596 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; 603 } 604 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; 611 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; 619 } 620 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; 625 } 626 627 RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get())); 628 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; 636 } 637 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; 649 } 650 651 if (pending_window_tabs > 0) { 652 if (!current_window) { 653 // We should have created a window already. 654 NOTREACHED(); 655 return; 656 } 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); 668 } 669 current_tab->current_navigation_index = payload.index; 670 break; 671 } 672 673 case kCommandUpdateTabNavigation: { 674 if (!current_tab) { 675 // Should be in a tab when we get this. 676 return; 677 } 678 current_tab->navigations.resize(current_tab->navigations.size() + 1); 679 SessionID::id_type tab_id; 680 if (!RestoreUpdateTabNavigationCommand( 681 command, ¤t_tab->navigations.back(), &tab_id)) { 682 return; 683 } 684 break; 685 } 686 687 case kCommandPinnedState: { 688 if (!current_tab) { 689 // Should be in a tab when we get this. 690 return; 691 } 692 // NOTE: payload doesn't matter. kCommandPinnedState is only written if 693 // tab is pinned. 694 current_tab->pinned = true; 695 break; 696 } 697 698 case kCommandSetWindowAppName: { 699 if (!current_window) { 700 // We should have created a window already. 701 NOTREACHED(); 702 return; 703 } 704 705 SessionID::id_type window_id; 706 std::string app_name; 707 if (!RestoreSetWindowAppNameCommand(command, &window_id, &app_name)) 708 return; 709 710 current_window->app_name.swap(app_name); 711 break; 712 } 713 714 case kCommandSetExtensionAppID: { 715 if (!current_tab) { 716 // Should be in a tab when we get this. 717 return; 718 } 719 SessionID::id_type tab_id; 720 std::string extension_app_id; 721 if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id, 722 &extension_app_id)) { 723 return; 724 } 725 current_tab->extension_app_id.swap(extension_app_id); 726 break; 727 } 728 729 case kCommandSetTabUserAgentOverride: { 730 if (!current_tab) { 731 // Should be in a tab when we get this. 732 return; 733 } 734 SessionID::id_type tab_id; 735 std::string user_agent_override; 736 if (!RestoreSetTabUserAgentOverrideCommand(command, &tab_id, 737 &user_agent_override)) { 738 return; 739 } 740 current_tab->user_agent_override.swap(user_agent_override); 741 break; 742 } 743 744 default: 745 // Unknown type, usually indicates corruption of file. Ignore it. 746 return; 747 } 748 } 749 750 // If there was corruption some of the entries won't be valid. 751 ValidateAndDeleteEmptyEntries(&(entries.get())); 752 753 loaded_entries->swap(entries.get()); 754} 755 756// static 757void PersistentTabRestoreService::Delegate::ValidateAndDeleteEmptyEntries( 758 std::vector<Entry*>* entries) { 759 std::vector<Entry*> valid_entries; 760 std::vector<Entry*> invalid_entries; 761 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); 769 } 770 // NOTE: at this point the entries are ordered with newest at the front. 771 entries->swap(valid_entries); 772 773 // Delete the remaining entries. 774 STLDeleteElements(&invalid_entries); 775} 776 777void 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(); 787} 788 789bool 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(); 802 } 803 } 804 if (window->tabs.empty()) 805 return false; 806 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; 812} 813 814void 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; 819 } 820 821 // We're done loading. 822 load_state_ ^= LOADING; 823 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; 829 } 830 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()); 841 } 842 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); 847 } 848 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(); 852 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(); 857 858 tab_restore_service_helper_->PruneEntries(); 859 tab_restore_service_helper_->NotifyTabsChanged(); 860 861 tab_restore_service_helper_->NotifyLoaded(); 862} 863 864void 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; 876 } 877 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; 892 } 893 } 894 } 895 } 896} 897 898// PersistentTabRestoreService ------------------------------------------------- 899 900PersistentTabRestoreService::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_); 906} 907 908PersistentTabRestoreService::~PersistentTabRestoreService() {} 909 910void PersistentTabRestoreService::AddObserver( 911 TabRestoreServiceObserver* observer) { 912 helper_.AddObserver(observer); 913} 914 915void PersistentTabRestoreService::RemoveObserver( 916 TabRestoreServiceObserver* observer) { 917 helper_.RemoveObserver(observer); 918} 919 920void PersistentTabRestoreService::CreateHistoricalTab( 921 content::WebContents* contents, 922 int index) { 923 helper_.CreateHistoricalTab(contents, index); 924} 925 926void PersistentTabRestoreService::BrowserClosing( 927 TabRestoreServiceDelegate* delegate) { 928 helper_.BrowserClosing(delegate); 929} 930 931void PersistentTabRestoreService::BrowserClosed( 932 TabRestoreServiceDelegate* delegate) { 933 helper_.BrowserClosed(delegate); 934} 935 936void PersistentTabRestoreService::ClearEntries() { 937 helper_.ClearEntries(); 938} 939 940const TabRestoreService::Entries& PersistentTabRestoreService::entries() const { 941 return helper_.entries(); 942} 943 944std::vector<content::WebContents*> 945PersistentTabRestoreService::RestoreMostRecentEntry( 946 TabRestoreServiceDelegate* delegate, 947 chrome::HostDesktopType host_desktop_type) { 948 return helper_.RestoreMostRecentEntry(delegate, host_desktop_type); 949} 950 951TabRestoreService::Tab* PersistentTabRestoreService::RemoveTabEntryById( 952 SessionID::id_type id) { 953 return helper_.RemoveTabEntryById(id); 954} 955 956std::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); 963} 964 965bool PersistentTabRestoreService::IsLoaded() const { 966 return delegate_->IsLoaded(); 967} 968 969void PersistentTabRestoreService::DeleteLastSession() { 970 return delegate_->DeleteLastSession(); 971} 972 973void PersistentTabRestoreService::Shutdown() { 974 return delegate_->Shutdown(); 975} 976 977void PersistentTabRestoreService::LoadTabsFromLastSession() { 978 delegate_->LoadTabsFromLastSession(); 979} 980 981TabRestoreService::Entries* PersistentTabRestoreService::mutable_entries() { 982 return &helper_.entries_; 983} 984 985void PersistentTabRestoreService::PruneEntries() { 986 helper_.PruneEntries(); 987} 988 989KeyedService* TabRestoreServiceFactory::BuildServiceInstanceFor( 990 content::BrowserContext* profile) const { 991 return new PersistentTabRestoreService(static_cast<Profile*>(profile), NULL); 992} 993