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/sessions/tab_restore_service_delegate.h"
14 #include "chrome/browser/sessions/tab_restore_service_observer.h"
15 #include "components/sessions/content/content_serialized_navigation_builder.h"
16 #include "components/sessions/core/tab_restore_service_client.h"
17 #include "components/sessions/session_types.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"
23 #if defined(ENABLE_EXTENSIONS)
24 #include "chrome/browser/extensions/tab_helper.h"
27 using content::NavigationController
;
28 using content::NavigationEntry
;
29 using content::WebContents
;
31 // TabRestoreServiceHelper::Observer -------------------------------------------
33 TabRestoreServiceHelper::Observer::~Observer() {}
35 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
37 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
38 SessionID::id_type id
,
39 Entries::const_iterator entry_iterator
) {
42 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
44 // TabRestoreServiceHelper -----------------------------------------------------
46 TabRestoreServiceHelper::TabRestoreServiceHelper(
47 TabRestoreService
* tab_restore_service
,
50 sessions::TabRestoreServiceClient
* client
,
51 TabRestoreService::TimeFactory
* time_factory
)
52 : tab_restore_service_(tab_restore_service
),
57 time_factory_(time_factory
) {
58 DCHECK(tab_restore_service_
);
61 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
62 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
63 TabRestoreServiceDestroyed(tab_restore_service_
));
64 STLDeleteElements(&entries_
);
67 void TabRestoreServiceHelper::AddObserver(
68 TabRestoreServiceObserver
* observer
) {
69 observer_list_
.AddObserver(observer
);
72 void TabRestoreServiceHelper::RemoveObserver(
73 TabRestoreServiceObserver
* observer
) {
74 observer_list_
.RemoveObserver(observer
);
77 void TabRestoreServiceHelper::CreateHistoricalTab(
78 content::WebContents
* contents
,
83 TabRestoreServiceDelegate
* delegate
=
84 TabRestoreServiceDelegate::FindDelegateForWebContents(contents
);
85 if (closing_delegates_
.find(delegate
) != closing_delegates_
.end())
88 scoped_ptr
<Tab
> local_tab(new Tab());
89 PopulateTab(local_tab
.get(), index
, delegate
, &contents
->GetController());
90 if (local_tab
->navigations
.empty())
93 AddEntry(local_tab
.release(), true, true);
96 void TabRestoreServiceHelper::BrowserClosing(
97 TabRestoreServiceDelegate
* delegate
) {
98 closing_delegates_
.insert(delegate
);
100 scoped_ptr
<Window
> window(new Window());
101 window
->selected_tab_index
= delegate
->GetSelectedIndex();
102 window
->timestamp
= TimeNow();
103 window
->app_name
= delegate
->GetAppName();
105 // Don't use std::vector::resize() because it will push copies of an empty tab
106 // into the vector, which will give all tabs in a window the same ID.
107 for (int i
= 0; i
< delegate
->GetTabCount(); ++i
) {
108 window
->tabs
.push_back(Tab());
110 size_t entry_index
= 0;
111 for (int tab_index
= 0; tab_index
< delegate
->GetTabCount(); ++tab_index
) {
112 PopulateTab(&(window
->tabs
[entry_index
]),
115 &delegate
->GetWebContentsAt(tab_index
)->GetController());
116 if (window
->tabs
[entry_index
].navigations
.empty()) {
117 window
->tabs
.erase(window
->tabs
.begin() + entry_index
);
119 window
->tabs
[entry_index
].browser_id
= delegate
->GetSessionID().id();
123 if (window
->tabs
.size() == 1 && window
->app_name
.empty()) {
124 // Short-circuit creating a Window if only 1 tab was present. This fixes
125 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
127 AddEntry(new Tab(window
->tabs
[0]), true, true);
128 } else if (!window
->tabs
.empty()) {
129 window
->selected_tab_index
=
130 std::min(static_cast<int>(window
->tabs
.size() - 1),
131 window
->selected_tab_index
);
132 AddEntry(window
.release(), true, true);
136 void TabRestoreServiceHelper::BrowserClosed(
137 TabRestoreServiceDelegate
* delegate
) {
138 closing_delegates_
.erase(delegate
);
141 void TabRestoreServiceHelper::ClearEntries() {
143 observer_
->OnClearEntries();
144 STLDeleteElements(&entries_
);
148 const TabRestoreService::Entries
& TabRestoreServiceHelper::entries() const {
152 std::vector
<content::WebContents
*>
153 TabRestoreServiceHelper::RestoreMostRecentEntry(
154 TabRestoreServiceDelegate
* delegate
,
155 chrome::HostDesktopType host_desktop_type
) {
156 if (entries_
.empty())
157 return std::vector
<WebContents
*>();
159 return RestoreEntryById(delegate
, entries_
.front()->id
, host_desktop_type
,
163 TabRestoreService::Tab
* TabRestoreServiceHelper::RemoveTabEntryById(
164 SessionID::id_type id
) {
165 Entries::iterator i
= GetEntryIteratorById(id
);
166 if (i
== entries_
.end())
170 if (entry
->type
!= TabRestoreService::TAB
)
173 Tab
* tab
= static_cast<Tab
*>(entry
);
178 std::vector
<content::WebContents
*> TabRestoreServiceHelper::RestoreEntryById(
179 TabRestoreServiceDelegate
* delegate
,
180 SessionID::id_type id
,
181 chrome::HostDesktopType host_desktop_type
,
182 WindowOpenDisposition disposition
) {
183 Entries::iterator entry_iterator
= GetEntryIteratorById(id
);
184 if (entry_iterator
== entries_
.end())
185 // Don't hoark here, we allow an invalid id.
186 return std::vector
<WebContents
*>();
189 observer_
->OnRestoreEntryById(id
, entry_iterator
);
191 Entry
* entry
= *entry_iterator
;
193 // If the entry's ID does not match the ID that is being restored, then the
194 // entry is a window from which a single tab will be restored.
195 bool restoring_tab_in_window
= entry
->id
!= id
;
197 if (!restoring_tab_in_window
) {
198 entries_
.erase(entry_iterator
);
199 entry_iterator
= entries_
.end();
202 // |delegate| will be NULL in cases where one isn't already available (eg,
203 // when invoked on Mac OS X with no windows open). In this case, create a
204 // new browser into which we restore the tabs.
205 std::vector
<WebContents
*> web_contents
;
206 if (entry
->type
== TabRestoreService::TAB
) {
207 Tab
* tab
= static_cast<Tab
*>(entry
);
208 WebContents
* restored_tab
= NULL
;
209 delegate
= RestoreTab(*tab
, delegate
, host_desktop_type
, disposition
,
211 web_contents
.push_back(restored_tab
);
212 delegate
->ShowBrowserWindow();
213 } else if (entry
->type
== TabRestoreService::WINDOW
) {
214 TabRestoreServiceDelegate
* current_delegate
= delegate
;
215 Window
* window
= static_cast<Window
*>(entry
);
217 // When restoring a window, either the entire window can be restored, or a
218 // single tab within it. If the entry's ID matches the one to restore, then
219 // the entire window will be restored.
220 if (!restoring_tab_in_window
) {
221 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
223 for (size_t tab_i
= 0; tab_i
< window
->tabs
.size(); ++tab_i
) {
224 const Tab
& tab
= window
->tabs
[tab_i
];
225 WebContents
* restored_tab
= delegate
->AddRestoredTab(
227 delegate
->GetTabCount(),
228 tab
.current_navigation_index
,
229 tab
.extension_app_id
,
230 static_cast<int>(tab_i
) == window
->selected_tab_index
,
232 tab
.from_last_session
,
233 tab
.session_storage_namespace
.get(),
234 tab
.user_agent_override
);
236 restored_tab
->GetController().LoadIfNecessary();
237 client_
->OnTabRestored(
238 tab
.navigations
.at(tab
.current_navigation_index
).virtual_url());
239 web_contents
.push_back(restored_tab
);
242 // All the window's tabs had the same former browser_id.
243 if (window
->tabs
[0].has_browser()) {
244 UpdateTabBrowserIDs(window
->tabs
[0].browser_id
,
245 delegate
->GetSessionID().id());
248 // Restore a single tab from the window. Find the tab that matches the ID
249 // in the window and restore it.
250 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
251 tab_i
!= window
->tabs
.end(); ++tab_i
) {
252 const Tab
& tab
= *tab_i
;
254 WebContents
* restored_tab
= NULL
;
255 delegate
= RestoreTab(tab
, delegate
, host_desktop_type
, disposition
,
257 web_contents
.push_back(restored_tab
);
258 window
->tabs
.erase(tab_i
);
259 // If restoring the tab leaves the window with nothing else, delete it
261 if (!window
->tabs
.size()) {
262 entries_
.erase(entry_iterator
);
265 // Update the browser ID of the rest of the tabs in the window so if
266 // any one is restored, it goes into the same window as the tab
267 // being restored now.
268 UpdateTabBrowserIDs(tab
.browser_id
,
269 delegate
->GetSessionID().id());
270 for (std::vector
<Tab
>::iterator tab_j
= window
->tabs
.begin();
271 tab_j
!= window
->tabs
.end(); ++tab_j
) {
272 (*tab_j
).browser_id
= delegate
->GetSessionID().id();
279 delegate
->ShowBrowserWindow();
281 if (disposition
== CURRENT_TAB
&& current_delegate
&&
282 current_delegate
->GetActiveWebContents()) {
283 current_delegate
->CloseTab();
289 if (!restoring_tab_in_window
) {
298 void TabRestoreServiceHelper::NotifyTabsChanged() {
299 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
300 TabRestoreServiceChanged(tab_restore_service_
));
303 void TabRestoreServiceHelper::NotifyLoaded() {
304 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
305 TabRestoreServiceLoaded(tab_restore_service_
));
308 void TabRestoreServiceHelper::AddEntry(Entry
* entry
,
311 if (!FilterEntry(entry
) || (entries_
.size() >= kMaxEntries
&& !to_front
)) {
317 entries_
.push_front(entry
);
319 entries_
.push_back(entry
);
327 observer_
->OnAddEntry();
330 void TabRestoreServiceHelper::PruneEntries() {
333 for (TabRestoreService::Entries::const_iterator iter
= entries_
.begin();
334 iter
!= entries_
.end(); ++iter
) {
335 TabRestoreService::Entry
* entry
= *iter
;
337 if (FilterEntry(entry
) &&
338 new_entries
.size() < kMaxEntries
) {
339 new_entries
.push_back(entry
);
345 entries_
= new_entries
;
348 TabRestoreService::Entries::iterator
349 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id
) {
350 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
354 // For Window entries, see if the ID matches a tab. If so, report the window
356 if ((*i
)->type
== TabRestoreService::WINDOW
) {
357 std::vector
<Tab
>& tabs
= static_cast<Window
*>(*i
)->tabs
;
358 for (std::vector
<Tab
>::iterator j
= tabs
.begin();
359 j
!= tabs
.end(); ++j
) {
366 return entries_
.end();
370 bool TabRestoreServiceHelper::ValidateEntry(Entry
* entry
) {
371 if (entry
->type
== TabRestoreService::TAB
)
372 return ValidateTab(static_cast<Tab
*>(entry
));
374 if (entry
->type
== TabRestoreService::WINDOW
)
375 return ValidateWindow(static_cast<Window
*>(entry
));
381 void TabRestoreServiceHelper::PopulateTab(
384 TabRestoreServiceDelegate
* delegate
,
385 NavigationController
* controller
) {
386 const int pending_index
= controller
->GetPendingEntryIndex();
387 int entry_count
= controller
->GetEntryCount();
388 if (entry_count
== 0 && pending_index
== 0)
390 tab
->navigations
.resize(static_cast<int>(entry_count
));
391 for (int i
= 0; i
< entry_count
; ++i
) {
392 NavigationEntry
* entry
= (i
== pending_index
) ?
393 controller
->GetPendingEntry() : controller
->GetEntryAtIndex(i
);
394 tab
->navigations
[i
] =
395 sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(
398 tab
->timestamp
= TimeNow();
399 tab
->current_navigation_index
= controller
->GetCurrentEntryIndex();
400 if (tab
->current_navigation_index
== -1 && entry_count
> 0)
401 tab
->current_navigation_index
= 0;
402 tab
->tabstrip_index
= index
;
404 #if defined(ENABLE_EXTENSIONS)
405 extensions::TabHelper
* extensions_tab_helper
=
406 extensions::TabHelper::FromWebContents(controller
->GetWebContents());
407 // extensions_tab_helper is NULL in some browser tests.
408 if (extensions_tab_helper
) {
409 const extensions::Extension
* extension
=
410 extensions_tab_helper
->extension_app();
412 tab
->extension_app_id
= extension
->id();
416 tab
->user_agent_override
=
417 controller
->GetWebContents()->GetUserAgentOverride();
419 // TODO(ajwong): This does not correctly handle storage for isolated apps.
420 tab
->session_storage_namespace
=
421 controller
->GetDefaultSessionStorageNamespace();
423 // Delegate may be NULL during unit tests.
425 tab
->browser_id
= delegate
->GetSessionID().id();
426 tab
->pinned
= delegate
->IsTabPinned(tab
->tabstrip_index
);
430 TabRestoreServiceDelegate
* TabRestoreServiceHelper::RestoreTab(
432 TabRestoreServiceDelegate
* delegate
,
433 chrome::HostDesktopType host_desktop_type
,
434 WindowOpenDisposition disposition
,
435 WebContents
** contents
) {
436 WebContents
* web_contents
;
437 if (disposition
== CURRENT_TAB
&& delegate
) {
438 web_contents
= delegate
->ReplaceRestoredTab(
440 tab
.current_navigation_index
,
441 tab
.from_last_session
,
442 tab
.extension_app_id
,
443 tab
.session_storage_namespace
.get(),
444 tab
.user_agent_override
);
446 // We only respsect the tab's original browser if there's no disposition.
447 if (disposition
== UNKNOWN
&& tab
.has_browser()) {
448 delegate
= TabRestoreServiceDelegate::FindDelegateWithID(
449 tab
.browser_id
, host_desktop_type
);
454 // |delegate| will be NULL in cases where one isn't already available (eg,
455 // when invoked on Mac OS X with no windows open). In this case, create a
456 // new browser into which we restore the tabs.
457 if (delegate
&& disposition
!= NEW_WINDOW
) {
458 tab_index
= tab
.tabstrip_index
;
460 delegate
= TabRestoreServiceDelegate::Create(profile_
, host_desktop_type
,
462 if (tab
.has_browser())
463 UpdateTabBrowserIDs(tab
.browser_id
, delegate
->GetSessionID().id());
466 // Place the tab at the end if the tab index is no longer valid or
467 // we were passed a specific disposition.
468 if (tab_index
< 0 || tab_index
> delegate
->GetTabCount() ||
469 disposition
!= UNKNOWN
) {
470 tab_index
= delegate
->GetTabCount();
473 web_contents
= delegate
->AddRestoredTab(tab
.navigations
,
475 tab
.current_navigation_index
,
476 tab
.extension_app_id
,
477 disposition
!= NEW_BACKGROUND_TAB
,
479 tab
.from_last_session
,
480 tab
.session_storage_namespace
.get(),
481 tab
.user_agent_override
);
482 web_contents
->GetController().LoadIfNecessary();
484 client_
->OnTabRestored(
485 tab
.navigations
.at(tab
.current_navigation_index
).virtual_url());
487 *contents
= web_contents
;
493 bool TabRestoreServiceHelper::ValidateTab(Tab
* tab
) {
494 if (tab
->navigations
.empty())
497 tab
->current_navigation_index
=
498 std::max(0, std::min(tab
->current_navigation_index
,
499 static_cast<int>(tab
->navigations
.size()) - 1));
504 bool TabRestoreServiceHelper::ValidateWindow(Window
* window
) {
505 window
->selected_tab_index
=
506 std::max(0, std::min(window
->selected_tab_index
,
507 static_cast<int>(window
->tabs
.size() - 1)));
510 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
511 tab_i
!= window
->tabs
.end();) {
512 if (!ValidateTab(&(*tab_i
))) {
513 tab_i
= window
->tabs
.erase(tab_i
);
514 if (i
< window
->selected_tab_index
)
515 window
->selected_tab_index
--;
516 else if (i
== window
->selected_tab_index
)
517 window
->selected_tab_index
= 0;
524 if (window
->tabs
.empty())
530 bool TabRestoreServiceHelper::IsTabInteresting(const Tab
* tab
) {
531 if (tab
->navigations
.empty())
534 if (tab
->navigations
.size() > 1)
537 return tab
->pinned
||
538 tab
->navigations
.at(0).virtual_url() != client_
->GetNewTabURL();
541 bool TabRestoreServiceHelper::IsWindowInteresting(const Window
* window
) {
542 if (window
->tabs
.empty())
545 if (window
->tabs
.size() > 1)
548 return IsTabInteresting(&window
->tabs
[0]);
551 bool TabRestoreServiceHelper::FilterEntry(Entry
* entry
) {
552 if (!ValidateEntry(entry
))
555 if (entry
->type
== TabRestoreService::TAB
)
556 return IsTabInteresting(static_cast<Tab
*>(entry
));
557 else if (entry
->type
== TabRestoreService::WINDOW
)
558 return IsWindowInteresting(static_cast<Window
*>(entry
));
564 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id
,
565 SessionID::id_type new_id
) {
566 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
568 if (entry
->type
== TabRestoreService::TAB
) {
569 Tab
* tab
= static_cast<Tab
*>(entry
);
570 if (tab
->browser_id
== old_id
)
571 tab
->browser_id
= new_id
;
576 base::Time
TabRestoreServiceHelper::TimeNow() const {
577 return time_factory_
? time_factory_
->TimeNow() : base::Time::Now();