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/session_service.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/command_line.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/metrics/histogram.h"
17 #include "base/pickle.h"
18 #include "base/threading/thread.h"
19 #include "chrome/browser/background/background_mode_manager.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/defaults.h"
23 #include "chrome/browser/extensions/tab_helper.h"
24 #include "chrome/browser/prefs/session_startup_pref.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/profiles/profile_manager.h"
27 #include "chrome/browser/sessions/base_session_service_delegate_impl.h"
28 #include "chrome/browser/sessions/session_command.h"
29 #include "chrome/browser/sessions/session_data_deleter.h"
30 #include "chrome/browser/sessions/session_restore.h"
31 #include "chrome/browser/sessions/session_service_utils.h"
32 #include "chrome/browser/sessions/session_tab_helper.h"
33 #include "chrome/browser/sessions/session_types.h"
34 #include "chrome/browser/ui/browser_iterator.h"
35 #include "chrome/browser/ui/browser_list.h"
36 #include "chrome/browser/ui/browser_tabstrip.h"
37 #include "chrome/browser/ui/browser_window.h"
38 #include "chrome/browser/ui/host_desktop.h"
39 #include "chrome/browser/ui/startup/startup_browser_creator.h"
40 #include "chrome/browser/ui/tabs/tab_strip_model.h"
41 #include "components/sessions/content/content_serialized_navigation_builder.h"
42 #include "components/startup_metric_utils/startup_metric_utils.h"
43 #include "content/public/browser/navigation_details.h"
44 #include "content/public/browser/navigation_entry.h"
45 #include "content/public/browser/notification_details.h"
46 #include "content/public/browser/notification_service.h"
47 #include "content/public/browser/session_storage_namespace.h"
48 #include "content/public/browser/web_contents.h"
49 #include "extensions/common/extension.h"
51 #if defined(OS_MACOSX)
52 #include "chrome/browser/app_controller_mac.h"
56 using content::NavigationEntry;
57 using content::WebContents;
58 using sessions::ContentSerializedNavigationBuilder;
59 using sessions::SerializedNavigationEntry;
61 // Every kWritesPerReset commands triggers recreating the file.
62 static const int kWritesPerReset = 250;
64 // SessionService -------------------------------------------------------------
66 SessionService::SessionService(Profile* profile)
67 : BaseSessionServiceDelegateImpl(true),
69 base_session_service_(
70 new BaseSessionService(BaseSessionService::SESSION_RESTORE,
73 has_open_trackable_browsers_(false),
74 move_on_new_browser_(false),
75 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
76 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
77 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
78 force_browser_not_alive_with_no_windows_(false),
80 // We should never be created when incognito.
81 DCHECK(!profile->IsOffTheRecord());
85 SessionService::SessionService(const base::FilePath& save_path)
86 : BaseSessionServiceDelegateImpl(false),
88 base_session_service_(
89 new BaseSessionService(BaseSessionService::SESSION_RESTORE,
92 has_open_trackable_browsers_(false),
93 move_on_new_browser_(false),
94 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
95 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
96 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
97 force_browser_not_alive_with_no_windows_(false),
102 SessionService::~SessionService() {
103 // The BrowserList should outlive the SessionService since it's static and
104 // the SessionService is a KeyedService.
105 BrowserList::RemoveObserver(this);
106 base_session_service_->Save();
109 bool SessionService::ShouldNewWindowStartSession() {
110 // ChromeOS and OSX have different ideas of application lifetime than
111 // the other platforms.
112 // On ChromeOS opening a new window should never start a new session.
113 #if defined(OS_CHROMEOS)
114 if (!force_browser_not_alive_with_no_windows_)
117 if (!has_open_trackable_browsers_ &&
118 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
119 !SessionRestore::IsRestoring(profile())
120 #if defined(OS_MACOSX)
121 // On OSX, a new window should not start a new session if it was opened
122 // from the dock or the menubar.
123 && !app_controller_mac::IsOpeningNewWindow()
131 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
132 return RestoreIfNecessary(urls_to_open, NULL);
135 void SessionService::ResetFromCurrentBrowsers() {
136 ScheduleResetCommands();
139 void SessionService::MoveCurrentSessionToLastSession() {
140 pending_tab_close_ids_.clear();
141 window_closing_ids_.clear();
142 pending_window_close_ids_.clear();
144 base_session_service_->MoveCurrentSessionToLastSession();
147 void SessionService::DeleteLastSession() {
148 base_session_service_->DeleteLastSession();
151 void SessionService::SetTabWindow(const SessionID& window_id,
152 const SessionID& tab_id) {
153 if (!ShouldTrackChangesToWindow(window_id))
156 ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id).Pass());
159 void SessionService::SetWindowBounds(const SessionID& window_id,
160 const gfx::Rect& bounds,
161 ui::WindowShowState show_state) {
162 if (!ShouldTrackChangesToWindow(window_id))
166 CreateSetWindowBoundsCommand(window_id, bounds, show_state).Pass());
169 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
170 const SessionID& tab_id,
172 if (!ShouldTrackChangesToWindow(window_id))
175 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index).Pass());
178 void SessionService::SetPinnedState(const SessionID& window_id,
179 const SessionID& tab_id,
181 if (!ShouldTrackChangesToWindow(window_id))
184 ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned).Pass());
187 void SessionService::TabClosed(const SessionID& window_id,
188 const SessionID& tab_id,
189 bool closed_by_user_gesture) {
191 return; // Hapens when the tab is replaced.
193 if (!ShouldTrackChangesToWindow(window_id))
196 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
197 if (i != tab_to_available_range_.end())
198 tab_to_available_range_.erase(i);
200 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
201 window_id.id()) != pending_window_close_ids_.end()) {
202 // Tab is in last window. Don't commit it immediately, instead add it to the
203 // list of tabs to close. If the user creates another window, the close is
205 pending_tab_close_ids_.insert(tab_id.id());
206 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
207 window_id.id()) != window_closing_ids_.end() ||
208 !IsOnlyOneTabLeft() ||
209 closed_by_user_gesture) {
210 // Close is the result of one of the following:
211 // . window close (and it isn't the last window).
212 // . closing a tab and there are other windows/tabs open.
213 // . closed by a user gesture.
214 // In all cases we need to mark the tab as explicitly closed.
215 ScheduleCommand(CreateTabClosedCommand(tab_id.id()).Pass());
217 // User closed the last tab in the last tabbed browser. Don't mark the
219 pending_tab_close_ids_.insert(tab_id.id());
220 has_open_trackable_browsers_ = false;
224 void SessionService::WindowOpened(Browser* browser) {
225 if (!ShouldTrackBrowser(browser))
228 RestoreIfNecessary(std::vector<GURL>(), browser);
229 SetWindowType(browser->session_id(),
231 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
232 SetWindowAppName(browser->session_id(), browser->app_name());
235 void SessionService::WindowClosing(const SessionID& window_id) {
236 if (!ShouldTrackChangesToWindow(window_id))
239 // The window is about to close. If there are other tabbed browsers with the
240 // same original profile commit the close immediately.
242 // NOTE: if the user chooses the exit menu item session service is destroyed
243 // and this code isn't hit.
244 if (has_open_trackable_browsers_) {
245 // Closing a window can never make has_open_trackable_browsers_ go from
246 // false to true, so only update it if already true.
247 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
249 bool use_pending_close = !has_open_trackable_browsers_;
250 if (!use_pending_close) {
251 // Somewhat outside of "normal behavior" is profile locking. In this case
252 // (when IsSiginRequired has already been set True), we're closing all
253 // browser windows in turn but want them all to be restored when the user
254 // unlocks. To accomplish this, we do a "pending close" on all windows
255 // instead of just the last one (which has no open_trackable_browsers).
256 // http://crbug.com/356818
258 // Some editions (like iOS) don't have a profile_manager and some tests
259 // don't supply one so be lenient.
260 if (g_browser_process) {
261 ProfileManager* profile_manager = g_browser_process->profile_manager();
262 if (profile_manager) {
263 ProfileInfoCache& profile_info =
264 profile_manager->GetProfileInfoCache();
265 size_t profile_index = profile_info.GetIndexOfProfileWithPath(
266 profile()->GetPath());
267 use_pending_close = profile_index != std::string::npos &&
268 profile_info.ProfileIsSigninRequiredAtIndex(profile_index);
272 if (use_pending_close)
273 pending_window_close_ids_.insert(window_id.id());
275 window_closing_ids_.insert(window_id.id());
278 void SessionService::WindowClosed(const SessionID& window_id) {
279 if (!ShouldTrackChangesToWindow(window_id)) {
280 // The last window may be one that is not tracked.
281 MaybeDeleteSessionOnlyData();
285 windows_tracking_.erase(window_id.id());
287 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
288 window_closing_ids_.erase(window_id.id());
289 ScheduleCommand(CreateWindowClosedCommand(window_id.id()).Pass());
290 } else if (pending_window_close_ids_.find(window_id.id()) ==
291 pending_window_close_ids_.end()) {
292 // We'll hit this if user closed the last tab in a window.
293 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
294 if (!has_open_trackable_browsers_)
295 pending_window_close_ids_.insert(window_id.id());
297 ScheduleCommand(CreateWindowClosedCommand(window_id.id()).Pass());
299 MaybeDeleteSessionOnlyData();
302 void SessionService::TabInserted(WebContents* contents) {
303 SessionTabHelper* session_tab_helper =
304 SessionTabHelper::FromWebContents(contents);
305 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
307 SetTabWindow(session_tab_helper->window_id(),
308 session_tab_helper->session_id());
309 extensions::TabHelper* extensions_tab_helper =
310 extensions::TabHelper::FromWebContents(contents);
311 if (extensions_tab_helper &&
312 extensions_tab_helper->extension_app()) {
313 SetTabExtensionAppID(
314 session_tab_helper->window_id(),
315 session_tab_helper->session_id(),
316 extensions_tab_helper->extension_app()->id());
319 // Record the association between the SessionStorageNamespace and the
322 // TODO(ajwong): This should be processing the whole map rather than
323 // just the default. This in particular will not work for tabs with only
324 // isolated apps which won't have a default partition.
325 content::SessionStorageNamespace* session_storage_namespace =
326 contents->GetController().GetDefaultSessionStorageNamespace();
327 ScheduleCommand(CreateSessionStorageAssociatedCommand(
328 session_tab_helper->session_id(),
329 session_storage_namespace->persistent_id()).Pass());
330 session_storage_namespace->SetShouldPersist(true);
333 void SessionService::TabClosing(WebContents* contents) {
334 // Allow the associated sessionStorage to get deleted; it won't be needed
335 // in the session restore.
336 content::SessionStorageNamespace* session_storage_namespace =
337 contents->GetController().GetDefaultSessionStorageNamespace();
338 session_storage_namespace->SetShouldPersist(false);
339 SessionTabHelper* session_tab_helper =
340 SessionTabHelper::FromWebContents(contents);
341 TabClosed(session_tab_helper->window_id(),
342 session_tab_helper->session_id(),
343 contents->GetClosedByUserGesture());
344 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
345 &last_updated_tab_closed_time_);
348 void SessionService::SetWindowType(const SessionID& window_id,
351 SessionWindow::WindowType window_type = WindowTypeForBrowserType(type);
352 if (!ShouldRestoreWindowOfType(window_type, app_type))
355 windows_tracking_.insert(window_id.id());
357 // The user created a new tabbed browser with our profile. Commit any
359 CommitPendingCloses();
361 has_open_trackable_browsers_ = true;
362 move_on_new_browser_ = true;
364 ScheduleCommand(CreateSetWindowTypeCommand(window_id, window_type).Pass());
367 void SessionService::SetWindowAppName(
368 const SessionID& window_id,
369 const std::string& app_name) {
370 if (!ShouldTrackChangesToWindow(window_id))
373 ScheduleCommand(CreateSetWindowAppNameCommand(window_id, app_name).Pass());
376 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
377 const SessionID& tab_id,
379 if (!ShouldTrackChangesToWindow(window_id))
383 CreateTabNavigationPathPrunedFromBackCommand(tab_id, count).Pass());
386 void SessionService::TabNavigationPathPrunedFromFront(
387 const SessionID& window_id,
388 const SessionID& tab_id,
390 if (!ShouldTrackChangesToWindow(window_id))
393 // Update the range of indices.
394 if (tab_to_available_range_.find(tab_id.id()) !=
395 tab_to_available_range_.end()) {
396 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
397 range.first = std::max(0, range.first - count);
398 range.second = std::max(0, range.second - count);
402 CreateTabNavigationPathPrunedFromFrontCommand(tab_id, count).Pass());
405 void SessionService::UpdateTabNavigation(
406 const SessionID& window_id,
407 const SessionID& tab_id,
408 const SerializedNavigationEntry& navigation) {
409 if (!ShouldTrackEntry(navigation.virtual_url()) ||
410 !ShouldTrackChangesToWindow(window_id)) {
414 if (tab_to_available_range_.find(tab_id.id()) !=
415 tab_to_available_range_.end()) {
416 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
417 range.first = std::min(navigation.index(), range.first);
418 range.second = std::max(navigation.index(), range.second);
420 ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, navigation).Pass());
423 void SessionService::TabRestored(WebContents* tab, bool pinned) {
424 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
425 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
428 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, pinned, NULL);
429 base_session_service_->StartSaveTimer();
432 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
433 const SessionID& tab_id,
435 if (!ShouldTrackChangesToWindow(window_id))
438 if (tab_to_available_range_.find(tab_id.id()) !=
439 tab_to_available_range_.end()) {
440 if (index < tab_to_available_range_[tab_id.id()].first ||
441 index > tab_to_available_range_[tab_id.id()].second) {
442 // The new index is outside the range of what we've archived, schedule
444 ResetFromCurrentBrowsers();
449 CreateSetSelectedNavigationIndexCommand(tab_id, index).Pass());
452 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
454 if (!ShouldTrackChangesToWindow(window_id))
457 ScheduleCommand(CreateSetSelectedTabInWindowCommand(window_id, index).Pass());
460 void SessionService::SetTabUserAgentOverride(
461 const SessionID& window_id,
462 const SessionID& tab_id,
463 const std::string& user_agent_override) {
464 if (!ShouldTrackChangesToWindow(window_id))
468 CreateSetTabUserAgentOverrideCommand(tab_id, user_agent_override).Pass());
471 void SessionService::SetTabExtensionAppID(
472 const SessionID& window_id,
473 const SessionID& tab_id,
474 const std::string& extension_app_id) {
475 if (!ShouldTrackChangesToWindow(window_id))
479 CreateSetTabExtensionAppIDCommand(tab_id, extension_app_id).Pass());
482 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
483 const SessionCallback& callback,
484 base::CancelableTaskTracker* tracker) {
485 // OnGotSessionCommands maps the SessionCommands to browser state, then run
487 return base_session_service_->ScheduleGetLastSessionCommands(
488 base::Bind(&SessionService::OnGotSessionCommands,
489 weak_factory_.GetWeakPtr(),
494 void SessionService::OnSavedCommands() {
495 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
496 &last_updated_save_time_);
497 content::NotificationService::current()->Notify(
498 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
499 content::Source<Profile>(profile()),
500 content::NotificationService::NoDetails());
503 void SessionService::Init() {
504 // Register for the notifications we're interested in.
505 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
506 content::NotificationService::AllSources());
507 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
508 content::NotificationService::AllSources());
509 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
510 content::NotificationService::AllSources());
512 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
513 content::NotificationService::AllSources());
515 BrowserList::AddObserver(this);
518 bool SessionService::ShouldRestoreWindowOfType(
519 SessionWindow::WindowType window_type,
520 AppType app_type) const {
521 #if defined(OS_CHROMEOS)
522 // Restore app popups for ChromeOS alone.
523 if (window_type == SessionWindow::TYPE_POPUP && app_type == TYPE_APP)
527 return window_type == SessionWindow::TYPE_TABBED;
530 void SessionService::RemoveUnusedRestoreWindows(
531 std::vector<SessionWindow*>* window_list) {
532 std::vector<SessionWindow*>::iterator i = window_list->begin();
533 while (i != window_list->end()) {
534 SessionWindow* window = *i;
535 if (!ShouldRestoreWindowOfType(window->type,
536 window->app_name.empty() ? TYPE_NORMAL :
539 window_list->erase(i++);
546 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
548 if (ShouldNewWindowStartSession()) {
549 // We're going from no tabbed browsers to a tabbed browser (and not in
550 // process startup), restore the last session.
551 if (move_on_new_browser_) {
552 // Make the current session the last.
553 MoveCurrentSessionToLastSession();
554 move_on_new_browser_ = false;
556 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
557 *CommandLine::ForCurrentProcess(), profile());
558 if (pref.type == SessionStartupPref::LAST) {
559 SessionRestore::RestoreSession(
561 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
562 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
570 void SessionService::Observe(int type,
571 const content::NotificationSource& source,
572 const content::NotificationDetails& details) {
573 // All of our messages have the NavigationController as the source.
575 case content::NOTIFICATION_NAV_LIST_PRUNED: {
576 WebContents* web_contents =
577 content::Source<content::NavigationController>(source).ptr()->
579 SessionTabHelper* session_tab_helper =
580 SessionTabHelper::FromWebContents(web_contents);
581 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
583 content::Details<content::PrunedDetails> pruned_details(details);
584 if (pruned_details->from_front) {
585 TabNavigationPathPrunedFromFront(
586 session_tab_helper->window_id(),
587 session_tab_helper->session_id(),
588 pruned_details->count);
590 TabNavigationPathPrunedFromBack(
591 session_tab_helper->window_id(),
592 session_tab_helper->session_id(),
593 web_contents->GetController().GetEntryCount());
595 RecordSessionUpdateHistogramData(type,
596 &last_updated_nav_list_pruned_time_);
600 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
601 WebContents* web_contents =
602 content::Source<content::NavigationController>(source).ptr()->
604 SessionTabHelper* session_tab_helper =
605 SessionTabHelper::FromWebContents(web_contents);
606 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
608 content::Details<content::EntryChangedDetails> changed(details);
609 const SerializedNavigationEntry navigation =
610 ContentSerializedNavigationBuilder::FromNavigationEntry(
611 changed->index, *changed->changed_entry);
612 UpdateTabNavigation(session_tab_helper->window_id(),
613 session_tab_helper->session_id(),
618 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
619 WebContents* web_contents =
620 content::Source<content::NavigationController>(source).ptr()->
622 SessionTabHelper* session_tab_helper =
623 SessionTabHelper::FromWebContents(web_contents);
624 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
626 int current_entry_index =
627 web_contents->GetController().GetCurrentEntryIndex();
628 SetSelectedNavigationIndex(
629 session_tab_helper->window_id(),
630 session_tab_helper->session_id(),
631 current_entry_index);
632 const SerializedNavigationEntry navigation =
633 ContentSerializedNavigationBuilder::FromNavigationEntry(
635 *web_contents->GetController().GetEntryAtIndex(
636 current_entry_index));
638 session_tab_helper->window_id(),
639 session_tab_helper->session_id(),
641 content::Details<content::LoadCommittedDetails> changed(details);
642 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
643 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
644 RecordSessionUpdateHistogramData(type,
645 &last_updated_nav_entry_commit_time_);
650 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
651 extensions::TabHelper* extension_tab_helper =
652 content::Source<extensions::TabHelper>(source).ptr();
653 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
657 if (extension_tab_helper->extension_app()) {
658 SessionTabHelper* session_tab_helper =
659 SessionTabHelper::FromWebContents(
660 extension_tab_helper->web_contents());
661 SetTabExtensionAppID(session_tab_helper->window_id(),
662 session_tab_helper->session_id(),
663 extension_tab_helper->extension_app()->id());
673 void SessionService::OnBrowserSetLastActive(Browser* browser) {
674 if (ShouldTrackBrowser(browser))
675 ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id()).Pass());
678 void SessionService::OnGotSessionCommands(
679 const SessionCallback& callback,
680 ScopedVector<SessionCommand> commands) {
681 ScopedVector<SessionWindow> valid_windows;
682 SessionID::id_type active_window_id = 0;
684 startup_metric_utils::ScopedSlowStartupUMA
685 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
687 RestoreSessionFromCommands(commands, &valid_windows.get(), &active_window_id);
688 RemoveUnusedRestoreWindows(&valid_windows.get());
690 callback.Run(valid_windows.Pass(), active_window_id);
693 void SessionService::BuildCommandsForTab(const SessionID& window_id,
697 IdToRange* tab_to_available_range) {
698 DCHECK(tab && window_id.id());
699 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
700 const SessionID& session_id(session_tab_helper->session_id());
701 base_session_service_->AppendRebuildCommand(
702 CreateSetTabWindowCommand(window_id, session_id));
704 const int current_index = tab->GetController().GetCurrentEntryIndex();
705 const int min_index = std::max(current_index - gMaxPersistNavigationCount, 0);
706 const int max_index = std::min(current_index + gMaxPersistNavigationCount,
707 tab->GetController().GetEntryCount());
708 const int pending_index = tab->GetController().GetPendingEntryIndex();
709 if (tab_to_available_range) {
710 (*tab_to_available_range)[session_id.id()] =
711 std::pair<int, int>(min_index, max_index);
715 base_session_service_->AppendRebuildCommand(
716 CreatePinnedStateCommand(session_id, true));
719 extensions::TabHelper* extensions_tab_helper =
720 extensions::TabHelper::FromWebContents(tab);
721 if (extensions_tab_helper->extension_app()) {
722 base_session_service_->AppendRebuildCommand(
723 CreateSetTabExtensionAppIDCommand(
725 extensions_tab_helper->extension_app()->id()));
728 const std::string& ua_override = tab->GetUserAgentOverride();
729 if (!ua_override.empty()) {
730 base_session_service_->AppendRebuildCommand(
731 CreateSetTabUserAgentOverrideCommand(session_id, ua_override));
734 for (int i = min_index; i < max_index; ++i) {
735 const NavigationEntry* entry = (i == pending_index) ?
736 tab->GetController().GetPendingEntry() :
737 tab->GetController().GetEntryAtIndex(i);
739 if (ShouldTrackEntry(entry->GetVirtualURL())) {
740 const SerializedNavigationEntry navigation =
741 ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry);
742 base_session_service_->AppendRebuildCommand(
743 CreateUpdateTabNavigationCommand(session_id, navigation));
746 base_session_service_->AppendRebuildCommand(
747 CreateSetSelectedNavigationIndexCommand(session_id,
750 if (index_in_window != -1) {
751 base_session_service_->AppendRebuildCommand(
752 CreateSetTabIndexInWindowCommand(session_id,
756 // Record the association between the sessionStorage namespace and the tab.
757 content::SessionStorageNamespace* session_storage_namespace =
758 tab->GetController().GetDefaultSessionStorageNamespace();
759 ScheduleCommand(CreateSessionStorageAssociatedCommand(
760 session_tab_helper->session_id(),
761 session_storage_namespace->persistent_id()).Pass());
764 void SessionService::BuildCommandsForBrowser(
766 IdToRange* tab_to_available_range,
767 std::set<SessionID::id_type>* windows_to_track) {
769 DCHECK(browser->session_id().id());
771 base_session_service_->AppendRebuildCommand(CreateSetWindowBoundsCommand(
772 browser->session_id(),
773 browser->window()->GetRestoredBounds(),
774 browser->window()->GetRestoredState()));
776 base_session_service_->AppendRebuildCommand(CreateSetWindowTypeCommand(
777 browser->session_id(),
778 WindowTypeForBrowserType(browser->type())));
780 if (!browser->app_name().empty()) {
781 base_session_service_->AppendRebuildCommand(CreateSetWindowAppNameCommand(
782 browser->session_id(),
783 browser->app_name()));
786 windows_to_track->insert(browser->session_id().id());
787 TabStripModel* tab_strip = browser->tab_strip_model();
788 for (int i = 0; i < tab_strip->count(); ++i) {
789 WebContents* tab = tab_strip->GetWebContentsAt(i);
791 BuildCommandsForTab(browser->session_id(),
794 tab_strip->IsTabPinned(i),
795 tab_to_available_range);
798 base_session_service_->AppendRebuildCommand(
799 CreateSetSelectedTabInWindowCommand(
800 browser->session_id(),
801 browser->tab_strip_model()->active_index()));
804 void SessionService::BuildCommandsFromBrowsers(
805 IdToRange* tab_to_available_range,
806 std::set<SessionID::id_type>* windows_to_track) {
807 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
808 Browser* browser = *it;
809 // Make sure the browser has tabs and a window. Browser's destructor
810 // removes itself from the BrowserList. When a browser is closed the
811 // destructor is not necessarily run immediately. This means it's possible
812 // for us to get a handle to a browser that is about to be removed. If
813 // the tab count is 0 or the window is NULL, the browser is about to be
814 // deleted, so we ignore it.
815 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
817 BuildCommandsForBrowser(browser,
818 tab_to_available_range,
824 void SessionService::ScheduleResetCommands() {
825 base_session_service_->set_pending_reset(true);
826 base_session_service_->ClearPendingCommands();
827 tab_to_available_range_.clear();
828 windows_tracking_.clear();
829 BuildCommandsFromBrowsers(&tab_to_available_range_,
831 if (!windows_tracking_.empty()) {
832 // We're lazily created on startup and won't get an initial batch of
833 // SetWindowType messages. Set these here to make sure our state is correct.
834 has_open_trackable_browsers_ = true;
835 move_on_new_browser_ = true;
837 base_session_service_->StartSaveTimer();
840 void SessionService::ScheduleCommand(scoped_ptr<SessionCommand> command) {
842 if (ReplacePendingCommand(base_session_service_.get(), &command))
844 bool is_closing_command = IsClosingCommand(command.get());
845 base_session_service_->ScheduleCommand(command.Pass());
846 // Don't schedule a reset on tab closed/window closed. Otherwise we may
847 // lose tabs/windows we want to restore from if we exit right after this.
848 if (!base_session_service_->pending_reset() &&
849 pending_window_close_ids_.empty() &&
850 base_session_service_->commands_since_reset() >= kWritesPerReset &&
851 !is_closing_command) {
852 ScheduleResetCommands();
856 void SessionService::CommitPendingCloses() {
857 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
858 i != pending_tab_close_ids_.end(); ++i) {
859 ScheduleCommand(CreateTabClosedCommand(*i).Pass());
861 pending_tab_close_ids_.clear();
863 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
864 i != pending_window_close_ids_.end(); ++i) {
865 ScheduleCommand(CreateWindowClosedCommand(*i).Pass());
867 pending_window_close_ids_.clear();
870 bool SessionService::IsOnlyOneTabLeft() const {
871 if (!profile() || profile()->AsTestingProfile()) {
872 // We're testing, always return false.
876 int window_count = 0;
877 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
878 Browser* browser = *it;
879 const SessionID::id_type window_id = browser->session_id().id();
880 if (ShouldTrackBrowser(browser) &&
881 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
882 if (++window_count > 1)
884 // By the time this is invoked the tab has been removed. As such, we use
885 // > 0 here rather than > 1.
886 if (browser->tab_strip_model()->count() > 0)
893 bool SessionService::HasOpenTrackableBrowsers(
894 const SessionID& window_id) const {
895 if (!profile() || profile()->AsTestingProfile()) {
896 // We're testing, always return true.
900 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
901 Browser* browser = *it;
902 const SessionID::id_type browser_id = browser->session_id().id();
903 if (browser_id != window_id.id() &&
904 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
905 ShouldTrackBrowser(browser)) {
912 bool SessionService::ShouldTrackChangesToWindow(
913 const SessionID& window_id) const {
914 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
917 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
918 if (browser->profile() != profile())
920 // Never track app popup windows that do not have a trusted source (i.e.
921 // popup windows spawned by an app). If this logic changes, be sure to also
922 // change SessionRestoreImpl::CreateRestoredBrowser().
923 if (browser->is_app() && browser->is_type_popup() &&
924 !browser->is_trusted_source()) {
927 return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()),
928 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
931 void SessionService::RecordSessionUpdateHistogramData(int type,
932 base::TimeTicks* last_updated_time) {
933 if (!last_updated_time->is_null()) {
934 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
935 // We're interested in frequent updates periods longer than
937 bool use_long_period = false;
938 if (delta >= save_delay_in_mins_) {
939 use_long_period = true;
942 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
943 RecordUpdatedSaveTime(delta, use_long_period);
944 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
946 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
947 RecordUpdatedTabClosed(delta, use_long_period);
948 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
950 case content::NOTIFICATION_NAV_LIST_PRUNED:
951 RecordUpdatedNavListPruned(delta, use_long_period);
952 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
954 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
955 RecordUpdatedNavEntryCommit(delta, use_long_period);
956 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
959 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
963 (*last_updated_time) = base::TimeTicks::Now();
966 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
967 bool use_long_period) {
968 std::string name("SessionRestore.TabClosedPeriod");
969 UMA_HISTOGRAM_CUSTOM_TIMES(name,
971 // 2500ms is the default save delay.
972 save_delay_in_millis_,
975 if (use_long_period) {
976 std::string long_name_("SessionRestore.TabClosedLongPeriod");
977 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
985 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
986 bool use_long_period) {
987 std::string name("SessionRestore.NavigationListPrunedPeriod");
988 UMA_HISTOGRAM_CUSTOM_TIMES(name,
990 // 2500ms is the default save delay.
991 save_delay_in_millis_,
994 if (use_long_period) {
995 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
996 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1004 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1005 bool use_long_period) {
1006 std::string name("SessionRestore.NavEntryCommittedPeriod");
1007 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1009 // 2500ms is the default save delay.
1010 save_delay_in_millis_,
1011 save_delay_in_mins_,
1013 if (use_long_period) {
1014 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1015 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1017 save_delay_in_mins_,
1023 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1024 bool use_long_period) {
1025 std::string name("SessionRestore.SavePeriod");
1026 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1028 // 2500ms is the default save delay.
1029 save_delay_in_millis_,
1030 save_delay_in_mins_,
1032 if (use_long_period) {
1033 std::string long_name_("SessionRestore.SaveLongPeriod");
1034 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1036 save_delay_in_mins_,
1042 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1043 bool use_long_period) {
1044 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1045 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1047 // 2500ms is the default save delay.
1048 save_delay_in_millis_,
1049 save_delay_in_mins_,
1051 if (use_long_period) {
1052 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1053 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1055 save_delay_in_mins_,
1061 void SessionService::MaybeDeleteSessionOnlyData() {
1062 // Don't try anything if we're testing. The browser_process is not fully
1063 // created and DeleteSession will crash if we actually attempt it.
1064 if (!profile() || profile()->AsTestingProfile())
1067 // Clear session data if the last window for a profile has been closed and
1068 // closing the last window would normally close Chrome, unless background mode
1069 // is active. Tests don't have a background_mode_manager.
1070 if (has_open_trackable_browsers_ ||
1071 browser_defaults::kBrowserAliveWithNoWindows ||
1072 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1076 // Check for any open windows for the current profile that we aren't tracking.
1077 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1078 if ((*it)->profile() == profile())
1081 DeleteSessionOnlyData(profile());
1084 BaseSessionService* SessionService::GetBaseSessionServiceForTest() {
1085 return base_session_service_.get();