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/extension_service.h"
14 #include "chrome/browser/extensions/tab_helper.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_types.h"
17 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
18 #include "chrome/browser/sessions/tab_restore_service_observer.h"
19 #include "chrome/common/extensions/extension_constants.h"
20 #include "chrome/common/url_constants.h"
21 #include "content/public/browser/navigation_controller.h"
22 #include "content/public/browser/navigation_entry.h"
23 #include "content/public/browser/session_storage_namespace.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/extension.h"
27 #if !defined(OS_ANDROID)
28 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
31 using content::NavigationController
;
32 using content::NavigationEntry
;
33 using content::WebContents
;
37 void RecordAppLaunch(Profile
* profile
, const TabRestoreService::Tab
& tab
) {
38 #if !defined(OS_ANDROID)
39 GURL url
= tab
.navigations
.at(tab
.current_navigation_index
).virtual_url();
40 DCHECK(profile
->GetExtensionService());
41 const extensions::Extension
* extension
=
42 profile
->GetExtensionService()->GetInstalledApp(url
);
46 CoreAppLauncherHandler::RecordAppLaunchType(
47 extension_misc::APP_LAUNCH_NTP_RECENTLY_CLOSED
,
48 extension
->GetType());
49 #endif // !defined(OS_ANDROID)
54 // TabRestoreServiceHelper::Observer -------------------------------------------
56 TabRestoreServiceHelper::Observer::~Observer() {}
58 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
60 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
61 SessionID::id_type id
,
62 Entries::const_iterator entry_iterator
) {
65 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
67 // TabRestoreServiceHelper -----------------------------------------------------
69 TabRestoreServiceHelper::TabRestoreServiceHelper(
70 TabRestoreService
* tab_restore_service
,
73 TabRestoreService::TimeFactory
* time_factory
)
74 : tab_restore_service_(tab_restore_service
),
78 time_factory_(time_factory
) {
79 DCHECK(tab_restore_service_
);
82 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
83 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
84 TabRestoreServiceDestroyed(tab_restore_service_
));
85 STLDeleteElements(&entries_
);
88 void TabRestoreServiceHelper::AddObserver(
89 TabRestoreServiceObserver
* observer
) {
90 observer_list_
.AddObserver(observer
);
93 void TabRestoreServiceHelper::RemoveObserver(
94 TabRestoreServiceObserver
* observer
) {
95 observer_list_
.RemoveObserver(observer
);
98 void TabRestoreServiceHelper::CreateHistoricalTab(
99 content::WebContents
* contents
,
104 TabRestoreServiceDelegate
* delegate
=
105 TabRestoreServiceDelegate::FindDelegateForWebContents(contents
);
106 if (closing_delegates_
.find(delegate
) != closing_delegates_
.end())
109 scoped_ptr
<Tab
> local_tab(new Tab());
110 PopulateTab(local_tab
.get(), index
, delegate
, &contents
->GetController());
111 if (local_tab
->navigations
.empty())
114 AddEntry(local_tab
.release(), true, true);
117 void TabRestoreServiceHelper::BrowserClosing(
118 TabRestoreServiceDelegate
* delegate
) {
119 closing_delegates_
.insert(delegate
);
121 scoped_ptr
<Window
> window(new Window());
122 window
->selected_tab_index
= delegate
->GetSelectedIndex();
123 window
->timestamp
= TimeNow();
124 window
->app_name
= delegate
->GetAppName();
126 // Don't use std::vector::resize() because it will push copies of an empty tab
127 // into the vector, which will give all tabs in a window the same ID.
128 for (int i
= 0; i
< delegate
->GetTabCount(); ++i
) {
129 window
->tabs
.push_back(Tab());
131 size_t entry_index
= 0;
132 for (int tab_index
= 0; tab_index
< delegate
->GetTabCount(); ++tab_index
) {
133 PopulateTab(&(window
->tabs
[entry_index
]),
136 &delegate
->GetWebContentsAt(tab_index
)->GetController());
137 if (window
->tabs
[entry_index
].navigations
.empty()) {
138 window
->tabs
.erase(window
->tabs
.begin() + entry_index
);
140 window
->tabs
[entry_index
].browser_id
= delegate
->GetSessionID().id();
144 if (window
->tabs
.size() == 1 && window
->app_name
.empty()) {
145 // Short-circuit creating a Window if only 1 tab was present. This fixes
146 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
148 AddEntry(new Tab(window
->tabs
[0]), true, true);
149 } else if (!window
->tabs
.empty()) {
150 window
->selected_tab_index
=
151 std::min(static_cast<int>(window
->tabs
.size() - 1),
152 window
->selected_tab_index
);
153 AddEntry(window
.release(), true, true);
157 void TabRestoreServiceHelper::BrowserClosed(
158 TabRestoreServiceDelegate
* delegate
) {
159 closing_delegates_
.erase(delegate
);
162 void TabRestoreServiceHelper::ClearEntries() {
164 observer_
->OnClearEntries();
165 STLDeleteElements(&entries_
);
169 const TabRestoreService::Entries
& TabRestoreServiceHelper::entries() const {
173 std::vector
<content::WebContents
*>
174 TabRestoreServiceHelper::RestoreMostRecentEntry(
175 TabRestoreServiceDelegate
* delegate
,
176 chrome::HostDesktopType host_desktop_type
) {
177 if (entries_
.empty())
178 return std::vector
<WebContents
*>();
180 return RestoreEntryById(delegate
, entries_
.front()->id
, host_desktop_type
,
184 TabRestoreService::Tab
* TabRestoreServiceHelper::RemoveTabEntryById(
185 SessionID::id_type id
) {
186 Entries::iterator i
= GetEntryIteratorById(id
);
187 if (i
== entries_
.end())
191 if (entry
->type
!= TabRestoreService::TAB
)
194 Tab
* tab
= static_cast<Tab
*>(entry
);
199 std::vector
<content::WebContents
*> TabRestoreServiceHelper::RestoreEntryById(
200 TabRestoreServiceDelegate
* delegate
,
201 SessionID::id_type id
,
202 chrome::HostDesktopType host_desktop_type
,
203 WindowOpenDisposition disposition
) {
204 Entries::iterator entry_iterator
= GetEntryIteratorById(id
);
205 if (entry_iterator
== entries_
.end())
206 // Don't hoark here, we allow an invalid id.
207 return std::vector
<WebContents
*>();
210 observer_
->OnRestoreEntryById(id
, entry_iterator
);
212 Entry
* entry
= *entry_iterator
;
214 // If the entry's ID does not match the ID that is being restored, then the
215 // entry is a window from which a single tab will be restored.
216 bool restoring_tab_in_window
= entry
->id
!= id
;
218 if (!restoring_tab_in_window
) {
219 entries_
.erase(entry_iterator
);
220 entry_iterator
= entries_
.end();
223 // |delegate| will be NULL in cases where one isn't already available (eg,
224 // when invoked on Mac OS X with no windows open). In this case, create a
225 // new browser into which we restore the tabs.
226 std::vector
<WebContents
*> web_contents
;
227 if (entry
->type
== TabRestoreService::TAB
) {
228 Tab
* tab
= static_cast<Tab
*>(entry
);
229 WebContents
* restored_tab
= NULL
;
230 delegate
= RestoreTab(*tab
, delegate
, host_desktop_type
, disposition
,
232 web_contents
.push_back(restored_tab
);
233 delegate
->ShowBrowserWindow();
234 } else if (entry
->type
== TabRestoreService::WINDOW
) {
235 TabRestoreServiceDelegate
* current_delegate
= delegate
;
236 Window
* window
= static_cast<Window
*>(entry
);
238 // When restoring a window, either the entire window can be restored, or a
239 // single tab within it. If the entry's ID matches the one to restore, then
240 // the entire window will be restored.
241 if (!restoring_tab_in_window
) {
242 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
244 for (size_t tab_i
= 0; tab_i
< window
->tabs
.size(); ++tab_i
) {
245 const Tab
& tab
= window
->tabs
[tab_i
];
246 WebContents
* restored_tab
= delegate
->AddRestoredTab(
248 delegate
->GetTabCount(),
249 tab
.current_navigation_index
,
250 tab
.extension_app_id
,
251 static_cast<int>(tab_i
) == window
->selected_tab_index
,
253 tab
.from_last_session
,
254 tab
.session_storage_namespace
.get(),
255 tab
.user_agent_override
);
257 restored_tab
->GetController().LoadIfNecessary();
258 RecordAppLaunch(profile_
, tab
);
259 web_contents
.push_back(restored_tab
);
262 // All the window's tabs had the same former browser_id.
263 if (window
->tabs
[0].has_browser()) {
264 UpdateTabBrowserIDs(window
->tabs
[0].browser_id
,
265 delegate
->GetSessionID().id());
268 // Restore a single tab from the window. Find the tab that matches the ID
269 // in the window and restore it.
270 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
271 tab_i
!= window
->tabs
.end(); ++tab_i
) {
272 const Tab
& tab
= *tab_i
;
274 WebContents
* restored_tab
= NULL
;
275 delegate
= RestoreTab(tab
, delegate
, host_desktop_type
, disposition
,
277 web_contents
.push_back(restored_tab
);
278 window
->tabs
.erase(tab_i
);
279 // If restoring the tab leaves the window with nothing else, delete it
281 if (!window
->tabs
.size()) {
282 entries_
.erase(entry_iterator
);
285 // Update the browser ID of the rest of the tabs in the window so if
286 // any one is restored, it goes into the same window as the tab
287 // being restored now.
288 UpdateTabBrowserIDs(tab
.browser_id
,
289 delegate
->GetSessionID().id());
290 for (std::vector
<Tab
>::iterator tab_j
= window
->tabs
.begin();
291 tab_j
!= window
->tabs
.end(); ++tab_j
) {
292 (*tab_j
).browser_id
= delegate
->GetSessionID().id();
299 delegate
->ShowBrowserWindow();
301 if (disposition
== CURRENT_TAB
&& current_delegate
&&
302 current_delegate
->GetActiveWebContents()) {
303 current_delegate
->CloseTab();
309 if (!restoring_tab_in_window
) {
318 void TabRestoreServiceHelper::NotifyTabsChanged() {
319 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
320 TabRestoreServiceChanged(tab_restore_service_
));
323 void TabRestoreServiceHelper::NotifyLoaded() {
324 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
325 TabRestoreServiceLoaded(tab_restore_service_
));
328 void TabRestoreServiceHelper::AddEntry(Entry
* entry
,
331 if (!FilterEntry(entry
) || (entries_
.size() >= kMaxEntries
&& !to_front
)) {
337 entries_
.push_front(entry
);
339 entries_
.push_back(entry
);
347 observer_
->OnAddEntry();
350 void TabRestoreServiceHelper::PruneEntries() {
353 for (TabRestoreService::Entries::const_iterator iter
= entries_
.begin();
354 iter
!= entries_
.end(); ++iter
) {
355 TabRestoreService::Entry
* entry
= *iter
;
357 if (FilterEntry(entry
) &&
358 new_entries
.size() < kMaxEntries
) {
359 new_entries
.push_back(entry
);
365 entries_
= new_entries
;
368 TabRestoreService::Entries::iterator
369 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id
) {
370 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
374 // For Window entries, see if the ID matches a tab. If so, report the window
376 if ((*i
)->type
== TabRestoreService::WINDOW
) {
377 std::vector
<Tab
>& tabs
= static_cast<Window
*>(*i
)->tabs
;
378 for (std::vector
<Tab
>::iterator j
= tabs
.begin();
379 j
!= tabs
.end(); ++j
) {
386 return entries_
.end();
390 bool TabRestoreServiceHelper::ValidateEntry(Entry
* entry
) {
391 if (entry
->type
== TabRestoreService::TAB
)
392 return ValidateTab(static_cast<Tab
*>(entry
));
394 if (entry
->type
== TabRestoreService::WINDOW
)
395 return ValidateWindow(static_cast<Window
*>(entry
));
401 void TabRestoreServiceHelper::PopulateTab(
404 TabRestoreServiceDelegate
* delegate
,
405 NavigationController
* controller
) {
406 const int pending_index
= controller
->GetPendingEntryIndex();
407 int entry_count
= controller
->GetEntryCount();
408 if (entry_count
== 0 && pending_index
== 0)
410 tab
->navigations
.resize(static_cast<int>(entry_count
));
411 for (int i
= 0; i
< entry_count
; ++i
) {
412 NavigationEntry
* entry
= (i
== pending_index
) ?
413 controller
->GetPendingEntry() : controller
->GetEntryAtIndex(i
);
414 tab
->navigations
[i
] =
415 sessions::SerializedNavigationEntry::FromNavigationEntry(i
, *entry
);
417 tab
->timestamp
= TimeNow();
418 tab
->current_navigation_index
= controller
->GetCurrentEntryIndex();
419 if (tab
->current_navigation_index
== -1 && entry_count
> 0)
420 tab
->current_navigation_index
= 0;
421 tab
->tabstrip_index
= index
;
423 extensions::TabHelper
* extensions_tab_helper
=
424 extensions::TabHelper::FromWebContents(controller
->GetWebContents());
425 // extensions_tab_helper is NULL in some browser tests.
426 if (extensions_tab_helper
) {
427 const extensions::Extension
* extension
=
428 extensions_tab_helper
->extension_app();
430 tab
->extension_app_id
= extension
->id();
433 tab
->user_agent_override
=
434 controller
->GetWebContents()->GetUserAgentOverride();
436 // TODO(ajwong): This does not correctly handle storage for isolated apps.
437 tab
->session_storage_namespace
=
438 controller
->GetDefaultSessionStorageNamespace();
440 // Delegate may be NULL during unit tests.
442 tab
->browser_id
= delegate
->GetSessionID().id();
443 tab
->pinned
= delegate
->IsTabPinned(tab
->tabstrip_index
);
447 TabRestoreServiceDelegate
* TabRestoreServiceHelper::RestoreTab(
449 TabRestoreServiceDelegate
* delegate
,
450 chrome::HostDesktopType host_desktop_type
,
451 WindowOpenDisposition disposition
,
452 WebContents
** contents
) {
453 WebContents
* web_contents
;
454 if (disposition
== CURRENT_TAB
&& delegate
) {
455 web_contents
= delegate
->ReplaceRestoredTab(
457 tab
.current_navigation_index
,
458 tab
.from_last_session
,
459 tab
.extension_app_id
,
460 tab
.session_storage_namespace
.get(),
461 tab
.user_agent_override
);
463 // We only respsect the tab's original browser if there's no disposition.
464 if (disposition
== UNKNOWN
&& tab
.has_browser()) {
465 delegate
= TabRestoreServiceDelegate::FindDelegateWithID(
466 tab
.browser_id
, host_desktop_type
);
471 // |delegate| will be NULL in cases where one isn't already available (eg,
472 // when invoked on Mac OS X with no windows open). In this case, create a
473 // new browser into which we restore the tabs.
474 if (delegate
&& disposition
!= NEW_WINDOW
) {
475 tab_index
= tab
.tabstrip_index
;
477 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
479 if (tab
.has_browser())
480 UpdateTabBrowserIDs(tab
.browser_id
, delegate
->GetSessionID().id());
483 // Place the tab at the end if the tab index is no longer valid or
484 // we were passed a specific disposition.
485 if (tab_index
< 0 || tab_index
> delegate
->GetTabCount() ||
486 disposition
!= UNKNOWN
) {
487 tab_index
= delegate
->GetTabCount();
490 web_contents
= delegate
->AddRestoredTab(tab
.navigations
,
492 tab
.current_navigation_index
,
493 tab
.extension_app_id
,
494 disposition
!= NEW_BACKGROUND_TAB
,
496 tab
.from_last_session
,
497 tab
.session_storage_namespace
.get(),
498 tab
.user_agent_override
);
499 web_contents
->GetController().LoadIfNecessary();
501 RecordAppLaunch(profile_
, tab
);
503 *contents
= web_contents
;
509 bool TabRestoreServiceHelper::ValidateTab(Tab
* tab
) {
510 if (tab
->navigations
.empty())
513 tab
->current_navigation_index
=
514 std::max(0, std::min(tab
->current_navigation_index
,
515 static_cast<int>(tab
->navigations
.size()) - 1));
520 bool TabRestoreServiceHelper::ValidateWindow(Window
* window
) {
521 window
->selected_tab_index
=
522 std::max(0, std::min(window
->selected_tab_index
,
523 static_cast<int>(window
->tabs
.size() - 1)));
526 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
527 tab_i
!= window
->tabs
.end();) {
528 if (!ValidateTab(&(*tab_i
))) {
529 tab_i
= window
->tabs
.erase(tab_i
);
530 if (i
< window
->selected_tab_index
)
531 window
->selected_tab_index
--;
532 else if (i
== window
->selected_tab_index
)
533 window
->selected_tab_index
= 0;
540 if (window
->tabs
.empty())
546 bool TabRestoreServiceHelper::IsTabInteresting(const Tab
* tab
) {
547 if (tab
->navigations
.empty())
550 if (tab
->navigations
.size() > 1)
553 return tab
->pinned
||
554 tab
->navigations
.at(0).virtual_url() !=
555 GURL(chrome::kChromeUINewTabURL
);
558 bool TabRestoreServiceHelper::IsWindowInteresting(const Window
* window
) {
559 if (window
->tabs
.empty())
562 if (window
->tabs
.size() > 1)
565 return IsTabInteresting(&window
->tabs
[0]);
568 bool TabRestoreServiceHelper::FilterEntry(Entry
* entry
) {
569 if (!ValidateEntry(entry
))
572 if (entry
->type
== TabRestoreService::TAB
)
573 return IsTabInteresting(static_cast<Tab
*>(entry
));
574 else if (entry
->type
== TabRestoreService::WINDOW
)
575 return IsWindowInteresting(static_cast<Window
*>(entry
));
581 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id
,
582 SessionID::id_type new_id
) {
583 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
585 if (entry
->type
== TabRestoreService::TAB
) {
586 Tab
* tab
= static_cast<Tab
*>(entry
);
587 if (tab
->browser_id
== old_id
)
588 tab
->browser_id
= new_id
;
593 base::Time
TabRestoreServiceHelper::TimeNow() const {
594 return time_factory_
? time_factory_
->TimeNow() : base::Time::Now();