Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / sessions / session_service.cc
blob99de7204e61bdf7d52e078b0e979d175b29f58eb
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"
7 #include <algorithm>
8 #include <set>
9 #include <utility>
10 #include <vector>
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_data_deleter.h"
29 #include "chrome/browser/sessions/session_restore.h"
30 #include "chrome/browser/sessions/session_service_utils.h"
31 #include "chrome/browser/sessions/session_tab_helper.h"
32 #include "chrome/browser/ui/browser_iterator.h"
33 #include "chrome/browser/ui/browser_list.h"
34 #include "chrome/browser/ui/browser_tabstrip.h"
35 #include "chrome/browser/ui/browser_window.h"
36 #include "chrome/browser/ui/host_desktop.h"
37 #include "chrome/browser/ui/startup/startup_browser_creator.h"
38 #include "chrome/browser/ui/tabs/tab_strip_model.h"
39 #include "components/sessions/content/content_serialized_navigation_builder.h"
40 #include "components/sessions/session_command.h"
41 #include "components/sessions/session_types.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"
53 #endif
55 using base::Time;
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),
68 profile_(profile),
69 base_session_service_(
70 new sessions::BaseSessionService(
71 sessions::BaseSessionService::SESSION_RESTORE,
72 profile->GetPath(),
73 this)),
74 has_open_trackable_browsers_(false),
75 move_on_new_browser_(false),
76 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
77 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
78 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
79 force_browser_not_alive_with_no_windows_(false),
80 weak_factory_(this) {
81 // We should never be created when incognito.
82 DCHECK(!profile->IsOffTheRecord());
83 Init();
86 SessionService::SessionService(const base::FilePath& save_path)
87 : BaseSessionServiceDelegateImpl(false),
88 profile_(NULL),
89 base_session_service_(
90 new sessions::BaseSessionService(
91 sessions::BaseSessionService::SESSION_RESTORE,
92 save_path,
93 this)),
94 has_open_trackable_browsers_(false),
95 move_on_new_browser_(false),
96 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
97 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
98 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
99 force_browser_not_alive_with_no_windows_(false),
100 weak_factory_(this) {
101 Init();
104 SessionService::~SessionService() {
105 // The BrowserList should outlive the SessionService since it's static and
106 // the SessionService is a KeyedService.
107 BrowserList::RemoveObserver(this);
108 base_session_service_->Save();
111 bool SessionService::ShouldNewWindowStartSession() {
112 // ChromeOS and OSX have different ideas of application lifetime than
113 // the other platforms.
114 // On ChromeOS opening a new window should never start a new session.
115 #if defined(OS_CHROMEOS)
116 if (!force_browser_not_alive_with_no_windows_)
117 return false;
118 #endif
119 if (!has_open_trackable_browsers_ &&
120 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
121 !SessionRestore::IsRestoring(profile())
122 #if defined(OS_MACOSX)
123 // On OSX, a new window should not start a new session if it was opened
124 // from the dock or the menubar.
125 && !app_controller_mac::IsOpeningNewWindow()
126 #endif // OS_MACOSX
128 return true;
130 return false;
133 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
134 return RestoreIfNecessary(urls_to_open, NULL);
137 void SessionService::ResetFromCurrentBrowsers() {
138 ScheduleResetCommands();
141 void SessionService::MoveCurrentSessionToLastSession() {
142 pending_tab_close_ids_.clear();
143 window_closing_ids_.clear();
144 pending_window_close_ids_.clear();
146 base_session_service_->MoveCurrentSessionToLastSession();
149 void SessionService::DeleteLastSession() {
150 base_session_service_->DeleteLastSession();
153 void SessionService::SetTabWindow(const SessionID& window_id,
154 const SessionID& tab_id) {
155 if (!ShouldTrackChangesToWindow(window_id))
156 return;
158 ScheduleCommand(sessions::CreateSetTabWindowCommand(window_id,
159 tab_id).Pass());
162 void SessionService::SetWindowBounds(const SessionID& window_id,
163 const gfx::Rect& bounds,
164 ui::WindowShowState show_state) {
165 if (!ShouldTrackChangesToWindow(window_id))
166 return;
168 ScheduleCommand(sessions::CreateSetWindowBoundsCommand(
169 window_id, bounds, show_state).Pass());
172 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
173 const SessionID& tab_id,
174 int new_index) {
175 if (!ShouldTrackChangesToWindow(window_id))
176 return;
178 ScheduleCommand(sessions::CreateSetTabIndexInWindowCommand(tab_id,
179 new_index).Pass());
182 void SessionService::SetPinnedState(const SessionID& window_id,
183 const SessionID& tab_id,
184 bool is_pinned) {
185 if (!ShouldTrackChangesToWindow(window_id))
186 return;
188 ScheduleCommand(sessions::CreatePinnedStateCommand(tab_id, is_pinned).Pass());
191 void SessionService::TabClosed(const SessionID& window_id,
192 const SessionID& tab_id,
193 bool closed_by_user_gesture) {
194 if (!tab_id.id())
195 return; // Hapens when the tab is replaced.
197 if (!ShouldTrackChangesToWindow(window_id))
198 return;
200 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
201 if (i != tab_to_available_range_.end())
202 tab_to_available_range_.erase(i);
204 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
205 window_id.id()) != pending_window_close_ids_.end()) {
206 // Tab is in last window. Don't commit it immediately, instead add it to the
207 // list of tabs to close. If the user creates another window, the close is
208 // committed.
209 pending_tab_close_ids_.insert(tab_id.id());
210 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
211 window_id.id()) != window_closing_ids_.end() ||
212 !IsOnlyOneTabLeft() ||
213 closed_by_user_gesture) {
214 // Close is the result of one of the following:
215 // . window close (and it isn't the last window).
216 // . closing a tab and there are other windows/tabs open.
217 // . closed by a user gesture.
218 // In all cases we need to mark the tab as explicitly closed.
219 ScheduleCommand(sessions::CreateTabClosedCommand(tab_id.id()).Pass());
220 } else {
221 // User closed the last tab in the last tabbed browser. Don't mark the
222 // tab closed.
223 pending_tab_close_ids_.insert(tab_id.id());
224 has_open_trackable_browsers_ = false;
228 void SessionService::WindowOpened(Browser* browser) {
229 if (!ShouldTrackBrowser(browser))
230 return;
232 RestoreIfNecessary(std::vector<GURL>(), browser);
233 SetWindowType(browser->session_id(),
234 browser->type(),
235 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
236 SetWindowAppName(browser->session_id(), browser->app_name());
239 void SessionService::WindowClosing(const SessionID& window_id) {
240 if (!ShouldTrackChangesToWindow(window_id))
241 return;
243 // The window is about to close. If there are other tabbed browsers with the
244 // same original profile commit the close immediately.
246 // NOTE: if the user chooses the exit menu item session service is destroyed
247 // and this code isn't hit.
248 if (has_open_trackable_browsers_) {
249 // Closing a window can never make has_open_trackable_browsers_ go from
250 // false to true, so only update it if already true.
251 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
253 bool use_pending_close = !has_open_trackable_browsers_;
254 if (!use_pending_close) {
255 // Somewhat outside of "normal behavior" is profile locking. In this case
256 // (when IsSiginRequired has already been set True), we're closing all
257 // browser windows in turn but want them all to be restored when the user
258 // unlocks. To accomplish this, we do a "pending close" on all windows
259 // instead of just the last one (which has no open_trackable_browsers).
260 // http://crbug.com/356818
262 // Some editions (like iOS) don't have a profile_manager and some tests
263 // don't supply one so be lenient.
264 if (g_browser_process) {
265 ProfileManager* profile_manager = g_browser_process->profile_manager();
266 if (profile_manager) {
267 ProfileInfoCache& profile_info =
268 profile_manager->GetProfileInfoCache();
269 size_t profile_index = profile_info.GetIndexOfProfileWithPath(
270 profile()->GetPath());
271 use_pending_close = profile_index != std::string::npos &&
272 profile_info.ProfileIsSigninRequiredAtIndex(profile_index);
276 if (use_pending_close)
277 pending_window_close_ids_.insert(window_id.id());
278 else
279 window_closing_ids_.insert(window_id.id());
282 void SessionService::WindowClosed(const SessionID& window_id) {
283 if (!ShouldTrackChangesToWindow(window_id)) {
284 // The last window may be one that is not tracked.
285 MaybeDeleteSessionOnlyData();
286 return;
289 windows_tracking_.erase(window_id.id());
291 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
292 window_closing_ids_.erase(window_id.id());
293 ScheduleCommand(sessions::CreateWindowClosedCommand(window_id.id()).Pass());
294 } else if (pending_window_close_ids_.find(window_id.id()) ==
295 pending_window_close_ids_.end()) {
296 // We'll hit this if user closed the last tab in a window.
297 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
298 if (!has_open_trackable_browsers_)
299 pending_window_close_ids_.insert(window_id.id());
300 else
301 ScheduleCommand(sessions::CreateWindowClosedCommand(
302 window_id.id()).Pass());
304 MaybeDeleteSessionOnlyData();
307 void SessionService::TabInserted(WebContents* contents) {
308 SessionTabHelper* session_tab_helper =
309 SessionTabHelper::FromWebContents(contents);
310 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
311 return;
312 SetTabWindow(session_tab_helper->window_id(),
313 session_tab_helper->session_id());
314 extensions::TabHelper* extensions_tab_helper =
315 extensions::TabHelper::FromWebContents(contents);
316 if (extensions_tab_helper &&
317 extensions_tab_helper->extension_app()) {
318 SetTabExtensionAppID(
319 session_tab_helper->window_id(),
320 session_tab_helper->session_id(),
321 extensions_tab_helper->extension_app()->id());
324 // Record the association between the SessionStorageNamespace and the
325 // tab.
327 // TODO(ajwong): This should be processing the whole map rather than
328 // just the default. This in particular will not work for tabs with only
329 // isolated apps which won't have a default partition.
330 content::SessionStorageNamespace* session_storage_namespace =
331 contents->GetController().GetDefaultSessionStorageNamespace();
332 ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
333 session_tab_helper->session_id(),
334 session_storage_namespace->persistent_id()).Pass());
335 session_storage_namespace->SetShouldPersist(true);
338 void SessionService::TabClosing(WebContents* contents) {
339 // Allow the associated sessionStorage to get deleted; it won't be needed
340 // in the session restore.
341 content::SessionStorageNamespace* session_storage_namespace =
342 contents->GetController().GetDefaultSessionStorageNamespace();
343 session_storage_namespace->SetShouldPersist(false);
344 SessionTabHelper* session_tab_helper =
345 SessionTabHelper::FromWebContents(contents);
346 TabClosed(session_tab_helper->window_id(),
347 session_tab_helper->session_id(),
348 contents->GetClosedByUserGesture());
349 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
350 &last_updated_tab_closed_time_);
353 void SessionService::SetWindowType(const SessionID& window_id,
354 Browser::Type type,
355 AppType app_type) {
356 sessions::SessionWindow::WindowType window_type =
357 WindowTypeForBrowserType(type);
358 if (!ShouldRestoreWindowOfType(window_type, app_type))
359 return;
361 windows_tracking_.insert(window_id.id());
363 // The user created a new tabbed browser with our profile. Commit any
364 // pending closes.
365 CommitPendingCloses();
367 has_open_trackable_browsers_ = true;
368 move_on_new_browser_ = true;
370 ScheduleCommand(CreateSetWindowTypeCommand(window_id, window_type).Pass());
373 void SessionService::SetWindowAppName(
374 const SessionID& window_id,
375 const std::string& app_name) {
376 if (!ShouldTrackChangesToWindow(window_id))
377 return;
379 ScheduleCommand(sessions::CreateSetWindowAppNameCommand(window_id,
380 app_name).Pass());
383 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
384 const SessionID& tab_id,
385 int count) {
386 if (!ShouldTrackChangesToWindow(window_id))
387 return;
389 ScheduleCommand(sessions::CreateTabNavigationPathPrunedFromBackCommand(
390 tab_id, count).Pass());
393 void SessionService::TabNavigationPathPrunedFromFront(
394 const SessionID& window_id,
395 const SessionID& tab_id,
396 int count) {
397 if (!ShouldTrackChangesToWindow(window_id))
398 return;
400 // Update the range of indices.
401 if (tab_to_available_range_.find(tab_id.id()) !=
402 tab_to_available_range_.end()) {
403 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
404 range.first = std::max(0, range.first - count);
405 range.second = std::max(0, range.second - count);
408 ScheduleCommand(sessions::CreateTabNavigationPathPrunedFromFrontCommand(
409 tab_id, count).Pass());
412 void SessionService::UpdateTabNavigation(
413 const SessionID& window_id,
414 const SessionID& tab_id,
415 const SerializedNavigationEntry& navigation) {
416 if (!ShouldTrackEntry(navigation.virtual_url()) ||
417 !ShouldTrackChangesToWindow(window_id)) {
418 return;
421 if (tab_to_available_range_.find(tab_id.id()) !=
422 tab_to_available_range_.end()) {
423 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
424 range.first = std::min(navigation.index(), range.first);
425 range.second = std::max(navigation.index(), range.second);
427 ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, navigation).Pass());
430 void SessionService::TabRestored(WebContents* tab, bool pinned) {
431 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
432 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
433 return;
435 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, pinned, NULL);
436 base_session_service_->StartSaveTimer();
439 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
440 const SessionID& tab_id,
441 int index) {
442 if (!ShouldTrackChangesToWindow(window_id))
443 return;
445 if (tab_to_available_range_.find(tab_id.id()) !=
446 tab_to_available_range_.end()) {
447 if (index < tab_to_available_range_[tab_id.id()].first ||
448 index > tab_to_available_range_[tab_id.id()].second) {
449 // The new index is outside the range of what we've archived, schedule
450 // a reset.
451 ResetFromCurrentBrowsers();
452 return;
455 ScheduleCommand(
456 sessions::CreateSetSelectedNavigationIndexCommand(tab_id, index).Pass());
459 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
460 int index) {
461 if (!ShouldTrackChangesToWindow(window_id))
462 return;
464 ScheduleCommand(
465 sessions::CreateSetSelectedTabInWindowCommand(window_id, index).Pass());
468 void SessionService::SetTabUserAgentOverride(
469 const SessionID& window_id,
470 const SessionID& tab_id,
471 const std::string& user_agent_override) {
472 if (!ShouldTrackChangesToWindow(window_id))
473 return;
475 ScheduleCommand(sessions::CreateSetTabUserAgentOverrideCommand(
476 tab_id, user_agent_override).Pass());
479 void SessionService::SetTabExtensionAppID(
480 const SessionID& window_id,
481 const SessionID& tab_id,
482 const std::string& extension_app_id) {
483 if (!ShouldTrackChangesToWindow(window_id))
484 return;
486 ScheduleCommand(sessions::CreateSetTabExtensionAppIDCommand(
487 tab_id, extension_app_id).Pass());
490 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
491 const SessionCallback& callback,
492 base::CancelableTaskTracker* tracker) {
493 // OnGotSessionCommands maps the SessionCommands to browser state, then run
494 // the callback.
495 return base_session_service_->ScheduleGetLastSessionCommands(
496 base::Bind(&SessionService::OnGotSessionCommands,
497 weak_factory_.GetWeakPtr(),
498 callback),
499 tracker);
502 void SessionService::OnSavedCommands() {
503 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
504 &last_updated_save_time_);
505 content::NotificationService::current()->Notify(
506 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
507 content::Source<Profile>(profile()),
508 content::NotificationService::NoDetails());
511 void SessionService::Init() {
512 // Register for the notifications we're interested in.
513 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
514 content::NotificationService::AllSources());
515 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
516 content::NotificationService::AllSources());
517 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
518 content::NotificationService::AllSources());
519 registrar_.Add(
520 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
521 content::NotificationService::AllSources());
523 BrowserList::AddObserver(this);
526 bool SessionService::ShouldRestoreWindowOfType(
527 sessions::SessionWindow::WindowType window_type,
528 AppType app_type) const {
529 #if defined(OS_CHROMEOS)
530 // Restore app popups for ChromeOS alone.
531 if (window_type == sessions::SessionWindow::TYPE_POPUP &&
532 app_type == TYPE_APP)
533 return true;
534 #endif
536 return window_type == sessions::SessionWindow::TYPE_TABBED;
539 void SessionService::RemoveUnusedRestoreWindows(
540 std::vector<sessions::SessionWindow*>* window_list) {
541 std::vector<sessions::SessionWindow*>::iterator i = window_list->begin();
542 while (i != window_list->end()) {
543 sessions::SessionWindow* window = *i;
544 if (!ShouldRestoreWindowOfType(window->type,
545 window->app_name.empty() ? TYPE_NORMAL :
546 TYPE_APP)) {
547 delete window;
548 i = window_list->erase(i);
549 } else {
550 ++i;
555 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
556 Browser* browser) {
557 if (ShouldNewWindowStartSession()) {
558 // We're going from no tabbed browsers to a tabbed browser (and not in
559 // process startup), restore the last session.
560 if (move_on_new_browser_) {
561 // Make the current session the last.
562 MoveCurrentSessionToLastSession();
563 move_on_new_browser_ = false;
565 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
566 *base::CommandLine::ForCurrentProcess(), profile());
567 if (pref.type == SessionStartupPref::LAST) {
568 SessionRestore::RestoreSession(
569 profile(), browser,
570 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
571 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
572 urls_to_open);
573 return true;
576 return false;
579 void SessionService::Observe(int type,
580 const content::NotificationSource& source,
581 const content::NotificationDetails& details) {
582 // All of our messages have the NavigationController as the source.
583 switch (type) {
584 case content::NOTIFICATION_NAV_LIST_PRUNED: {
585 WebContents* web_contents =
586 content::Source<content::NavigationController>(source).ptr()->
587 GetWebContents();
588 SessionTabHelper* session_tab_helper =
589 SessionTabHelper::FromWebContents(web_contents);
590 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
591 return;
592 content::Details<content::PrunedDetails> pruned_details(details);
593 if (pruned_details->from_front) {
594 TabNavigationPathPrunedFromFront(
595 session_tab_helper->window_id(),
596 session_tab_helper->session_id(),
597 pruned_details->count);
598 } else {
599 TabNavigationPathPrunedFromBack(
600 session_tab_helper->window_id(),
601 session_tab_helper->session_id(),
602 web_contents->GetController().GetEntryCount());
604 RecordSessionUpdateHistogramData(type,
605 &last_updated_nav_list_pruned_time_);
606 break;
609 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
610 WebContents* web_contents =
611 content::Source<content::NavigationController>(source).ptr()->
612 GetWebContents();
613 SessionTabHelper* session_tab_helper =
614 SessionTabHelper::FromWebContents(web_contents);
615 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
616 return;
617 content::Details<content::EntryChangedDetails> changed(details);
618 const SerializedNavigationEntry navigation =
619 ContentSerializedNavigationBuilder::FromNavigationEntry(
620 changed->index, *changed->changed_entry);
621 UpdateTabNavigation(session_tab_helper->window_id(),
622 session_tab_helper->session_id(),
623 navigation);
624 break;
627 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
628 WebContents* web_contents =
629 content::Source<content::NavigationController>(source).ptr()->
630 GetWebContents();
631 SessionTabHelper* session_tab_helper =
632 SessionTabHelper::FromWebContents(web_contents);
633 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
634 return;
635 int current_entry_index =
636 web_contents->GetController().GetCurrentEntryIndex();
637 SetSelectedNavigationIndex(
638 session_tab_helper->window_id(),
639 session_tab_helper->session_id(),
640 current_entry_index);
641 const SerializedNavigationEntry navigation =
642 ContentSerializedNavigationBuilder::FromNavigationEntry(
643 current_entry_index,
644 *web_contents->GetController().GetEntryAtIndex(
645 current_entry_index));
646 UpdateTabNavigation(
647 session_tab_helper->window_id(),
648 session_tab_helper->session_id(),
649 navigation);
650 content::Details<content::LoadCommittedDetails> changed(details);
651 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
652 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
653 RecordSessionUpdateHistogramData(type,
654 &last_updated_nav_entry_commit_time_);
656 break;
659 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
660 extensions::TabHelper* extension_tab_helper =
661 content::Source<extensions::TabHelper>(source).ptr();
662 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
663 profile()) {
664 return;
666 if (extension_tab_helper->extension_app()) {
667 SessionTabHelper* session_tab_helper =
668 SessionTabHelper::FromWebContents(
669 extension_tab_helper->web_contents());
670 SetTabExtensionAppID(session_tab_helper->window_id(),
671 session_tab_helper->session_id(),
672 extension_tab_helper->extension_app()->id());
674 break;
677 default:
678 NOTREACHED();
682 void SessionService::OnBrowserSetLastActive(Browser* browser) {
683 if (ShouldTrackBrowser(browser))
684 ScheduleCommand(sessions::CreateSetActiveWindowCommand(
685 browser->session_id()).Pass());
688 void SessionService::OnGotSessionCommands(
689 const SessionCallback& callback,
690 ScopedVector<sessions::SessionCommand> commands) {
691 ScopedVector<sessions::SessionWindow> valid_windows;
692 SessionID::id_type active_window_id = 0;
694 startup_metric_utils::ScopedSlowStartupUMA
695 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
697 sessions::RestoreSessionFromCommands(
698 commands, &valid_windows.get(), &active_window_id);
699 RemoveUnusedRestoreWindows(&valid_windows.get());
701 callback.Run(valid_windows.Pass(), active_window_id);
704 void SessionService::BuildCommandsForTab(const SessionID& window_id,
705 WebContents* tab,
706 int index_in_window,
707 bool is_pinned,
708 IdToRange* tab_to_available_range) {
709 DCHECK(tab && window_id.id());
710 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
711 const SessionID& session_id(session_tab_helper->session_id());
712 base_session_service_->AppendRebuildCommand(
713 sessions::CreateSetTabWindowCommand(window_id, session_id));
715 const int current_index = tab->GetController().GetCurrentEntryIndex();
716 const int min_index = std::max(current_index - gMaxPersistNavigationCount, 0);
717 const int max_index = std::min(current_index + gMaxPersistNavigationCount,
718 tab->GetController().GetEntryCount());
719 const int pending_index = tab->GetController().GetPendingEntryIndex();
720 if (tab_to_available_range) {
721 (*tab_to_available_range)[session_id.id()] =
722 std::pair<int, int>(min_index, max_index);
725 if (is_pinned) {
726 base_session_service_->AppendRebuildCommand(
727 sessions::CreatePinnedStateCommand(session_id, true));
730 extensions::TabHelper* extensions_tab_helper =
731 extensions::TabHelper::FromWebContents(tab);
732 if (extensions_tab_helper->extension_app()) {
733 base_session_service_->AppendRebuildCommand(
734 sessions::CreateSetTabExtensionAppIDCommand(
735 session_id,
736 extensions_tab_helper->extension_app()->id()));
739 const std::string& ua_override = tab->GetUserAgentOverride();
740 if (!ua_override.empty()) {
741 base_session_service_->AppendRebuildCommand(
742 sessions::CreateSetTabUserAgentOverrideCommand(session_id,
743 ua_override));
746 for (int i = min_index; i < max_index; ++i) {
747 const NavigationEntry* entry = (i == pending_index) ?
748 tab->GetController().GetPendingEntry() :
749 tab->GetController().GetEntryAtIndex(i);
750 DCHECK(entry);
751 if (ShouldTrackEntry(entry->GetVirtualURL())) {
752 const SerializedNavigationEntry navigation =
753 ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry);
754 base_session_service_->AppendRebuildCommand(
755 CreateUpdateTabNavigationCommand(session_id, navigation));
758 base_session_service_->AppendRebuildCommand(
759 sessions::CreateSetSelectedNavigationIndexCommand(session_id,
760 current_index));
762 if (index_in_window != -1) {
763 base_session_service_->AppendRebuildCommand(
764 sessions::CreateSetTabIndexInWindowCommand(session_id,
765 index_in_window));
768 // Record the association between the sessionStorage namespace and the tab.
769 content::SessionStorageNamespace* session_storage_namespace =
770 tab->GetController().GetDefaultSessionStorageNamespace();
771 ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
772 session_tab_helper->session_id(),
773 session_storage_namespace->persistent_id()).Pass());
776 void SessionService::BuildCommandsForBrowser(
777 Browser* browser,
778 IdToRange* tab_to_available_range,
779 std::set<SessionID::id_type>* windows_to_track) {
780 DCHECK(browser);
781 DCHECK(browser->session_id().id());
783 base_session_service_->AppendRebuildCommand(
784 sessions::CreateSetWindowBoundsCommand(
785 browser->session_id(),
786 browser->window()->GetRestoredBounds(),
787 browser->window()->GetRestoredState()));
789 base_session_service_->AppendRebuildCommand(
790 sessions::CreateSetWindowTypeCommand(
791 browser->session_id(),
792 WindowTypeForBrowserType(browser->type())));
794 if (!browser->app_name().empty()) {
795 base_session_service_->AppendRebuildCommand(
796 sessions::CreateSetWindowAppNameCommand(
797 browser->session_id(),
798 browser->app_name()));
801 windows_to_track->insert(browser->session_id().id());
802 TabStripModel* tab_strip = browser->tab_strip_model();
803 for (int i = 0; i < tab_strip->count(); ++i) {
804 WebContents* tab = tab_strip->GetWebContentsAt(i);
805 DCHECK(tab);
806 BuildCommandsForTab(browser->session_id(),
807 tab,
809 tab_strip->IsTabPinned(i),
810 tab_to_available_range);
813 base_session_service_->AppendRebuildCommand(
814 sessions::CreateSetSelectedTabInWindowCommand(
815 browser->session_id(),
816 browser->tab_strip_model()->active_index()));
819 void SessionService::BuildCommandsFromBrowsers(
820 IdToRange* tab_to_available_range,
821 std::set<SessionID::id_type>* windows_to_track) {
822 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
823 Browser* browser = *it;
824 // Make sure the browser has tabs and a window. Browser's destructor
825 // removes itself from the BrowserList. When a browser is closed the
826 // destructor is not necessarily run immediately. This means it's possible
827 // for us to get a handle to a browser that is about to be removed. If
828 // the tab count is 0 or the window is NULL, the browser is about to be
829 // deleted, so we ignore it.
830 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
831 browser->window()) {
832 BuildCommandsForBrowser(browser,
833 tab_to_available_range,
834 windows_to_track);
839 void SessionService::ScheduleResetCommands() {
840 base_session_service_->set_pending_reset(true);
841 base_session_service_->ClearPendingCommands();
842 tab_to_available_range_.clear();
843 windows_tracking_.clear();
844 BuildCommandsFromBrowsers(&tab_to_available_range_,
845 &windows_tracking_);
846 if (!windows_tracking_.empty()) {
847 // We're lazily created on startup and won't get an initial batch of
848 // SetWindowType messages. Set these here to make sure our state is correct.
849 has_open_trackable_browsers_ = true;
850 move_on_new_browser_ = true;
852 base_session_service_->StartSaveTimer();
855 void SessionService::ScheduleCommand(
856 scoped_ptr<sessions::SessionCommand> command) {
857 DCHECK(command);
858 if (ReplacePendingCommand(base_session_service_.get(), &command))
859 return;
860 bool is_closing_command = IsClosingCommand(command.get());
861 base_session_service_->ScheduleCommand(command.Pass());
862 // Don't schedule a reset on tab closed/window closed. Otherwise we may
863 // lose tabs/windows we want to restore from if we exit right after this.
864 if (!base_session_service_->pending_reset() &&
865 pending_window_close_ids_.empty() &&
866 base_session_service_->commands_since_reset() >= kWritesPerReset &&
867 !is_closing_command) {
868 ScheduleResetCommands();
872 void SessionService::CommitPendingCloses() {
873 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
874 i != pending_tab_close_ids_.end(); ++i) {
875 ScheduleCommand(sessions::CreateTabClosedCommand(*i).Pass());
877 pending_tab_close_ids_.clear();
879 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
880 i != pending_window_close_ids_.end(); ++i) {
881 ScheduleCommand(sessions::CreateWindowClosedCommand(*i).Pass());
883 pending_window_close_ids_.clear();
886 bool SessionService::IsOnlyOneTabLeft() const {
887 if (!profile() || profile()->AsTestingProfile()) {
888 // We're testing, always return false.
889 return false;
892 int window_count = 0;
893 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
894 Browser* browser = *it;
895 const SessionID::id_type window_id = browser->session_id().id();
896 if (ShouldTrackBrowser(browser) &&
897 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
898 if (++window_count > 1)
899 return false;
900 // By the time this is invoked the tab has been removed. As such, we use
901 // > 0 here rather than > 1.
902 if (browser->tab_strip_model()->count() > 0)
903 return false;
906 return true;
909 bool SessionService::HasOpenTrackableBrowsers(
910 const SessionID& window_id) const {
911 if (!profile() || profile()->AsTestingProfile()) {
912 // We're testing, always return true.
913 return true;
916 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
917 Browser* browser = *it;
918 const SessionID::id_type browser_id = browser->session_id().id();
919 if (browser_id != window_id.id() &&
920 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
921 ShouldTrackBrowser(browser)) {
922 return true;
925 return false;
928 bool SessionService::ShouldTrackChangesToWindow(
929 const SessionID& window_id) const {
930 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
933 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
934 if (browser->profile() != profile())
935 return false;
936 // Never track app popup windows that do not have a trusted source (i.e.
937 // popup windows spawned by an app). If this logic changes, be sure to also
938 // change SessionRestoreImpl::CreateRestoredBrowser().
939 if (browser->is_app() && browser->is_type_popup() &&
940 !browser->is_trusted_source()) {
941 return false;
943 return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()),
944 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
947 void SessionService::RecordSessionUpdateHistogramData(int type,
948 base::TimeTicks* last_updated_time) {
949 if (!last_updated_time->is_null()) {
950 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
951 // We're interested in frequent updates periods longer than
952 // 10 minutes.
953 bool use_long_period = false;
954 if (delta >= save_delay_in_mins_) {
955 use_long_period = true;
957 switch (type) {
958 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
959 RecordUpdatedSaveTime(delta, use_long_period);
960 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
961 break;
962 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
963 RecordUpdatedTabClosed(delta, use_long_period);
964 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
965 break;
966 case content::NOTIFICATION_NAV_LIST_PRUNED:
967 RecordUpdatedNavListPruned(delta, use_long_period);
968 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
969 break;
970 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
971 RecordUpdatedNavEntryCommit(delta, use_long_period);
972 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
973 break;
974 default:
975 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
976 break;
979 (*last_updated_time) = base::TimeTicks::Now();
982 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
983 bool use_long_period) {
984 std::string name("SessionRestore.TabClosedPeriod");
985 UMA_HISTOGRAM_CUSTOM_TIMES(name,
986 delta,
987 // 2500ms is the default save delay.
988 save_delay_in_millis_,
989 save_delay_in_mins_,
990 50);
991 if (use_long_period) {
992 std::string long_name_("SessionRestore.TabClosedLongPeriod");
993 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
994 delta,
995 save_delay_in_mins_,
996 save_delay_in_hrs_,
997 50);
1001 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1002 bool use_long_period) {
1003 std::string name("SessionRestore.NavigationListPrunedPeriod");
1004 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1005 delta,
1006 // 2500ms is the default save delay.
1007 save_delay_in_millis_,
1008 save_delay_in_mins_,
1009 50);
1010 if (use_long_period) {
1011 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1012 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1013 delta,
1014 save_delay_in_mins_,
1015 save_delay_in_hrs_,
1016 50);
1020 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1021 bool use_long_period) {
1022 std::string name("SessionRestore.NavEntryCommittedPeriod");
1023 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1024 delta,
1025 // 2500ms is the default save delay.
1026 save_delay_in_millis_,
1027 save_delay_in_mins_,
1028 50);
1029 if (use_long_period) {
1030 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1031 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1032 delta,
1033 save_delay_in_mins_,
1034 save_delay_in_hrs_,
1035 50);
1039 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1040 bool use_long_period) {
1041 std::string name("SessionRestore.SavePeriod");
1042 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1043 delta,
1044 // 2500ms is the default save delay.
1045 save_delay_in_millis_,
1046 save_delay_in_mins_,
1047 50);
1048 if (use_long_period) {
1049 std::string long_name_("SessionRestore.SaveLongPeriod");
1050 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1051 delta,
1052 save_delay_in_mins_,
1053 save_delay_in_hrs_,
1054 50);
1058 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1059 bool use_long_period) {
1060 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1061 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1062 delta,
1063 // 2500ms is the default save delay.
1064 save_delay_in_millis_,
1065 save_delay_in_mins_,
1066 50);
1067 if (use_long_period) {
1068 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1069 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1070 delta,
1071 save_delay_in_mins_,
1072 save_delay_in_hrs_,
1073 50);
1077 void SessionService::MaybeDeleteSessionOnlyData() {
1078 // Don't try anything if we're testing. The browser_process is not fully
1079 // created and DeleteSession will crash if we actually attempt it.
1080 if (!profile() || profile()->AsTestingProfile())
1081 return;
1083 // Clear session data if the last window for a profile has been closed and
1084 // closing the last window would normally close Chrome, unless background mode
1085 // is active. Tests don't have a background_mode_manager.
1086 if (has_open_trackable_browsers_ ||
1087 browser_defaults::kBrowserAliveWithNoWindows ||
1088 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1089 return;
1092 // Check for any open windows for the current profile that we aren't tracking.
1093 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1094 if ((*it)->profile() == profile())
1095 return;
1097 DeleteSessionOnlyData(profile());
1100 sessions::BaseSessionService* SessionService::GetBaseSessionServiceForTest() {
1101 return base_session_service_.get();