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