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/extensions/tab_helper.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/sessions/session_types.h"
16 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
17 #include "chrome/browser/sessions/tab_restore_service_observer.h"
18 #include "chrome/common/extensions/extension_constants.h"
19 #include "chrome/common/url_constants.h"
20 #include "content/public/browser/navigation_controller.h"
21 #include "content/public/browser/navigation_entry.h"
22 #include "content/public/browser/session_storage_namespace.h"
23 #include "content/public/browser/web_contents.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_set.h"
28 #if !defined(OS_ANDROID)
29 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
32 using content::NavigationController
;
33 using content::NavigationEntry
;
34 using content::WebContents
;
38 void RecordAppLaunch(Profile
* profile
, const TabRestoreService::Tab
& tab
) {
39 #if !defined(OS_ANDROID)
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
);
47 CoreAppLauncherHandler::RecordAppLaunchType(
48 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED
,
49 extension
->GetType());
50 #endif // !defined(OS_ANDROID)
55 // TabRestoreServiceHelper::Observer -------------------------------------------
57 TabRestoreServiceHelper::Observer::~Observer() {}
59 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
61 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
62 SessionID::id_type id
,
63 Entries::const_iterator entry_iterator
) {
66 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
68 // TabRestoreServiceHelper -----------------------------------------------------
70 TabRestoreServiceHelper::TabRestoreServiceHelper(
71 TabRestoreService
* tab_restore_service
,
74 TabRestoreService::TimeFactory
* time_factory
)
75 : tab_restore_service_(tab_restore_service
),
79 time_factory_(time_factory
) {
80 DCHECK(tab_restore_service_
);
83 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
84 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
85 TabRestoreServiceDestroyed(tab_restore_service_
));
86 STLDeleteElements(&entries_
);
89 void TabRestoreServiceHelper::AddObserver(
90 TabRestoreServiceObserver
* observer
) {
91 observer_list_
.AddObserver(observer
);
94 void TabRestoreServiceHelper::RemoveObserver(
95 TabRestoreServiceObserver
* observer
) {
96 observer_list_
.RemoveObserver(observer
);
99 void TabRestoreServiceHelper::CreateHistoricalTab(
100 content::WebContents
* contents
,
105 TabRestoreServiceDelegate
* delegate
=
106 TabRestoreServiceDelegate::FindDelegateForWebContents(contents
);
107 if (closing_delegates_
.find(delegate
) != closing_delegates_
.end())
110 scoped_ptr
<Tab
> local_tab(new Tab());
111 PopulateTab(local_tab
.get(), index
, delegate
, &contents
->GetController());
112 if (local_tab
->navigations
.empty())
115 AddEntry(local_tab
.release(), true, true);
118 void TabRestoreServiceHelper::BrowserClosing(
119 TabRestoreServiceDelegate
* delegate
) {
120 closing_delegates_
.insert(delegate
);
122 scoped_ptr
<Window
> window(new Window());
123 window
->selected_tab_index
= delegate
->GetSelectedIndex();
124 window
->timestamp
= TimeNow();
125 window
->app_name
= delegate
->GetAppName();
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());
132 size_t entry_index
= 0;
133 for (int tab_index
= 0; tab_index
< delegate
->GetTabCount(); ++tab_index
) {
134 PopulateTab(&(window
->tabs
[entry_index
]),
137 &delegate
->GetWebContentsAt(tab_index
)->GetController());
138 if (window
->tabs
[entry_index
].navigations
.empty()) {
139 window
->tabs
.erase(window
->tabs
.begin() + entry_index
);
141 window
->tabs
[entry_index
].browser_id
= delegate
->GetSessionID().id();
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
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);
158 void TabRestoreServiceHelper::BrowserClosed(
159 TabRestoreServiceDelegate
* delegate
) {
160 closing_delegates_
.erase(delegate
);
163 void TabRestoreServiceHelper::ClearEntries() {
165 observer_
->OnClearEntries();
166 STLDeleteElements(&entries_
);
170 const TabRestoreService::Entries
& TabRestoreServiceHelper::entries() const {
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
*>();
181 return RestoreEntryById(delegate
, entries_
.front()->id
, host_desktop_type
,
185 TabRestoreService::Tab
* TabRestoreServiceHelper::RemoveTabEntryById(
186 SessionID::id_type id
) {
187 Entries::iterator i
= GetEntryIteratorById(id
);
188 if (i
== entries_
.end())
192 if (entry
->type
!= TabRestoreService::TAB
)
195 Tab
* tab
= static_cast<Tab
*>(entry
);
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
*>();
211 observer_
->OnRestoreEntryById(id
, entry_iterator
);
213 Entry
* entry
= *entry_iterator
;
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
;
219 if (!restoring_tab_in_window
) {
220 entries_
.erase(entry_iterator
);
221 entry_iterator
= entries_
.end();
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
,
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
);
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
,
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(
249 delegate
->GetTabCount(),
250 tab
.current_navigation_index
,
251 tab
.extension_app_id
,
252 static_cast<int>(tab_i
) == window
->selected_tab_index
,
254 tab
.from_last_session
,
255 tab
.session_storage_namespace
.get(),
256 tab
.user_agent_override
);
258 restored_tab
->GetController().LoadIfNecessary();
259 RecordAppLaunch(profile_
, tab
);
260 web_contents
.push_back(restored_tab
);
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());
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
;
275 WebContents
* restored_tab
= NULL
;
276 delegate
= RestoreTab(tab
, delegate
, host_desktop_type
, disposition
,
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
282 if (!window
->tabs
.size()) {
283 entries_
.erase(entry_iterator
);
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();
300 delegate
->ShowBrowserWindow();
302 if (disposition
== CURRENT_TAB
&& current_delegate
&&
303 current_delegate
->GetActiveWebContents()) {
304 current_delegate
->CloseTab();
310 if (!restoring_tab_in_window
) {
319 void TabRestoreServiceHelper::NotifyTabsChanged() {
320 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
321 TabRestoreServiceChanged(tab_restore_service_
));
324 void TabRestoreServiceHelper::NotifyLoaded() {
325 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
326 TabRestoreServiceLoaded(tab_restore_service_
));
329 void TabRestoreServiceHelper::AddEntry(Entry
* entry
,
332 if (!FilterEntry(entry
) || (entries_
.size() >= kMaxEntries
&& !to_front
)) {
338 entries_
.push_front(entry
);
340 entries_
.push_back(entry
);
348 observer_
->OnAddEntry();
351 void TabRestoreServiceHelper::PruneEntries() {
354 for (TabRestoreService::Entries::const_iterator iter
= entries_
.begin();
355 iter
!= entries_
.end(); ++iter
) {
356 TabRestoreService::Entry
* entry
= *iter
;
358 if (FilterEntry(entry
) &&
359 new_entries
.size() < kMaxEntries
) {
360 new_entries
.push_back(entry
);
366 entries_
= new_entries
;
369 TabRestoreService::Entries::iterator
370 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id
) {
371 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
375 // For Window entries, see if the ID matches a tab. If so, report the window
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
) {
387 return entries_
.end();
391 bool TabRestoreServiceHelper::ValidateEntry(Entry
* entry
) {
392 if (entry
->type
== TabRestoreService::TAB
)
393 return ValidateTab(static_cast<Tab
*>(entry
));
395 if (entry
->type
== TabRestoreService::WINDOW
)
396 return ValidateWindow(static_cast<Window
*>(entry
));
402 void TabRestoreServiceHelper::PopulateTab(
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)
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
);
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
;
424 extensions::TabHelper
* extensions_tab_helper
=
425 extensions::TabHelper::FromWebContents(controller
->GetWebContents());
426 // extensions_tab_helper is NULL in some browser tests.
427 if (extensions_tab_helper
) {
428 const extensions::Extension
* extension
=
429 extensions_tab_helper
->extension_app();
431 tab
->extension_app_id
= extension
->id();
434 tab
->user_agent_override
=
435 controller
->GetWebContents()->GetUserAgentOverride();
437 // TODO(ajwong): This does not correctly handle storage for isolated apps.
438 tab
->session_storage_namespace
=
439 controller
->GetDefaultSessionStorageNamespace();
441 // Delegate may be NULL during unit tests.
443 tab
->browser_id
= delegate
->GetSessionID().id();
444 tab
->pinned
= delegate
->IsTabPinned(tab
->tabstrip_index
);
448 TabRestoreServiceDelegate
* TabRestoreServiceHelper::RestoreTab(
450 TabRestoreServiceDelegate
* delegate
,
451 chrome::HostDesktopType host_desktop_type
,
452 WindowOpenDisposition disposition
,
453 WebContents
** contents
) {
454 WebContents
* web_contents
;
455 if (disposition
== CURRENT_TAB
&& delegate
) {
456 web_contents
= delegate
->ReplaceRestoredTab(
458 tab
.current_navigation_index
,
459 tab
.from_last_session
,
460 tab
.extension_app_id
,
461 tab
.session_storage_namespace
.get(),
462 tab
.user_agent_override
);
464 // We only respsect the tab's original browser if there's no disposition.
465 if (disposition
== UNKNOWN
&& tab
.has_browser()) {
466 delegate
= TabRestoreServiceDelegate::FindDelegateWithID(
467 tab
.browser_id
, host_desktop_type
);
472 // |delegate| will be NULL in cases where one isn't already available (eg,
473 // when invoked on Mac OS X with no windows open). In this case, create a
474 // new browser into which we restore the tabs.
475 if (delegate
&& disposition
!= NEW_WINDOW
) {
476 tab_index
= tab
.tabstrip_index
;
478 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
480 if (tab
.has_browser())
481 UpdateTabBrowserIDs(tab
.browser_id
, delegate
->GetSessionID().id());
484 // Place the tab at the end if the tab index is no longer valid or
485 // we were passed a specific disposition.
486 if (tab_index
< 0 || tab_index
> delegate
->GetTabCount() ||
487 disposition
!= UNKNOWN
) {
488 tab_index
= delegate
->GetTabCount();
491 web_contents
= delegate
->AddRestoredTab(tab
.navigations
,
493 tab
.current_navigation_index
,
494 tab
.extension_app_id
,
495 disposition
!= NEW_BACKGROUND_TAB
,
497 tab
.from_last_session
,
498 tab
.session_storage_namespace
.get(),
499 tab
.user_agent_override
);
500 web_contents
->GetController().LoadIfNecessary();
502 RecordAppLaunch(profile_
, tab
);
504 *contents
= web_contents
;
510 bool TabRestoreServiceHelper::ValidateTab(Tab
* tab
) {
511 if (tab
->navigations
.empty())
514 tab
->current_navigation_index
=
515 std::max(0, std::min(tab
->current_navigation_index
,
516 static_cast<int>(tab
->navigations
.size()) - 1));
521 bool TabRestoreServiceHelper::ValidateWindow(Window
* window
) {
522 window
->selected_tab_index
=
523 std::max(0, std::min(window
->selected_tab_index
,
524 static_cast<int>(window
->tabs
.size() - 1)));
527 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
528 tab_i
!= window
->tabs
.end();) {
529 if (!ValidateTab(&(*tab_i
))) {
530 tab_i
= window
->tabs
.erase(tab_i
);
531 if (i
< window
->selected_tab_index
)
532 window
->selected_tab_index
--;
533 else if (i
== window
->selected_tab_index
)
534 window
->selected_tab_index
= 0;
541 if (window
->tabs
.empty())
547 bool TabRestoreServiceHelper::IsTabInteresting(const Tab
* tab
) {
548 if (tab
->navigations
.empty())
551 if (tab
->navigations
.size() > 1)
554 return tab
->pinned
||
555 tab
->navigations
.at(0).virtual_url() !=
556 GURL(chrome::kChromeUINewTabURL
);
559 bool TabRestoreServiceHelper::IsWindowInteresting(const Window
* window
) {
560 if (window
->tabs
.empty())
563 if (window
->tabs
.size() > 1)
566 return IsTabInteresting(&window
->tabs
[0]);
569 bool TabRestoreServiceHelper::FilterEntry(Entry
* entry
) {
570 if (!ValidateEntry(entry
))
573 if (entry
->type
== TabRestoreService::TAB
)
574 return IsTabInteresting(static_cast<Tab
*>(entry
));
575 else if (entry
->type
== TabRestoreService::WINDOW
)
576 return IsWindowInteresting(static_cast<Window
*>(entry
));
582 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id
,
583 SessionID::id_type new_id
) {
584 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
586 if (entry
->type
== TabRestoreService::TAB
) {
587 Tab
* tab
= static_cast<Tab
*>(entry
);
588 if (tab
->browser_id
== old_id
)
589 tab
->browser_id
= new_id
;
594 base::Time
TabRestoreServiceHelper::TimeNow() const {
595 return time_factory_
? time_factory_
->TimeNow() : base::Time::Now();