tab_restore_service.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/sessions/tab_restore_service.h" 6 7#include <algorithm> 8#include <iterator> 9#include <map> 10 11#include "base/callback.h" 12#include "base/scoped_vector.h" 13#include "base/stl_util-inl.h" 14#include "chrome/browser/browser_list.h" 15#include "chrome/browser/browser_window.h" 16#include "chrome/browser/profile.h" 17#include "chrome/browser/sessions/session_service.h" 18#include "chrome/browser/sessions/session_command.h" 19#include "chrome/browser/sessions/session_types.h" 20#include "chrome/browser/sessions/tab_restore_service_observer.h" 21#include "chrome/browser/tab_contents/navigation_controller.h" 22#include "chrome/browser/tab_contents/navigation_entry.h" 23#include "chrome/browser/tab_contents/tab_contents.h" 24#include "chrome/browser/tabs/tab_strip_model.h" 25#include "chrome/common/extensions/extension.h" 26 27using base::Time; 28 29// TimeFactory----------------------------------------------------------------- 30 31TabRestoreService::TimeFactory::~TimeFactory() {} 32 33// Entry ---------------------------------------------------------------------- 34 35// ID of the next Entry. 36static SessionID::id_type next_entry_id = 1; 37 38TabRestoreService::Entry::Entry() 39 : id(next_entry_id++), 40 type(TAB), 41 from_last_session(false) {} 42 43TabRestoreService::Entry::Entry(Type type) 44 : id(next_entry_id++), 45 type(type), 46 from_last_session(false) {} 47 48TabRestoreService::Entry::~Entry() {} 49 50// TabRestoreService ---------------------------------------------------------- 51 52// static 53const size_t TabRestoreService::kMaxEntries = 10; 54 55// Identifier for commands written to file. 56// The ordering in the file is as follows: 57// . When the user closes a tab a command of type 58// kCommandSelectedNavigationInTab is written identifying the tab and 59// the selected index, then a kCommandPinnedState command if the tab was 60// pinned and kCommandSetExtensionAppID if the tab has an app id. This is 61// followed by any number of kCommandUpdateTabNavigation commands (1 per 62// navigation entry). 63// . When the user closes a window a kCommandSelectedNavigationInTab command 64// is written out and followed by n tab closed sequences (as previoulsy 65// described). 66// . When the user restores an entry a command of type kCommandRestoredEntry 67// is written. 68static const SessionCommand::id_type kCommandUpdateTabNavigation = 1; 69static const SessionCommand::id_type kCommandRestoredEntry = 2; 70static const SessionCommand::id_type kCommandWindow = 3; 71static const SessionCommand::id_type kCommandSelectedNavigationInTab = 4; 72static const SessionCommand::id_type kCommandPinnedState = 5; 73static const SessionCommand::id_type kCommandSetExtensionAppID = 6; 74 75// Number of entries (not commands) before we clobber the file and write 76// everything. 77static const int kEntriesPerReset = 40; 78 79namespace { 80 81// Payload structures. 82 83typedef int32 RestoredEntryPayload; 84 85// Payload used for the start of a window close. This is the old struct that is 86// used for backwards compat when it comes to reading the session files. This 87// struct must be POD, because we memset the contents. 88struct WindowPayload { 89 SessionID::id_type window_id; 90 int32 selected_tab_index; 91 int32 num_tabs; 92}; 93 94// Payload used for the start of a tab close. This is the old struct that is 95// used for backwards compat when it comes to reading the session files. 96struct SelectedNavigationInTabPayload { 97 SessionID::id_type id; 98 int32 index; 99}; 100 101// Payload used for the start of a window close. This struct must be POD, 102// because we memset the contents. 103struct WindowPayload2 : WindowPayload { 104 int64 timestamp; 105}; 106 107// Payload used for the start of a tab close. 108struct SelectedNavigationInTabPayload2 : SelectedNavigationInTabPayload { 109 int64 timestamp; 110}; 111 112// Only written if the tab is pinned. 113typedef bool PinnedStatePayload; 114 115typedef std::map<SessionID::id_type, TabRestoreService::Entry*> IDToEntry; 116 117// If |id_to_entry| contains an entry for |id| the corresponding entry is 118// deleted and removed from both |id_to_entry| and |entries|. This is used 119// when creating entries from the backend file. 120void RemoveEntryByID(SessionID::id_type id, 121 IDToEntry* id_to_entry, 122 std::vector<TabRestoreService::Entry*>* entries) { 123 // Look for the entry in the map. If it is present, erase it from both 124 // collections and return. 125 IDToEntry::iterator i = id_to_entry->find(id); 126 if (i != id_to_entry->end()) { 127 entries->erase(std::find(entries->begin(), entries->end(), i->second)); 128 delete i->second; 129 id_to_entry->erase(i); 130 return; 131 } 132 133 // Otherwise, loop over all items in the map and see if any of the Windows 134 // have Tabs with the |id|. 135 for (IDToEntry::iterator i = id_to_entry->begin(); i != id_to_entry->end(); 136 ++i) { 137 if (i->second->type == TabRestoreService::WINDOW) { 138 TabRestoreService::Window* window = 139 static_cast<TabRestoreService::Window*>(i->second); 140 std::vector<TabRestoreService::Tab>::iterator j = window->tabs.begin(); 141 for ( ; j != window->tabs.end(); ++j) { 142 // If the ID matches one of this window's tabs, remove it from the list. 143 if ((*j).id == id) { 144 window->tabs.erase(j); 145 return; 146 } 147 } 148 } 149 } 150} 151 152} // namespace 153 154TabRestoreService::Tab::Tab() 155 : Entry(TAB), 156 current_navigation_index(-1), 157 browser_id(0), 158 tabstrip_index(-1), 159 pinned(false) { 160} 161 162TabRestoreService::Tab::~Tab() { 163} 164 165TabRestoreService::Window::Window() : Entry(WINDOW), selected_tab_index(-1) { 166} 167 168TabRestoreService::Window::~Window() { 169} 170 171TabRestoreService::TabRestoreService(Profile* profile, 172 TabRestoreService::TimeFactory* time_factory) 173 : BaseSessionService(BaseSessionService::TAB_RESTORE, profile, 174 FilePath()), 175 load_state_(NOT_LOADED), 176 restoring_(false), 177 reached_max_(false), 178 entries_to_write_(0), 179 entries_written_(0), 180 time_factory_(time_factory) { 181} 182 183TabRestoreService::~TabRestoreService() { 184 if (backend()) 185 Save(); 186 187 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_, 188 TabRestoreServiceDestroyed(this)); 189 STLDeleteElements(&entries_); 190 STLDeleteElements(&staging_entries_); 191 time_factory_ = NULL; 192} 193 194void TabRestoreService::AddObserver(TabRestoreServiceObserver* observer) { 195 observer_list_.AddObserver(observer); 196} 197 198void TabRestoreService::RemoveObserver(TabRestoreServiceObserver* observer) { 199 observer_list_.RemoveObserver(observer); 200} 201 202void TabRestoreService::CreateHistoricalTab(NavigationController* tab) { 203 if (restoring_) 204 return; 205 206 Browser* browser = Browser::GetBrowserForController(tab, NULL); 207 if (closing_browsers_.find(browser) != closing_browsers_.end()) 208 return; 209 210 scoped_ptr<Tab> local_tab(new Tab()); 211 PopulateTab(local_tab.get(), browser, tab); 212 if (local_tab->navigations.empty()) 213 return; 214 215 AddEntry(local_tab.release(), true, true); 216} 217 218void TabRestoreService::BrowserClosing(Browser* browser) { 219 if (browser->type() != Browser::TYPE_NORMAL || 220 browser->tab_count() == 0) 221 return; 222 223 closing_browsers_.insert(browser); 224 225 Window* window = new Window(); 226 window->selected_tab_index = browser->selected_index(); 227 window->timestamp = TimeNow(); 228 // Don't use std::vector::resize() because it will push copies of an empty tab 229 // into the vector, which will give all tabs in a window the same ID. 230 for (int i = 0; i < browser->tab_count(); ++i) { 231 window->tabs.push_back(Tab()); 232 } 233 size_t entry_index = 0; 234 for (int tab_index = 0; tab_index < browser->tab_count(); ++tab_index) { 235 PopulateTab(&(window->tabs[entry_index]), 236 browser, 237 &browser->GetTabContentsAt(tab_index)->controller()); 238 if (window->tabs[entry_index].navigations.empty()) { 239 window->tabs.erase(window->tabs.begin() + entry_index); 240 } else { 241 window->tabs[entry_index].browser_id = browser->session_id().id(); 242 entry_index++; 243 } 244 } 245 if (window->tabs.empty()) { 246 delete window; 247 window = NULL; 248 } else { 249 window->selected_tab_index = 250 std::min(static_cast<int>(window->tabs.size() - 1), 251 window->selected_tab_index); 252 AddEntry(window, true, true); 253 } 254} 255 256void TabRestoreService::BrowserClosed(Browser* browser) { 257 closing_browsers_.erase(browser); 258} 259 260void TabRestoreService::ClearEntries() { 261 // Mark all the tabs as closed so that we don't attempt to restore them. 262 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) 263 ScheduleCommand(CreateRestoredEntryCommand((*i)->id)); 264 265 entries_to_write_ = 0; 266 267 // Schedule a pending reset so that we nuke the file on next write. 268 set_pending_reset(true); 269 270 // Schedule a command, otherwise if there are no pending commands Save does 271 // nothing. 272 ScheduleCommand(CreateRestoredEntryCommand(1)); 273 274 STLDeleteElements(&entries_); 275 NotifyTabsChanged(); 276} 277 278const TabRestoreService::Entries& TabRestoreService::entries() const { 279 return entries_; 280} 281 282void TabRestoreService::RestoreMostRecentEntry(Browser* browser) { 283 if (entries_.empty()) 284 return; 285 286 RestoreEntryById(browser, entries_.front()->id, false); 287} 288 289void TabRestoreService::RestoreEntryById(Browser* browser, 290 SessionID::id_type id, 291 bool replace_existing_tab) { 292 Entries::iterator i = GetEntryIteratorById(id); 293 if (i == entries_.end()) { 294 // Don't hoark here, we allow an invalid id. 295 return; 296 } 297 298 size_t index = 0; 299 for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end(); 300 ++j, ++index) {} 301 if (static_cast<int>(index) < entries_to_write_) 302 entries_to_write_--; 303 304 ScheduleCommand(CreateRestoredEntryCommand(id)); 305 306 restoring_ = true; 307 Entry* entry = *i; 308 309 // If the entry's ID does not match the ID that is being restored, then the 310 // entry is a window from which a single tab will be restored. 311 bool restoring_tab_in_window = entry->id != id; 312 313 if (!restoring_tab_in_window) { 314 entries_.erase(i); 315 i = entries_.end(); 316 } 317 318 // |browser| will be NULL in cases where one isn't already available (eg, 319 // when invoked on Mac OS X with no windows open). In this case, create a 320 // new browser into which we restore the tabs. 321 if (entry->type == TAB) { 322 Tab* tab = static_cast<Tab*>(entry); 323 browser = RestoreTab(*tab, browser, replace_existing_tab); 324 browser->window()->Show(); 325 } else if (entry->type == WINDOW) { 326 Browser* current_browser = browser; 327 Window* window = static_cast<Window*>(entry); 328 329 // When restoring a window, either the entire window can be restored, or a 330 // single tab within it. If the entry's ID matches the one to restore, then 331 // the entire window will be restored. 332 if (!restoring_tab_in_window) { 333 browser = Browser::Create(profile()); 334 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) { 335 const Tab& tab = window->tabs[tab_i]; 336 TabContents* restored_tab = 337 browser->AddRestoredTab(tab.navigations, browser->tab_count(), 338 tab.current_navigation_index, 339 tab.extension_app_id, 340 (static_cast<int>(tab_i) == 341 window->selected_tab_index), 342 tab.pinned, tab.from_last_session, 343 tab.session_storage_namespace); 344 if (restored_tab) 345 restored_tab->controller().LoadIfNecessary(); 346 } 347 // All the window's tabs had the same former browser_id. 348 if (window->tabs[0].has_browser()) { 349 UpdateTabBrowserIDs(window->tabs[0].browser_id, 350 browser->session_id().id()); 351 } 352 } else { 353 // Restore a single tab from the window. Find the tab that matches the ID 354 // in the window and restore it. 355 for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); 356 tab_i != window->tabs.end(); ++tab_i) { 357 const Tab& tab = *tab_i; 358 if (tab.id == id) { 359 browser = RestoreTab(tab, browser, replace_existing_tab); 360 window->tabs.erase(tab_i); 361 // If restoring the tab leaves the window with nothing else, delete it 362 // as well. 363 if (!window->tabs.size()) { 364 entries_.erase(i); 365 delete entry; 366 } else { 367 // Update the browser ID of the rest of the tabs in the window so if 368 // any one is restored, it goes into the same window as the tab 369 // being restored now. 370 UpdateTabBrowserIDs(tab.browser_id, 371 browser->session_id().id()); 372 for (std::vector<Tab>::iterator tab_j = window->tabs.begin(); 373 tab_j != window->tabs.end(); ++tab_j) { 374 (*tab_j).browser_id = browser->session_id().id(); 375 } 376 } 377 break; 378 } 379 } 380 } 381 browser->window()->Show(); 382 383 if (replace_existing_tab && current_browser && 384 current_browser->GetSelectedTabContents()) { 385 current_browser->CloseTab(); 386 } 387 } else { 388 NOTREACHED(); 389 } 390 391 if (!restoring_tab_in_window) { 392 delete entry; 393 } 394 395 restoring_ = false; 396 NotifyTabsChanged(); 397} 398 399void TabRestoreService::LoadTabsFromLastSession() { 400 if (load_state_ != NOT_LOADED || reached_max_) 401 return; 402 403 load_state_ = LOADING; 404 405 if (!profile()->restored_last_session() && 406 !profile()->DidLastSessionExitCleanly() && 407 profile()->GetSessionService()) { 408 // The previous session crashed and wasn't restored. Load the tabs/windows 409 // that were open at the point of crash from the session service. 410 profile()->GetSessionService()->GetLastSession( 411 &load_consumer_, 412 NewCallback(this, &TabRestoreService::OnGotPreviousSession)); 413 } else { 414 load_state_ |= LOADED_LAST_SESSION; 415 } 416 417 // Request the tabs closed in the last session. If the last session crashed, 418 // this won't contain the tabs/window that were open at the point of the 419 // crash (the call to GetLastSession above requests those). 420 ScheduleGetLastSessionCommands( 421 new InternalGetCommandsRequest( 422 NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)), 423 &load_consumer_); 424} 425 426void TabRestoreService::Save() { 427 int to_write_count = std::min(entries_to_write_, 428 static_cast<int>(entries_.size())); 429 entries_to_write_ = 0; 430 if (entries_written_ + to_write_count > kEntriesPerReset) { 431 to_write_count = entries_.size(); 432 set_pending_reset(true); 433 } 434 if (to_write_count) { 435 // Write the to_write_count most recently added entries out. The most 436 // recently added entry is at the front, so we use a reverse iterator to 437 // write in the order the entries were added. 438 Entries::reverse_iterator i = entries_.rbegin(); 439 DCHECK(static_cast<size_t>(to_write_count) <= entries_.size()); 440 std::advance(i, entries_.size() - static_cast<int>(to_write_count)); 441 for (; i != entries_.rend(); ++i) { 442 Entry* entry = *i; 443 if (entry->type == TAB) { 444 Tab* tab = static_cast<Tab*>(entry); 445 int selected_index = GetSelectedNavigationIndexToPersist(*tab); 446 if (selected_index != -1) 447 ScheduleCommandsForTab(*tab, selected_index); 448 } else { 449 ScheduleCommandsForWindow(*static_cast<Window*>(entry)); 450 } 451 entries_written_++; 452 } 453 } 454 if (pending_reset()) 455 entries_written_ = 0; 456 BaseSessionService::Save(); 457} 458 459void TabRestoreService::PopulateTab(Tab* tab, 460 Browser* browser, 461 NavigationController* controller) { 462 const int pending_index = controller->pending_entry_index(); 463 int entry_count = controller->entry_count(); 464 if (entry_count == 0 && pending_index == 0) 465 entry_count++; 466 tab->navigations.resize(static_cast<int>(entry_count)); 467 for (int i = 0; i < entry_count; ++i) { 468 NavigationEntry* entry = (i == pending_index) ? 469 controller->pending_entry() : controller->GetEntryAtIndex(i); 470 tab->navigations[i].SetFromNavigationEntry(*entry); 471 } 472 tab->timestamp = TimeNow(); 473 tab->current_navigation_index = controller->GetCurrentEntryIndex(); 474 if (tab->current_navigation_index == -1 && entry_count > 0) 475 tab->current_navigation_index = 0; 476 477 Extension* extension = controller->tab_contents()->extension_app(); 478 if (extension) 479 tab->extension_app_id = extension->id(); 480 481 tab->session_storage_namespace = controller->session_storage_namespace(); 482 483 // Browser may be NULL during unit tests. 484 if (browser) { 485 tab->browser_id = browser->session_id().id(); 486 tab->tabstrip_index = 487 browser->tabstrip_model()->GetIndexOfController(controller); 488 tab->pinned = browser->tabstrip_model()->IsTabPinned(tab->tabstrip_index); 489 } 490} 491 492void TabRestoreService::NotifyTabsChanged() { 493 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_, 494 TabRestoreServiceChanged(this)); 495} 496 497void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) { 498 if (to_front) 499 entries_.push_front(entry); 500 else 501 entries_.push_back(entry); 502 if (notify) 503 PruneAndNotify(); 504 // Start the save timer, when it fires we'll generate the commands. 505 StartSaveTimer(); 506 entries_to_write_++; 507} 508 509void TabRestoreService::PruneAndNotify() { 510 while (entries_.size() > kMaxEntries) { 511 delete entries_.back(); 512 entries_.pop_back(); 513 reached_max_ = true; 514 } 515 516 NotifyTabsChanged(); 517} 518 519TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById( 520 SessionID::id_type id) { 521 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { 522 if ((*i)->id == id) 523 return i; 524 525 // For Window entries, see if the ID matches a tab. If so, report the window 526 // as the Entry. 527 if ((*i)->type == WINDOW) { 528 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs; 529 for (std::vector<Tab>::iterator j = tabs.begin(); 530 j != tabs.end(); ++j) { 531 if ((*j).id == id) { 532 return i; 533 } 534 } 535 } 536 } 537 return entries_.end(); 538} 539 540void TabRestoreService::ScheduleCommandsForWindow(const Window& window) { 541 DCHECK(!window.tabs.empty()); 542 int selected_tab = window.selected_tab_index; 543 int valid_tab_count = 0; 544 int real_selected_tab = selected_tab; 545 for (size_t i = 0; i < window.tabs.size(); ++i) { 546 if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) { 547 valid_tab_count++; 548 } else if (static_cast<int>(i) < selected_tab) { 549 real_selected_tab--; 550 } 551 } 552 if (valid_tab_count == 0) 553 return; // No tabs to persist. 554 555 ScheduleCommand( 556 CreateWindowCommand(window.id, 557 std::min(real_selected_tab, valid_tab_count - 1), 558 valid_tab_count, 559 window.timestamp)); 560 561 for (size_t i = 0; i < window.tabs.size(); ++i) { 562 int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]); 563 if (selected_index != -1) 564 ScheduleCommandsForTab(window.tabs[i], selected_index); 565 } 566} 567 568void TabRestoreService::ScheduleCommandsForTab(const Tab& tab, 569 int selected_index) { 570 const std::vector<TabNavigation>& navigations = tab.navigations; 571 int max_index = static_cast<int>(navigations.size()); 572 573 // Determine the first navigation we'll persist. 574 int valid_count_before_selected = 0; 575 int first_index_to_persist = selected_index; 576 for (int i = selected_index - 1; i >= 0 && 577 valid_count_before_selected < max_persist_navigation_count; --i) { 578 if (ShouldTrackEntry(navigations[i])) { 579 first_index_to_persist = i; 580 valid_count_before_selected++; 581 } 582 } 583 584 // Write the command that identifies the selected tab. 585 ScheduleCommand( 586 CreateSelectedNavigationInTabCommand(tab.id, 587 valid_count_before_selected, 588 tab.timestamp)); 589 590 if (tab.pinned) { 591 PinnedStatePayload payload = true; 592 SessionCommand* command = 593 new SessionCommand(kCommandPinnedState, sizeof(payload)); 594 memcpy(command->contents(), &payload, sizeof(payload)); 595 ScheduleCommand(command); 596 } 597 598 if (!tab.extension_app_id.empty()) { 599 ScheduleCommand( 600 CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id, 601 tab.extension_app_id)); 602 } 603 604 // Then write the navigations. 605 for (int i = first_index_to_persist, wrote_count = 0; 606 i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) { 607 if (ShouldTrackEntry(navigations[i])) { 608 // Creating a NavigationEntry isn't the most efficient way to go about 609 // this, but it simplifies the code and makes it less error prone as we 610 // add new data to NavigationEntry. 611 scoped_ptr<NavigationEntry> entry( 612 navigations[i].ToNavigationEntry(wrote_count, profile())); 613 ScheduleCommand( 614 CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id, 615 wrote_count++, *entry)); 616 } 617 } 618} 619 620SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id, 621 int selected_tab_index, 622 int num_tabs, 623 Time timestamp) { 624 WindowPayload2 payload; 625 // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of 626 // uninitialized memory in the struct. 627 memset(&payload, 0, sizeof(payload)); 628 payload.window_id = id; 629 payload.selected_tab_index = selected_tab_index; 630 payload.num_tabs = num_tabs; 631 payload.timestamp = timestamp.ToInternalValue(); 632 633 SessionCommand* command = 634 new SessionCommand(kCommandWindow, sizeof(payload)); 635 memcpy(command->contents(), &payload, sizeof(payload)); 636 return command; 637} 638 639SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand( 640 SessionID::id_type tab_id, 641 int32 index, 642 Time timestamp) { 643 SelectedNavigationInTabPayload2 payload; 644 payload.id = tab_id; 645 payload.index = index; 646 payload.timestamp = timestamp.ToInternalValue(); 647 SessionCommand* command = 648 new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload)); 649 memcpy(command->contents(), &payload, sizeof(payload)); 650 return command; 651} 652 653SessionCommand* TabRestoreService::CreateRestoredEntryCommand( 654 SessionID::id_type entry_id) { 655 RestoredEntryPayload payload = entry_id; 656 SessionCommand* command = 657 new SessionCommand(kCommandRestoredEntry, sizeof(payload)); 658 memcpy(command->contents(), &payload, sizeof(payload)); 659 return command; 660} 661 662int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) { 663 const std::vector<TabNavigation>& navigations = tab.navigations; 664 int selected_index = tab.current_navigation_index; 665 int max_index = static_cast<int>(navigations.size()); 666 667 // Find the first navigation to persist. We won't persist the selected 668 // navigation if ShouldTrackEntry returns false. 669 while (selected_index >= 0 && 670 !ShouldTrackEntry(navigations[selected_index])) { 671 selected_index--; 672 } 673 674 if (selected_index != -1) 675 return selected_index; 676 677 // Couldn't find a navigation to persist going back, go forward. 678 selected_index = tab.current_navigation_index + 1; 679 while (selected_index < max_index && 680 !ShouldTrackEntry(navigations[selected_index])) { 681 selected_index++; 682 } 683 684 return (selected_index == max_index) ? -1 : selected_index; 685} 686 687void TabRestoreService::OnGotLastSessionCommands( 688 Handle handle, 689 scoped_refptr<InternalGetCommandsRequest> request) { 690 std::vector<Entry*> entries; 691 CreateEntriesFromCommands(request, &entries); 692 // Closed tabs always go to the end. 693 staging_entries_.insert(staging_entries_.end(), entries.begin(), 694 entries.end()); 695 load_state_ |= LOADED_LAST_TABS; 696 LoadStateChanged(); 697} 698 699void TabRestoreService::CreateEntriesFromCommands( 700 scoped_refptr<InternalGetCommandsRequest> request, 701 std::vector<Entry*>* loaded_entries) { 702 if (request->canceled() || entries_.size() == kMaxEntries) 703 return; 704 705 std::vector<SessionCommand*>& commands = request->commands; 706 // Iterate through the commands populating entries and id_to_entry. 707 ScopedVector<Entry> entries; 708 IDToEntry id_to_entry; 709 // If non-null we're processing the navigations of this tab. 710 Tab* current_tab = NULL; 711 // If non-null we're processing the tabs of this window. 712 Window* current_window = NULL; 713 // If > 0, we've gotten a window command but not all the tabs yet. 714 int pending_window_tabs = 0; 715 for (std::vector<SessionCommand*>::const_iterator i = commands.begin(); 716 i != commands.end(); ++i) { 717 const SessionCommand& command = *(*i); 718 switch (command.id()) { 719 case kCommandRestoredEntry: { 720 if (pending_window_tabs > 0) { 721 // Should never receive a restored command while waiting for all the 722 // tabs in a window. 723 return; 724 } 725 726 current_tab = NULL; 727 current_window = NULL; 728 729 RestoredEntryPayload payload; 730 if (!command.GetPayload(&payload, sizeof(payload))) 731 return; 732 RemoveEntryByID(payload, &id_to_entry, &(entries.get())); 733 break; 734 } 735 736 case kCommandWindow: { 737 WindowPayload2 payload; 738 if (pending_window_tabs > 0) { 739 // Should never receive a window command while waiting for all the 740 // tabs in a window. 741 return; 742 } 743 744 // Try the new payload first 745 if (!command.GetPayload(&payload, sizeof(payload))) { 746 // then the old payload 747 WindowPayload old_payload; 748 if (!command.GetPayload(&old_payload, sizeof(old_payload))) 749 return; 750 751 // Copy the old payload data to the new payload. 752 payload.window_id = old_payload.window_id; 753 payload.selected_tab_index = old_payload.selected_tab_index; 754 payload.num_tabs = old_payload.num_tabs; 755 // Since we don't have a time use time 0 which is used to mark as an 756 // unknown timestamp. 757 payload.timestamp = 0; 758 } 759 760 pending_window_tabs = payload.num_tabs; 761 if (pending_window_tabs <= 0) { 762 // Should always have at least 1 tab. Likely indicates corruption. 763 return; 764 } 765 766 RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get())); 767 768 current_window = new Window(); 769 current_window->selected_tab_index = payload.selected_tab_index; 770 current_window->timestamp = Time::FromInternalValue(payload.timestamp); 771 entries->push_back(current_window); 772 id_to_entry[payload.window_id] = current_window; 773 break; 774 } 775 776 case kCommandSelectedNavigationInTab: { 777 SelectedNavigationInTabPayload2 payload; 778 if (!command.GetPayload(&payload, sizeof(payload))) { 779 SelectedNavigationInTabPayload old_payload; 780 if (!command.GetPayload(&old_payload, sizeof(old_payload))) 781 return; 782 payload.id = old_payload.id; 783 payload.index = old_payload.index; 784 // Since we don't have a time use time 0 which is used to mark as an 785 // unknown timestamp. 786 payload.timestamp = 0; 787 } 788 789 if (pending_window_tabs > 0) { 790 if (!current_window) { 791 // We should have created a window already. 792 NOTREACHED(); 793 return; 794 } 795 current_window->tabs.resize(current_window->tabs.size() + 1); 796 current_tab = &(current_window->tabs.back()); 797 if (--pending_window_tabs == 0) 798 current_window = NULL; 799 } else { 800 RemoveEntryByID(payload.id, &id_to_entry, &(entries.get())); 801 current_tab = new Tab(); 802 id_to_entry[payload.id] = current_tab; 803 current_tab->timestamp = Time::FromInternalValue(payload.timestamp); 804 entries->push_back(current_tab); 805 } 806 current_tab->current_navigation_index = payload.index; 807 break; 808 } 809 810 case kCommandUpdateTabNavigation: { 811 if (!current_tab) { 812 // Should be in a tab when we get this. 813 return; 814 } 815 current_tab->navigations.resize(current_tab->navigations.size() + 1); 816 SessionID::id_type tab_id; 817 if (!RestoreUpdateTabNavigationCommand( 818 command, ¤t_tab->navigations.back(), &tab_id)) { 819 return; 820 } 821 break; 822 } 823 824 case kCommandPinnedState: { 825 if (!current_tab) { 826 // Should be in a tab when we get this. 827 return; 828 } 829 // NOTE: payload doesn't matter. kCommandPinnedState is only written if 830 // tab is pinned. 831 current_tab->pinned = true; 832 break; 833 } 834 835 case kCommandSetExtensionAppID: { 836 if (!current_tab) { 837 // Should be in a tab when we get this. 838 return; 839 } 840 SessionID::id_type tab_id; 841 std::string extension_app_id; 842 if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id, 843 &extension_app_id)) { 844 return; 845 } 846 current_tab->extension_app_id.swap(extension_app_id); 847 break; 848 } 849 850 default: 851 // Unknown type, usually indicates corruption of file. Ignore it. 852 return; 853 } 854 } 855 856 // If there was corruption some of the entries won't be valid. Prune any 857 // entries with no navigations. 858 ValidateAndDeleteEmptyEntries(&(entries.get())); 859 860 loaded_entries->swap(entries.get()); 861} 862 863Browser* TabRestoreService::RestoreTab(const Tab& tab, 864 Browser* browser, 865 bool replace_existing_tab) { 866 // |browser| will be NULL in cases where one isn't already available (eg, 867 // when invoked on Mac OS X with no windows open). In this case, create a 868 // new browser into which we restore the tabs. 869 if (replace_existing_tab && browser) { 870 browser->ReplaceRestoredTab(tab.navigations, 871 tab.current_navigation_index, 872 tab.from_last_session, 873 tab.extension_app_id, 874 tab.session_storage_namespace); 875 } else { 876 if (tab.has_browser()) 877 browser = BrowserList::FindBrowserWithID(tab.browser_id); 878 879 int tab_index = -1; 880 if (browser) { 881 tab_index = tab.tabstrip_index; 882 } else { 883 browser = Browser::Create(profile()); 884 if (tab.has_browser()) { 885 UpdateTabBrowserIDs(tab.browser_id, browser->session_id().id()); 886 } 887 } 888 889 if (tab_index < 0 || tab_index > browser->tab_count()) { 890 tab_index = browser->tab_count(); 891 } 892 893 browser->AddRestoredTab(tab.navigations, 894 tab_index, 895 tab.current_navigation_index, 896 tab.extension_app_id, 897 true, tab.pinned, tab.from_last_session, 898 tab.session_storage_namespace); 899 } 900 return browser; 901} 902 903 904bool TabRestoreService::ValidateTab(Tab* tab) { 905 if (tab->navigations.empty()) 906 return false; 907 908 tab->current_navigation_index = 909 std::max(0, std::min(tab->current_navigation_index, 910 static_cast<int>(tab->navigations.size()) - 1)); 911 return true; 912} 913 914void TabRestoreService::ValidateAndDeleteEmptyEntries( 915 std::vector<Entry*>* entries) { 916 std::vector<Entry*> valid_entries; 917 std::vector<Entry*> invalid_entries; 918 919 size_t max_valid = kMaxEntries - entries_.size(); 920 // Iterate from the back so that we keep the most recently closed entries. 921 for (std::vector<Entry*>::reverse_iterator i = entries->rbegin(); 922 i != entries->rend(); ++i) { 923 bool valid_entry = false; 924 if (valid_entries.size() != max_valid) { 925 if ((*i)->type == TAB) { 926 Tab* tab = static_cast<Tab*>(*i); 927 if (ValidateTab(tab)) 928 valid_entry = true; 929 } else { 930 Window* window = static_cast<Window*>(*i); 931 for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); 932 tab_i != window->tabs.end();) { 933 if (!ValidateTab(&(*tab_i))) 934 tab_i = window->tabs.erase(tab_i); 935 else 936 ++tab_i; 937 } 938 if (!window->tabs.empty()) { 939 window->selected_tab_index = 940 std::max(0, std::min(window->selected_tab_index, 941 static_cast<int>(window->tabs.size() - 1))); 942 valid_entry = true; 943 } 944 } 945 } 946 if (valid_entry) 947 valid_entries.push_back(*i); 948 else 949 invalid_entries.push_back(*i); 950 } 951 // NOTE: at this point the entries are ordered with newest at the front. 952 entries->swap(valid_entries); 953 954 // Delete the remaining entries. 955 STLDeleteElements(&invalid_entries); 956} 957 958void TabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id, 959 SessionID::id_type new_id) { 960 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { 961 Entry* entry = *i; 962 if (entry->type == TAB) { 963 Tab* tab = static_cast<Tab*>(entry); 964 if (tab->browser_id == old_id) 965 tab->browser_id = new_id; 966 } 967 } 968} 969 970void TabRestoreService::OnGotPreviousSession( 971 Handle handle, 972 std::vector<SessionWindow*>* windows) { 973 std::vector<Entry*> entries; 974 CreateEntriesFromWindows(windows, &entries); 975 // Previous session tabs go first. 976 staging_entries_.insert(staging_entries_.begin(), entries.begin(), 977 entries.end()); 978 load_state_ |= LOADED_LAST_SESSION; 979 LoadStateChanged(); 980} 981 982void TabRestoreService::CreateEntriesFromWindows( 983 std::vector<SessionWindow*>* windows, 984 std::vector<Entry*>* entries) { 985 for (size_t i = 0; i < windows->size(); ++i) { 986 scoped_ptr<Window> window(new Window()); 987 if (ConvertSessionWindowToWindow((*windows)[i], window.get())) 988 entries->push_back(window.release()); 989 } 990} 991 992bool TabRestoreService::ConvertSessionWindowToWindow( 993 SessionWindow* session_window, 994 Window* window) { 995 for (size_t i = 0; i < session_window->tabs.size(); ++i) { 996 if (!session_window->tabs[i]->navigations.empty()) { 997 window->tabs.resize(window->tabs.size() + 1); 998 Tab& tab = window->tabs.back(); 999 tab.pinned = session_window->tabs[i]->pinned; 1000 tab.navigations.swap(session_window->tabs[i]->navigations); 1001 tab.current_navigation_index = 1002 session_window->tabs[i]->current_navigation_index; 1003 tab.extension_app_id = session_window->tabs[i]->extension_app_id; 1004 tab.timestamp = Time(); 1005 } 1006 } 1007 if (window->tabs.empty()) 1008 return false; 1009 1010 window->selected_tab_index = 1011 std::min(session_window->selected_tab_index, 1012 static_cast<int>(window->tabs.size() - 1)); 1013 window->timestamp = Time(); 1014 return true; 1015} 1016 1017void TabRestoreService::LoadStateChanged() { 1018 if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) != 1019 (LOADED_LAST_TABS | LOADED_LAST_SESSION)) { 1020 // Still waiting on previous session or previous tabs. 1021 return; 1022 } 1023 1024 // We're done loading. 1025 load_state_ ^= LOADING; 1026 1027 if (staging_entries_.empty() || reached_max_) { 1028 STLDeleteElements(&staging_entries_); 1029 return; 1030 } 1031 1032 if (staging_entries_.size() + entries_.size() > kMaxEntries) { 1033 // If we add all the staged entries we'll end up with more than 1034 // kMaxEntries. Delete entries such that we only end up with 1035 // at most kMaxEntries. 1036 DCHECK(entries_.size() < kMaxEntries); 1037 STLDeleteContainerPointers( 1038 staging_entries_.begin() + (kMaxEntries - entries_.size()), 1039 staging_entries_.end()); 1040 staging_entries_.erase( 1041 staging_entries_.begin() + (kMaxEntries - entries_.size()), 1042 staging_entries_.end()); 1043 } 1044 1045 // And add them. 1046 for (size_t i = 0; i < staging_entries_.size(); ++i) { 1047 staging_entries_[i]->from_last_session = true; 1048 AddEntry(staging_entries_[i], false, false); 1049 } 1050 1051 // AddEntry takes ownership of the entry, need to clear out entries so that 1052 // it doesn't delete them. 1053 staging_entries_.clear(); 1054 1055 // Make it so we rewrite all the tabs. We need to do this otherwise we won't 1056 // correctly write out the entries when Save is invoked (Save starts from 1057 // the front, not the end and we just added the entries to the end). 1058 entries_to_write_ = staging_entries_.size(); 1059 1060 PruneAndNotify(); 1061} 1062 1063Time TabRestoreService::TimeNow() const { 1064 return time_factory_ ? time_factory_->TimeNow() : Time::Now(); 1065} 1066