1 // Copyright 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/tab_restore_service_delegate.h"
15 #include "chrome/browser/sessions/tab_restore_service_observer.h"
16 #include "chrome/common/url_constants.h"
17 #include "components/sessions/content/content_serialized_navigation_builder.h"
18 #include "components/sessions/session_types.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"
24 #if defined(ENABLE_EXTENSIONS)
25 #include "chrome/browser/extensions/tab_helper.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chrome/common/extensions/extension_metrics.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/extension_set.h"
33 using content::NavigationController
;
34 using content::NavigationEntry
;
35 using content::WebContents
;
39 void RecordAppLaunch(Profile
* profile
, const TabRestoreService::Tab
& tab
) {
40 #if defined(ENABLE_EXTENSIONS)
41 GURL url
= tab
.navigations
.at(tab
.current_navigation_index
).virtual_url();
42 const extensions::Extension
* extension
=
43 extensions::ExtensionRegistry::Get(profile
)
44 ->enabled_extensions().GetAppByURL(url
);
48 extensions::RecordAppLaunchType(
49 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED
,
50 extension
->GetType());
51 #endif // defined(ENABLE_EXTENSIONS)
56 // TabRestoreServiceHelper::Observer -------------------------------------------
58 TabRestoreServiceHelper::Observer::~Observer() {}
60 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
62 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
63 SessionID::id_type id
,
64 Entries::const_iterator entry_iterator
) {
67 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
69 // TabRestoreServiceHelper -----------------------------------------------------
71 TabRestoreServiceHelper::TabRestoreServiceHelper(
72 TabRestoreService
* tab_restore_service
,
75 TabRestoreService::TimeFactory
* time_factory
)
76 : tab_restore_service_(tab_restore_service
),
80 time_factory_(time_factory
) {
81 DCHECK(tab_restore_service_
);
84 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
85 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
86 TabRestoreServiceDestroyed(tab_restore_service_
));
87 STLDeleteElements(&entries_
);
90 void TabRestoreServiceHelper::AddObserver(
91 TabRestoreServiceObserver
* observer
) {
92 observer_list_
.AddObserver(observer
);
95 void TabRestoreServiceHelper::RemoveObserver(
96 TabRestoreServiceObserver
* observer
) {
97 observer_list_
.RemoveObserver(observer
);
100 void TabRestoreServiceHelper::CreateHistoricalTab(
101 content::WebContents
* contents
,
106 TabRestoreServiceDelegate
* delegate
=
107 TabRestoreServiceDelegate::FindDelegateForWebContents(contents
);
108 if (closing_delegates_
.find(delegate
) != closing_delegates_
.end())
111 scoped_ptr
<Tab
> local_tab(new Tab());
112 PopulateTab(local_tab
.get(), index
, delegate
, &contents
->GetController());
113 if (local_tab
->navigations
.empty())
116 AddEntry(local_tab
.release(), true, true);
119 void TabRestoreServiceHelper::BrowserClosing(
120 TabRestoreServiceDelegate
* delegate
) {
121 closing_delegates_
.insert(delegate
);
123 scoped_ptr
<Window
> window(new Window());
124 window
->selected_tab_index
= delegate
->GetSelectedIndex();
125 window
->timestamp
= TimeNow();
126 window
->app_name
= delegate
->GetAppName();
128 // Don't use std::vector::resize() because it will push copies of an empty tab
129 // into the vector, which will give all tabs in a window the same ID.
130 for (int i
= 0; i
< delegate
->GetTabCount(); ++i
) {
131 window
->tabs
.push_back(Tab());
133 size_t entry_index
= 0;
134 for (int tab_index
= 0; tab_index
< delegate
->GetTabCount(); ++tab_index
) {
135 PopulateTab(&(window
->tabs
[entry_index
]),
138 &delegate
->GetWebContentsAt(tab_index
)->GetController());
139 if (window
->tabs
[entry_index
].navigations
.empty()) {
140 window
->tabs
.erase(window
->tabs
.begin() + entry_index
);
142 window
->tabs
[entry_index
].browser_id
= delegate
->GetSessionID().id();
146 if (window
->tabs
.size() == 1 && window
->app_name
.empty()) {
147 // Short-circuit creating a Window if only 1 tab was present. This fixes
148 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
150 AddEntry(new Tab(window
->tabs
[0]), true, true);
151 } else if (!window
->tabs
.empty()) {
152 window
->selected_tab_index
=
153 std::min(static_cast<int>(window
->tabs
.size() - 1),
154 window
->selected_tab_index
);
155 AddEntry(window
.release(), true, true);
159 void TabRestoreServiceHelper::BrowserClosed(
160 TabRestoreServiceDelegate
* delegate
) {
161 closing_delegates_
.erase(delegate
);
164 void TabRestoreServiceHelper::ClearEntries() {
166 observer_
->OnClearEntries();
167 STLDeleteElements(&entries_
);
171 const TabRestoreService::Entries
& TabRestoreServiceHelper::entries() const {
175 std::vector
<content::WebContents
*>
176 TabRestoreServiceHelper::RestoreMostRecentEntry(
177 TabRestoreServiceDelegate
* delegate
,
178 chrome::HostDesktopType host_desktop_type
) {
179 if (entries_
.empty())
180 return std::vector
<WebContents
*>();
182 return RestoreEntryById(delegate
, entries_
.front()->id
, host_desktop_type
,
186 TabRestoreService::Tab
* TabRestoreServiceHelper::RemoveTabEntryById(
187 SessionID::id_type id
) {
188 Entries::iterator i
= GetEntryIteratorById(id
);
189 if (i
== entries_
.end())
193 if (entry
->type
!= TabRestoreService::TAB
)
196 Tab
* tab
= static_cast<Tab
*>(entry
);
201 std::vector
<content::WebContents
*> TabRestoreServiceHelper::RestoreEntryById(
202 TabRestoreServiceDelegate
* delegate
,
203 SessionID::id_type id
,
204 chrome::HostDesktopType host_desktop_type
,
205 WindowOpenDisposition disposition
) {
206 Entries::iterator entry_iterator
= GetEntryIteratorById(id
);
207 if (entry_iterator
== entries_
.end())
208 // Don't hoark here, we allow an invalid id.
209 return std::vector
<WebContents
*>();
212 observer_
->OnRestoreEntryById(id
, entry_iterator
);
214 Entry
* entry
= *entry_iterator
;
216 // If the entry's ID does not match the ID that is being restored, then the
217 // entry is a window from which a single tab will be restored.
218 bool restoring_tab_in_window
= entry
->id
!= id
;
220 if (!restoring_tab_in_window
) {
221 entries_
.erase(entry_iterator
);
222 entry_iterator
= entries_
.end();
225 // |delegate| will be NULL in cases where one isn't already available (eg,
226 // when invoked on Mac OS X with no windows open). In this case, create a
227 // new browser into which we restore the tabs.
228 std::vector
<WebContents
*> web_contents
;
229 if (entry
->type
== TabRestoreService::TAB
) {
230 Tab
* tab
= static_cast<Tab
*>(entry
);
231 WebContents
* restored_tab
= NULL
;
232 delegate
= RestoreTab(*tab
, delegate
, host_desktop_type
, disposition
,
234 web_contents
.push_back(restored_tab
);
235 delegate
->ShowBrowserWindow();
236 } else if (entry
->type
== TabRestoreService::WINDOW
) {
237 TabRestoreServiceDelegate
* current_delegate
= delegate
;
238 Window
* window
= static_cast<Window
*>(entry
);
240 // When restoring a window, either the entire window can be restored, or a
241 // single tab within it. If the entry's ID matches the one to restore, then
242 // the entire window will be restored.
243 if (!restoring_tab_in_window
) {
244 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
246 for (size_t tab_i
= 0; tab_i
< window
->tabs
.size(); ++tab_i
) {
247 const Tab
& tab
= window
->tabs
[tab_i
];
248 WebContents
* restored_tab
= delegate
->AddRestoredTab(
250 delegate
->GetTabCount(),
251 tab
.current_navigation_index
,
252 tab
.extension_app_id
,
253 static_cast<int>(tab_i
) == window
->selected_tab_index
,
255 tab
.from_last_session
,
256 tab
.session_storage_namespace
.get(),
257 tab
.user_agent_override
);
259 restored_tab
->GetController().LoadIfNecessary();
260 RecordAppLaunch(profile_
, tab
);
261 web_contents
.push_back(restored_tab
);
264 // All the window's tabs had the same former browser_id.
265 if (window
->tabs
[0].has_browser()) {
266 UpdateTabBrowserIDs(window
->tabs
[0].browser_id
,
267 delegate
->GetSessionID().id());
270 // Restore a single tab from the window. Find the tab that matches the ID
271 // in the window and restore it.
272 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
273 tab_i
!= window
->tabs
.end(); ++tab_i
) {
274 const Tab
& tab
= *tab_i
;
276 WebContents
* restored_tab
= NULL
;
277 delegate
= RestoreTab(tab
, delegate
, host_desktop_type
, disposition
,
279 web_contents
.push_back(restored_tab
);
280 window
->tabs
.erase(tab_i
);
281 // If restoring the tab leaves the window with nothing else, delete it
283 if (!window
->tabs
.size()) {
284 entries_
.erase(entry_iterator
);
287 // Update the browser ID of the rest of the tabs in the window so if
288 // any one is restored, it goes into the same window as the tab
289 // being restored now.
290 UpdateTabBrowserIDs(tab
.browser_id
,
291 delegate
->GetSessionID().id());
292 for (std::vector
<Tab
>::iterator tab_j
= window
->tabs
.begin();
293 tab_j
!= window
->tabs
.end(); ++tab_j
) {
294 (*tab_j
).browser_id
= delegate
->GetSessionID().id();
301 delegate
->ShowBrowserWindow();
303 if (disposition
== CURRENT_TAB
&& current_delegate
&&
304 current_delegate
->GetActiveWebContents()) {
305 current_delegate
->CloseTab();
311 if (!restoring_tab_in_window
) {
320 void TabRestoreServiceHelper::NotifyTabsChanged() {
321 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
322 TabRestoreServiceChanged(tab_restore_service_
));
325 void TabRestoreServiceHelper::NotifyLoaded() {
326 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
327 TabRestoreServiceLoaded(tab_restore_service_
));
330 void TabRestoreServiceHelper::AddEntry(Entry
* entry
,
333 if (!FilterEntry(entry
) || (entries_
.size() >= kMaxEntries
&& !to_front
)) {
339 entries_
.push_front(entry
);
341 entries_
.push_back(entry
);
349 observer_
->OnAddEntry();
352 void TabRestoreServiceHelper::PruneEntries() {
355 for (TabRestoreService::Entries::const_iterator iter
= entries_
.begin();
356 iter
!= entries_
.end(); ++iter
) {
357 TabRestoreService::Entry
* entry
= *iter
;
359 if (FilterEntry(entry
) &&
360 new_entries
.size() < kMaxEntries
) {
361 new_entries
.push_back(entry
);
367 entries_
= new_entries
;
370 TabRestoreService::Entries::iterator
371 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id
) {
372 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
376 // For Window entries, see if the ID matches a tab. If so, report the window
378 if ((*i
)->type
== TabRestoreService::WINDOW
) {
379 std::vector
<Tab
>& tabs
= static_cast<Window
*>(*i
)->tabs
;
380 for (std::vector
<Tab
>::iterator j
= tabs
.begin();
381 j
!= tabs
.end(); ++j
) {
388 return entries_
.end();
392 bool TabRestoreServiceHelper::ValidateEntry(Entry
* entry
) {
393 if (entry
->type
== TabRestoreService::TAB
)
394 return ValidateTab(static_cast<Tab
*>(entry
));
396 if (entry
->type
== TabRestoreService::WINDOW
)
397 return ValidateWindow(static_cast<Window
*>(entry
));
403 void TabRestoreServiceHelper::PopulateTab(
406 TabRestoreServiceDelegate
* delegate
,
407 NavigationController
* controller
) {
408 const int pending_index
= controller
->GetPendingEntryIndex();
409 int entry_count
= controller
->GetEntryCount();
410 if (entry_count
== 0 && pending_index
== 0)
412 tab
->navigations
.resize(static_cast<int>(entry_count
));
413 for (int i
= 0; i
< entry_count
; ++i
) {
414 NavigationEntry
* entry
= (i
== pending_index
) ?
415 controller
->GetPendingEntry() : controller
->GetEntryAtIndex(i
);
416 tab
->navigations
[i
] =
417 sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
420 tab
->timestamp
= TimeNow();
421 tab
->current_navigation_index
= controller
->GetCurrentEntryIndex();
422 if (tab
->current_navigation_index
== -1 && entry_count
> 0)
423 tab
->current_navigation_index
= 0;
424 tab
->tabstrip_index
= index
;
426 #if defined(ENABLE_EXTENSIONS)
427 extensions::TabHelper
* extensions_tab_helper
=
428 extensions::TabHelper::FromWebContents(controller
->GetWebContents());
429 // extensions_tab_helper is NULL in some browser tests.
430 if (extensions_tab_helper
) {
431 const extensions::Extension
* extension
=
432 extensions_tab_helper
->extension_app();
434 tab
->extension_app_id
= extension
->id();
438 tab
->user_agent_override
=
439 controller
->GetWebContents()->GetUserAgentOverride();
441 // TODO(ajwong): This does not correctly handle storage for isolated apps.
442 tab
->session_storage_namespace
=
443 controller
->GetDefaultSessionStorageNamespace();
445 // Delegate may be NULL during unit tests.
447 tab
->browser_id
= delegate
->GetSessionID().id();
448 tab
->pinned
= delegate
->IsTabPinned(tab
->tabstrip_index
);
452 TabRestoreServiceDelegate
* TabRestoreServiceHelper::RestoreTab(
454 TabRestoreServiceDelegate
* delegate
,
455 chrome::HostDesktopType host_desktop_type
,
456 WindowOpenDisposition disposition
,
457 WebContents
** contents
) {
458 WebContents
* web_contents
;
459 if (disposition
== CURRENT_TAB
&& delegate
) {
460 web_contents
= delegate
->ReplaceRestoredTab(
462 tab
.current_navigation_index
,
463 tab
.from_last_session
,
464 tab
.extension_app_id
,
465 tab
.session_storage_namespace
.get(),
466 tab
.user_agent_override
);
468 // We only respsect the tab's original browser if there's no disposition.
469 if (disposition
== UNKNOWN
&& tab
.has_browser()) {
470 delegate
= TabRestoreServiceDelegate::FindDelegateWithID(
471 tab
.browser_id
, host_desktop_type
);
476 // |delegate| will be NULL in cases where one isn't already available (eg,
477 // when invoked on Mac OS X with no windows open). In this case, create a
478 // new browser into which we restore the tabs.
479 if (delegate
&& disposition
!= NEW_WINDOW
) {
480 tab_index
= tab
.tabstrip_index
;
482 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
484 if (tab
.has_browser())
485 UpdateTabBrowserIDs(tab
.browser_id
, delegate
->GetSessionID().id());
488 // Place the tab at the end if the tab index is no longer valid or
489 // we were passed a specific disposition.
490 if (tab_index
< 0 || tab_index
> delegate
->GetTabCount() ||
491 disposition
!= UNKNOWN
) {
492 tab_index
= delegate
->GetTabCount();
495 web_contents
= delegate
->AddRestoredTab(tab
.navigations
,
497 tab
.current_navigation_index
,
498 tab
.extension_app_id
,
499 disposition
!= NEW_BACKGROUND_TAB
,
501 tab
.from_last_session
,
502 tab
.session_storage_namespace
.get(),
503 tab
.user_agent_override
);
504 web_contents
->GetController().LoadIfNecessary();
506 RecordAppLaunch(profile_
, tab
);
508 *contents
= web_contents
;
514 bool TabRestoreServiceHelper::ValidateTab(Tab
* tab
) {
515 if (tab
->navigations
.empty())
518 tab
->current_navigation_index
=
519 std::max(0, std::min(tab
->current_navigation_index
,
520 static_cast<int>(tab
->navigations
.size()) - 1));
525 bool TabRestoreServiceHelper::ValidateWindow(Window
* window
) {
526 window
->selected_tab_index
=
527 std::max(0, std::min(window
->selected_tab_index
,
528 static_cast<int>(window
->tabs
.size() - 1)));
531 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
532 tab_i
!= window
->tabs
.end();) {
533 if (!ValidateTab(&(*tab_i
))) {
534 tab_i
= window
->tabs
.erase(tab_i
);
535 if (i
< window
->selected_tab_index
)
536 window
->selected_tab_index
--;
537 else if (i
== window
->selected_tab_index
)
538 window
->selected_tab_index
= 0;
545 if (window
->tabs
.empty())
551 bool TabRestoreServiceHelper::IsTabInteresting(const Tab
* tab
) {
552 if (tab
->navigations
.empty())
555 if (tab
->navigations
.size() > 1)
558 return tab
->pinned
||
559 tab
->navigations
.at(0).virtual_url() !=
560 GURL(chrome::kChromeUINewTabURL
);
563 bool TabRestoreServiceHelper::IsWindowInteresting(const Window
* window
) {
564 if (window
->tabs
.empty())
567 if (window
->tabs
.size() > 1)
570 return IsTabInteresting(&window
->tabs
[0]);
573 bool TabRestoreServiceHelper::FilterEntry(Entry
* entry
) {
574 if (!ValidateEntry(entry
))
577 if (entry
->type
== TabRestoreService::TAB
)
578 return IsTabInteresting(static_cast<Tab
*>(entry
));
579 else if (entry
->type
== TabRestoreService::WINDOW
)
580 return IsWindowInteresting(static_cast<Window
*>(entry
));
586 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id
,
587 SessionID::id_type new_id
) {
588 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
590 if (entry
->type
== TabRestoreService::TAB
) {
591 Tab
* tab
= static_cast<Tab
*>(entry
);
592 if (tab
->browser_id
== old_id
)
593 tab
->browser_id
= new_id
;
598 base::Time
TabRestoreServiceHelper::TimeNow() const {
599 return time_factory_
? time_factory_
->TimeNow() : base::Time::Now();