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.
5 #include "chrome/browser/sessions/tab_restore_service_helper.h"
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/extensions/extension_constants.h"
18 #include "chrome/common/url_constants.h"
19 #include "content/public/browser/navigation_controller.h"
20 #include "content/public/browser/navigation_entry.h"
21 #include "content/public/browser/session_storage_namespace.h"
22 #include "content/public/browser/web_contents.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/common/extension_set.h"
27 #if !defined(OS_ANDROID)
28 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
31 #if defined(ENABLE_EXTENSIONS)
32 #include "chrome/browser/extensions/tab_helper.h"
35 using content::NavigationController;
36 using content::NavigationEntry;
37 using content::WebContents;
41 void RecordAppLaunch(Profile* profile, const TabRestoreService::Tab& tab) {
42 #if !defined(OS_ANDROID)
43 GURL url = tab.navigations.at(tab.current_navigation_index).virtual_url();
44 const extensions::Extension* extension =
45 extensions::ExtensionRegistry::Get(profile)
46 ->enabled_extensions().GetAppByURL(url);
50 CoreAppLauncherHandler::RecordAppLaunchType(
51 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED,
52 extension->GetType());
53 #endif // !defined(OS_ANDROID)
58 // TabRestoreServiceHelper::Observer -------------------------------------------
60 TabRestoreServiceHelper::Observer::~Observer() {}
62 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
64 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
65 SessionID::id_type id,
66 Entries::const_iterator entry_iterator) {
69 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
71 // TabRestoreServiceHelper -----------------------------------------------------
73 TabRestoreServiceHelper::TabRestoreServiceHelper(
74 TabRestoreService* tab_restore_service,
77 TabRestoreService::TimeFactory* time_factory)
78 : tab_restore_service_(tab_restore_service),
82 time_factory_(time_factory) {
83 DCHECK(tab_restore_service_);
86 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
87 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
88 TabRestoreServiceDestroyed(tab_restore_service_));
89 STLDeleteElements(&entries_);
92 void TabRestoreServiceHelper::AddObserver(
93 TabRestoreServiceObserver* observer) {
94 observer_list_.AddObserver(observer);
97 void TabRestoreServiceHelper::RemoveObserver(
98 TabRestoreServiceObserver* observer) {
99 observer_list_.RemoveObserver(observer);
102 void TabRestoreServiceHelper::CreateHistoricalTab(
103 content::WebContents* contents,
108 TabRestoreServiceDelegate* delegate =
109 TabRestoreServiceDelegate::FindDelegateForWebContents(contents);
110 if (closing_delegates_.find(delegate) != closing_delegates_.end())
113 scoped_ptr<Tab> local_tab(new Tab());
114 PopulateTab(local_tab.get(), index, delegate, &contents->GetController());
115 if (local_tab->navigations.empty())
118 AddEntry(local_tab.release(), true, true);
121 void TabRestoreServiceHelper::BrowserClosing(
122 TabRestoreServiceDelegate* delegate) {
123 closing_delegates_.insert(delegate);
125 scoped_ptr<Window> window(new Window());
126 window->selected_tab_index = delegate->GetSelectedIndex();
127 window->timestamp = TimeNow();
128 window->app_name = delegate->GetAppName();
130 // Don't use std::vector::resize() because it will push copies of an empty tab
131 // into the vector, which will give all tabs in a window the same ID.
132 for (int i = 0; i < delegate->GetTabCount(); ++i) {
133 window->tabs.push_back(Tab());
135 size_t entry_index = 0;
136 for (int tab_index = 0; tab_index < delegate->GetTabCount(); ++tab_index) {
137 PopulateTab(&(window->tabs[entry_index]),
140 &delegate->GetWebContentsAt(tab_index)->GetController());
141 if (window->tabs[entry_index].navigations.empty()) {
142 window->tabs.erase(window->tabs.begin() + entry_index);
144 window->tabs[entry_index].browser_id = delegate->GetSessionID().id();
148 if (window->tabs.size() == 1 && window->app_name.empty()) {
149 // Short-circuit creating a Window if only 1 tab was present. This fixes
150 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
152 AddEntry(new Tab(window->tabs[0]), true, true);
153 } else if (!window->tabs.empty()) {
154 window->selected_tab_index =
155 std::min(static_cast<int>(window->tabs.size() - 1),
156 window->selected_tab_index);
157 AddEntry(window.release(), true, true);
161 void TabRestoreServiceHelper::BrowserClosed(
162 TabRestoreServiceDelegate* delegate) {
163 closing_delegates_.erase(delegate);
166 void TabRestoreServiceHelper::ClearEntries() {
168 observer_->OnClearEntries();
169 STLDeleteElements(&entries_);
173 const TabRestoreService::Entries& TabRestoreServiceHelper::entries() const {
177 std::vector<content::WebContents*>
178 TabRestoreServiceHelper::RestoreMostRecentEntry(
179 TabRestoreServiceDelegate* delegate,
180 chrome::HostDesktopType host_desktop_type) {
181 if (entries_.empty())
182 return std::vector<WebContents*>();
184 return RestoreEntryById(delegate, entries_.front()->id, host_desktop_type,
188 TabRestoreService::Tab* TabRestoreServiceHelper::RemoveTabEntryById(
189 SessionID::id_type id) {
190 Entries::iterator i = GetEntryIteratorById(id);
191 if (i == entries_.end())
195 if (entry->type != TabRestoreService::TAB)
198 Tab* tab = static_cast<Tab*>(entry);
203 std::vector<content::WebContents*> TabRestoreServiceHelper::RestoreEntryById(
204 TabRestoreServiceDelegate* delegate,
205 SessionID::id_type id,
206 chrome::HostDesktopType host_desktop_type,
207 WindowOpenDisposition disposition) {
208 Entries::iterator entry_iterator = GetEntryIteratorById(id);
209 if (entry_iterator == entries_.end())
210 // Don't hoark here, we allow an invalid id.
211 return std::vector<WebContents*>();
214 observer_->OnRestoreEntryById(id, entry_iterator);
216 Entry* entry = *entry_iterator;
218 // If the entry's ID does not match the ID that is being restored, then the
219 // entry is a window from which a single tab will be restored.
220 bool restoring_tab_in_window = entry->id != id;
222 if (!restoring_tab_in_window) {
223 entries_.erase(entry_iterator);
224 entry_iterator = entries_.end();
227 // |delegate| will be NULL in cases where one isn't already available (eg,
228 // when invoked on Mac OS X with no windows open). In this case, create a
229 // new browser into which we restore the tabs.
230 std::vector<WebContents*> web_contents;
231 if (entry->type == TabRestoreService::TAB) {
232 Tab* tab = static_cast<Tab*>(entry);
233 WebContents* restored_tab = NULL;
234 delegate = RestoreTab(*tab, delegate, host_desktop_type, disposition,
236 web_contents.push_back(restored_tab);
237 delegate->ShowBrowserWindow();
238 } else if (entry->type == TabRestoreService::WINDOW) {
239 TabRestoreServiceDelegate* current_delegate = delegate;
240 Window* window = static_cast<Window*>(entry);
242 // When restoring a window, either the entire window can be restored, or a
243 // single tab within it. If the entry's ID matches the one to restore, then
244 // the entire window will be restored.
245 if (!restoring_tab_in_window) {
246 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
248 for (size_t tab_i = 0; tab_i < window->tabs.size(); ++tab_i) {
249 const Tab& tab = window->tabs[tab_i];
250 WebContents* restored_tab = delegate->AddRestoredTab(
252 delegate->GetTabCount(),
253 tab.current_navigation_index,
254 tab.extension_app_id,
255 static_cast<int>(tab_i) == window->selected_tab_index,
257 tab.from_last_session,
258 tab.session_storage_namespace.get(),
259 tab.user_agent_override);
261 restored_tab->GetController().LoadIfNecessary();
262 RecordAppLaunch(profile_, tab);
263 web_contents.push_back(restored_tab);
266 // All the window's tabs had the same former browser_id.
267 if (window->tabs[0].has_browser()) {
268 UpdateTabBrowserIDs(window->tabs[0].browser_id,
269 delegate->GetSessionID().id());
272 // Restore a single tab from the window. Find the tab that matches the ID
273 // in the window and restore it.
274 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
275 tab_i != window->tabs.end(); ++tab_i) {
276 const Tab& tab = *tab_i;
278 WebContents* restored_tab = NULL;
279 delegate = RestoreTab(tab, delegate, host_desktop_type, disposition,
281 web_contents.push_back(restored_tab);
282 window->tabs.erase(tab_i);
283 // If restoring the tab leaves the window with nothing else, delete it
285 if (!window->tabs.size()) {
286 entries_.erase(entry_iterator);
289 // Update the browser ID of the rest of the tabs in the window so if
290 // any one is restored, it goes into the same window as the tab
291 // being restored now.
292 UpdateTabBrowserIDs(tab.browser_id,
293 delegate->GetSessionID().id());
294 for (std::vector<Tab>::iterator tab_j = window->tabs.begin();
295 tab_j != window->tabs.end(); ++tab_j) {
296 (*tab_j).browser_id = delegate->GetSessionID().id();
303 delegate->ShowBrowserWindow();
305 if (disposition == CURRENT_TAB && current_delegate &&
306 current_delegate->GetActiveWebContents()) {
307 current_delegate->CloseTab();
313 if (!restoring_tab_in_window) {
322 void TabRestoreServiceHelper::NotifyTabsChanged() {
323 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
324 TabRestoreServiceChanged(tab_restore_service_));
327 void TabRestoreServiceHelper::NotifyLoaded() {
328 FOR_EACH_OBSERVER(TabRestoreServiceObserver, observer_list_,
329 TabRestoreServiceLoaded(tab_restore_service_));
332 void TabRestoreServiceHelper::AddEntry(Entry* entry,
335 if (!FilterEntry(entry) || (entries_.size() >= kMaxEntries && !to_front)) {
341 entries_.push_front(entry);
343 entries_.push_back(entry);
351 observer_->OnAddEntry();
354 void TabRestoreServiceHelper::PruneEntries() {
357 for (TabRestoreService::Entries::const_iterator iter = entries_.begin();
358 iter != entries_.end(); ++iter) {
359 TabRestoreService::Entry* entry = *iter;
361 if (FilterEntry(entry) &&
362 new_entries.size() < kMaxEntries) {
363 new_entries.push_back(entry);
369 entries_ = new_entries;
372 TabRestoreService::Entries::iterator
373 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id) {
374 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
378 // For Window entries, see if the ID matches a tab. If so, report the window
380 if ((*i)->type == TabRestoreService::WINDOW) {
381 std::vector<Tab>& tabs = static_cast<Window*>(*i)->tabs;
382 for (std::vector<Tab>::iterator j = tabs.begin();
383 j != tabs.end(); ++j) {
390 return entries_.end();
394 bool TabRestoreServiceHelper::ValidateEntry(Entry* entry) {
395 if (entry->type == TabRestoreService::TAB)
396 return ValidateTab(static_cast<Tab*>(entry));
398 if (entry->type == TabRestoreService::WINDOW)
399 return ValidateWindow(static_cast<Window*>(entry));
405 void TabRestoreServiceHelper::PopulateTab(
408 TabRestoreServiceDelegate* delegate,
409 NavigationController* controller) {
410 const int pending_index = controller->GetPendingEntryIndex();
411 int entry_count = controller->GetEntryCount();
412 if (entry_count == 0 && pending_index == 0)
414 tab->navigations.resize(static_cast<int>(entry_count));
415 for (int i = 0; i < entry_count; ++i) {
416 NavigationEntry* entry = (i == pending_index) ?
417 controller->GetPendingEntry() : controller->GetEntryAtIndex(i);
418 tab->navigations[i] =
419 sessions::SerializedNavigationEntry::FromNavigationEntry(i, *entry);
421 tab->timestamp = TimeNow();
422 tab->current_navigation_index = controller->GetCurrentEntryIndex();
423 if (tab->current_navigation_index == -1 && entry_count > 0)
424 tab->current_navigation_index = 0;
425 tab->tabstrip_index = index;
427 #if defined(ENABLE_EXTENSIONS)
428 extensions::TabHelper* extensions_tab_helper =
429 extensions::TabHelper::FromWebContents(controller->GetWebContents());
430 // extensions_tab_helper is NULL in some browser tests.
431 if (extensions_tab_helper) {
432 const extensions::Extension* extension =
433 extensions_tab_helper->extension_app();
435 tab->extension_app_id = extension->id();
439 tab->user_agent_override =
440 controller->GetWebContents()->GetUserAgentOverride();
442 // TODO(ajwong): This does not correctly handle storage for isolated apps.
443 tab->session_storage_namespace =
444 controller->GetDefaultSessionStorageNamespace();
446 // Delegate may be NULL during unit tests.
448 tab->browser_id = delegate->GetSessionID().id();
449 tab->pinned = delegate->IsTabPinned(tab->tabstrip_index);
453 TabRestoreServiceDelegate* TabRestoreServiceHelper::RestoreTab(
455 TabRestoreServiceDelegate* delegate,
456 chrome::HostDesktopType host_desktop_type,
457 WindowOpenDisposition disposition,
458 WebContents** contents) {
459 WebContents* web_contents;
460 if (disposition == CURRENT_TAB && delegate) {
461 web_contents = delegate->ReplaceRestoredTab(
463 tab.current_navigation_index,
464 tab.from_last_session,
465 tab.extension_app_id,
466 tab.session_storage_namespace.get(),
467 tab.user_agent_override);
469 // We only respsect the tab's original browser if there's no disposition.
470 if (disposition == UNKNOWN && tab.has_browser()) {
471 delegate = TabRestoreServiceDelegate::FindDelegateWithID(
472 tab.browser_id, host_desktop_type);
477 // |delegate| will be NULL in cases where one isn't already available (eg,
478 // when invoked on Mac OS X with no windows open). In this case, create a
479 // new browser into which we restore the tabs.
480 if (delegate && disposition != NEW_WINDOW) {
481 tab_index = tab.tabstrip_index;
483 delegate = TabRestoreServiceDelegate::Create(profile_, host_desktop_type,
485 if (tab.has_browser())
486 UpdateTabBrowserIDs(tab.browser_id, delegate->GetSessionID().id());
489 // Place the tab at the end if the tab index is no longer valid or
490 // we were passed a specific disposition.
491 if (tab_index < 0 || tab_index > delegate->GetTabCount() ||
492 disposition != UNKNOWN) {
493 tab_index = delegate->GetTabCount();
496 web_contents = delegate->AddRestoredTab(tab.navigations,
498 tab.current_navigation_index,
499 tab.extension_app_id,
500 disposition != NEW_BACKGROUND_TAB,
502 tab.from_last_session,
503 tab.session_storage_namespace.get(),
504 tab.user_agent_override);
505 web_contents->GetController().LoadIfNecessary();
507 RecordAppLaunch(profile_, tab);
509 *contents = web_contents;
515 bool TabRestoreServiceHelper::ValidateTab(Tab* tab) {
516 if (tab->navigations.empty())
519 tab->current_navigation_index =
520 std::max(0, std::min(tab->current_navigation_index,
521 static_cast<int>(tab->navigations.size()) - 1));
526 bool TabRestoreServiceHelper::ValidateWindow(Window* window) {
527 window->selected_tab_index =
528 std::max(0, std::min(window->selected_tab_index,
529 static_cast<int>(window->tabs.size() - 1)));
532 for (std::vector<Tab>::iterator tab_i = window->tabs.begin();
533 tab_i != window->tabs.end();) {
534 if (!ValidateTab(&(*tab_i))) {
535 tab_i = window->tabs.erase(tab_i);
536 if (i < window->selected_tab_index)
537 window->selected_tab_index--;
538 else if (i == window->selected_tab_index)
539 window->selected_tab_index = 0;
546 if (window->tabs.empty())
552 bool TabRestoreServiceHelper::IsTabInteresting(const Tab* tab) {
553 if (tab->navigations.empty())
556 if (tab->navigations.size() > 1)
559 return tab->pinned ||
560 tab->navigations.at(0).virtual_url() !=
561 GURL(chrome::kChromeUINewTabURL);
564 bool TabRestoreServiceHelper::IsWindowInteresting(const Window* window) {
565 if (window->tabs.empty())
568 if (window->tabs.size() > 1)
571 return IsTabInteresting(&window->tabs[0]);
574 bool TabRestoreServiceHelper::FilterEntry(Entry* entry) {
575 if (!ValidateEntry(entry))
578 if (entry->type == TabRestoreService::TAB)
579 return IsTabInteresting(static_cast<Tab*>(entry));
580 else if (entry->type == TabRestoreService::WINDOW)
581 return IsWindowInteresting(static_cast<Window*>(entry));
587 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id,
588 SessionID::id_type new_id) {
589 for (Entries::iterator i = entries_.begin(); i != entries_.end(); ++i) {
591 if (entry->type == TabRestoreService::TAB) {
592 Tab* tab = static_cast<Tab*>(entry);
593 if (tab->browser_id == old_id)
594 tab->browser_id = new_id;
599 base::Time TabRestoreServiceHelper::TimeNow() const {
600 return time_factory_ ? time_factory_->TimeNow() : base::Time::Now();