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/core/live_tab.h"
16 #include "components/sessions/core/tab_restore_service_client.h"
17 #include "components/sessions/serialized_navigation_entry.h"
18 #include "components/sessions/session_types.h"
20 using sessions::LiveTab
;
22 // TabRestoreServiceHelper::Observer -------------------------------------------
24 TabRestoreServiceHelper::Observer::~Observer() {}
26 void TabRestoreServiceHelper::Observer::OnClearEntries() {}
28 void TabRestoreServiceHelper::Observer::OnRestoreEntryById(
29 SessionID::id_type id
,
30 Entries::const_iterator entry_iterator
) {
33 void TabRestoreServiceHelper::Observer::OnAddEntry() {}
35 // TabRestoreServiceHelper -----------------------------------------------------
37 TabRestoreServiceHelper::TabRestoreServiceHelper(
38 TabRestoreService
* tab_restore_service
,
40 sessions::TabRestoreServiceClient
* client
,
41 TabRestoreService::TimeFactory
* time_factory
)
42 : tab_restore_service_(tab_restore_service
),
46 time_factory_(time_factory
) {
47 DCHECK(tab_restore_service_
);
50 TabRestoreServiceHelper::~TabRestoreServiceHelper() {
51 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
52 TabRestoreServiceDestroyed(tab_restore_service_
));
53 STLDeleteElements(&entries_
);
56 void TabRestoreServiceHelper::AddObserver(
57 TabRestoreServiceObserver
* observer
) {
58 observer_list_
.AddObserver(observer
);
61 void TabRestoreServiceHelper::RemoveObserver(
62 TabRestoreServiceObserver
* observer
) {
63 observer_list_
.RemoveObserver(observer
);
66 void TabRestoreServiceHelper::CreateHistoricalTab(LiveTab
* live_tab
,
71 TabRestoreServiceDelegate
* delegate
=
72 client_
->FindTabRestoreServiceDelegateForTab(live_tab
);
73 if (closing_delegates_
.find(delegate
) != closing_delegates_
.end())
76 scoped_ptr
<Tab
> local_tab(new Tab());
77 PopulateTab(local_tab
.get(), index
, delegate
, live_tab
);
78 if (local_tab
->navigations
.empty())
81 AddEntry(local_tab
.release(), true, true);
84 void TabRestoreServiceHelper::BrowserClosing(
85 TabRestoreServiceDelegate
* delegate
) {
86 closing_delegates_
.insert(delegate
);
88 scoped_ptr
<Window
> window(new Window());
89 window
->selected_tab_index
= delegate
->GetSelectedIndex();
90 window
->timestamp
= TimeNow();
91 window
->app_name
= delegate
->GetAppName();
93 // Don't use std::vector::resize() because it will push copies of an empty tab
94 // into the vector, which will give all tabs in a window the same ID.
95 for (int i
= 0; i
< delegate
->GetTabCount(); ++i
) {
96 window
->tabs
.push_back(Tab());
98 size_t entry_index
= 0;
99 for (int tab_index
= 0; tab_index
< delegate
->GetTabCount(); ++tab_index
) {
100 PopulateTab(&(window
->tabs
[entry_index
]), tab_index
, delegate
,
101 delegate
->GetLiveTabAt(tab_index
));
102 if (window
->tabs
[entry_index
].navigations
.empty()) {
103 window
->tabs
.erase(window
->tabs
.begin() + entry_index
);
105 window
->tabs
[entry_index
].browser_id
= delegate
->GetSessionID().id();
109 if (window
->tabs
.size() == 1 && window
->app_name
.empty()) {
110 // Short-circuit creating a Window if only 1 tab was present. This fixes
111 // http://crbug.com/56744. Copy the Tab because it's owned by an object on
113 AddEntry(new Tab(window
->tabs
[0]), true, true);
114 } else if (!window
->tabs
.empty()) {
115 window
->selected_tab_index
=
116 std::min(static_cast<int>(window
->tabs
.size() - 1),
117 window
->selected_tab_index
);
118 AddEntry(window
.release(), true, true);
122 void TabRestoreServiceHelper::BrowserClosed(
123 TabRestoreServiceDelegate
* delegate
) {
124 closing_delegates_
.erase(delegate
);
127 void TabRestoreServiceHelper::ClearEntries() {
129 observer_
->OnClearEntries();
130 STLDeleteElements(&entries_
);
134 const TabRestoreService::Entries
& TabRestoreServiceHelper::entries() const {
138 std::vector
<LiveTab
*> TabRestoreServiceHelper::RestoreMostRecentEntry(
139 TabRestoreServiceDelegate
* delegate
,
140 int host_desktop_type
) {
141 if (entries_
.empty())
142 return std::vector
<LiveTab
*>();
144 return RestoreEntryById(delegate
, entries_
.front()->id
, host_desktop_type
,
148 TabRestoreService::Tab
* TabRestoreServiceHelper::RemoveTabEntryById(
149 SessionID::id_type id
) {
150 Entries::iterator i
= GetEntryIteratorById(id
);
151 if (i
== entries_
.end())
155 if (entry
->type
!= TabRestoreService::TAB
)
158 Tab
* tab
= static_cast<Tab
*>(entry
);
163 std::vector
<LiveTab
*> TabRestoreServiceHelper::RestoreEntryById(
164 TabRestoreServiceDelegate
* delegate
,
165 SessionID::id_type id
,
166 int host_desktop_type
,
167 WindowOpenDisposition disposition
) {
168 Entries::iterator entry_iterator
= GetEntryIteratorById(id
);
169 if (entry_iterator
== entries_
.end())
170 // Don't hoark here, we allow an invalid id.
171 return std::vector
<LiveTab
*>();
174 observer_
->OnRestoreEntryById(id
, entry_iterator
);
176 Entry
* entry
= *entry_iterator
;
178 // If the entry's ID does not match the ID that is being restored, then the
179 // entry is a window from which a single tab will be restored.
180 bool restoring_tab_in_window
= entry
->id
!= id
;
182 if (!restoring_tab_in_window
) {
183 entries_
.erase(entry_iterator
);
184 entry_iterator
= entries_
.end();
187 // |delegate| will be NULL in cases where one isn't already available (eg,
188 // when invoked on Mac OS X with no windows open). In this case, create a
189 // new browser into which we restore the tabs.
190 std::vector
<LiveTab
*> live_tabs
;
191 if (entry
->type
== TabRestoreService::TAB
) {
192 Tab
* tab
= static_cast<Tab
*>(entry
);
193 LiveTab
* restored_tab
= NULL
;
194 delegate
= RestoreTab(*tab
, delegate
, host_desktop_type
, disposition
,
196 live_tabs
.push_back(restored_tab
);
197 delegate
->ShowBrowserWindow();
198 } else if (entry
->type
== TabRestoreService::WINDOW
) {
199 TabRestoreServiceDelegate
* current_delegate
= delegate
;
200 Window
* window
= static_cast<Window
*>(entry
);
202 // When restoring a window, either the entire window can be restored, or a
203 // single tab within it. If the entry's ID matches the one to restore, then
204 // the entire window will be restored.
205 if (!restoring_tab_in_window
) {
206 delegate
= client_
->CreateTabRestoreServiceDelegate(host_desktop_type
,
208 for (size_t tab_i
= 0; tab_i
< window
->tabs
.size(); ++tab_i
) {
209 const Tab
& tab
= window
->tabs
[tab_i
];
210 LiveTab
* restored_tab
= delegate
->AddRestoredTab(
211 tab
.navigations
, delegate
->GetTabCount(),
212 tab
.current_navigation_index
, tab
.extension_app_id
,
213 static_cast<int>(tab_i
) == window
->selected_tab_index
, tab
.pinned
,
214 tab
.from_last_session
, tab
.client_data
.get(),
215 tab
.user_agent_override
);
217 restored_tab
->LoadIfNecessary();
218 client_
->OnTabRestored(
219 tab
.navigations
.at(tab
.current_navigation_index
).virtual_url());
220 live_tabs
.push_back(restored_tab
);
223 // All the window's tabs had the same former browser_id.
224 if (window
->tabs
[0].has_browser()) {
225 UpdateTabBrowserIDs(window
->tabs
[0].browser_id
,
226 delegate
->GetSessionID().id());
229 // Restore a single tab from the window. Find the tab that matches the ID
230 // in the window and restore it.
231 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
232 tab_i
!= window
->tabs
.end(); ++tab_i
) {
233 const Tab
& tab
= *tab_i
;
235 LiveTab
* restored_tab
= NULL
;
236 delegate
= RestoreTab(tab
, delegate
, host_desktop_type
, disposition
,
238 live_tabs
.push_back(restored_tab
);
239 window
->tabs
.erase(tab_i
);
240 // If restoring the tab leaves the window with nothing else, delete it
242 if (!window
->tabs
.size()) {
243 entries_
.erase(entry_iterator
);
246 // Update the browser ID of the rest of the tabs in the window so if
247 // any one is restored, it goes into the same window as the tab
248 // being restored now.
249 UpdateTabBrowserIDs(tab
.browser_id
,
250 delegate
->GetSessionID().id());
251 for (std::vector
<Tab
>::iterator tab_j
= window
->tabs
.begin();
252 tab_j
!= window
->tabs
.end(); ++tab_j
) {
253 (*tab_j
).browser_id
= delegate
->GetSessionID().id();
260 delegate
->ShowBrowserWindow();
262 if (disposition
== CURRENT_TAB
&& current_delegate
&&
263 current_delegate
->GetActiveLiveTab()) {
264 current_delegate
->CloseTab();
270 if (!restoring_tab_in_window
) {
279 void TabRestoreServiceHelper::NotifyTabsChanged() {
280 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
281 TabRestoreServiceChanged(tab_restore_service_
));
284 void TabRestoreServiceHelper::NotifyLoaded() {
285 FOR_EACH_OBSERVER(TabRestoreServiceObserver
, observer_list_
,
286 TabRestoreServiceLoaded(tab_restore_service_
));
289 void TabRestoreServiceHelper::AddEntry(Entry
* entry
,
292 if (!FilterEntry(entry
) || (entries_
.size() >= kMaxEntries
&& !to_front
)) {
298 entries_
.push_front(entry
);
300 entries_
.push_back(entry
);
308 observer_
->OnAddEntry();
311 void TabRestoreServiceHelper::PruneEntries() {
314 for (TabRestoreService::Entries::const_iterator iter
= entries_
.begin();
315 iter
!= entries_
.end(); ++iter
) {
316 TabRestoreService::Entry
* entry
= *iter
;
318 if (FilterEntry(entry
) &&
319 new_entries
.size() < kMaxEntries
) {
320 new_entries
.push_back(entry
);
326 entries_
= new_entries
;
329 TabRestoreService::Entries::iterator
330 TabRestoreServiceHelper::GetEntryIteratorById(SessionID::id_type id
) {
331 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
335 // For Window entries, see if the ID matches a tab. If so, report the window
337 if ((*i
)->type
== TabRestoreService::WINDOW
) {
338 std::vector
<Tab
>& tabs
= static_cast<Window
*>(*i
)->tabs
;
339 for (std::vector
<Tab
>::iterator j
= tabs
.begin();
340 j
!= tabs
.end(); ++j
) {
347 return entries_
.end();
351 bool TabRestoreServiceHelper::ValidateEntry(Entry
* entry
) {
352 if (entry
->type
== TabRestoreService::TAB
)
353 return ValidateTab(static_cast<Tab
*>(entry
));
355 if (entry
->type
== TabRestoreService::WINDOW
)
356 return ValidateWindow(static_cast<Window
*>(entry
));
362 void TabRestoreServiceHelper::PopulateTab(Tab
* tab
,
364 TabRestoreServiceDelegate
* delegate
,
366 const int pending_index
= live_tab
->GetPendingEntryIndex();
367 int entry_count
= live_tab
->GetEntryCount();
368 if (entry_count
== 0 && pending_index
== 0)
370 tab
->navigations
.resize(static_cast<int>(entry_count
));
371 for (int i
= 0; i
< entry_count
; ++i
) {
372 sessions::SerializedNavigationEntry entry
=
373 (i
== pending_index
) ? live_tab
->GetPendingEntry()
374 : live_tab
->GetEntryAtIndex(i
);
375 tab
->navigations
[i
] = entry
;
377 tab
->timestamp
= TimeNow();
378 tab
->current_navigation_index
= live_tab
->GetCurrentEntryIndex();
379 if (tab
->current_navigation_index
== -1 && entry_count
> 0)
380 tab
->current_navigation_index
= 0;
381 tab
->tabstrip_index
= index
;
383 tab
->extension_app_id
= client_
->GetExtensionAppIDForTab(live_tab
);
385 tab
->user_agent_override
= live_tab
->GetUserAgentOverride();
387 tab
->client_data
= client_
->GetTabClientDataForTab(live_tab
);
389 // Delegate may be NULL during unit tests.
391 tab
->browser_id
= delegate
->GetSessionID().id();
392 tab
->pinned
= delegate
->IsTabPinned(tab
->tabstrip_index
);
396 TabRestoreServiceDelegate
* TabRestoreServiceHelper::RestoreTab(
398 TabRestoreServiceDelegate
* delegate
,
399 int host_desktop_type
,
400 WindowOpenDisposition disposition
,
401 LiveTab
** live_tab
) {
402 LiveTab
* restored_tab
;
403 if (disposition
== CURRENT_TAB
&& delegate
) {
404 restored_tab
= delegate
->ReplaceRestoredTab(
405 tab
.navigations
, tab
.current_navigation_index
, tab
.from_last_session
,
406 tab
.extension_app_id
, tab
.client_data
.get(), tab
.user_agent_override
);
408 // We only respsect the tab's original browser if there's no disposition.
409 if (disposition
== UNKNOWN
&& tab
.has_browser()) {
410 delegate
= client_
->FindTabRestoreServiceDelegateWithID(
411 tab
.browser_id
, host_desktop_type
);
416 // |delegate| will be NULL in cases where one isn't already available (eg,
417 // when invoked on Mac OS X with no windows open). In this case, create a
418 // new browser into which we restore the tabs.
419 if (delegate
&& disposition
!= NEW_WINDOW
) {
420 tab_index
= tab
.tabstrip_index
;
422 delegate
= client_
->CreateTabRestoreServiceDelegate(host_desktop_type
,
424 if (tab
.has_browser())
425 UpdateTabBrowserIDs(tab
.browser_id
, delegate
->GetSessionID().id());
428 // Place the tab at the end if the tab index is no longer valid or
429 // we were passed a specific disposition.
430 if (tab_index
< 0 || tab_index
> delegate
->GetTabCount() ||
431 disposition
!= UNKNOWN
) {
432 tab_index
= delegate
->GetTabCount();
435 restored_tab
= delegate
->AddRestoredTab(
436 tab
.navigations
, tab_index
, tab
.current_navigation_index
,
437 tab
.extension_app_id
, disposition
!= NEW_BACKGROUND_TAB
, tab
.pinned
,
438 tab
.from_last_session
, tab
.client_data
.get(), tab
.user_agent_override
);
439 restored_tab
->LoadIfNecessary();
441 client_
->OnTabRestored(
442 tab
.navigations
.at(tab
.current_navigation_index
).virtual_url());
444 *live_tab
= restored_tab
;
450 bool TabRestoreServiceHelper::ValidateTab(Tab
* tab
) {
451 if (tab
->navigations
.empty())
454 tab
->current_navigation_index
=
455 std::max(0, std::min(tab
->current_navigation_index
,
456 static_cast<int>(tab
->navigations
.size()) - 1));
461 bool TabRestoreServiceHelper::ValidateWindow(Window
* window
) {
462 window
->selected_tab_index
=
463 std::max(0, std::min(window
->selected_tab_index
,
464 static_cast<int>(window
->tabs
.size() - 1)));
467 for (std::vector
<Tab
>::iterator tab_i
= window
->tabs
.begin();
468 tab_i
!= window
->tabs
.end();) {
469 if (!ValidateTab(&(*tab_i
))) {
470 tab_i
= window
->tabs
.erase(tab_i
);
471 if (i
< window
->selected_tab_index
)
472 window
->selected_tab_index
--;
473 else if (i
== window
->selected_tab_index
)
474 window
->selected_tab_index
= 0;
481 if (window
->tabs
.empty())
487 bool TabRestoreServiceHelper::IsTabInteresting(const Tab
* tab
) {
488 if (tab
->navigations
.empty())
491 if (tab
->navigations
.size() > 1)
494 return tab
->pinned
||
495 tab
->navigations
.at(0).virtual_url() != client_
->GetNewTabURL();
498 bool TabRestoreServiceHelper::IsWindowInteresting(const Window
* window
) {
499 if (window
->tabs
.empty())
502 if (window
->tabs
.size() > 1)
505 return IsTabInteresting(&window
->tabs
[0]);
508 bool TabRestoreServiceHelper::FilterEntry(Entry
* entry
) {
509 if (!ValidateEntry(entry
))
512 if (entry
->type
== TabRestoreService::TAB
)
513 return IsTabInteresting(static_cast<Tab
*>(entry
));
514 else if (entry
->type
== TabRestoreService::WINDOW
)
515 return IsWindowInteresting(static_cast<Window
*>(entry
));
521 void TabRestoreServiceHelper::UpdateTabBrowserIDs(SessionID::id_type old_id
,
522 SessionID::id_type new_id
) {
523 for (Entries::iterator i
= entries_
.begin(); i
!= entries_
.end(); ++i
) {
525 if (entry
->type
== TabRestoreService::TAB
) {
526 Tab
* tab
= static_cast<Tab
*>(entry
);
527 if (tab
->browser_id
== old_id
)
528 tab
->browser_id
= new_id
;
533 base::Time
TabRestoreServiceHelper::TimeNow() const {
534 return time_factory_
? time_factory_
->TimeNow() : base::Time::Now();