tab_restore_service.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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/profiles/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 scoped_ptr<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.size() == 1) { 246 // Short-circuit creating a Window if only 1 tab was present. This fixes 247 // http://crbug.com/56744. Copy the Tab because it's owned by an object on 248 // the stack. 249 AddEntry(new Tab(window->tabs[0]), true, true); 250 } else if (!window->tabs.empty()) { 251 window->selected_tab_index = 252 std::min(static_cast<int>(window->tabs.size() - 1), 253 window->selected_tab_index); 254 AddEntry(window.release(), true, true); 255 } 256} 257 258void TabRestoreService::BrowserClosed(Browser* browser) { 259 closing_browsers_.erase(browser); 260} 261 262void TabRestoreService::ClearEntries() { 263 // Mark all the tabs as closed so that we don't attempt to restore them. 264 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) 265 ScheduleCommand(CreateRestoredEntryCommand((*i)->id)); 266 267 entries_to_write_ = 0; 268 269 // Schedule a pending reset so that we nuke the file on next write. 270 set_pending_reset(true); 271 272 // Schedule a command, otherwise if there are no pending commands Save does 273 // nothing. 274 ScheduleCommand(CreateRestoredEntryCommand(1)); 275 276 STLDeleteElements(&entries_); 277 NotifyTabsChanged(); 278} 279 280const TabRestoreService::Entries& TabRestoreService::entries() const { 281 return entries_; 282} 283 284void TabRestoreService::RestoreMostRecentEntry(Browser* browser) { 285 if (entries_.empty()) 286 return; 287 288 RestoreEntryById(browser, entries_.front()->id, false); 289} 290 291void TabRestoreService::RestoreEntryById(Browser* browser, 292 SessionID::id_type id, 293 bool replace_existing_tab) { 294 Entries::iterator i = GetEntryIteratorById(id); 295 if (i == entries_.end()) { 296 // Don't hoark here, we allow an invalid id. 297 return; 298 } 299 300 size_t index = 0; 301 for (Entries::iterator j = entries_.begin(); j != i && j != entries_.end(); 302 ++j, ++index) {} 303 if (static_cast<int>(index) < entries_to_write_) 304 entries_to_write_--; 305 306 ScheduleCommand(CreateRestoredEntryCommand(id)); 307 308 restoring_ = true; 309 Entry* entry = *i; 310 311 // If the entry's ID does not match the ID that is being restored, then the 312 // entry is a window from which a single tab will be restored. 313 bool restoring_tab_in_window = entry->id != id; 314 315 if (!restoring_tab_in_window) { 316 entries_.erase(i); 317 i = entries_.end(); 318 } 319 320 // |browser| will be NULL in cases where one isn't already available (eg, 321 // when invoked on Mac OS X with no windows open). In this case, create a 322 // new browser into which we restore the tabs. 323 if (entry->type == TAB) { 324 Tab* tab = static_cast<Tab*>(entry); 325 browser = RestoreTab(*tab, browser, replace_existing_tab); 326 browser->window()->Show(); 327 } else if (entry->type == WINDOW) { 328 Browser* current_browser = browser; 329 Window* window = static_cast<Window*>(entry); 330 331 // When restoring a window, either the entire window can be restored, or a 332 // single tab within it. If the entry's ID matches the one to restore, then 333 // the entire window will be restored. 334 if (!restoring_tab_in_window) { 335 browser = Browser::Create(profile()); 336 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) { 337 const Tab& tab = window->tabs[tab_i]; 338 TabContents* restored_tab = 339 browser->AddRestoredTab(tab.navigations, browser->tab_count(), 340 tab.current_navigation_index, 341 tab.extension_app_id, 342 (static_cast<int>(tab_i) == 343 window->selected_tab_index), 344 tab.pinned, tab.from_last_session, 345 tab.session_storage_namespace); 346 if (restored_tab) 347 restored_tab->controller().LoadIfNecessary(); 348 } 349 // All the window's tabs had the same former browser_id. 350 if (window->tabs[0].has_browser()) { 351 UpdateTabBrowserIDs(window->tabs[0].browser_id, 352 browser->session_id().id()); 353 } 354 } else { 355 // Restore a single tab from the window. Find the tab that matches the ID 356 // in the window and restore it. 357 for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); 358 tab_i != window->tabs.end(); ++tab_i) { 359 const Tab& tab = *tab_i; 360 if (tab.id == id) { 361 browser = RestoreTab(tab, browser, replace_existing_tab); 362 window->tabs.erase(tab_i); 363 // If restoring the tab leaves the window with nothing else, delete it 364 // as well. 365 if (!window->tabs.size()) { 366 entries_.erase(i); 367 delete entry; 368 } else { 369 // Update the browser ID of the rest of the tabs in the window so if 370 // any one is restored, it goes into the same window as the tab 371 // being restored now. 372 UpdateTabBrowserIDs(tab.browser_id, 373 browser->session_id().id()); 374 for (std::vector<Tab>::iterator tab_j = window->tabs.begin(); 375 tab_j != window->tabs.end(); ++tab_j) { 376 (*tab_j).browser_id = browser->session_id().id(); 377 } 378 } 379 break; 380 } 381 } 382 } 383 browser->window()->Show(); 384 385 if (replace_existing_tab && current_browser && 386 current_browser->GetSelectedTabContents()) { 387 current_browser->CloseTab(); 388 } 389 } else { 390 NOTREACHED(); 391 } 392 393 if (!restoring_tab_in_window) { 394 delete entry; 395 } 396 397 restoring_ = false; 398 NotifyTabsChanged(); 399} 400 401void TabRestoreService::LoadTabsFromLastSession() { 402 if (load_state_ != NOT_LOADED || reached_max_) 403 return; 404 405 load_state_ = LOADING; 406 407 if (!profile()->restored_last_session() && 408 !profile()->DidLastSessionExitCleanly() && 409 profile()->GetSessionService()) { 410 // The previous session crashed and wasn't restored. Load the tabs/windows 411 // that were open at the point of crash from the session service. 412 profile()->GetSessionService()->GetLastSession( 413 &load_consumer_, 414 NewCallback(this, &TabRestoreService::OnGotPreviousSession)); 415 } else { 416 load_state_ |= LOADED_LAST_SESSION; 417 } 418 419 // Request the tabs closed in the last session. If the last session crashed, 420 // this won't contain the tabs/window that were open at the point of the 421 // crash (the call to GetLastSession above requests those). 422 ScheduleGetLastSessionCommands( 423 new InternalGetCommandsRequest( 424 NewCallback(this, &TabRestoreService::OnGotLastSessionCommands)), 425 &load_consumer_); 426} 427 428void TabRestoreService::Save() { 429 int to_write_count = std::min(entries_to_write_, 430 static_cast<int>(entries_.size())); 431 entries_to_write_ = 0; 432 if (entries_written_ + to_write_count > kEntriesPerReset) { 433 to_write_count = entries_.size(); 434 set_pending_reset(true); 435 } 436 if (to_write_count) { 437 // Write the to_write_count most recently added entries out. The most 438 // recently added entry is at the front, so we use a reverse iterator to 439 // write in the order the entries were added. 440 Entries::reverse_iterator i = entries_.rbegin(); 441 DCHECK(static_cast<size_t>(to_write_count) <= entries_.size()); 442 std::advance(i, entries_.size() - static_cast<int>(to_write_count)); 443 for (; i != entries_.rend(); ++i) { 444 Entry* entry = *i; 445 if (entry->type == TAB) { 446 Tab* tab = static_cast<Tab*>(entry); 447 int selected_index = GetSelectedNavigationIndexToPersist(*tab); 448 if (selected_index != -1) 449 ScheduleCommandsForTab(*tab, selected_index); 450 } else { 451 ScheduleCommandsForWindow(*static_cast<Window*>(entry)); 452 } 453 entries_written_++; 454 } 455 } 456 if (pending_reset()) 457 entries_written_ = 0; 458 BaseSessionService::Save(); 459} 460 461void TabRestoreService::PopulateTab(Tab* tab, 462 Browser* browser, 463 NavigationController* controller) { 464 const int pending_index = controller->pending_entry_index(); 465 int entry_count = controller->entry_count(); 466 if (entry_count == 0 && pending_index == 0) 467 entry_count++; 468 tab->navigations.resize(static_cast<int>(entry_count)); 469 for (int i = 0; i < entry_count; ++i) { 470 NavigationEntry* entry = (i == pending_index) ? 471 controller->pending_entry() : controller->GetEntryAtIndex(i); 472 tab->navigations[i].SetFromNavigationEntry(*entry); 473 } 474 tab->timestamp = TimeNow(); 475 tab->current_navigation_index = controller->GetCurrentEntryIndex(); 476 if (tab->current_navigation_index == -1 && entry_count > 0) 477 tab->current_navigation_index = 0; 478 479 const Extension* extension = controller->tab_contents()->extension_app(); 480 if (extension) 481 tab->extension_app_id = extension->id(); 482 483 tab->session_storage_namespace = controller->session_storage_namespace(); 484 485 // Browser may be NULL during unit tests. 486 if (browser) { 487 tab->browser_id = browser->session_id().id(); 488 tab->tabstrip_index = 489 browser->tabstrip_model()->GetIndexOfController(controller); 490 tab->pinned = browser->tabstrip_model()->IsTabPinned(tab->tabstrip_index); 491 } 492} 493 494void TabRestoreService::NotifyTabsChanged() { 495 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_, 496 TabRestoreServiceChanged(this)); 497} 498 499void TabRestoreService::AddEntry(Entry* entry, bool notify, bool to_front) { 500 if (to_front) 501 entries_.push_front(entry); 502 else 503 entries_.push_back(entry); 504 if (notify) 505 PruneAndNotify(); 506 // Start the save timer, when it fires we'll generate the commands. 507 StartSaveTimer(); 508 entries_to_write_++; 509} 510 511void TabRestoreService::PruneAndNotify() { 512 while (entries_.size() > kMaxEntries) { 513 delete entries_.back(); 514 entries_.pop_back(); 515 reached_max_ = true; 516 } 517 518 NotifyTabsChanged(); 519} 520 521TabRestoreService::Entries::iterator TabRestoreService::GetEntryIteratorById( 522 SessionID::id_type id) { 523 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { 524 if ((*i)->id == id) 525 return i; 526 527 // For Window entries, see if the ID matches a tab. If so, report the window 528 // as the Entry. 529 if ((*i)->type == WINDOW) { 530 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs; 531 for (std::vector<Tab>::iterator j = tabs.begin(); 532 j != tabs.end(); ++j) { 533 if ((*j).id == id) { 534 return i; 535 } 536 } 537 } 538 } 539 return entries_.end(); 540} 541 542void TabRestoreService::ScheduleCommandsForWindow(const Window& window) { 543 DCHECK(!window.tabs.empty()); 544 int selected_tab = window.selected_tab_index; 545 int valid_tab_count = 0; 546 int real_selected_tab = selected_tab; 547 for (size_t i = 0; i < window.tabs.size(); ++i) { 548 if (GetSelectedNavigationIndexToPersist(window.tabs[i]) != -1) { 549 valid_tab_count++; 550 } else if (static_cast<int>(i) < selected_tab) { 551 real_selected_tab--; 552 } 553 } 554 if (valid_tab_count == 0) 555 return; // No tabs to persist. 556 557 ScheduleCommand( 558 CreateWindowCommand(window.id, 559 std::min(real_selected_tab, valid_tab_count - 1), 560 valid_tab_count, 561 window.timestamp)); 562 563 for (size_t i = 0; i < window.tabs.size(); ++i) { 564 int selected_index = GetSelectedNavigationIndexToPersist(window.tabs[i]); 565 if (selected_index != -1) 566 ScheduleCommandsForTab(window.tabs[i], selected_index); 567 } 568} 569 570void TabRestoreService::ScheduleCommandsForTab(const Tab& tab, 571 int selected_index) { 572 const std::vector<TabNavigation>& navigations = tab.navigations; 573 int max_index = static_cast<int>(navigations.size()); 574 575 // Determine the first navigation we'll persist. 576 int valid_count_before_selected = 0; 577 int first_index_to_persist = selected_index; 578 for (int i = selected_index - 1; i >= 0 && 579 valid_count_before_selected < max_persist_navigation_count; --i) { 580 if (ShouldTrackEntry(navigations[i])) { 581 first_index_to_persist = i; 582 valid_count_before_selected++; 583 } 584 } 585 586 // Write the command that identifies the selected tab. 587 ScheduleCommand( 588 CreateSelectedNavigationInTabCommand(tab.id, 589 valid_count_before_selected, 590 tab.timestamp)); 591 592 if (tab.pinned) { 593 PinnedStatePayload payload = true; 594 SessionCommand* command = 595 new SessionCommand(kCommandPinnedState, sizeof(payload)); 596 memcpy(command->contents(), &payload, sizeof(payload)); 597 ScheduleCommand(command); 598 } 599 600 if (!tab.extension_app_id.empty()) { 601 ScheduleCommand( 602 CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID, tab.id, 603 tab.extension_app_id)); 604 } 605 606 // Then write the navigations. 607 for (int i = first_index_to_persist, wrote_count = 0; 608 i < max_index && wrote_count < 2 * max_persist_navigation_count; ++i) { 609 if (ShouldTrackEntry(navigations[i])) { 610 // Creating a NavigationEntry isn't the most efficient way to go about 611 // this, but it simplifies the code and makes it less error prone as we 612 // add new data to NavigationEntry. 613 scoped_ptr<NavigationEntry> entry( 614 navigations[i].ToNavigationEntry(wrote_count, profile())); 615 ScheduleCommand( 616 CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, tab.id, 617 wrote_count++, *entry)); 618 } 619 } 620} 621 622SessionCommand* TabRestoreService::CreateWindowCommand(SessionID::id_type id, 623 int selected_tab_index, 624 int num_tabs, 625 Time timestamp) { 626 WindowPayload2 payload; 627 // |timestamp| is aligned on a 16 byte boundary, leaving 4 bytes of 628 // uninitialized memory in the struct. 629 memset(&payload, 0, sizeof(payload)); 630 payload.window_id = id; 631 payload.selected_tab_index = selected_tab_index; 632 payload.num_tabs = num_tabs; 633 payload.timestamp = timestamp.ToInternalValue(); 634 635 SessionCommand* command = 636 new SessionCommand(kCommandWindow, sizeof(payload)); 637 memcpy(command->contents(), &payload, sizeof(payload)); 638 return command; 639} 640 641SessionCommand* TabRestoreService::CreateSelectedNavigationInTabCommand( 642 SessionID::id_type tab_id, 643 int32 index, 644 Time timestamp) { 645 SelectedNavigationInTabPayload2 payload; 646 payload.id = tab_id; 647 payload.index = index; 648 payload.timestamp = timestamp.ToInternalValue(); 649 SessionCommand* command = 650 new SessionCommand(kCommandSelectedNavigationInTab, sizeof(payload)); 651 memcpy(command->contents(), &payload, sizeof(payload)); 652 return command; 653} 654 655SessionCommand* TabRestoreService::CreateRestoredEntryCommand( 656 SessionID::id_type entry_id) { 657 RestoredEntryPayload payload = entry_id; 658 SessionCommand* command = 659 new SessionCommand(kCommandRestoredEntry, sizeof(payload)); 660 memcpy(command->contents(), &payload, sizeof(payload)); 661 return command; 662} 663 664int TabRestoreService::GetSelectedNavigationIndexToPersist(const Tab& tab) { 665 const std::vector<TabNavigation>& navigations = tab.navigations; 666 int selected_index = tab.current_navigation_index; 667 int max_index = static_cast<int>(navigations.size()); 668 669 // Find the first navigation to persist. We won't persist the selected 670 // navigation if ShouldTrackEntry returns false. 671 while (selected_index >= 0 && 672 !ShouldTrackEntry(navigations[selected_index])) { 673 selected_index--; 674 } 675 676 if (selected_index != -1) 677 return selected_index; 678 679 // Couldn't find a navigation to persist going back, go forward. 680 selected_index = tab.current_navigation_index + 1; 681 while (selected_index < max_index && 682 !ShouldTrackEntry(navigations[selected_index])) { 683 selected_index++; 684 } 685 686 return (selected_index == max_index) ? -1 : selected_index; 687} 688 689void TabRestoreService::OnGotLastSessionCommands( 690 Handle handle, 691 scoped_refptr<InternalGetCommandsRequest> request) { 692 std::vector<Entry*> entries; 693 CreateEntriesFromCommands(request, &entries); 694 // Closed tabs always go to the end. 695 staging_entries_.insert(staging_entries_.end(), entries.begin(), 696 entries.end()); 697 load_state_ |= LOADED_LAST_TABS; 698 LoadStateChanged(); 699} 700 701void TabRestoreService::CreateEntriesFromCommands( 702 scoped_refptr<InternalGetCommandsRequest> request, 703 std::vector<Entry*>* loaded_entries) { 704 if (request->canceled() || entries_.size() == kMaxEntries) 705 return; 706 707 std::vector<SessionCommand*>& commands = request->commands; 708 // Iterate through the commands populating entries and id_to_entry. 709 ScopedVector<Entry> entries; 710 IDToEntry id_to_entry; 711 // If non-null we're processing the navigations of this tab. 712 Tab* current_tab = NULL; 713 // If non-null we're processing the tabs of this window. 714 Window* current_window = NULL; 715 // If > 0, we've gotten a window command but not all the tabs yet. 716 int pending_window_tabs = 0; 717 for (std::vector<SessionCommand*>::const_iterator i = commands.begin(); 718 i != commands.end(); ++i) { 719 const SessionCommand& command = *(*i); 720 switch (command.id()) { 721 case kCommandRestoredEntry: { 722 if (pending_window_tabs > 0) { 723 // Should never receive a restored command while waiting for all the 724 // tabs in a window. 725 return; 726 } 727 728 current_tab = NULL; 729 current_window = NULL; 730 731 RestoredEntryPayload payload; 732 if (!command.GetPayload(&payload, sizeof(payload))) 733 return; 734 RemoveEntryByID(payload, &id_to_entry, &(entries.get())); 735 break; 736 } 737 738 case kCommandWindow: { 739 WindowPayload2 payload; 740 if (pending_window_tabs > 0) { 741 // Should never receive a window command while waiting for all the 742 // tabs in a window. 743 return; 744 } 745 746 // Try the new payload first 747 if (!command.GetPayload(&payload, sizeof(payload))) { 748 // then the old payload 749 WindowPayload old_payload; 750 if (!command.GetPayload(&old_payload, sizeof(old_payload))) 751 return; 752 753 // Copy the old payload data to the new payload. 754 payload.window_id = old_payload.window_id; 755 payload.selected_tab_index = old_payload.selected_tab_index; 756 payload.num_tabs = old_payload.num_tabs; 757 // Since we don't have a time use time 0 which is used to mark as an 758 // unknown timestamp. 759 payload.timestamp = 0; 760 } 761 762 pending_window_tabs = payload.num_tabs; 763 if (pending_window_tabs <= 0) { 764 // Should always have at least 1 tab. Likely indicates corruption. 765 return; 766 } 767 768 RemoveEntryByID(payload.window_id, &id_to_entry, &(entries.get())); 769 770 current_window = new Window(); 771 current_window->selected_tab_index = payload.selected_tab_index; 772 current_window->timestamp = Time::FromInternalValue(payload.timestamp); 773 entries->push_back(current_window); 774 id_to_entry[payload.window_id] = current_window; 775 break; 776 } 777 778 case kCommandSelectedNavigationInTab: { 779 SelectedNavigationInTabPayload2 payload; 780 if (!command.GetPayload(&payload, sizeof(payload))) { 781 SelectedNavigationInTabPayload old_payload; 782 if (!command.GetPayload(&old_payload, sizeof(old_payload))) 783 return; 784 payload.id = old_payload.id; 785 payload.index = old_payload.index; 786 // Since we don't have a time use time 0 which is used to mark as an 787 // unknown timestamp. 788 payload.timestamp = 0; 789 } 790 791 if (pending_window_tabs > 0) { 792 if (!current_window) { 793 // We should have created a window already. 794 NOTREACHED(); 795 return; 796 } 797 current_window->tabs.resize(current_window->tabs.size() + 1); 798 current_tab = &(current_window->tabs.back()); 799 if (--pending_window_tabs == 0) 800 current_window = NULL; 801 } else { 802 RemoveEntryByID(payload.id, &id_to_entry, &(entries.get())); 803 current_tab = new Tab(); 804 id_to_entry[payload.id] = current_tab; 805 current_tab->timestamp = Time::FromInternalValue(payload.timestamp); 806 entries->push_back(current_tab); 807 } 808 current_tab->current_navigation_index = payload.index; 809 break; 810 } 811 812 case kCommandUpdateTabNavigation: { 813 if (!current_tab) { 814 // Should be in a tab when we get this. 815 return; 816 } 817 current_tab->navigations.resize(current_tab->navigations.size() + 1); 818 SessionID::id_type tab_id; 819 if (!RestoreUpdateTabNavigationCommand( 820 command, ¤t_tab->navigations.back(), &tab_id)) { 821 return; 822 } 823 break; 824 } 825 826 case kCommandPinnedState: { 827 if (!current_tab) { 828 // Should be in a tab when we get this. 829 return; 830 } 831 // NOTE: payload doesn't matter. kCommandPinnedState is only written if 832 // tab is pinned. 833 current_tab->pinned = true; 834 break; 835 } 836 837 case kCommandSetExtensionAppID: { 838 if (!current_tab) { 839 // Should be in a tab when we get this. 840 return; 841 } 842 SessionID::id_type tab_id; 843 std::string extension_app_id; 844 if (!RestoreSetTabExtensionAppIDCommand(command, &tab_id, 845 &extension_app_id)) { 846 return; 847 } 848 current_tab->extension_app_id.swap(extension_app_id); 849 break; 850 } 851 852 default: 853 // Unknown type, usually indicates corruption of file. Ignore it. 854 return; 855 } 856 } 857 858 // If there was corruption some of the entries won't be valid. Prune any 859 // entries with no navigations. 860 ValidateAndDeleteEmptyEntries(&(entries.get())); 861 862 loaded_entries->swap(entries.get()); 863} 864 865Browser* TabRestoreService::RestoreTab(const Tab& tab, 866 Browser* browser, 867 bool replace_existing_tab) { 868 // |browser| will be NULL in cases where one isn't already available (eg, 869 // when invoked on Mac OS X with no windows open). In this case, create a 870 // new browser into which we restore the tabs. 871 if (replace_existing_tab && browser) { 872 browser->ReplaceRestoredTab(tab.navigations, 873 tab.current_navigation_index, 874 tab.from_last_session, 875 tab.extension_app_id, 876 tab.session_storage_namespace); 877 } else { 878 if (tab.has_browser()) 879 browser = BrowserList::FindBrowserWithID(tab.browser_id); 880 881 int tab_index = -1; 882 if (browser) { 883 tab_index = tab.tabstrip_index; 884 } else { 885 browser = Browser::Create(profile()); 886 if (tab.has_browser()) { 887 UpdateTabBrowserIDs(tab.browser_id, browser->session_id().id()); 888 } 889 } 890 891 if (tab_index < 0 || tab_index > browser->tab_count()) { 892 tab_index = browser->tab_count(); 893 } 894 895 browser->AddRestoredTab(tab.navigations, 896 tab_index, 897 tab.current_navigation_index, 898 tab.extension_app_id, 899 true, tab.pinned, tab.from_last_session, 900 tab.session_storage_namespace); 901 } 902 return browser; 903} 904 905 906bool TabRestoreService::ValidateTab(Tab* tab) { 907 if (tab->navigations.empty()) 908 return false; 909 910 tab->current_navigation_index = 911 std::max(0, std::min(tab->current_navigation_index, 912 static_cast<int>(tab->navigations.size()) - 1)); 913 return true; 914} 915 916void TabRestoreService::ValidateAndDeleteEmptyEntries( 917 std::vector<Entry*>* entries) { 918 std::vector<Entry*> valid_entries; 919 std::vector<Entry*> invalid_entries; 920 921 size_t max_valid = kMaxEntries - entries_.size(); 922 // Iterate from the back so that we keep the most recently closed entries. 923 for (std::vector<Entry*>::reverse_iterator i = entries->rbegin(); 924 i != entries->rend(); ++i) { 925 bool valid_entry = false; 926 if (valid_entries.size() != max_valid) { 927 if ((*i)->type == TAB) { 928 Tab* tab = static_cast<Tab*>(*i); 929 if (ValidateTab(tab)) 930 valid_entry = true; 931 } else { 932 Window* window = static_cast<Window*>(*i); 933 for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); 934 tab_i != window->tabs.end();) { 935 if (!ValidateTab(&(*tab_i))) 936 tab_i = window->tabs.erase(tab_i); 937 else 938 ++tab_i; 939 } 940 if (!window->tabs.empty()) { 941 window->selected_tab_index = 942 std::max(0, std::min(window->selected_tab_index, 943 static_cast<int>(window->tabs.size() - 1))); 944 valid_entry = true; 945 } 946 } 947 } 948 if (valid_entry) 949 valid_entries.push_back(*i); 950 else 951 invalid_entries.push_back(*i); 952 } 953 // NOTE: at this point the entries are ordered with newest at the front. 954 entries->swap(valid_entries); 955 956 // Delete the remaining entries. 957 STLDeleteElements(&invalid_entries); 958} 959 960void TabRestoreService::UpdateTabBrowserIDs(SessionID::id_type old_id, 961 SessionID::id_type new_id) { 962 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { 963 Entry* entry = *i; 964 if (entry->type == TAB) { 965 Tab* tab = static_cast<Tab*>(entry); 966 if (tab->browser_id == old_id) 967 tab->browser_id = new_id; 968 } 969 } 970} 971 972void TabRestoreService::OnGotPreviousSession( 973 Handle handle, 974 std::vector<SessionWindow*>* windows) { 975 std::vector<Entry*> entries; 976 CreateEntriesFromWindows(windows, &entries); 977 // Previous session tabs go first. 978 staging_entries_.insert(staging_entries_.begin(), entries.begin(), 979 entries.end()); 980 load_state_ |= LOADED_LAST_SESSION; 981 LoadStateChanged(); 982} 983 984void TabRestoreService::CreateEntriesFromWindows( 985 std::vector<SessionWindow*>* windows, 986 std::vector<Entry*>* entries) { 987 for (size_t i = 0; i < windows->size(); ++i) { 988 scoped_ptr<Window> window(new Window()); 989 if (ConvertSessionWindowToWindow((*windows)[i], window.get())) 990 entries->push_back(window.release()); 991 } 992} 993 994bool TabRestoreService::ConvertSessionWindowToWindow( 995 SessionWindow* session_window, 996 Window* window) { 997 for (size_t i = 0; i < session_window->tabs.size(); ++i) { 998 if (!session_window->tabs[i]->navigations.empty()) { 999 window->tabs.resize(window->tabs.size() + 1); 1000 Tab& tab = window->tabs.back(); 1001 tab.pinned = session_window->tabs[i]->pinned; 1002 tab.navigations.swap(session_window->tabs[i]->navigations); 1003 tab.current_navigation_index = 1004 session_window->tabs[i]->current_navigation_index; 1005 tab.extension_app_id = session_window->tabs[i]->extension_app_id; 1006 tab.timestamp = Time(); 1007 } 1008 } 1009 if (window->tabs.empty()) 1010 return false; 1011 1012 window->selected_tab_index = 1013 std::min(session_window->selected_tab_index, 1014 static_cast<int>(window->tabs.size() - 1)); 1015 window->timestamp = Time(); 1016 return true; 1017} 1018 1019void TabRestoreService::LoadStateChanged() { 1020 if ((load_state_ & (LOADED_LAST_TABS | LOADED_LAST_SESSION)) != 1021 (LOADED_LAST_TABS | LOADED_LAST_SESSION)) { 1022 // Still waiting on previous session or previous tabs. 1023 return; 1024 } 1025 1026 // We're done loading. 1027 load_state_ ^= LOADING; 1028 1029 if (staging_entries_.empty() || reached_max_) { 1030 STLDeleteElements(&staging_entries_); 1031 return; 1032 } 1033 1034 if (staging_entries_.size() + entries_.size() > kMaxEntries) { 1035 // If we add all the staged entries we'll end up with more than 1036 // kMaxEntries. Delete entries such that we only end up with 1037 // at most kMaxEntries. 1038 DCHECK(entries_.size() < kMaxEntries); 1039 STLDeleteContainerPointers( 1040 staging_entries_.begin() + (kMaxEntries - entries_.size()), 1041 staging_entries_.end()); 1042 staging_entries_.erase( 1043 staging_entries_.begin() + (kMaxEntries - entries_.size()), 1044 staging_entries_.end()); 1045 } 1046 1047 // And add them. 1048 for (size_t i = 0; i < staging_entries_.size(); ++i) { 1049 staging_entries_[i]->from_last_session = true; 1050 AddEntry(staging_entries_[i], false, false); 1051 } 1052 1053 // AddEntry takes ownership of the entry, need to clear out entries so that 1054 // it doesn't delete them. 1055 staging_entries_.clear(); 1056 1057 // Make it so we rewrite all the tabs. We need to do this otherwise we won't 1058 // correctly write out the entries when Save is invoked (Save starts from 1059 // the front, not the end and we just added the entries to the end). 1060 entries_to_write_ = staging_entries_.size(); 1061 1062 PruneAndNotify(); 1063} 1064 1065Time TabRestoreService::TimeNow() const { 1066 return time_factory_ ? time_factory_->TimeNow() : Time::Now(); 1067} 1068