Componentize HistoryURLProvider/ScoredHistoryMatch.
[chromium-blink-merge.git] / chrome / browser / sessions / session_service.cc
blob1e241d68f110fdc622e48b087ef43c4789b9ec2b
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 "content/public/browser/navigation_details.h"
43 #include "content/public/browser/navigation_entry.h"
44 #include "content/public/browser/notification_details.h"
45 #include "content/public/browser/notification_service.h"
46 #include "content/public/browser/session_storage_namespace.h"
47 #include "content/public/browser/web_contents.h"
48 #include "extensions/common/extension.h"
50 #if defined(OS_MACOSX)
51 #include "chrome/browser/app_controller_mac.h"
52 #endif
54 using base::Time;
55 using content::NavigationEntry;
56 using content::WebContents;
57 using sessions::ContentSerializedNavigationBuilder;
58 using sessions::SerializedNavigationEntry;
60 // Every kWritesPerReset commands triggers recreating the file.
61 static const int kWritesPerReset = 250;
63 // SessionService -------------------------------------------------------------
65 SessionService::SessionService(Profile* profile)
66 : BaseSessionServiceDelegateImpl(true),
67 profile_(profile),
68 base_session_service_(
69 new sessions::BaseSessionService(
70 sessions::BaseSessionService::SESSION_RESTORE,
71 profile->GetPath(),
72 this)),
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),
79 weak_factory_(this) {
80 // We should never be created when incognito.
81 DCHECK(!profile->IsOffTheRecord());
82 Init();
85 SessionService::SessionService(const base::FilePath& save_path)
86 : BaseSessionServiceDelegateImpl(false),
87 profile_(NULL),
88 base_session_service_(
89 new sessions::BaseSessionService(
90 sessions::BaseSessionService::SESSION_RESTORE,
91 save_path,
92 this)),
93 has_open_trackable_browsers_(false),
94 move_on_new_browser_(false),
95 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
96 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
97 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
98 force_browser_not_alive_with_no_windows_(false),
99 weak_factory_(this) {
100 Init();
103 SessionService::~SessionService() {
104 // The BrowserList should outlive the SessionService since it's static and
105 // the SessionService is a KeyedService.
106 BrowserList::RemoveObserver(this);
107 base_session_service_->Save();
110 bool SessionService::ShouldNewWindowStartSession() {
111 // ChromeOS and OSX have different ideas of application lifetime than
112 // the other platforms.
113 // On ChromeOS opening a new window should never start a new session.
114 #if defined(OS_CHROMEOS)
115 if (!force_browser_not_alive_with_no_windows_)
116 return false;
117 #endif
118 if (!has_open_trackable_browsers_ &&
119 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
120 !SessionRestore::IsRestoring(profile())
121 #if defined(OS_MACOSX)
122 // On OSX, a new window should not start a new session if it was opened
123 // from the dock or the menubar.
124 && !app_controller_mac::IsOpeningNewWindow()
125 #endif // OS_MACOSX
127 return true;
129 return false;
132 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
133 return RestoreIfNecessary(urls_to_open, NULL);
136 void SessionService::ResetFromCurrentBrowsers() {
137 ScheduleResetCommands();
140 void SessionService::MoveCurrentSessionToLastSession() {
141 pending_tab_close_ids_.clear();
142 window_closing_ids_.clear();
143 pending_window_close_ids_.clear();
145 base_session_service_->MoveCurrentSessionToLastSession();
148 void SessionService::DeleteLastSession() {
149 base_session_service_->DeleteLastSession();
152 void SessionService::SetTabWindow(const SessionID& window_id,
153 const SessionID& tab_id) {
154 if (!ShouldTrackChangesToWindow(window_id))
155 return;
157 ScheduleCommand(sessions::CreateSetTabWindowCommand(window_id,
158 tab_id).Pass());
161 void SessionService::SetWindowBounds(const SessionID& window_id,
162 const gfx::Rect& bounds,
163 ui::WindowShowState show_state) {
164 if (!ShouldTrackChangesToWindow(window_id))
165 return;
167 ScheduleCommand(sessions::CreateSetWindowBoundsCommand(
168 window_id, bounds, show_state).Pass());
171 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
172 const SessionID& tab_id,
173 int new_index) {
174 if (!ShouldTrackChangesToWindow(window_id))
175 return;
177 ScheduleCommand(sessions::CreateSetTabIndexInWindowCommand(tab_id,
178 new_index).Pass());
181 void SessionService::SetPinnedState(const SessionID& window_id,
182 const SessionID& tab_id,
183 bool is_pinned) {
184 if (!ShouldTrackChangesToWindow(window_id))
185 return;
187 ScheduleCommand(sessions::CreatePinnedStateCommand(tab_id, is_pinned).Pass());
190 void SessionService::TabClosed(const SessionID& window_id,
191 const SessionID& tab_id,
192 bool closed_by_user_gesture) {
193 if (!tab_id.id())
194 return; // Hapens when the tab is replaced.
196 if (!ShouldTrackChangesToWindow(window_id))
197 return;
199 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
200 if (i != tab_to_available_range_.end())
201 tab_to_available_range_.erase(i);
203 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
204 window_id.id()) != pending_window_close_ids_.end()) {
205 // Tab is in last window. Don't commit it immediately, instead add it to the
206 // list of tabs to close. If the user creates another window, the close is
207 // committed.
208 pending_tab_close_ids_.insert(tab_id.id());
209 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
210 window_id.id()) != window_closing_ids_.end() ||
211 !IsOnlyOneTabLeft() ||
212 closed_by_user_gesture) {
213 // Close is the result of one of the following:
214 // . window close (and it isn't the last window).
215 // . closing a tab and there are other windows/tabs open.
216 // . closed by a user gesture.
217 // In all cases we need to mark the tab as explicitly closed.
218 ScheduleCommand(sessions::CreateTabClosedCommand(tab_id.id()).Pass());
219 } else {
220 // User closed the last tab in the last tabbed browser. Don't mark the
221 // tab closed.
222 pending_tab_close_ids_.insert(tab_id.id());
223 has_open_trackable_browsers_ = false;
227 void SessionService::WindowOpened(Browser* browser) {
228 if (!ShouldTrackBrowser(browser))
229 return;
231 RestoreIfNecessary(std::vector<GURL>(), browser);
232 SetWindowType(browser->session_id(),
233 browser->type(),
234 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
235 SetWindowAppName(browser->session_id(), browser->app_name());
238 void SessionService::WindowClosing(const SessionID& window_id) {
239 if (!ShouldTrackChangesToWindow(window_id))
240 return;
242 // The window is about to close. If there are other tabbed browsers with the
243 // same original profile commit the close immediately.
245 // NOTE: if the user chooses the exit menu item session service is destroyed
246 // and this code isn't hit.
247 if (has_open_trackable_browsers_) {
248 // Closing a window can never make has_open_trackable_browsers_ go from
249 // false to true, so only update it if already true.
250 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
252 bool use_pending_close = !has_open_trackable_browsers_;
253 if (!use_pending_close) {
254 // Somewhat outside of "normal behavior" is profile locking. In this case
255 // (when IsSiginRequired has already been set True), we're closing all
256 // browser windows in turn but want them all to be restored when the user
257 // unlocks. To accomplish this, we do a "pending close" on all windows
258 // instead of just the last one (which has no open_trackable_browsers).
259 // http://crbug.com/356818
261 // Some editions (like iOS) don't have a profile_manager and some tests
262 // don't supply one so be lenient.
263 if (g_browser_process) {
264 ProfileManager* profile_manager = g_browser_process->profile_manager();
265 if (profile_manager) {
266 ProfileInfoCache& profile_info =
267 profile_manager->GetProfileInfoCache();
268 size_t profile_index = profile_info.GetIndexOfProfileWithPath(
269 profile()->GetPath());
270 use_pending_close = profile_index != std::string::npos &&
271 profile_info.ProfileIsSigninRequiredAtIndex(profile_index);
275 if (use_pending_close)
276 pending_window_close_ids_.insert(window_id.id());
277 else
278 window_closing_ids_.insert(window_id.id());
281 void SessionService::WindowClosed(const SessionID& window_id) {
282 if (!ShouldTrackChangesToWindow(window_id)) {
283 // The last window may be one that is not tracked.
284 MaybeDeleteSessionOnlyData();
285 return;
288 windows_tracking_.erase(window_id.id());
290 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
291 window_closing_ids_.erase(window_id.id());
292 ScheduleCommand(sessions::CreateWindowClosedCommand(window_id.id()).Pass());
293 } else if (pending_window_close_ids_.find(window_id.id()) ==
294 pending_window_close_ids_.end()) {
295 // We'll hit this if user closed the last tab in a window.
296 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
297 if (!has_open_trackable_browsers_)
298 pending_window_close_ids_.insert(window_id.id());
299 else
300 ScheduleCommand(sessions::CreateWindowClosedCommand(
301 window_id.id()).Pass());
303 MaybeDeleteSessionOnlyData();
306 void SessionService::TabInserted(WebContents* contents) {
307 SessionTabHelper* session_tab_helper =
308 SessionTabHelper::FromWebContents(contents);
309 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
310 return;
311 SetTabWindow(session_tab_helper->window_id(),
312 session_tab_helper->session_id());
313 extensions::TabHelper* extensions_tab_helper =
314 extensions::TabHelper::FromWebContents(contents);
315 if (extensions_tab_helper &&
316 extensions_tab_helper->extension_app()) {
317 SetTabExtensionAppID(
318 session_tab_helper->window_id(),
319 session_tab_helper->session_id(),
320 extensions_tab_helper->extension_app()->id());
323 // Record the association between the SessionStorageNamespace and the
324 // tab.
326 // TODO(ajwong): This should be processing the whole map rather than
327 // just the default. This in particular will not work for tabs with only
328 // isolated apps which won't have a default partition.
329 content::SessionStorageNamespace* session_storage_namespace =
330 contents->GetController().GetDefaultSessionStorageNamespace();
331 ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
332 session_tab_helper->session_id(),
333 session_storage_namespace->persistent_id()).Pass());
334 session_storage_namespace->SetShouldPersist(true);
337 void SessionService::TabClosing(WebContents* contents) {
338 // Allow the associated sessionStorage to get deleted; it won't be needed
339 // in the session restore.
340 content::SessionStorageNamespace* session_storage_namespace =
341 contents->GetController().GetDefaultSessionStorageNamespace();
342 session_storage_namespace->SetShouldPersist(false);
343 SessionTabHelper* session_tab_helper =
344 SessionTabHelper::FromWebContents(contents);
345 TabClosed(session_tab_helper->window_id(),
346 session_tab_helper->session_id(),
347 contents->GetClosedByUserGesture());
348 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
349 &last_updated_tab_closed_time_);
352 void SessionService::SetWindowType(const SessionID& window_id,
353 Browser::Type type,
354 AppType app_type) {
355 sessions::SessionWindow::WindowType window_type =
356 WindowTypeForBrowserType(type);
357 if (!ShouldRestoreWindowOfType(window_type, app_type))
358 return;
360 windows_tracking_.insert(window_id.id());
362 // The user created a new tabbed browser with our profile. Commit any
363 // pending closes.
364 CommitPendingCloses();
366 has_open_trackable_browsers_ = true;
367 move_on_new_browser_ = true;
369 ScheduleCommand(CreateSetWindowTypeCommand(window_id, window_type).Pass());
372 void SessionService::SetWindowAppName(
373 const SessionID& window_id,
374 const std::string& app_name) {
375 if (!ShouldTrackChangesToWindow(window_id))
376 return;
378 ScheduleCommand(sessions::CreateSetWindowAppNameCommand(window_id,
379 app_name).Pass());
382 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
383 const SessionID& tab_id,
384 int count) {
385 if (!ShouldTrackChangesToWindow(window_id))
386 return;
388 ScheduleCommand(sessions::CreateTabNavigationPathPrunedFromBackCommand(
389 tab_id, count).Pass());
392 void SessionService::TabNavigationPathPrunedFromFront(
393 const SessionID& window_id,
394 const SessionID& tab_id,
395 int count) {
396 if (!ShouldTrackChangesToWindow(window_id))
397 return;
399 // Update the range of indices.
400 if (tab_to_available_range_.find(tab_id.id()) !=
401 tab_to_available_range_.end()) {
402 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
403 range.first = std::max(0, range.first - count);
404 range.second = std::max(0, range.second - count);
407 ScheduleCommand(sessions::CreateTabNavigationPathPrunedFromFrontCommand(
408 tab_id, count).Pass());
411 void SessionService::UpdateTabNavigation(
412 const SessionID& window_id,
413 const SessionID& tab_id,
414 const SerializedNavigationEntry& navigation) {
415 if (!ShouldTrackEntry(navigation.virtual_url()) ||
416 !ShouldTrackChangesToWindow(window_id)) {
417 return;
420 if (tab_to_available_range_.find(tab_id.id()) !=
421 tab_to_available_range_.end()) {
422 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
423 range.first = std::min(navigation.index(), range.first);
424 range.second = std::max(navigation.index(), range.second);
426 ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, navigation).Pass());
429 void SessionService::TabRestored(WebContents* tab, bool pinned) {
430 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
431 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
432 return;
434 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, pinned, NULL);
435 base_session_service_->StartSaveTimer();
438 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
439 const SessionID& tab_id,
440 int index) {
441 if (!ShouldTrackChangesToWindow(window_id))
442 return;
444 if (tab_to_available_range_.find(tab_id.id()) !=
445 tab_to_available_range_.end()) {
446 if (index < tab_to_available_range_[tab_id.id()].first ||
447 index > tab_to_available_range_[tab_id.id()].second) {
448 // The new index is outside the range of what we've archived, schedule
449 // a reset.
450 ResetFromCurrentBrowsers();
451 return;
454 ScheduleCommand(
455 sessions::CreateSetSelectedNavigationIndexCommand(tab_id, index).Pass());
458 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
459 int index) {
460 if (!ShouldTrackChangesToWindow(window_id))
461 return;
463 ScheduleCommand(
464 sessions::CreateSetSelectedTabInWindowCommand(window_id, index).Pass());
467 void SessionService::SetTabUserAgentOverride(
468 const SessionID& window_id,
469 const SessionID& tab_id,
470 const std::string& user_agent_override) {
471 if (!ShouldTrackChangesToWindow(window_id))
472 return;
474 ScheduleCommand(sessions::CreateSetTabUserAgentOverrideCommand(
475 tab_id, user_agent_override).Pass());
478 void SessionService::SetTabExtensionAppID(
479 const SessionID& window_id,
480 const SessionID& tab_id,
481 const std::string& extension_app_id) {
482 if (!ShouldTrackChangesToWindow(window_id))
483 return;
485 ScheduleCommand(sessions::CreateSetTabExtensionAppIDCommand(
486 tab_id, extension_app_id).Pass());
489 void SessionService::SetLastActiveTime(const SessionID& window_id,
490 const SessionID& tab_id,
491 base::TimeTicks last_active_time) {
492 if (!ShouldTrackChangesToWindow(window_id))
493 return;
495 ScheduleCommand(
496 sessions::CreateLastActiveTimeCommand(tab_id, last_active_time).Pass());
499 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
500 const SessionCallback& callback,
501 base::CancelableTaskTracker* tracker) {
502 // OnGotSessionCommands maps the SessionCommands to browser state, then run
503 // the callback.
504 return base_session_service_->ScheduleGetLastSessionCommands(
505 base::Bind(&SessionService::OnGotSessionCommands,
506 weak_factory_.GetWeakPtr(),
507 callback),
508 tracker);
511 void SessionService::OnSavedCommands() {
512 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
513 &last_updated_save_time_);
514 content::NotificationService::current()->Notify(
515 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
516 content::Source<Profile>(profile()),
517 content::NotificationService::NoDetails());
520 void SessionService::Init() {
521 // Register for the notifications we're interested in.
522 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
523 content::NotificationService::AllSources());
524 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
525 content::NotificationService::AllSources());
526 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
527 content::NotificationService::AllSources());
528 registrar_.Add(
529 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
530 content::NotificationService::AllSources());
532 BrowserList::AddObserver(this);
535 bool SessionService::ShouldRestoreWindowOfType(
536 sessions::SessionWindow::WindowType window_type,
537 AppType app_type) const {
538 #if defined(OS_CHROMEOS)
539 // Restore app popups for ChromeOS alone.
540 if (window_type == sessions::SessionWindow::TYPE_POPUP &&
541 app_type == TYPE_APP)
542 return true;
543 #endif
545 return window_type == sessions::SessionWindow::TYPE_TABBED;
548 void SessionService::RemoveUnusedRestoreWindows(
549 std::vector<sessions::SessionWindow*>* window_list) {
550 std::vector<sessions::SessionWindow*>::iterator i = window_list->begin();
551 while (i != window_list->end()) {
552 sessions::SessionWindow* window = *i;
553 if (!ShouldRestoreWindowOfType(window->type,
554 window->app_name.empty() ? TYPE_NORMAL :
555 TYPE_APP)) {
556 delete window;
557 i = window_list->erase(i);
558 } else {
559 ++i;
564 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
565 Browser* browser) {
566 if (ShouldNewWindowStartSession()) {
567 // We're going from no tabbed browsers to a tabbed browser (and not in
568 // process startup), restore the last session.
569 if (move_on_new_browser_) {
570 // Make the current session the last.
571 MoveCurrentSessionToLastSession();
572 move_on_new_browser_ = false;
574 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
575 *base::CommandLine::ForCurrentProcess(), profile());
576 if (pref.type == SessionStartupPref::LAST) {
577 SessionRestore::RestoreSession(
578 profile(), browser,
579 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
580 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
581 urls_to_open);
582 return true;
585 return false;
588 void SessionService::Observe(int type,
589 const content::NotificationSource& source,
590 const content::NotificationDetails& details) {
591 // All of our messages have the NavigationController as the source.
592 switch (type) {
593 case content::NOTIFICATION_NAV_LIST_PRUNED: {
594 WebContents* web_contents =
595 content::Source<content::NavigationController>(source).ptr()->
596 GetWebContents();
597 SessionTabHelper* session_tab_helper =
598 SessionTabHelper::FromWebContents(web_contents);
599 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
600 return;
601 content::Details<content::PrunedDetails> pruned_details(details);
602 if (pruned_details->from_front) {
603 TabNavigationPathPrunedFromFront(
604 session_tab_helper->window_id(),
605 session_tab_helper->session_id(),
606 pruned_details->count);
607 } else {
608 TabNavigationPathPrunedFromBack(
609 session_tab_helper->window_id(),
610 session_tab_helper->session_id(),
611 web_contents->GetController().GetEntryCount());
613 RecordSessionUpdateHistogramData(type,
614 &last_updated_nav_list_pruned_time_);
615 break;
618 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
619 WebContents* web_contents =
620 content::Source<content::NavigationController>(source).ptr()->
621 GetWebContents();
622 SessionTabHelper* session_tab_helper =
623 SessionTabHelper::FromWebContents(web_contents);
624 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
625 return;
626 content::Details<content::EntryChangedDetails> changed(details);
627 const SerializedNavigationEntry navigation =
628 ContentSerializedNavigationBuilder::FromNavigationEntry(
629 changed->index, *changed->changed_entry);
630 UpdateTabNavigation(session_tab_helper->window_id(),
631 session_tab_helper->session_id(),
632 navigation);
633 break;
636 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
637 WebContents* web_contents =
638 content::Source<content::NavigationController>(source).ptr()->
639 GetWebContents();
640 SessionTabHelper* session_tab_helper =
641 SessionTabHelper::FromWebContents(web_contents);
642 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
643 return;
644 int current_entry_index =
645 web_contents->GetController().GetCurrentEntryIndex();
646 SetSelectedNavigationIndex(
647 session_tab_helper->window_id(),
648 session_tab_helper->session_id(),
649 current_entry_index);
650 const SerializedNavigationEntry navigation =
651 ContentSerializedNavigationBuilder::FromNavigationEntry(
652 current_entry_index,
653 *web_contents->GetController().GetEntryAtIndex(
654 current_entry_index));
655 UpdateTabNavigation(
656 session_tab_helper->window_id(),
657 session_tab_helper->session_id(),
658 navigation);
659 content::Details<content::LoadCommittedDetails> changed(details);
660 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
661 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
662 RecordSessionUpdateHistogramData(type,
663 &last_updated_nav_entry_commit_time_);
665 break;
668 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
669 extensions::TabHelper* extension_tab_helper =
670 content::Source<extensions::TabHelper>(source).ptr();
671 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
672 profile()) {
673 return;
675 if (extension_tab_helper->extension_app()) {
676 SessionTabHelper* session_tab_helper =
677 SessionTabHelper::FromWebContents(
678 extension_tab_helper->web_contents());
679 SetTabExtensionAppID(session_tab_helper->window_id(),
680 session_tab_helper->session_id(),
681 extension_tab_helper->extension_app()->id());
683 break;
686 default:
687 NOTREACHED();
691 void SessionService::OnBrowserSetLastActive(Browser* browser) {
692 if (ShouldTrackBrowser(browser))
693 ScheduleCommand(sessions::CreateSetActiveWindowCommand(
694 browser->session_id()).Pass());
697 void SessionService::OnGotSessionCommands(
698 const SessionCallback& callback,
699 ScopedVector<sessions::SessionCommand> commands) {
700 ScopedVector<sessions::SessionWindow> valid_windows;
701 SessionID::id_type active_window_id = 0;
703 sessions::RestoreSessionFromCommands(
704 commands, &valid_windows.get(), &active_window_id);
705 RemoveUnusedRestoreWindows(&valid_windows.get());
707 callback.Run(valid_windows.Pass(), active_window_id);
710 void SessionService::BuildCommandsForTab(const SessionID& window_id,
711 WebContents* tab,
712 int index_in_window,
713 bool is_pinned,
714 IdToRange* tab_to_available_range) {
715 DCHECK(tab && window_id.id());
716 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
717 const SessionID& session_id(session_tab_helper->session_id());
718 base_session_service_->AppendRebuildCommand(
719 sessions::CreateSetTabWindowCommand(window_id, session_id));
721 const int current_index = tab->GetController().GetCurrentEntryIndex();
722 const int min_index = std::max(current_index - gMaxPersistNavigationCount, 0);
723 const int max_index = std::min(current_index + gMaxPersistNavigationCount,
724 tab->GetController().GetEntryCount());
725 const int pending_index = tab->GetController().GetPendingEntryIndex();
726 if (tab_to_available_range) {
727 (*tab_to_available_range)[session_id.id()] =
728 std::pair<int, int>(min_index, max_index);
731 if (is_pinned) {
732 base_session_service_->AppendRebuildCommand(
733 sessions::CreatePinnedStateCommand(session_id, true));
736 if (SessionRestore::GetSmartRestoreMode() ==
737 SessionRestore::SMART_RESTORE_MODE_MRU) {
738 base_session_service_->AppendRebuildCommand(
739 sessions::CreateLastActiveTimeCommand(session_id,
740 tab->GetLastActiveTime()));
743 extensions::TabHelper* extensions_tab_helper =
744 extensions::TabHelper::FromWebContents(tab);
745 if (extensions_tab_helper->extension_app()) {
746 base_session_service_->AppendRebuildCommand(
747 sessions::CreateSetTabExtensionAppIDCommand(
748 session_id,
749 extensions_tab_helper->extension_app()->id()));
752 const std::string& ua_override = tab->GetUserAgentOverride();
753 if (!ua_override.empty()) {
754 base_session_service_->AppendRebuildCommand(
755 sessions::CreateSetTabUserAgentOverrideCommand(session_id,
756 ua_override));
759 for (int i = min_index; i < max_index; ++i) {
760 const NavigationEntry* entry = (i == pending_index) ?
761 tab->GetController().GetPendingEntry() :
762 tab->GetController().GetEntryAtIndex(i);
763 DCHECK(entry);
764 if (ShouldTrackEntry(entry->GetVirtualURL())) {
765 const SerializedNavigationEntry navigation =
766 ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry);
767 base_session_service_->AppendRebuildCommand(
768 CreateUpdateTabNavigationCommand(session_id, navigation));
771 base_session_service_->AppendRebuildCommand(
772 sessions::CreateSetSelectedNavigationIndexCommand(session_id,
773 current_index));
775 if (index_in_window != -1) {
776 base_session_service_->AppendRebuildCommand(
777 sessions::CreateSetTabIndexInWindowCommand(session_id,
778 index_in_window));
781 // Record the association between the sessionStorage namespace and the tab.
782 content::SessionStorageNamespace* session_storage_namespace =
783 tab->GetController().GetDefaultSessionStorageNamespace();
784 ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
785 session_tab_helper->session_id(),
786 session_storage_namespace->persistent_id()).Pass());
789 void SessionService::BuildCommandsForBrowser(
790 Browser* browser,
791 IdToRange* tab_to_available_range,
792 std::set<SessionID::id_type>* windows_to_track) {
793 DCHECK(browser);
794 DCHECK(browser->session_id().id());
796 base_session_service_->AppendRebuildCommand(
797 sessions::CreateSetWindowBoundsCommand(
798 browser->session_id(),
799 browser->window()->GetRestoredBounds(),
800 browser->window()->GetRestoredState()));
802 base_session_service_->AppendRebuildCommand(
803 sessions::CreateSetWindowTypeCommand(
804 browser->session_id(),
805 WindowTypeForBrowserType(browser->type())));
807 if (!browser->app_name().empty()) {
808 base_session_service_->AppendRebuildCommand(
809 sessions::CreateSetWindowAppNameCommand(
810 browser->session_id(),
811 browser->app_name()));
814 windows_to_track->insert(browser->session_id().id());
815 TabStripModel* tab_strip = browser->tab_strip_model();
816 for (int i = 0; i < tab_strip->count(); ++i) {
817 WebContents* tab = tab_strip->GetWebContentsAt(i);
818 DCHECK(tab);
819 BuildCommandsForTab(browser->session_id(),
820 tab,
822 tab_strip->IsTabPinned(i),
823 tab_to_available_range);
826 base_session_service_->AppendRebuildCommand(
827 sessions::CreateSetSelectedTabInWindowCommand(
828 browser->session_id(),
829 browser->tab_strip_model()->active_index()));
832 void SessionService::BuildCommandsFromBrowsers(
833 IdToRange* tab_to_available_range,
834 std::set<SessionID::id_type>* windows_to_track) {
835 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
836 Browser* browser = *it;
837 // Make sure the browser has tabs and a window. Browser's destructor
838 // removes itself from the BrowserList. When a browser is closed the
839 // destructor is not necessarily run immediately. This means it's possible
840 // for us to get a handle to a browser that is about to be removed. If
841 // the tab count is 0 or the window is NULL, the browser is about to be
842 // deleted, so we ignore it.
843 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
844 browser->window()) {
845 BuildCommandsForBrowser(browser,
846 tab_to_available_range,
847 windows_to_track);
852 void SessionService::ScheduleResetCommands() {
853 base_session_service_->set_pending_reset(true);
854 base_session_service_->ClearPendingCommands();
855 tab_to_available_range_.clear();
856 windows_tracking_.clear();
857 BuildCommandsFromBrowsers(&tab_to_available_range_,
858 &windows_tracking_);
859 if (!windows_tracking_.empty()) {
860 // We're lazily created on startup and won't get an initial batch of
861 // SetWindowType messages. Set these here to make sure our state is correct.
862 has_open_trackable_browsers_ = true;
863 move_on_new_browser_ = true;
865 base_session_service_->StartSaveTimer();
868 void SessionService::ScheduleCommand(
869 scoped_ptr<sessions::SessionCommand> command) {
870 DCHECK(command);
871 if (ReplacePendingCommand(base_session_service_.get(), &command))
872 return;
873 bool is_closing_command = IsClosingCommand(command.get());
874 base_session_service_->ScheduleCommand(command.Pass());
875 // Don't schedule a reset on tab closed/window closed. Otherwise we may
876 // lose tabs/windows we want to restore from if we exit right after this.
877 if (!base_session_service_->pending_reset() &&
878 pending_window_close_ids_.empty() &&
879 base_session_service_->commands_since_reset() >= kWritesPerReset &&
880 !is_closing_command) {
881 ScheduleResetCommands();
885 void SessionService::CommitPendingCloses() {
886 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
887 i != pending_tab_close_ids_.end(); ++i) {
888 ScheduleCommand(sessions::CreateTabClosedCommand(*i).Pass());
890 pending_tab_close_ids_.clear();
892 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
893 i != pending_window_close_ids_.end(); ++i) {
894 ScheduleCommand(sessions::CreateWindowClosedCommand(*i).Pass());
896 pending_window_close_ids_.clear();
899 bool SessionService::IsOnlyOneTabLeft() const {
900 if (!profile() || profile()->AsTestingProfile()) {
901 // We're testing, always return false.
902 return false;
905 int window_count = 0;
906 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
907 Browser* browser = *it;
908 const SessionID::id_type window_id = browser->session_id().id();
909 if (ShouldTrackBrowser(browser) &&
910 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
911 if (++window_count > 1)
912 return false;
913 // By the time this is invoked the tab has been removed. As such, we use
914 // > 0 here rather than > 1.
915 if (browser->tab_strip_model()->count() > 0)
916 return false;
919 return true;
922 bool SessionService::HasOpenTrackableBrowsers(
923 const SessionID& window_id) const {
924 if (!profile() || profile()->AsTestingProfile()) {
925 // We're testing, always return true.
926 return true;
929 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
930 Browser* browser = *it;
931 const SessionID::id_type browser_id = browser->session_id().id();
932 if (browser_id != window_id.id() &&
933 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
934 ShouldTrackBrowser(browser)) {
935 return true;
938 return false;
941 bool SessionService::ShouldTrackChangesToWindow(
942 const SessionID& window_id) const {
943 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
946 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
947 if (browser->profile() != profile())
948 return false;
949 // Never track app popup windows that do not have a trusted source (i.e.
950 // popup windows spawned by an app). If this logic changes, be sure to also
951 // change SessionRestoreImpl::CreateRestoredBrowser().
952 if (browser->is_app() && browser->is_type_popup() &&
953 !browser->is_trusted_source()) {
954 return false;
956 return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()),
957 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
960 void SessionService::RecordSessionUpdateHistogramData(int type,
961 base::TimeTicks* last_updated_time) {
962 if (!last_updated_time->is_null()) {
963 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
964 // We're interested in frequent updates periods longer than
965 // 10 minutes.
966 bool use_long_period = false;
967 if (delta >= save_delay_in_mins_) {
968 use_long_period = true;
970 switch (type) {
971 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
972 RecordUpdatedSaveTime(delta, use_long_period);
973 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
974 break;
975 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
976 RecordUpdatedTabClosed(delta, use_long_period);
977 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
978 break;
979 case content::NOTIFICATION_NAV_LIST_PRUNED:
980 RecordUpdatedNavListPruned(delta, use_long_period);
981 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
982 break;
983 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
984 RecordUpdatedNavEntryCommit(delta, use_long_period);
985 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
986 break;
987 default:
988 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
989 break;
992 (*last_updated_time) = base::TimeTicks::Now();
995 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
996 bool use_long_period) {
997 std::string name("SessionRestore.TabClosedPeriod");
998 UMA_HISTOGRAM_CUSTOM_TIMES(name,
999 delta,
1000 // 2500ms is the default save delay.
1001 save_delay_in_millis_,
1002 save_delay_in_mins_,
1003 50);
1004 if (use_long_period) {
1005 std::string long_name_("SessionRestore.TabClosedLongPeriod");
1006 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1007 delta,
1008 save_delay_in_mins_,
1009 save_delay_in_hrs_,
1010 50);
1014 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1015 bool use_long_period) {
1016 std::string name("SessionRestore.NavigationListPrunedPeriod");
1017 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1018 delta,
1019 // 2500ms is the default save delay.
1020 save_delay_in_millis_,
1021 save_delay_in_mins_,
1022 50);
1023 if (use_long_period) {
1024 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1025 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1026 delta,
1027 save_delay_in_mins_,
1028 save_delay_in_hrs_,
1029 50);
1033 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1034 bool use_long_period) {
1035 std::string name("SessionRestore.NavEntryCommittedPeriod");
1036 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1037 delta,
1038 // 2500ms is the default save delay.
1039 save_delay_in_millis_,
1040 save_delay_in_mins_,
1041 50);
1042 if (use_long_period) {
1043 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1044 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1045 delta,
1046 save_delay_in_mins_,
1047 save_delay_in_hrs_,
1048 50);
1052 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1053 bool use_long_period) {
1054 std::string name("SessionRestore.SavePeriod");
1055 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1056 delta,
1057 // 2500ms is the default save delay.
1058 save_delay_in_millis_,
1059 save_delay_in_mins_,
1060 50);
1061 if (use_long_period) {
1062 std::string long_name_("SessionRestore.SaveLongPeriod");
1063 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1064 delta,
1065 save_delay_in_mins_,
1066 save_delay_in_hrs_,
1067 50);
1071 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1072 bool use_long_period) {
1073 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1074 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1075 delta,
1076 // 2500ms is the default save delay.
1077 save_delay_in_millis_,
1078 save_delay_in_mins_,
1079 50);
1080 if (use_long_period) {
1081 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1082 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1083 delta,
1084 save_delay_in_mins_,
1085 save_delay_in_hrs_,
1086 50);
1090 void SessionService::MaybeDeleteSessionOnlyData() {
1091 // Don't try anything if we're testing. The browser_process is not fully
1092 // created and DeleteSession will crash if we actually attempt it.
1093 if (!profile() || profile()->AsTestingProfile())
1094 return;
1096 // Clear session data if the last window for a profile has been closed and
1097 // closing the last window would normally close Chrome, unless background mode
1098 // is active. Tests don't have a background_mode_manager.
1099 if (has_open_trackable_browsers_ ||
1100 browser_defaults::kBrowserAliveWithNoWindows ||
1101 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1102 return;
1105 // Check for any open windows for the current profile that we aren't tracking.
1106 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1107 if ((*it)->profile() == profile())
1108 return;
1110 DeleteSessionOnlyData(profile());
1113 sessions::BaseSessionService* SessionService::GetBaseSessionServiceForTest() {
1114 return base_session_service_.get();