1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/sessions/tab_restore_service_helper.h" 6 7 #include <algorithm> 8 #include <iterator> 9 10 #include "base/logging.h" 11 #include "base/metrics/histogram.h" 12 #include "base/stl_util.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/browser/sessions/session_types.h" 15 #include "chrome/browser/sessions/tab_restore_service_delegate.h" 16 #include "chrome/browser/sessions/tab_restore_service_observer.h" 17 #include "chrome/common/url_constants.h" 18 #include "content/public/browser/navigation_controller.h" 19 #include "content/public/browser/navigation_entry.h" 20 #include "content/public/browser/session_storage_namespace.h" 21 #include "content/public/browser/web_contents.h" 22 23 #if defined(ENABLE_EXTENSIONS) 24 #include "chrome/browser/extensions/tab_helper.h" 25 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h" 26 #include "chrome/common/extensions/extension_constants.h" 27 #include "extensions/browser/extension_registry.h" 28 #include "extensions/common/extension.h" 29 #include "extensions/common/extension_set.h" 30 #endif 31 32 using content::NavigationController; 33 using content::NavigationEntry; 34 using content::WebContents; 35 36 namespace { 37 38 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) { 39 #if defined(ENABLE_EXTENSIONS) 40 GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url(); 41 const extensions::Extension* extension = 42 extensions::ExtensionRegistry::Get(profile) 43 ->enabled_extensions().GetAppByURL(url); 44 if (!extension) 45 return; 46 47 CoreAppLauncherHandler::RecordAppLaunchType( 48 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED, 49 extension->GetType()); 50 #endif // defined(ENABLE_EXTENSIONS) 51 } 52 53 } // namespace 54 55 // TabRestoreServiceHelper::Observer ------------------------------------------- 56 57 TabRestoreServiceHelper::Observer::~Observer() {} 58 59 void TabRestoreServiceHelper::Observer::OnClearEntries() {} 60 61 void TabRestoreServiceHelper::Observer::OnRestoreEntryById( 62 SessionID::id_type id, 63 Entries::const_iterator entry_iterator) { 64 } 65 66 void TabRestoreServiceHelper::Observer::OnAddEntry() {} 67 68 // TabRestoreServiceHelper ----------------------------------------------------- 69 70 TabRestoreServiceHelper::TabRestoreServiceHelper( 71 TabRestoreService* tab_restore_service, 72 Observer* observer, 73 Profile* profile, 74 TabRestoreService::TimeFactory* time_factory) 75 : tab_restore_service_(tab_restore_service), 76 observer_(observer), 77 profile_(profile), 78 restoring_(false), 79 time_factory_(time_factory) { 80 DCHECK(tab_restore_service_); 81 } 82 83 TabRestoreServiceHelper::~TabRestoreServiceHelper() { 84 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_, 85 TabRestoreServiceDestroyed(tab_restore_service_)); 86 STLDeleteElements(&entries_); 87 } 88 89 void TabRestoreServiceHelper::AddObserver( 90 TabRestoreServiceObserver* observer) { 91 observer_list_.AddObserver(observer); 92 } 93 94 void TabRestoreServiceHelper::RemoveObserver( 95 TabRestoreServiceObserver* observer) { 96 observer_list_.RemoveObserver(observer); 97 } 98 99 void TabRestoreServiceHelper::CreateHistoricalTab( 100 content::WebContents* contents, 101 int index) { 102 if (restoring_) 103 return; 104 105 TabRestoreServiceDelegate* delegate = 106 TabRestoreServiceDelegate::FindDelegateForWebContents(contents); 107 if (closing_delegates_.find(delegate) != closing_delegates_.end()) 108 return; 109 110 scoped_ptr<Tab> local_tab(new Tab()); 111 PopulateTab(local_tab.get(), index, delegate, &contents->GetController()); 112 if (local_tab->navigations.empty()) 113 return; 114 115 AddEntry(local_tab.release(), true, true); 116 } 117 118 void TabRestoreServiceHelper::BrowserClosing( 119 TabRestoreServiceDelegate* delegate) { 120 closing_delegates_.insert(delegate); 121 122 scoped_ptr<Window> window(new Window()); 123 window->selected_tab_index = delegate->GetSelectedIndex(); 124 window->timestamp = TimeNow(); 125 window->app_name = delegate->GetAppName(); 126 127 // Don't use std::vector::resize() because it will push copies of an empty tab 128 // into the vector, which will give all tabs in a window the same ID. 129 for (int i = 0; i < delegate->GetTabCount(); ++i) { 130 window->tabs.push_back(Tab()); 131 } 132 size_t entry_index = 0; 133 for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) { 134 PopulateTab(&(window->tabs[entry_index]), 135 tab_index, 136 delegate, 137 &delegate->GetWebContentsAt(tab_index)->GetController()); 138 if (window->tabs[entry_index].navigations.empty()) { 139 window->tabs.erase(window->tabs.begin() + entry_index); 140 } else { 141 window->tabs[entry_index].browser_id = delegate->GetSessionID().id(); 142 entry_index++; 143 } 144 } 145 if (window->tabs.size() == 1 && window->app_name.empty()) { 146 // Short-circuit creating a Window if only 1 tab was present. This fixes 147 // http://crbug.com/56744. Copy the Tab because it's owned by an object on 148 // the stack. 149 AddEntry(new Tab(window->tabs[0]), true, true); 150 } else if (!window->tabs.empty()) { 151 window->selected_tab_index = 152 std::min(static_cast<int>(window->tabs.size() - 1), 153 window->selected_tab_index); 154 AddEntry(window.release(), true, true); 155 } 156 } 157 158 void TabRestoreServiceHelper::BrowserClosed( 159 TabRestoreServiceDelegate* delegate) { 160 closing_delegates_.erase(delegate); 161 } 162 163 void TabRestoreServiceHelper::ClearEntries() { 164 if (observer_) 165 observer_->OnClearEntries(); 166 STLDeleteElements(&entries_); 167 NotifyTabsChanged(); 168 } 169 170 const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const { 171 return entries_; 172 } 173 174 std::vector<content::WebContents*> 175 TabRestoreServiceHelper::RestoreMostRecentEntry( 176 TabRestoreServiceDelegate* delegate, 177 chrome::HostDesktopType host_desktop_type) { 178 if (entries_.empty()) 179 return std::vector<WebContents*>(); 180 181 return RestoreEntryById(delegate, entries_.front()->id, host_desktop_type, 182 UNKNOWN); 183 } 184 185 TabRestoreService::Tab* TabRestoreServiceHelper::RemoveTabEntryById( 186 SessionID::id_type id) { 187 Entries::iterator i = GetEntryIteratorById(id); 188 if (i == entries_.end()) 189 return NULL; 190 191 Entry* entry = *i; 192 if (entry->type != TabRestoreService::TAB) 193 return NULL; 194 195 Tab* tab = static_cast<Tab*>(entry); 196 entries_.erase(i); 197 return tab; 198 } 199 200 std::vector<content::WebContents*> TabRestoreServiceHelper::RestoreEntryById( 201 TabRestoreServiceDelegate* delegate, 202 SessionID::id_type id, 203 chrome::HostDesktopType host_desktop_type, 204 WindowOpenDisposition disposition) { 205 Entries::iterator entry_iterator = GetEntryIteratorById(id); 206 if (entry_iterator == entries_.end()) 207 // Don't hoark here, we allow an invalid id. 208 return std::vector<WebContents*>(); 209 210 if (observer_) 211 observer_->OnRestoreEntryById(id, entry_iterator); 212 restoring_ = true; 213 Entry* entry = *entry_iterator; 214 215 // If the entry's ID does not match the ID that is being restored, then the 216 // entry is a window from which a single tab will be restored. 217 bool restoring_tab_in_window = entry->id != id; 218 219 if (!restoring_tab_in_window) { 220 entries_.erase(entry_iterator); 221 entry_iterator = entries_.end(); 222 } 223 224 // |delegate| will be NULL in cases where one isn't already available (eg, 225 // when invoked on Mac OS X with no windows open). In this case, create a 226 // new browser into which we restore the tabs. 227 std::vector<WebContents*> web_contents; 228 if (entry->type == TabRestoreService::TAB) { 229 Tab* tab = static_cast<Tab*>(entry); 230 WebContents* restored_tab = NULL; 231 delegate = RestoreTab(*tab, delegate, host_desktop_type, disposition, 232 &restored_tab); 233 web_contents.push_back(restored_tab); 234 delegate->ShowBrowserWindow(); 235 } else if (entry->type == TabRestoreService::WINDOW) { 236 TabRestoreServiceDelegate* current_delegate = delegate; 237 Window* window = static_cast<Window*>(entry); 238 239 // When restoring a window, either the entire window can be restored, or a 240 // single tab within it. If the entry's ID matches the one to restore, then 241 // the entire window will be restored. 242 if (!restoring_tab_in_window) { 243 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type, 244 window->app_name); 245 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) { 246 const Tab& tab = window->tabs[tab_i]; 247 WebContents* restored_tab = delegate->AddRestoredTab( 248 tab.navigations, 249 delegate->GetTabCount(), 250 tab.current_navigation_index, 251 tab.extension_app_id, 252 static_cast<int>(tab_i) == window->selected_tab_index, 253 tab.pinned, 254 tab.from_last_session, 255 tab.session_storage_namespace.get(), 256 tab.user_agent_override); 257 if (restored_tab) { 258 restored_tab->GetController().LoadIfNecessary(); 259 RecordAppLaunch(profile_, tab); 260 web_contents.push_back(restored_tab); 261 } 262 } 263 // All the window's tabs had the same former browser_id. 264 if (window->tabs[0].has_browser()) { 265 UpdateTabBrowserIDs(window->tabs[0].browser_id, 266 delegate->GetSessionID().id()); 267 } 268 } else { 269 // Restore a single tab from the window. Find the tab that matches the ID 270 // in the window and restore it. 271 for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); 272 tab_i != window->tabs.end(); ++tab_i) { 273 const Tab& tab = *tab_i; 274 if (tab.id == id) { 275 WebContents* restored_tab = NULL; 276 delegate = RestoreTab(tab, delegate, host_desktop_type, disposition, 277 &restored_tab); 278 web_contents.push_back(restored_tab); 279 window->tabs.erase(tab_i); 280 // If restoring the tab leaves the window with nothing else, delete it 281 // as well. 282 if (!window->tabs.size()) { 283 entries_.erase(entry_iterator); 284 delete entry; 285 } else { 286 // Update the browser ID of the rest of the tabs in the window so if 287 // any one is restored, it goes into the same window as the tab 288 // being restored now. 289 UpdateTabBrowserIDs(tab.browser_id, 290 delegate->GetSessionID().id()); 291 for (std::vector<Tab>::iterator tab_j = window->tabs.begin(); 292 tab_j != window->tabs.end(); ++tab_j) { 293 (*tab_j).browser_id = delegate->GetSessionID().id(); 294 } 295 } 296 break; 297 } 298 } 299 } 300 delegate->ShowBrowserWindow(); 301 302 if (disposition == CURRENT_TAB && current_delegate && 303 current_delegate->GetActiveWebContents()) { 304 current_delegate->CloseTab(); 305 } 306 } else { 307 NOTREACHED(); 308 } 309 310 if (!restoring_tab_in_window) { 311 delete entry; 312 } 313 314 restoring_ = false; 315 NotifyTabsChanged(); 316 return web_contents; 317 } 318 319 void TabRestoreServiceHelper::NotifyTabsChanged() { 320 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_, 321 TabRestoreServiceChanged(tab_restore_service_)); 322 } 323 324 void TabRestoreServiceHelper::NotifyLoaded() { 325 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_, 326 TabRestoreServiceLoaded(tab_restore_service_)); 327 } 328 329 void TabRestoreServiceHelper::AddEntry(Entry* entry, 330 bool notify, 331 bool to_front) { 332 if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) { 333 delete entry; 334 return; 335 } 336 337 if (to_front) 338 entries_.push_front(entry); 339 else 340 entries_.push_back(entry); 341 342 PruneEntries(); 343 344 if (notify) 345 NotifyTabsChanged(); 346 347 if (observer_) 348 observer_->OnAddEntry(); 349 } 350 351 void TabRestoreServiceHelper::PruneEntries() { 352 Entries new_entries; 353 354 for (TabRestoreService::Entries::const_iterator iter = entries_.begin(); 355 iter != entries_.end(); ++iter) { 356 TabRestoreService::Entry* entry = *iter; 357 358 if (FilterEntry(entry) && 359 new_entries.size() < kMaxEntries) { 360 new_entries.push_back(entry); 361 } else { 362 delete entry; 363 } 364 } 365 366 entries_ = new_entries; 367 } 368 369 TabRestoreService::Entries::iterator 370 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id) { 371 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { 372 if ((*i)->id == id) 373 return i; 374 375 // For Window entries, see if the ID matches a tab. If so, report the window 376 // as the Entry. 377 if ((*i)->type == TabRestoreService::WINDOW) { 378 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs; 379 for (std::vector<Tab>::iterator j = tabs.begin(); 380 j != tabs.end(); ++j) { 381 if ((*j).id == id) { 382 return i; 383 } 384 } 385 } 386 } 387 return entries_.end(); 388 } 389 390 // static 391 bool TabRestoreServiceHelper::ValidateEntry(Entry* entry) { 392 if (entry->type == TabRestoreService::TAB) 393 return ValidateTab(static_cast<Tab*>(entry)); 394 395 if (entry->type == TabRestoreService::WINDOW) 396 return ValidateWindow(static_cast<Window*>(entry)); 397 398 NOTREACHED(); 399 return false; 400 } 401 402 void TabRestoreServiceHelper::PopulateTab( 403 Tab* tab, 404 int index, 405 TabRestoreServiceDelegate* delegate, 406 NavigationController* controller) { 407 const int pending_index = controller->GetPendingEntryIndex(); 408 int entry_count = controller->GetEntryCount(); 409 if (entry_count == 0 && pending_index == 0) 410 entry_count++; 411 tab->navigations.resize(static_cast<int>(entry_count)); 412 for (int i = 0; i < entry_count; ++i) { 413 NavigationEntry* entry = (i == pending_index) ? 414 controller->GetPendingEntry() : controller->GetEntryAtIndex(i); 415 tab->navigations[i] = 416 sessions::SerializedNavigationEntry::FromNavigationEntry(i, *entry); 417 } 418 tab->timestamp = TimeNow(); 419 tab->current_navigation_index = controller->GetCurrentEntryIndex(); 420 if (tab->current_navigation_index == -1 && entry_count > 0) 421 tab->current_navigation_index = 0; 422 tab->tabstrip_index = index; 423 424 #if defined(ENABLE_EXTENSIONS) 425 extensions::TabHelper* extensions_tab_helper = 426 extensions::TabHelper::FromWebContents(controller->GetWebContents()); 427 // extensions_tab_helper is NULL in some browser tests. 428 if (extensions_tab_helper) { 429 const extensions::Extension* extension = 430 extensions_tab_helper->extension_app(); 431 if (extension) 432 tab->extension_app_id = extension->id(); 433 } 434 #endif 435 436 tab->user_agent_override = 437 controller->GetWebContents()->GetUserAgentOverride(); 438 439 // TODO(ajwong): This does not correctly handle storage for isolated apps. 440 tab->session_storage_namespace = 441 controller->GetDefaultSessionStorageNamespace(); 442 443 // Delegate may be NULL during unit tests. 444 if (delegate) { 445 tab->browser_id = delegate->GetSessionID().id(); 446 tab->pinned = delegate->IsTabPinned(tab->tabstrip_index); 447 } 448 } 449 450 TabRestoreServiceDelegate* TabRestoreServiceHelper::RestoreTab( 451 const Tab& tab, 452 TabRestoreServiceDelegate* delegate, 453 chrome::HostDesktopType host_desktop_type, 454 WindowOpenDisposition disposition, 455 WebContents** contents) { 456 WebContents* web_contents; 457 if (disposition == CURRENT_TAB && delegate) { 458 web_contents = delegate->ReplaceRestoredTab( 459 tab.navigations, 460 tab.current_navigation_index, 461 tab.from_last_session, 462 tab.extension_app_id, 463 tab.session_storage_namespace.get(), 464 tab.user_agent_override); 465 } else { 466 // We only respsect the tab's original browser if there's no disposition. 467 if (disposition == UNKNOWN && tab.has_browser()) { 468 delegate = TabRestoreServiceDelegate::FindDelegateWithID( 469 tab.browser_id, host_desktop_type); 470 } 471 472 int tab_index = -1; 473 474 // |delegate| will be NULL in cases where one isn't already available (eg, 475 // when invoked on Mac OS X with no windows open). In this case, create a 476 // new browser into which we restore the tabs. 477 if (delegate && disposition != NEW_WINDOW) { 478 tab_index = tab.tabstrip_index; 479 } else { 480 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type, 481 std::string()); 482 if (tab.has_browser()) 483 UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id()); 484 } 485 486 // Place the tab at the end if the tab index is no longer valid or 487 // we were passed a specific disposition. 488 if (tab_index < 0 || tab_index > delegate->GetTabCount() || 489 disposition != UNKNOWN) { 490 tab_index = delegate->GetTabCount(); 491 } 492 493 web_contents = delegate->AddRestoredTab(tab.navigations, 494 tab_index, 495 tab.current_navigation_index, 496 tab.extension_app_id, 497 disposition != NEW_BACKGROUND_TAB, 498 tab.pinned, 499 tab.from_last_session, 500 tab.session_storage_namespace.get(), 501 tab.user_agent_override); 502 web_contents->GetController().LoadIfNecessary(); 503 } 504 RecordAppLaunch(profile_, tab); 505 if (contents) 506 *contents = web_contents; 507 508 return delegate; 509 } 510 511 512 bool TabRestoreServiceHelper::ValidateTab(Tab* tab) { 513 if (tab->navigations.empty()) 514 return false; 515 516 tab->current_navigation_index = 517 std::max(0, std::min(tab->current_navigation_index, 518 static_cast<int>(tab->navigations.size()) - 1)); 519 520 return true; 521 } 522 523 bool TabRestoreServiceHelper::ValidateWindow(Window* window) { 524 window->selected_tab_index = 525 std::max(0, std::min(window->selected_tab_index, 526 static_cast<int>(window->tabs.size() - 1))); 527 528 int i = 0; 529 for (std::vector<Tab>::iterator tab_i = window->tabs.begin(); 530 tab_i != window->tabs.end();) { 531 if (!ValidateTab(&(*tab_i))) { 532 tab_i = window->tabs.erase(tab_i); 533 if (i < window->selected_tab_index) 534 window->selected_tab_index--; 535 else if (i == window->selected_tab_index) 536 window->selected_tab_index = 0; 537 } else { 538 ++tab_i; 539 ++i; 540 } 541 } 542 543 if (window->tabs.empty()) 544 return false; 545 546 return true; 547 } 548 549 bool TabRestoreServiceHelper::IsTabInteresting(const Tab* tab) { 550 if (tab->navigations.empty()) 551 return false; 552 553 if (tab->navigations.size() > 1) 554 return true; 555 556 return tab->pinned || 557 tab->navigations.at(0).virtual_url() != 558 GURL(chrome::kChromeUINewTabURL); 559 } 560 561 bool TabRestoreServiceHelper::IsWindowInteresting(const Window* window) { 562 if (window->tabs.empty()) 563 return false; 564 565 if (window->tabs.size() > 1) 566 return true; 567 568 return IsTabInteresting(&window->tabs[0]); 569 } 570 571 bool TabRestoreServiceHelper::FilterEntry(Entry* entry) { 572 if (!ValidateEntry(entry)) 573 return false; 574 575 if (entry->type == TabRestoreService::TAB) 576 return IsTabInteresting(static_cast<Tab*>(entry)); 577 else if (entry->type == TabRestoreService::WINDOW) 578 return IsWindowInteresting(static_cast<Window*>(entry)); 579 580 NOTREACHED(); 581 return false; 582 } 583 584 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id, 585 SessionID::id_type new_id) { 586 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) { 587 Entry* entry = *i; 588 if (entry->type == TabRestoreService::TAB) { 589 Tab* tab = static_cast<Tab*>(entry); 590 if (tab->browser_id == old_id) 591 tab->browser_id = new_id; 592 } 593 } 594 } 595 596 base::Time TabRestoreServiceHelper::TimeNow() const { 597 return time_factory_ ? time_factory_->TimeNow() : base::Time::Now(); 598 } 599