Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / sessions / session_service.cc
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.
4
5 #include "chrome/browser/sessions/session_service.h"
6
7 #include <algorithm>
8 #include <set>
9 #include <utility>
10 #include <vector>
11
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"
50
51 #if defined(OS_MACOSX)
52 #include "chrome/browser/app_controller_mac.h"
53 #endif
54
55 using base::Time;
56 using content::NavigationEntry;
57 using content::WebContents;
58 using sessions::ContentSerializedNavigationBuilder;
59 using sessions::SerializedNavigationEntry;
60
61 // Every kWritesPerReset commands triggers recreating the file.
62 static const int kWritesPerReset = 250;
63
64 // SessionService -------------------------------------------------------------
65
66 SessionService::SessionService(Profile* profile)
67     : BaseSessionServiceDelegateImpl(true),
68       profile_(profile),
69       base_session_service_(
70         new BaseSessionService(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();
83 }
84
85 SessionService::SessionService(const base::FilePath& save_path)
86     : BaseSessionServiceDelegateImpl(false),
87       profile_(NULL),
88       base_session_service_(
89         new BaseSessionService(BaseSessionService::SESSION_RESTORE,
90                                save_path,
91                                this)),
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),
98       weak_factory_(this) {
99   Init();
100 }
101
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();
107 }
108
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_)
115     return false;
116 #endif
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()
124 #endif  // OS_MACOSX
125       ) {
126     return true;
127   }
128   return false;
129 }
130
131 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
132   return RestoreIfNecessary(urls_to_open, NULL);
133 }
134
135 void SessionService::ResetFromCurrentBrowsers() {
136   ScheduleResetCommands();
137 }
138
139 void SessionService::MoveCurrentSessionToLastSession() {
140   pending_tab_close_ids_.clear();
141   window_closing_ids_.clear();
142   pending_window_close_ids_.clear();
143
144   base_session_service_->MoveCurrentSessionToLastSession();
145 }
146
147 void SessionService::DeleteLastSession() {
148   base_session_service_->DeleteLastSession();
149 }
150
151 void SessionService::SetTabWindow(const SessionID& window_id,
152                                   const SessionID& tab_id) {
153   if (!ShouldTrackChangesToWindow(window_id))
154     return;
155
156   ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id).Pass());
157 }
158
159 void SessionService::SetWindowBounds(const SessionID& window_id,
160                                      const gfx::Rect& bounds,
161                                      ui::WindowShowState show_state) {
162   if (!ShouldTrackChangesToWindow(window_id))
163     return;
164
165   ScheduleCommand(
166       CreateSetWindowBoundsCommand(window_id, bounds, show_state).Pass());
167 }
168
169 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
170                                          const SessionID& tab_id,
171                                          int new_index) {
172   if (!ShouldTrackChangesToWindow(window_id))
173     return;
174
175   ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index).Pass());
176 }
177
178 void SessionService::SetPinnedState(const SessionID& window_id,
179                                     const SessionID& tab_id,
180                                     bool is_pinned) {
181   if (!ShouldTrackChangesToWindow(window_id))
182     return;
183
184   ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned).Pass());
185 }
186
187 void SessionService::TabClosed(const SessionID& window_id,
188                                const SessionID& tab_id,
189                                bool closed_by_user_gesture) {
190   if (!tab_id.id())
191     return;  // Hapens when the tab is replaced.
192
193   if (!ShouldTrackChangesToWindow(window_id))
194     return;
195
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);
199
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
204     // committed.
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());
216   } else {
217     // User closed the last tab in the last tabbed browser. Don't mark the
218     // tab closed.
219     pending_tab_close_ids_.insert(tab_id.id());
220     has_open_trackable_browsers_ = false;
221   }
222 }
223
224 void SessionService::WindowOpened(Browser* browser) {
225   if (!ShouldTrackBrowser(browser))
226     return;
227
228   RestoreIfNecessary(std::vector<GURL>(), browser);
229   SetWindowType(browser->session_id(),
230                 browser->type(),
231                 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
232   SetWindowAppName(browser->session_id(), browser->app_name());
233 }
234
235 void SessionService::WindowClosing(const SessionID& window_id) {
236   if (!ShouldTrackChangesToWindow(window_id))
237     return;
238
239   // The window is about to close. If there are other tabbed browsers with the
240   // same original profile commit the close immediately.
241   //
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);
248   }
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
257     //
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);
269       }
270     }
271   }
272   if (use_pending_close)
273     pending_window_close_ids_.insert(window_id.id());
274   else
275     window_closing_ids_.insert(window_id.id());
276 }
277
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();
282     return;
283   }
284
285   windows_tracking_.erase(window_id.id());
286
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());
296     else
297       ScheduleCommand(CreateWindowClosedCommand(window_id.id()).Pass());
298   }
299   MaybeDeleteSessionOnlyData();
300 }
301
302 void SessionService::TabInserted(WebContents* contents) {
303   SessionTabHelper* session_tab_helper =
304       SessionTabHelper::FromWebContents(contents);
305   if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
306     return;
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());
317   }
318
319   // Record the association between the SessionStorageNamespace and the
320   // tab.
321   //
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);
331 }
332
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_);
346 }
347
348 void SessionService::SetWindowType(const SessionID& window_id,
349                                    Browser::Type type,
350                                    AppType app_type) {
351   SessionWindow::WindowType window_type = WindowTypeForBrowserType(type);
352   if (!ShouldRestoreWindowOfType(window_type, app_type))
353     return;
354
355   windows_tracking_.insert(window_id.id());
356
357   // The user created a new tabbed browser with our profile. Commit any
358   // pending closes.
359   CommitPendingCloses();
360
361   has_open_trackable_browsers_ = true;
362   move_on_new_browser_ = true;
363
364   ScheduleCommand(CreateSetWindowTypeCommand(window_id, window_type).Pass());
365 }
366
367 void SessionService::SetWindowAppName(
368     const SessionID& window_id,
369     const std::string& app_name) {
370   if (!ShouldTrackChangesToWindow(window_id))
371     return;
372
373   ScheduleCommand(CreateSetWindowAppNameCommand(window_id, app_name).Pass());
374 }
375
376 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
377                                                      const SessionID& tab_id,
378                                                      int count) {
379   if (!ShouldTrackChangesToWindow(window_id))
380     return;
381
382   ScheduleCommand(
383       CreateTabNavigationPathPrunedFromBackCommand(tab_id, count).Pass());
384 }
385
386 void SessionService::TabNavigationPathPrunedFromFront(
387     const SessionID& window_id,
388     const SessionID& tab_id,
389     int count) {
390   if (!ShouldTrackChangesToWindow(window_id))
391     return;
392
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);
399   }
400
401   ScheduleCommand(
402       CreateTabNavigationPathPrunedFromFrontCommand(tab_id, count).Pass());
403 }
404
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)) {
411     return;
412   }
413
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);
419   }
420   ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, navigation).Pass());
421 }
422
423 void SessionService::TabRestored(WebContents* tab, bool pinned) {
424   SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
425   if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
426     return;
427
428   BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, pinned, NULL);
429   base_session_service_->StartSaveTimer();
430 }
431
432 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
433                                                 const SessionID& tab_id,
434                                                 int index) {
435   if (!ShouldTrackChangesToWindow(window_id))
436     return;
437
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
443       // a reset.
444       ResetFromCurrentBrowsers();
445       return;
446     }
447   }
448   ScheduleCommand(
449     CreateSetSelectedNavigationIndexCommand(tab_id, index).Pass());
450 }
451
452 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
453                                             int index) {
454   if (!ShouldTrackChangesToWindow(window_id))
455     return;
456
457   ScheduleCommand(CreateSetSelectedTabInWindowCommand(window_id, index).Pass());
458 }
459
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))
465     return;
466
467   ScheduleCommand(
468     CreateSetTabUserAgentOverrideCommand(tab_id, user_agent_override).Pass());
469 }
470
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))
476     return;
477
478   ScheduleCommand(
479     CreateSetTabExtensionAppIDCommand(tab_id, extension_app_id).Pass());
480 }
481
482 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
483     const SessionCallback& callback,
484     base::CancelableTaskTracker* tracker) {
485   // OnGotSessionCommands maps the SessionCommands to browser state, then run
486   // the callback.
487   return base_session_service_->ScheduleGetLastSessionCommands(
488       base::Bind(&SessionService::OnGotSessionCommands,
489                  weak_factory_.GetWeakPtr(),
490                  callback),
491       tracker);
492 }
493
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());
501 }
502
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());
511   registrar_.Add(
512       this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
513       content::NotificationService::AllSources());
514
515   BrowserList::AddObserver(this);
516 }
517
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)
524     return true;
525 #endif
526
527   return window_type == SessionWindow::TYPE_TABBED;
528 }
529
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 :
537                                                               TYPE_APP)) {
538       delete window;
539       window_list->erase(i++);
540     } else {
541       ++i;
542     }
543   }
544 }
545
546 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
547                                         Browser* browser) {
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;
555     }
556     SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
557         *CommandLine::ForCurrentProcess(), profile());
558     if (pref.type == SessionStartupPref::LAST) {
559       SessionRestore::RestoreSession(
560           profile(), browser,
561           browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
562           browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
563           urls_to_open);
564       return true;
565     }
566   }
567   return false;
568 }
569
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.
574   switch (type) {
575     case content::NOTIFICATION_NAV_LIST_PRUNED: {
576       WebContents* web_contents =
577           content::Source<content::NavigationController>(source).ptr()->
578               GetWebContents();
579       SessionTabHelper* session_tab_helper =
580           SessionTabHelper::FromWebContents(web_contents);
581       if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
582         return;
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);
589       } else {
590         TabNavigationPathPrunedFromBack(
591             session_tab_helper->window_id(),
592             session_tab_helper->session_id(),
593             web_contents->GetController().GetEntryCount());
594       }
595       RecordSessionUpdateHistogramData(type,
596                                        &last_updated_nav_list_pruned_time_);
597       break;
598     }
599
600     case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
601       WebContents* web_contents =
602           content::Source<content::NavigationController>(source).ptr()->
603               GetWebContents();
604       SessionTabHelper* session_tab_helper =
605           SessionTabHelper::FromWebContents(web_contents);
606       if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
607         return;
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(),
614                           navigation);
615       break;
616     }
617
618     case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
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       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(
634               current_entry_index,
635               *web_contents->GetController().GetEntryAtIndex(
636                   current_entry_index));
637       UpdateTabNavigation(
638           session_tab_helper->window_id(),
639           session_tab_helper->session_id(),
640           navigation);
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_);
646       }
647       break;
648     }
649
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() !=
654               profile()) {
655         return;
656       }
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());
664       }
665       break;
666     }
667
668     default:
669       NOTREACHED();
670   }
671 }
672
673 void SessionService::OnBrowserSetLastActive(Browser* browser) {
674   if (ShouldTrackBrowser(browser))
675     ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id()).Pass());
676 }
677
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;
683
684   startup_metric_utils::ScopedSlowStartupUMA
685       scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
686
687   RestoreSessionFromCommands(commands, &valid_windows.get(), &active_window_id);
688   RemoveUnusedRestoreWindows(&valid_windows.get());
689
690   callback.Run(valid_windows.Pass(), active_window_id);
691 }
692
693 void SessionService::BuildCommandsForTab(const SessionID& window_id,
694                                          WebContents* tab,
695                                          int index_in_window,
696                                          bool is_pinned,
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));
703
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);
712   }
713
714   if (is_pinned) {
715     base_session_service_->AppendRebuildCommand(
716         CreatePinnedStateCommand(session_id, true));
717   }
718
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(
724             session_id,
725             extensions_tab_helper->extension_app()->id()));
726   }
727
728   const std::string& ua_override = tab->GetUserAgentOverride();
729   if (!ua_override.empty()) {
730     base_session_service_->AppendRebuildCommand(
731       CreateSetTabUserAgentOverrideCommand(session_id, ua_override));
732   }
733
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);
738     DCHECK(entry);
739     if (ShouldTrackEntry(entry->GetVirtualURL())) {
740       const SerializedNavigationEntry navigation =
741           ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry);
742       base_session_service_->AppendRebuildCommand(
743           CreateUpdateTabNavigationCommand(session_id, navigation));
744     }
745   }
746   base_session_service_->AppendRebuildCommand(
747       CreateSetSelectedNavigationIndexCommand(session_id,
748                                               current_index));
749
750   if (index_in_window != -1) {
751     base_session_service_->AppendRebuildCommand(
752         CreateSetTabIndexInWindowCommand(session_id,
753                                          index_in_window));
754   }
755
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());
762 }
763
764 void SessionService::BuildCommandsForBrowser(
765     Browser* browser,
766     IdToRange* tab_to_available_range,
767     std::set<SessionID::id_type>* windows_to_track) {
768   DCHECK(browser);
769   DCHECK(browser->session_id().id());
770
771   base_session_service_->AppendRebuildCommand(CreateSetWindowBoundsCommand(
772       browser->session_id(),
773       browser->window()->GetRestoredBounds(),
774       browser->window()->GetRestoredState()));
775
776   base_session_service_->AppendRebuildCommand(CreateSetWindowTypeCommand(
777       browser->session_id(),
778       WindowTypeForBrowserType(browser->type())));
779
780   if (!browser->app_name().empty()) {
781     base_session_service_->AppendRebuildCommand(CreateSetWindowAppNameCommand(
782         browser->session_id(),
783         browser->app_name()));
784   }
785
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);
790     DCHECK(tab);
791     BuildCommandsForTab(browser->session_id(),
792                         tab,
793                         i,
794                         tab_strip->IsTabPinned(i),
795                         tab_to_available_range);
796   }
797
798   base_session_service_->AppendRebuildCommand(
799       CreateSetSelectedTabInWindowCommand(
800           browser->session_id(),
801           browser->tab_strip_model()->active_index()));
802 }
803
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() &&
816         browser->window()) {
817       BuildCommandsForBrowser(browser,
818                               tab_to_available_range,
819                               windows_to_track);
820     }
821   }
822 }
823
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_,
830                             &windows_tracking_);
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;
836   }
837   base_session_service_->StartSaveTimer();
838 }
839
840 void SessionService::ScheduleCommand(scoped_ptr<SessionCommand> command) {
841   DCHECK(command);
842   if (ReplacePendingCommand(base_session_service_.get(), &command))
843     return;
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();
853   }
854 }
855
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());
860   }
861   pending_tab_close_ids_.clear();
862
863   for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
864        i != pending_window_close_ids_.end(); ++i) {
865     ScheduleCommand(CreateWindowClosedCommand(*i).Pass());
866   }
867   pending_window_close_ids_.clear();
868 }
869
870 bool SessionService::IsOnlyOneTabLeft() const {
871   if (!profile() || profile()->AsTestingProfile()) {
872     // We're testing, always return false.
873     return false;
874   }
875
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)
883         return false;
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)
887         return false;
888     }
889   }
890   return true;
891 }
892
893 bool SessionService::HasOpenTrackableBrowsers(
894     const SessionID& window_id) const {
895   if (!profile() || profile()->AsTestingProfile()) {
896     // We're testing, always return true.
897     return true;
898   }
899
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)) {
906       return true;
907     }
908   }
909   return false;
910 }
911
912 bool SessionService::ShouldTrackChangesToWindow(
913     const SessionID& window_id) const {
914   return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
915 }
916
917 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
918   if (browser->profile() != profile())
919     return false;
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()) {
925     return false;
926   }
927   return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()),
928                                    browser->is_app() ? TYPE_APP : TYPE_NORMAL);
929 }
930
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
936     // 10 minutes.
937     bool use_long_period = false;
938     if (delta >= save_delay_in_mins_) {
939       use_long_period = true;
940     }
941     switch (type) {
942       case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
943         RecordUpdatedSaveTime(delta, use_long_period);
944         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
945         break;
946       case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
947         RecordUpdatedTabClosed(delta, use_long_period);
948         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
949         break;
950       case content::NOTIFICATION_NAV_LIST_PRUNED:
951         RecordUpdatedNavListPruned(delta, use_long_period);
952         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
953         break;
954       case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
955         RecordUpdatedNavEntryCommit(delta, use_long_period);
956         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
957         break;
958       default:
959         NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
960         break;
961     }
962   }
963   (*last_updated_time) = base::TimeTicks::Now();
964 }
965
966 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
967                                             bool use_long_period) {
968   std::string name("SessionRestore.TabClosedPeriod");
969   UMA_HISTOGRAM_CUSTOM_TIMES(name,
970       delta,
971       // 2500ms is the default save delay.
972       save_delay_in_millis_,
973       save_delay_in_mins_,
974       50);
975   if (use_long_period) {
976     std::string long_name_("SessionRestore.TabClosedLongPeriod");
977     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
978         delta,
979         save_delay_in_mins_,
980         save_delay_in_hrs_,
981         50);
982   }
983 }
984
985 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
986                                                 bool use_long_period) {
987   std::string name("SessionRestore.NavigationListPrunedPeriod");
988   UMA_HISTOGRAM_CUSTOM_TIMES(name,
989       delta,
990       // 2500ms is the default save delay.
991       save_delay_in_millis_,
992       save_delay_in_mins_,
993       50);
994   if (use_long_period) {
995     std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
996     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
997         delta,
998         save_delay_in_mins_,
999         save_delay_in_hrs_,
1000         50);
1001   }
1002 }
1003
1004 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1005                                                  bool use_long_period) {
1006   std::string name("SessionRestore.NavEntryCommittedPeriod");
1007   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1008       delta,
1009       // 2500ms is the default save delay.
1010       save_delay_in_millis_,
1011       save_delay_in_mins_,
1012       50);
1013   if (use_long_period) {
1014     std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1015     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1016         delta,
1017         save_delay_in_mins_,
1018         save_delay_in_hrs_,
1019         50);
1020   }
1021 }
1022
1023 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1024                                            bool use_long_period) {
1025   std::string name("SessionRestore.SavePeriod");
1026   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1027       delta,
1028       // 2500ms is the default save delay.
1029       save_delay_in_millis_,
1030       save_delay_in_mins_,
1031       50);
1032   if (use_long_period) {
1033     std::string long_name_("SessionRestore.SaveLongPeriod");
1034     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1035         delta,
1036         save_delay_in_mins_,
1037         save_delay_in_hrs_,
1038         50);
1039   }
1040 }
1041
1042 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1043                                                          bool use_long_period) {
1044   std::string name("SessionRestore.NavOrTabUpdatePeriod");
1045   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1046       delta,
1047       // 2500ms is the default save delay.
1048       save_delay_in_millis_,
1049       save_delay_in_mins_,
1050       50);
1051   if (use_long_period) {
1052     std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1053     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1054         delta,
1055         save_delay_in_mins_,
1056         save_delay_in_hrs_,
1057         50);
1058   }
1059 }
1060
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())
1065     return;
1066
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()) {
1073     return;
1074   }
1075
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())
1079       return;
1080   }
1081   DeleteSessionOnlyData(profile());
1082 }
1083
1084 BaseSessionService* SessionService::GetBaseSessionServiceForTest() {
1085   return base_session_service_.get();
1086 }