Upstream version 7.36.149.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/session_backend.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_tab_helper.h"
32 #include "chrome/browser/sessions/session_types.h"
33 #include "chrome/browser/ui/browser_iterator.h"
34 #include "chrome/browser/ui/browser_list.h"
35 #include "chrome/browser/ui/browser_tabstrip.h"
36 #include "chrome/browser/ui/browser_window.h"
37 #include "chrome/browser/ui/host_desktop.h"
38 #include "chrome/browser/ui/startup/startup_browser_creator.h"
39 #include "chrome/browser/ui/tabs/tab_strip_model.h"
40 #include "components/startup_metric_utils/startup_metric_utils.h"
41 #include "content/public/browser/navigation_details.h"
42 #include "content/public/browser/navigation_entry.h"
43 #include "content/public/browser/notification_details.h"
44 #include "content/public/browser/notification_service.h"
45 #include "content/public/browser/session_storage_namespace.h"
46 #include "content/public/browser/web_contents.h"
47 #include "extensions/common/extension.h"
48
49 #if defined(OS_MACOSX)
50 #include "chrome/browser/app_controller_mac.h"
51 #endif
52
53 using base::Time;
54 using content::NavigationEntry;
55 using content::WebContents;
56 using sessions::SerializedNavigationEntry;
57
58 // Identifier for commands written to file.
59 static const SessionCommand::id_type kCommandSetTabWindow = 0;
60 // OBSOLETE Superseded by kCommandSetWindowBounds3.
61 // static const SessionCommand::id_type kCommandSetWindowBounds = 1;
62 static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2;
63 // Original kCommandTabClosed/kCommandWindowClosed. See comment in
64 // MigrateClosedPayload for details on why they were replaced.
65 static const SessionCommand::id_type kCommandTabClosedObsolete = 3;
66 static const SessionCommand::id_type kCommandWindowClosedObsolete = 4;
67 static const SessionCommand::id_type
68     kCommandTabNavigationPathPrunedFromBack = 5;
69 static const SessionCommand::id_type kCommandUpdateTabNavigation = 6;
70 static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7;
71 static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8;
72 static const SessionCommand::id_type kCommandSetWindowType = 9;
73 // OBSOLETE Superseded by kCommandSetWindowBounds3. Except for data migration.
74 // static const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
75 static const SessionCommand::id_type
76     kCommandTabNavigationPathPrunedFromFront = 11;
77 static const SessionCommand::id_type kCommandSetPinnedState = 12;
78 static const SessionCommand::id_type kCommandSetExtensionAppID = 13;
79 static const SessionCommand::id_type kCommandSetWindowBounds3 = 14;
80 static const SessionCommand::id_type kCommandSetWindowAppName = 15;
81 static const SessionCommand::id_type kCommandTabClosed = 16;
82 static const SessionCommand::id_type kCommandWindowClosed = 17;
83 static const SessionCommand::id_type kCommandSetTabUserAgentOverride = 18;
84 static const SessionCommand::id_type kCommandSessionStorageAssociated = 19;
85 static const SessionCommand::id_type kCommandSetActiveWindow = 20;
86
87 // Every kWritesPerReset commands triggers recreating the file.
88 static const int kWritesPerReset = 250;
89
90 namespace {
91
92 // Various payload structures.
93 struct ClosedPayload {
94   SessionID::id_type id;
95   int64 close_time;
96 };
97
98 struct WindowBoundsPayload2 {
99   SessionID::id_type window_id;
100   int32 x;
101   int32 y;
102   int32 w;
103   int32 h;
104   bool is_maximized;
105 };
106
107 struct WindowBoundsPayload3 {
108   SessionID::id_type window_id;
109   int32 x;
110   int32 y;
111   int32 w;
112   int32 h;
113   int32 show_state;
114 };
115
116 typedef SessionID::id_type ActiveWindowPayload;
117
118 struct IDAndIndexPayload {
119   SessionID::id_type id;
120   int32 index;
121 };
122
123 typedef IDAndIndexPayload TabIndexInWindowPayload;
124
125 typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload;
126
127 typedef IDAndIndexPayload SelectedNavigationIndexPayload;
128
129 typedef IDAndIndexPayload SelectedTabInIndexPayload;
130
131 typedef IDAndIndexPayload WindowTypePayload;
132
133 typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload;
134
135 struct PinnedStatePayload {
136   SessionID::id_type tab_id;
137   bool pinned_state;
138 };
139
140 // Returns the show state to store to disk based |state|.
141 ui::WindowShowState AdjustShowState(ui::WindowShowState state) {
142   switch (state) {
143     case ui::SHOW_STATE_NORMAL:
144     case ui::SHOW_STATE_MINIMIZED:
145     case ui::SHOW_STATE_MAXIMIZED:
146     case ui::SHOW_STATE_FULLSCREEN:
147     case ui::SHOW_STATE_DETACHED:
148       return state;
149
150     case ui::SHOW_STATE_DEFAULT:
151     case ui::SHOW_STATE_INACTIVE:
152     case ui::SHOW_STATE_END:
153       return ui::SHOW_STATE_NORMAL;
154   }
155   return ui::SHOW_STATE_NORMAL;
156 }
157
158 // Migrates a |ClosedPayload|, returning true on success (migration was
159 // necessary and happened), or false (migration was not necessary or was not
160 // successful).
161 bool MigrateClosedPayload(const SessionCommand& command,
162                           ClosedPayload* payload) {
163 #if defined(OS_CHROMEOS)
164   // Pre M17 versions of chromeos were 32bit. Post M17 is 64 bit. Apparently the
165   // 32 bit versions of chrome on pre M17 resulted in a sizeof 12 for the
166   // ClosedPayload, where as post M17 64-bit gives a sizeof 16 (presumably the
167   // struct is padded).
168   if ((command.id() == kCommandWindowClosedObsolete ||
169        command.id() == kCommandTabClosedObsolete) &&
170       command.size() == 12 && sizeof(payload->id) == 4 &&
171       sizeof(payload->close_time) == 8) {
172     memcpy(&payload->id, command.contents(), 4);
173     memcpy(&payload->close_time, command.contents() + 4, 8);
174     return true;
175   } else {
176     return false;
177   }
178 #else
179   return false;
180 #endif
181 }
182
183 }  // namespace
184
185 // SessionService -------------------------------------------------------------
186
187 SessionService::SessionService(Profile* profile)
188     : BaseSessionService(SESSION_RESTORE, profile, base::FilePath()),
189       has_open_trackable_browsers_(false),
190       move_on_new_browser_(false),
191       save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
192       save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
193       save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
194       force_browser_not_alive_with_no_windows_(false) {
195   Init();
196 }
197
198 SessionService::SessionService(const base::FilePath& save_path)
199     : BaseSessionService(SESSION_RESTORE, NULL, save_path),
200       has_open_trackable_browsers_(false),
201       move_on_new_browser_(false),
202       save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
203       save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
204       save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
205       force_browser_not_alive_with_no_windows_(false)  {
206   Init();
207 }
208
209 SessionService::~SessionService() {
210   // The BrowserList should outlive the SessionService since it's static and
211   // the SessionService is a KeyedService.
212   BrowserList::RemoveObserver(this);
213   Save();
214 }
215
216 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
217   return RestoreIfNecessary(urls_to_open, NULL);
218 }
219
220 void SessionService::ResetFromCurrentBrowsers() {
221   ScheduleReset();
222 }
223
224 void SessionService::MoveCurrentSessionToLastSession() {
225   pending_tab_close_ids_.clear();
226   window_closing_ids_.clear();
227   pending_window_close_ids_.clear();
228
229   Save();
230
231   RunTaskOnBackendThread(
232       FROM_HERE, base::Bind(&SessionBackend::MoveCurrentSessionToLastSession,
233                             backend()));
234 }
235
236 void SessionService::SetTabWindow(const SessionID& window_id,
237                                   const SessionID& tab_id) {
238   if (!ShouldTrackChangesToWindow(window_id))
239     return;
240
241   ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id));
242 }
243
244 void SessionService::SetWindowBounds(const SessionID& window_id,
245                                      const gfx::Rect& bounds,
246                                      ui::WindowShowState show_state) {
247   if (!ShouldTrackChangesToWindow(window_id))
248     return;
249
250   ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, show_state));
251 }
252
253 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
254                                          const SessionID& tab_id,
255                                          int new_index) {
256   if (!ShouldTrackChangesToWindow(window_id))
257     return;
258
259   ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index));
260 }
261
262 void SessionService::SetPinnedState(const SessionID& window_id,
263                                     const SessionID& tab_id,
264                                     bool is_pinned) {
265   if (!ShouldTrackChangesToWindow(window_id))
266     return;
267
268   ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned));
269 }
270
271 void SessionService::TabClosed(const SessionID& window_id,
272                                const SessionID& tab_id,
273                                bool closed_by_user_gesture) {
274   if (!tab_id.id())
275     return;  // Hapens when the tab is replaced.
276
277   if (!ShouldTrackChangesToWindow(window_id))
278     return;
279
280   IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
281   if (i != tab_to_available_range_.end())
282     tab_to_available_range_.erase(i);
283
284   if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
285            window_id.id()) != pending_window_close_ids_.end()) {
286     // Tab is in last window. Don't commit it immediately, instead add it to the
287     // list of tabs to close. If the user creates another window, the close is
288     // committed.
289     pending_tab_close_ids_.insert(tab_id.id());
290   } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
291                   window_id.id()) != window_closing_ids_.end() ||
292              !IsOnlyOneTabLeft() ||
293              closed_by_user_gesture) {
294     // Close is the result of one of the following:
295     // . window close (and it isn't the last window).
296     // . closing a tab and there are other windows/tabs open.
297     // . closed by a user gesture.
298     // In all cases we need to mark the tab as explicitly closed.
299     ScheduleCommand(CreateTabClosedCommand(tab_id.id()));
300   } else {
301     // User closed the last tab in the last tabbed browser. Don't mark the
302     // tab closed.
303     pending_tab_close_ids_.insert(tab_id.id());
304     has_open_trackable_browsers_ = false;
305   }
306 }
307
308 void SessionService::WindowOpened(Browser* browser) {
309   if (!ShouldTrackBrowser(browser))
310     return;
311
312   AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
313   RestoreIfNecessary(std::vector<GURL>(), browser);
314   SetWindowType(browser->session_id(), browser->type(), app_type);
315   SetWindowAppName(browser->session_id(), browser->app_name());
316 }
317
318 void SessionService::WindowClosing(const SessionID& window_id) {
319   if (!ShouldTrackChangesToWindow(window_id))
320     return;
321
322   // The window is about to close. If there are other tabbed browsers with the
323   // same original profile commit the close immediately.
324   //
325   // NOTE: if the user chooses the exit menu item session service is destroyed
326   // and this code isn't hit.
327   if (has_open_trackable_browsers_) {
328     // Closing a window can never make has_open_trackable_browsers_ go from
329     // false to true, so only update it if already true.
330     has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
331   }
332   bool use_pending_close = !has_open_trackable_browsers_;
333   if (!use_pending_close) {
334     // Somewhat outside of "normal behavior" is profile locking.  In this case
335     // (when IsSiginRequired has already been set True), we're closing all
336     // browser windows in turn but want them all to be restored when the user
337     // unlocks.  To accomplish this, we do a "pending close" on all windows
338     // instead of just the last one (which has no open_trackable_browsers).
339     // http://crbug.com/356818
340     //
341     // Some editions (like iOS) don't have a profile_manager and some tests
342     // don't supply one so be lenient.
343     if (g_browser_process) {
344       ProfileManager* profile_manager = g_browser_process->profile_manager();
345       if (profile_manager) {
346         ProfileInfoCache& profile_info =
347             profile_manager->GetProfileInfoCache();
348         size_t profile_index = profile_info.GetIndexOfProfileWithPath(
349             profile()->GetPath());
350         use_pending_close = profile_index != std::string::npos &&
351             profile_info.ProfileIsSigninRequiredAtIndex(profile_index);
352       }
353     }
354   }
355   if (use_pending_close)
356     pending_window_close_ids_.insert(window_id.id());
357   else
358     window_closing_ids_.insert(window_id.id());
359 }
360
361 void SessionService::WindowClosed(const SessionID& window_id) {
362   if (!ShouldTrackChangesToWindow(window_id)) {
363     // The last window may be one that is not tracked.
364     MaybeDeleteSessionOnlyData();
365     return;
366   }
367
368   windows_tracking_.erase(window_id.id());
369
370   if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
371     window_closing_ids_.erase(window_id.id());
372     ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
373   } else if (pending_window_close_ids_.find(window_id.id()) ==
374              pending_window_close_ids_.end()) {
375     // We'll hit this if user closed the last tab in a window.
376     has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
377     if (!has_open_trackable_browsers_)
378       pending_window_close_ids_.insert(window_id.id());
379     else
380       ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
381   }
382   MaybeDeleteSessionOnlyData();
383 }
384
385 void SessionService::SetWindowType(const SessionID& window_id,
386                                    Browser::Type type,
387                                    AppType app_type) {
388   if (!should_track_changes_for_browser_type(type, app_type))
389     return;
390
391   windows_tracking_.insert(window_id.id());
392
393   // The user created a new tabbed browser with our profile. Commit any
394   // pending closes.
395   CommitPendingCloses();
396
397   has_open_trackable_browsers_ = true;
398   move_on_new_browser_ = true;
399
400   ScheduleCommand(
401       CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type)));
402 }
403
404 void SessionService::SetWindowAppName(
405     const SessionID& window_id,
406     const std::string& app_name) {
407   if (!ShouldTrackChangesToWindow(window_id))
408     return;
409
410   ScheduleCommand(CreateSetTabExtensionAppIDCommand(
411                       kCommandSetWindowAppName,
412                       window_id.id(),
413                       app_name));
414 }
415
416 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
417                                                      const SessionID& tab_id,
418                                                      int count) {
419   if (!ShouldTrackChangesToWindow(window_id))
420     return;
421
422   TabNavigationPathPrunedFromBackPayload payload = { 0 };
423   payload.id = tab_id.id();
424   payload.index = count;
425   SessionCommand* command =
426       new SessionCommand(kCommandTabNavigationPathPrunedFromBack,
427                          sizeof(payload));
428   memcpy(command->contents(), &payload, sizeof(payload));
429   ScheduleCommand(command);
430 }
431
432 void SessionService::TabNavigationPathPrunedFromFront(
433     const SessionID& window_id,
434     const SessionID& tab_id,
435     int count) {
436   if (!ShouldTrackChangesToWindow(window_id))
437     return;
438
439   // Update the range of indices.
440   if (tab_to_available_range_.find(tab_id.id()) !=
441       tab_to_available_range_.end()) {
442     std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
443     range.first = std::max(0, range.first - count);
444     range.second = std::max(0, range.second - count);
445   }
446
447   TabNavigationPathPrunedFromFrontPayload payload = { 0 };
448   payload.id = tab_id.id();
449   payload.index = count;
450   SessionCommand* command =
451       new SessionCommand(kCommandTabNavigationPathPrunedFromFront,
452                          sizeof(payload));
453   memcpy(command->contents(), &payload, sizeof(payload));
454   ScheduleCommand(command);
455 }
456
457 void SessionService::UpdateTabNavigation(
458     const SessionID& window_id,
459     const SessionID& tab_id,
460     const SerializedNavigationEntry& navigation) {
461   if (!ShouldTrackEntry(navigation.virtual_url()) ||
462       !ShouldTrackChangesToWindow(window_id)) {
463     return;
464   }
465
466   if (tab_to_available_range_.find(tab_id.id()) !=
467       tab_to_available_range_.end()) {
468     std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
469     range.first = std::min(navigation.index(), range.first);
470     range.second = std::max(navigation.index(), range.second);
471   }
472   ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation,
473                                                    tab_id.id(), navigation));
474 }
475
476 void SessionService::TabRestored(WebContents* tab, bool pinned) {
477   SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
478   if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
479     return;
480
481   BuildCommandsForTab(session_tab_helper->window_id(), tab, -1,
482                       pinned, &pending_commands(), NULL);
483   StartSaveTimer();
484 }
485
486 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
487                                                 const SessionID& tab_id,
488                                                 int index) {
489   if (!ShouldTrackChangesToWindow(window_id))
490     return;
491
492   if (tab_to_available_range_.find(tab_id.id()) !=
493       tab_to_available_range_.end()) {
494     if (index < tab_to_available_range_[tab_id.id()].first ||
495         index > tab_to_available_range_[tab_id.id()].second) {
496       // The new index is outside the range of what we've archived, schedule
497       // a reset.
498       ResetFromCurrentBrowsers();
499       return;
500     }
501   }
502   ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index));
503 }
504
505 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
506                                             int index) {
507   if (!ShouldTrackChangesToWindow(window_id))
508     return;
509
510   ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index));
511 }
512
513 void SessionService::SetTabUserAgentOverride(
514     const SessionID& window_id,
515     const SessionID& tab_id,
516     const std::string& user_agent_override) {
517   if (!ShouldTrackChangesToWindow(window_id))
518     return;
519
520   ScheduleCommand(CreateSetTabUserAgentOverrideCommand(
521       kCommandSetTabUserAgentOverride, tab_id.id(), user_agent_override));
522 }
523
524 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
525     const SessionCallback& callback,
526     base::CancelableTaskTracker* tracker) {
527   // OnGotSessionCommands maps the SessionCommands to browser state, then run
528   // the callback.
529   return ScheduleGetLastSessionCommands(
530       base::Bind(&SessionService::OnGotSessionCommands,
531                  base::Unretained(this), callback),
532       tracker);
533 }
534
535 void SessionService::Save() {
536   bool had_commands = !pending_commands().empty();
537   BaseSessionService::Save();
538   if (had_commands) {
539     RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
540                                      &last_updated_save_time_);
541     content::NotificationService::current()->Notify(
542         chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
543         content::Source<Profile>(profile()),
544         content::NotificationService::NoDetails());
545   }
546 }
547
548 void SessionService::Init() {
549   // Register for the notifications we're interested in.
550   registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
551                  content::NotificationService::AllSources());
552   registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
553                  content::NotificationService::AllSources());
554   registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
555                  content::NotificationService::AllSources());
556   registrar_.Add(
557       this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
558       content::NotificationService::AllSources());
559
560   BrowserList::AddObserver(this);
561 }
562
563 bool SessionService::processed_any_commands() {
564   return backend()->inited() || !pending_commands().empty();
565 }
566
567 bool SessionService::ShouldNewWindowStartSession() {
568   // ChromeOS and OSX have different ideas of application lifetime than
569   // the other platforms.
570   // On ChromeOS opening a new window should never start a new session.
571 #if defined(OS_CHROMEOS)
572   if (!force_browser_not_alive_with_no_windows_)
573     return false;
574 #endif
575   if (!has_open_trackable_browsers_ &&
576       !StartupBrowserCreator::InSynchronousProfileLaunch() &&
577       !SessionRestore::IsRestoring(profile())
578 #if defined(OS_MACOSX)
579       // On OSX, a new window should not start a new session if it was opened
580       // from the dock or the menubar.
581       && !app_controller_mac::IsOpeningNewWindow()
582 #endif  // OS_MACOSX
583       ) {
584     return true;
585   }
586   return false;
587 }
588
589 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
590                                         Browser* browser) {
591   if (ShouldNewWindowStartSession()) {
592     // We're going from no tabbed browsers to a tabbed browser (and not in
593     // process startup), restore the last session.
594     if (move_on_new_browser_) {
595       // Make the current session the last.
596       MoveCurrentSessionToLastSession();
597       move_on_new_browser_ = false;
598     }
599     SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
600         *CommandLine::ForCurrentProcess(), profile());
601     if (pref.type == SessionStartupPref::LAST) {
602       SessionRestore::RestoreSession(
603           profile(), browser,
604           browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
605           browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
606           urls_to_open);
607       return true;
608     }
609   }
610   return false;
611 }
612
613 void SessionService::Observe(int type,
614                              const content::NotificationSource& source,
615                              const content::NotificationDetails& details) {
616   // All of our messages have the NavigationController as the source.
617   switch (type) {
618     case content::NOTIFICATION_NAV_LIST_PRUNED: {
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::PrunedDetails> pruned_details(details);
627       if (pruned_details->from_front) {
628         TabNavigationPathPrunedFromFront(
629             session_tab_helper->window_id(),
630             session_tab_helper->session_id(),
631             pruned_details->count);
632       } else {
633         TabNavigationPathPrunedFromBack(
634             session_tab_helper->window_id(),
635             session_tab_helper->session_id(),
636             web_contents->GetController().GetEntryCount());
637       }
638       RecordSessionUpdateHistogramData(type,
639                                        &last_updated_nav_list_pruned_time_);
640       break;
641     }
642
643     case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
644       WebContents* web_contents =
645           content::Source<content::NavigationController>(source).ptr()->
646               GetWebContents();
647       SessionTabHelper* session_tab_helper =
648           SessionTabHelper::FromWebContents(web_contents);
649       if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
650         return;
651       content::Details<content::EntryChangedDetails> changed(details);
652       const SerializedNavigationEntry navigation =
653           SerializedNavigationEntry::FromNavigationEntry(
654               changed->index, *changed->changed_entry);
655       UpdateTabNavigation(session_tab_helper->window_id(),
656                           session_tab_helper->session_id(),
657                           navigation);
658       break;
659     }
660
661     case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
662       WebContents* web_contents =
663           content::Source<content::NavigationController>(source).ptr()->
664               GetWebContents();
665       SessionTabHelper* session_tab_helper =
666           SessionTabHelper::FromWebContents(web_contents);
667       if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
668         return;
669       int current_entry_index =
670           web_contents->GetController().GetCurrentEntryIndex();
671       SetSelectedNavigationIndex(
672           session_tab_helper->window_id(),
673           session_tab_helper->session_id(),
674           current_entry_index);
675       const SerializedNavigationEntry navigation =
676           SerializedNavigationEntry::FromNavigationEntry(
677               current_entry_index,
678               *web_contents->GetController().GetEntryAtIndex(
679                   current_entry_index));
680       UpdateTabNavigation(
681           session_tab_helper->window_id(),
682           session_tab_helper->session_id(),
683           navigation);
684       content::Details<content::LoadCommittedDetails> changed(details);
685       if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
686         changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
687         RecordSessionUpdateHistogramData(type,
688                                          &last_updated_nav_entry_commit_time_);
689       }
690       break;
691     }
692
693     case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
694       extensions::TabHelper* extension_tab_helper =
695           content::Source<extensions::TabHelper>(source).ptr();
696       if (extension_tab_helper->web_contents()->GetBrowserContext() !=
697               profile()) {
698         return;
699       }
700       if (extension_tab_helper->extension_app()) {
701         SessionTabHelper* session_tab_helper =
702             SessionTabHelper::FromWebContents(
703                 extension_tab_helper->web_contents());
704         SetTabExtensionAppID(session_tab_helper->window_id(),
705                              session_tab_helper->session_id(),
706                              extension_tab_helper->extension_app()->id());
707       }
708       break;
709     }
710
711     default:
712       NOTREACHED();
713   }
714 }
715
716 void SessionService::OnBrowserSetLastActive(Browser* browser) {
717   if (ShouldTrackBrowser(browser))
718     ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id()));
719 }
720
721 void SessionService::SetTabExtensionAppID(
722     const SessionID& window_id,
723     const SessionID& tab_id,
724     const std::string& extension_app_id) {
725   if (!ShouldTrackChangesToWindow(window_id))
726     return;
727
728   ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID,
729       tab_id.id(), extension_app_id));
730 }
731
732 SessionCommand* SessionService::CreateSetSelectedTabInWindow(
733     const SessionID& window_id,
734     int index) {
735   SelectedTabInIndexPayload payload = { 0 };
736   payload.id = window_id.id();
737   payload.index = index;
738   SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex,
739                                  sizeof(payload));
740   memcpy(command->contents(), &payload, sizeof(payload));
741   return command;
742 }
743
744 SessionCommand* SessionService::CreateSetTabWindowCommand(
745     const SessionID& window_id,
746     const SessionID& tab_id) {
747   SessionID::id_type payload[] = { window_id.id(), tab_id.id() };
748   SessionCommand* command =
749       new SessionCommand(kCommandSetTabWindow, sizeof(payload));
750   memcpy(command->contents(), payload, sizeof(payload));
751   return command;
752 }
753
754 SessionCommand* SessionService::CreateSetWindowBoundsCommand(
755     const SessionID& window_id,
756     const gfx::Rect& bounds,
757     ui::WindowShowState show_state) {
758   WindowBoundsPayload3 payload = { 0 };
759   payload.window_id = window_id.id();
760   payload.x = bounds.x();
761   payload.y = bounds.y();
762   payload.w = bounds.width();
763   payload.h = bounds.height();
764   payload.show_state = AdjustShowState(show_state);
765   SessionCommand* command = new SessionCommand(kCommandSetWindowBounds3,
766                                                sizeof(payload));
767   memcpy(command->contents(), &payload, sizeof(payload));
768   return command;
769 }
770
771 SessionCommand* SessionService::CreateSetTabIndexInWindowCommand(
772     const SessionID& tab_id,
773     int new_index) {
774   TabIndexInWindowPayload payload = { 0 };
775   payload.id = tab_id.id();
776   payload.index = new_index;
777   SessionCommand* command =
778       new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload));
779   memcpy(command->contents(), &payload, sizeof(payload));
780   return command;
781 }
782
783 SessionCommand* SessionService::CreateTabClosedCommand(
784     const SessionID::id_type tab_id) {
785   ClosedPayload payload;
786   // Because of what appears to be a compiler bug setting payload to {0} doesn't
787   // set the padding to 0, resulting in Purify reporting an UMR when we write
788   // the structure to disk. To avoid this we explicitly memset the struct.
789   memset(&payload, 0, sizeof(payload));
790   payload.id = tab_id;
791   payload.close_time = Time::Now().ToInternalValue();
792   SessionCommand* command =
793       new SessionCommand(kCommandTabClosed, sizeof(payload));
794   memcpy(command->contents(), &payload, sizeof(payload));
795   return command;
796 }
797
798 SessionCommand* SessionService::CreateWindowClosedCommand(
799     const SessionID::id_type window_id) {
800   ClosedPayload payload;
801   // See comment in CreateTabClosedCommand as to why we do this.
802   memset(&payload, 0, sizeof(payload));
803   payload.id = window_id;
804   payload.close_time = Time::Now().ToInternalValue();
805   SessionCommand* command =
806       new SessionCommand(kCommandWindowClosed, sizeof(payload));
807   memcpy(command->contents(), &payload, sizeof(payload));
808   return command;
809 }
810
811 SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand(
812     const SessionID& tab_id,
813     int index) {
814   SelectedNavigationIndexPayload payload = { 0 };
815   payload.id = tab_id.id();
816   payload.index = index;
817   SessionCommand* command = new SessionCommand(
818       kCommandSetSelectedNavigationIndex, sizeof(payload));
819   memcpy(command->contents(), &payload, sizeof(payload));
820   return command;
821 }
822
823 SessionCommand* SessionService::CreateSetWindowTypeCommand(
824     const SessionID& window_id,
825     WindowType type) {
826   WindowTypePayload payload = { 0 };
827   payload.id = window_id.id();
828   payload.index = static_cast<int32>(type);
829   SessionCommand* command = new SessionCommand(
830       kCommandSetWindowType, sizeof(payload));
831   memcpy(command->contents(), &payload, sizeof(payload));
832   return command;
833 }
834
835 SessionCommand* SessionService::CreatePinnedStateCommand(
836     const SessionID& tab_id,
837     bool is_pinned) {
838   PinnedStatePayload payload = { 0 };
839   payload.tab_id = tab_id.id();
840   payload.pinned_state = is_pinned;
841   SessionCommand* command =
842       new SessionCommand(kCommandSetPinnedState, sizeof(payload));
843   memcpy(command->contents(), &payload, sizeof(payload));
844   return command;
845 }
846
847 SessionCommand* SessionService::CreateSessionStorageAssociatedCommand(
848     const SessionID& tab_id,
849     const std::string& session_storage_persistent_id) {
850   Pickle pickle;
851   pickle.WriteInt(tab_id.id());
852   pickle.WriteString(session_storage_persistent_id);
853   return new SessionCommand(kCommandSessionStorageAssociated, pickle);
854 }
855
856 SessionCommand* SessionService::CreateSetActiveWindowCommand(
857     const SessionID& window_id) {
858   ActiveWindowPayload payload = 0;
859   payload = window_id.id();
860   SessionCommand* command =
861       new SessionCommand(kCommandSetActiveWindow, sizeof(payload));
862   memcpy(command->contents(), &payload, sizeof(payload));
863   return command;
864 }
865
866 void SessionService::OnGotSessionCommands(
867     const SessionCallback& callback,
868     ScopedVector<SessionCommand> commands) {
869   ScopedVector<SessionWindow> valid_windows;
870   SessionID::id_type active_window_id = 0;
871
872   RestoreSessionFromCommands(
873       commands.get(), &valid_windows.get(), &active_window_id);
874   callback.Run(valid_windows.Pass(), active_window_id);
875 }
876
877 void SessionService::RestoreSessionFromCommands(
878     const std::vector<SessionCommand*>& commands,
879     std::vector<SessionWindow*>* valid_windows,
880     SessionID::id_type* active_window_id) {
881   std::map<int, SessionTab*> tabs;
882   std::map<int, SessionWindow*> windows;
883
884   VLOG(1) << "RestoreSessionFromCommands " << commands.size();
885   if (CreateTabsAndWindows(commands, &tabs, &windows, active_window_id)) {
886     AddTabsToWindows(&tabs, &windows);
887     SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows);
888     UpdateSelectedTabIndex(valid_windows);
889   }
890   STLDeleteValues(&tabs);
891   // Don't delete conents of windows, that is done by the caller as all
892   // valid windows are added to valid_windows.
893 }
894
895 void SessionService::UpdateSelectedTabIndex(
896     std::vector<SessionWindow*>* windows) {
897   for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
898        i != windows->end(); ++i) {
899     // See note in SessionWindow as to why we do this.
900     int new_index = 0;
901     for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin();
902          j != (*i)->tabs.end(); ++j) {
903       if ((*j)->tab_visual_index == (*i)->selected_tab_index) {
904         new_index = static_cast<int>(j - (*i)->tabs.begin());
905         break;
906       }
907     }
908     (*i)->selected_tab_index = new_index;
909   }
910 }
911
912 SessionWindow* SessionService::GetWindow(
913     SessionID::id_type window_id,
914     IdToSessionWindow* windows) {
915   std::map<int, SessionWindow*>::iterator i = windows->find(window_id);
916   if (i == windows->end()) {
917     SessionWindow* window = new SessionWindow();
918     window->window_id.set_id(window_id);
919     (*windows)[window_id] = window;
920     return window;
921   }
922   return i->second;
923 }
924
925 SessionTab* SessionService::GetTab(
926     SessionID::id_type tab_id,
927     IdToSessionTab* tabs) {
928   DCHECK(tabs);
929   std::map<int, SessionTab*>::iterator i = tabs->find(tab_id);
930   if (i == tabs->end()) {
931     SessionTab* tab = new SessionTab();
932     tab->tab_id.set_id(tab_id);
933     (*tabs)[tab_id] = tab;
934     return tab;
935   }
936   return i->second;
937 }
938
939 std::vector<SerializedNavigationEntry>::iterator
940   SessionService::FindClosestNavigationWithIndex(
941     std::vector<SerializedNavigationEntry>* navigations,
942     int index) {
943   DCHECK(navigations);
944   for (std::vector<SerializedNavigationEntry>::iterator
945            i = navigations->begin(); i != navigations->end(); ++i) {
946     if (i->index() >= index)
947       return i;
948   }
949   return navigations->end();
950 }
951
952 // Function used in sorting windows. Sorting is done based on window id. As
953 // window ids increment for each new window, this effectively sorts by creation
954 // time.
955 static bool WindowOrderSortFunction(const SessionWindow* w1,
956                                     const SessionWindow* w2) {
957   return w1->window_id.id() < w2->window_id.id();
958 }
959
960 // Compares the two tabs based on visual index.
961 static bool TabVisualIndexSortFunction(const SessionTab* t1,
962                                        const SessionTab* t2) {
963   const int delta = t1->tab_visual_index - t2->tab_visual_index;
964   return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0);
965 }
966
967 void SessionService::SortTabsBasedOnVisualOrderAndPrune(
968     std::map<int, SessionWindow*>* windows,
969     std::vector<SessionWindow*>* valid_windows) {
970   std::map<int, SessionWindow*>::iterator i = windows->begin();
971   while (i != windows->end()) {
972     SessionWindow* window = i->second;
973     AppType app_type = window->app_name.empty() ? TYPE_NORMAL : TYPE_APP;
974     if (window->tabs.empty() || window->is_constrained ||
975         !should_track_changes_for_browser_type(
976             static_cast<Browser::Type>(window->type),
977             app_type)) {
978       delete window;
979       windows->erase(i++);
980     } else {
981       // Valid window; sort the tabs and add it to the list of valid windows.
982       std::sort(window->tabs.begin(), window->tabs.end(),
983                 &TabVisualIndexSortFunction);
984       // Otherwise, add the window such that older windows appear first.
985       if (valid_windows->empty()) {
986         valid_windows->push_back(window);
987       } else {
988         valid_windows->insert(
989             std::upper_bound(valid_windows->begin(), valid_windows->end(),
990                              window, &WindowOrderSortFunction),
991             window);
992       }
993       ++i;
994     }
995   }
996 }
997
998 void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs,
999                                       std::map<int, SessionWindow*>* windows) {
1000   VLOG(1) << "AddTabsToWindws";
1001   VLOG(1) << "Tabs " << tabs->size() << ", windows " << windows->size();
1002   std::map<int, SessionTab*>::iterator i = tabs->begin();
1003   while (i != tabs->end()) {
1004     SessionTab* tab = i->second;
1005     if (tab->window_id.id() && !tab->navigations.empty()) {
1006       SessionWindow* window = GetWindow(tab->window_id.id(), windows);
1007       window->tabs.push_back(tab);
1008       tabs->erase(i++);
1009
1010       // See note in SessionTab as to why we do this.
1011       std::vector<SerializedNavigationEntry>::iterator j =
1012           FindClosestNavigationWithIndex(&(tab->navigations),
1013                                          tab->current_navigation_index);
1014       if (j == tab->navigations.end()) {
1015         tab->current_navigation_index =
1016             static_cast<int>(tab->navigations.size() - 1);
1017       } else {
1018         tab->current_navigation_index =
1019             static_cast<int>(j - tab->navigations.begin());
1020       }
1021     } else {
1022       // Never got a set tab index in window, or tabs are empty, nothing
1023       // to do.
1024       ++i;
1025     }
1026   }
1027 }
1028
1029 bool SessionService::CreateTabsAndWindows(
1030     const std::vector<SessionCommand*>& data,
1031     std::map<int, SessionTab*>* tabs,
1032     std::map<int, SessionWindow*>* windows,
1033     SessionID::id_type* active_window_id) {
1034   // If the file is corrupt (command with wrong size, or unknown command), we
1035   // still return true and attempt to restore what we we can.
1036   VLOG(1) << "CreateTabsAndWindows";
1037
1038   startup_metric_utils::ScopedSlowStartupUMA
1039       scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
1040
1041   for (std::vector<SessionCommand*>::const_iterator i = data.begin();
1042        i != data.end(); ++i) {
1043     const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
1044     const SessionCommand* command = *i;
1045
1046     VLOG(1) << "Read command " << (int) command->id();
1047     switch (command->id()) {
1048       case kCommandSetTabWindow: {
1049         SessionID::id_type payload[2];
1050         if (!command->GetPayload(payload, sizeof(payload))) {
1051           VLOG(1) << "Failed reading command " << command->id();
1052           return true;
1053         }
1054         GetTab(payload[1], tabs)->window_id.set_id(payload[0]);
1055         break;
1056       }
1057
1058       // This is here for forward migration only.  New data is saved with
1059       // |kCommandSetWindowBounds3|.
1060       case kCommandSetWindowBounds2: {
1061         WindowBoundsPayload2 payload;
1062         if (!command->GetPayload(&payload, sizeof(payload))) {
1063           VLOG(1) << "Failed reading command " << command->id();
1064           return true;
1065         }
1066         GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1067                                                               payload.y,
1068                                                               payload.w,
1069                                                               payload.h);
1070         GetWindow(payload.window_id, windows)->show_state =
1071             payload.is_maximized ?
1072                 ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL;
1073         break;
1074       }
1075
1076       case kCommandSetWindowBounds3: {
1077         WindowBoundsPayload3 payload;
1078         if (!command->GetPayload(&payload, sizeof(payload))) {
1079           VLOG(1) << "Failed reading command " << command->id();
1080           return true;
1081         }
1082         GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1083                                                               payload.y,
1084                                                               payload.w,
1085                                                               payload.h);
1086         // SHOW_STATE_INACTIVE is not persisted.
1087         ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
1088         if (payload.show_state > ui::SHOW_STATE_DEFAULT &&
1089             payload.show_state < ui::SHOW_STATE_END &&
1090             payload.show_state != ui::SHOW_STATE_INACTIVE) {
1091           show_state = static_cast<ui::WindowShowState>(payload.show_state);
1092         } else {
1093           NOTREACHED();
1094         }
1095         GetWindow(payload.window_id, windows)->show_state = show_state;
1096         break;
1097       }
1098
1099       case kCommandSetTabIndexInWindow: {
1100         TabIndexInWindowPayload payload;
1101         if (!command->GetPayload(&payload, sizeof(payload))) {
1102           VLOG(1) << "Failed reading command " << command->id();
1103           return true;
1104         }
1105         GetTab(payload.id, tabs)->tab_visual_index = payload.index;
1106         break;
1107       }
1108
1109       case kCommandTabClosedObsolete:
1110       case kCommandWindowClosedObsolete:
1111       case kCommandTabClosed:
1112       case kCommandWindowClosed: {
1113         ClosedPayload payload;
1114         if (!command->GetPayload(&payload, sizeof(payload)) &&
1115             !MigrateClosedPayload(*command, &payload)) {
1116           VLOG(1) << "Failed reading command " << command->id();
1117           return true;
1118         }
1119         if (command->id() == kCommandTabClosed ||
1120             command->id() == kCommandTabClosedObsolete) {
1121           delete GetTab(payload.id, tabs);
1122           tabs->erase(payload.id);
1123         } else {
1124           delete GetWindow(payload.id, windows);
1125           windows->erase(payload.id);
1126         }
1127         break;
1128       }
1129
1130       case kCommandTabNavigationPathPrunedFromBack: {
1131         TabNavigationPathPrunedFromBackPayload payload;
1132         if (!command->GetPayload(&payload, sizeof(payload))) {
1133           VLOG(1) << "Failed reading command " << command->id();
1134           return true;
1135         }
1136         SessionTab* tab = GetTab(payload.id, tabs);
1137         tab->navigations.erase(
1138             FindClosestNavigationWithIndex(&(tab->navigations), payload.index),
1139             tab->navigations.end());
1140         break;
1141       }
1142
1143       case kCommandTabNavigationPathPrunedFromFront: {
1144         TabNavigationPathPrunedFromFrontPayload payload;
1145         if (!command->GetPayload(&payload, sizeof(payload)) ||
1146             payload.index <= 0) {
1147           VLOG(1) << "Failed reading command " << command->id();
1148           return true;
1149         }
1150         SessionTab* tab = GetTab(payload.id, tabs);
1151
1152         // Update the selected navigation index.
1153         tab->current_navigation_index =
1154             std::max(-1, tab->current_navigation_index - payload.index);
1155
1156         // And update the index of existing navigations.
1157         for (std::vector<SerializedNavigationEntry>::iterator
1158                  i = tab->navigations.begin();
1159              i != tab->navigations.end();) {
1160           i->set_index(i->index() - payload.index);
1161           if (i->index() < 0)
1162             i = tab->navigations.erase(i);
1163           else
1164             ++i;
1165         }
1166         break;
1167       }
1168
1169       case kCommandUpdateTabNavigation: {
1170         SerializedNavigationEntry navigation;
1171         SessionID::id_type tab_id;
1172         if (!RestoreUpdateTabNavigationCommand(
1173                 *command, &navigation, &tab_id)) {
1174           VLOG(1) << "Failed reading command " << command->id();
1175           return true;
1176         }
1177         SessionTab* tab = GetTab(tab_id, tabs);
1178         std::vector<SerializedNavigationEntry>::iterator i =
1179             FindClosestNavigationWithIndex(&(tab->navigations),
1180                                            navigation.index());
1181         if (i != tab->navigations.end() && i->index() == navigation.index())
1182           *i = navigation;
1183         else
1184           tab->navigations.insert(i, navigation);
1185         break;
1186       }
1187
1188       case kCommandSetSelectedNavigationIndex: {
1189         SelectedNavigationIndexPayload payload;
1190         if (!command->GetPayload(&payload, sizeof(payload))) {
1191           VLOG(1) << "Failed reading command " << command->id();
1192           return true;
1193         }
1194         GetTab(payload.id, tabs)->current_navigation_index = payload.index;
1195         break;
1196       }
1197
1198       case kCommandSetSelectedTabInIndex: {
1199         SelectedTabInIndexPayload payload;
1200         if (!command->GetPayload(&payload, sizeof(payload))) {
1201           VLOG(1) << "Failed reading command " << command->id();
1202           return true;
1203         }
1204         GetWindow(payload.id, windows)->selected_tab_index = payload.index;
1205         break;
1206       }
1207
1208       case kCommandSetWindowType: {
1209         WindowTypePayload payload;
1210         if (!command->GetPayload(&payload, sizeof(payload))) {
1211           VLOG(1) << "Failed reading command " << command->id();
1212           return true;
1213         }
1214         GetWindow(payload.id, windows)->is_constrained = false;
1215         GetWindow(payload.id, windows)->type =
1216             BrowserTypeForWindowType(
1217                 static_cast<WindowType>(payload.index));
1218         break;
1219       }
1220
1221       case kCommandSetPinnedState: {
1222         PinnedStatePayload payload;
1223         if (!command->GetPayload(&payload, sizeof(payload))) {
1224           VLOG(1) << "Failed reading command " << command->id();
1225           return true;
1226         }
1227         GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state;
1228         break;
1229       }
1230
1231       case kCommandSetWindowAppName: {
1232         SessionID::id_type window_id;
1233         std::string app_name;
1234         if (!RestoreSetWindowAppNameCommand(*command, &window_id, &app_name))
1235           return true;
1236
1237         GetWindow(window_id, windows)->app_name.swap(app_name);
1238         break;
1239       }
1240
1241       case kCommandSetExtensionAppID: {
1242         SessionID::id_type tab_id;
1243         std::string extension_app_id;
1244         if (!RestoreSetTabExtensionAppIDCommand(
1245                 *command, &tab_id, &extension_app_id)) {
1246           VLOG(1) << "Failed reading command " << command->id();
1247           return true;
1248         }
1249
1250         GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id);
1251         break;
1252       }
1253
1254       case kCommandSetTabUserAgentOverride: {
1255         SessionID::id_type tab_id;
1256         std::string user_agent_override;
1257         if (!RestoreSetTabUserAgentOverrideCommand(
1258                 *command, &tab_id, &user_agent_override)) {
1259           return true;
1260         }
1261
1262         GetTab(tab_id, tabs)->user_agent_override.swap(user_agent_override);
1263         break;
1264       }
1265
1266       case kCommandSessionStorageAssociated: {
1267         scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1268         SessionID::id_type command_tab_id;
1269         std::string session_storage_persistent_id;
1270         PickleIterator iter(*command_pickle.get());
1271         if (!command_pickle->ReadInt(&iter, &command_tab_id) ||
1272             !command_pickle->ReadString(&iter, &session_storage_persistent_id))
1273           return true;
1274         // Associate the session storage back.
1275         GetTab(command_tab_id, tabs)->session_storage_persistent_id =
1276             session_storage_persistent_id;
1277         break;
1278       }
1279
1280       case kCommandSetActiveWindow: {
1281         ActiveWindowPayload payload;
1282         if (!command->GetPayload(&payload, sizeof(payload))) {
1283           VLOG(1) << "Failed reading command " << command->id();
1284           return true;
1285         }
1286         *active_window_id = payload;
1287         break;
1288       }
1289
1290       default:
1291         VLOG(1) << "Failed reading an unknown command " << command->id();
1292         return true;
1293     }
1294   }
1295   return true;
1296 }
1297
1298 void SessionService::BuildCommandsForTab(const SessionID& window_id,
1299                                          WebContents* tab,
1300                                          int index_in_window,
1301                                          bool is_pinned,
1302                                          std::vector<SessionCommand*>* commands,
1303                                          IdToRange* tab_to_available_range) {
1304   DCHECK(tab && commands && window_id.id());
1305   SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
1306   const SessionID& session_id(session_tab_helper->session_id());
1307   commands->push_back(CreateSetTabWindowCommand(window_id, session_id));
1308
1309   const int current_index = tab->GetController().GetCurrentEntryIndex();
1310   const int min_index = std::max(0,
1311                                  current_index - max_persist_navigation_count);
1312   const int max_index =
1313       std::min(current_index + max_persist_navigation_count,
1314                tab->GetController().GetEntryCount());
1315   const int pending_index = tab->GetController().GetPendingEntryIndex();
1316   if (tab_to_available_range) {
1317     (*tab_to_available_range)[session_id.id()] =
1318         std::pair<int, int>(min_index, max_index);
1319   }
1320
1321   if (is_pinned) {
1322     commands->push_back(CreatePinnedStateCommand(session_id, true));
1323   }
1324
1325   extensions::TabHelper* extensions_tab_helper =
1326       extensions::TabHelper::FromWebContents(tab);
1327   if (extensions_tab_helper->extension_app()) {
1328     commands->push_back(
1329         CreateSetTabExtensionAppIDCommand(
1330             kCommandSetExtensionAppID, session_id.id(),
1331             extensions_tab_helper->extension_app()->id()));
1332   }
1333
1334   const std::string& ua_override = tab->GetUserAgentOverride();
1335   if (!ua_override.empty()) {
1336     commands->push_back(
1337         CreateSetTabUserAgentOverrideCommand(
1338             kCommandSetTabUserAgentOverride, session_id.id(), ua_override));
1339   }
1340
1341   for (int i = min_index; i < max_index; ++i) {
1342     const NavigationEntry* entry = (i == pending_index) ?
1343         tab->GetController().GetPendingEntry() :
1344         tab->GetController().GetEntryAtIndex(i);
1345     DCHECK(entry);
1346     if (ShouldTrackEntry(entry->GetVirtualURL())) {
1347       const SerializedNavigationEntry navigation =
1348           SerializedNavigationEntry::FromNavigationEntry(i, *entry);
1349       commands->push_back(
1350           CreateUpdateTabNavigationCommand(
1351               kCommandUpdateTabNavigation, session_id.id(), navigation));
1352     }
1353   }
1354   commands->push_back(
1355       CreateSetSelectedNavigationIndexCommand(session_id, current_index));
1356
1357   if (index_in_window != -1) {
1358     commands->push_back(
1359         CreateSetTabIndexInWindowCommand(session_id, index_in_window));
1360   }
1361
1362   // Record the association between the sessionStorage namespace and the tab.
1363   content::SessionStorageNamespace* session_storage_namespace =
1364       tab->GetController().GetDefaultSessionStorageNamespace();
1365   ScheduleCommand(CreateSessionStorageAssociatedCommand(
1366       session_tab_helper->session_id(),
1367       session_storage_namespace->persistent_id()));
1368 }
1369
1370 void SessionService::BuildCommandsForBrowser(
1371     Browser* browser,
1372     std::vector<SessionCommand*>* commands,
1373     IdToRange* tab_to_available_range,
1374     std::set<SessionID::id_type>* windows_to_track) {
1375   DCHECK(browser && commands);
1376   DCHECK(browser->session_id().id());
1377
1378   commands->push_back(
1379       CreateSetWindowBoundsCommand(browser->session_id(),
1380                                    browser->window()->GetRestoredBounds(),
1381                                    browser->window()->GetRestoredState()));
1382
1383   commands->push_back(CreateSetWindowTypeCommand(
1384       browser->session_id(), WindowTypeForBrowserType(browser->type())));
1385
1386   if (!browser->app_name().empty()) {
1387     commands->push_back(CreateSetWindowAppNameCommand(
1388         kCommandSetWindowAppName,
1389         browser->session_id().id(),
1390         browser->app_name()));
1391   }
1392
1393   windows_to_track->insert(browser->session_id().id());
1394   TabStripModel* tab_strip = browser->tab_strip_model();
1395   for (int i = 0; i < tab_strip->count(); ++i) {
1396     WebContents* tab = tab_strip->GetWebContentsAt(i);
1397     DCHECK(tab);
1398     BuildCommandsForTab(browser->session_id(), tab, i,
1399                         tab_strip->IsTabPinned(i),
1400                         commands, tab_to_available_range);
1401   }
1402
1403   commands->push_back(
1404       CreateSetSelectedTabInWindow(browser->session_id(),
1405                                    browser->tab_strip_model()->active_index()));
1406 }
1407
1408 void SessionService::BuildCommandsFromBrowsers(
1409     std::vector<SessionCommand*>* commands,
1410     IdToRange* tab_to_available_range,
1411     std::set<SessionID::id_type>* windows_to_track) {
1412   DCHECK(commands);
1413   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1414     Browser* browser = *it;
1415     // Make sure the browser has tabs and a window. Browser's destructor
1416     // removes itself from the BrowserList. When a browser is closed the
1417     // destructor is not necessarily run immediately. This means it's possible
1418     // for us to get a handle to a browser that is about to be removed. If
1419     // the tab count is 0 or the window is NULL, the browser is about to be
1420     // deleted, so we ignore it.
1421     if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
1422         browser->window()) {
1423       BuildCommandsForBrowser(browser, commands, tab_to_available_range,
1424                               windows_to_track);
1425     }
1426   }
1427 }
1428
1429 void SessionService::ScheduleReset() {
1430   set_pending_reset(true);
1431   STLDeleteElements(&pending_commands());
1432   tab_to_available_range_.clear();
1433   windows_tracking_.clear();
1434   BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_,
1435                             &windows_tracking_);
1436   if (!windows_tracking_.empty()) {
1437     // We're lazily created on startup and won't get an initial batch of
1438     // SetWindowType messages. Set these here to make sure our state is correct.
1439     has_open_trackable_browsers_ = true;
1440     move_on_new_browser_ = true;
1441   }
1442   StartSaveTimer();
1443 }
1444
1445 bool SessionService::ReplacePendingCommand(SessionCommand* command) {
1446   // We optimize page navigations, which can happen quite frequently and
1447   // are expensive. And activation is like Highlander, there can only be one!
1448   if (command->id() != kCommandUpdateTabNavigation &&
1449       command->id() != kCommandSetActiveWindow) {
1450     return false;
1451   }
1452   for (std::vector<SessionCommand*>::reverse_iterator i =
1453        pending_commands().rbegin(); i != pending_commands().rend(); ++i) {
1454     SessionCommand* existing_command = *i;
1455     if (command->id() == kCommandUpdateTabNavigation &&
1456         existing_command->id() == kCommandUpdateTabNavigation) {
1457       scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1458       PickleIterator iterator(*command_pickle);
1459       SessionID::id_type command_tab_id;
1460       int command_nav_index;
1461       if (!command_pickle->ReadInt(&iterator, &command_tab_id) ||
1462           !command_pickle->ReadInt(&iterator, &command_nav_index)) {
1463         return false;
1464       }
1465       SessionID::id_type existing_tab_id;
1466       int existing_nav_index;
1467       {
1468         // Creating a pickle like this means the Pickle references the data from
1469         // the command. Make sure we delete the pickle before the command, else
1470         // the pickle references deleted memory.
1471         scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle());
1472         iterator = PickleIterator(*existing_pickle);
1473         if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) ||
1474             !existing_pickle->ReadInt(&iterator, &existing_nav_index)) {
1475           return false;
1476         }
1477       }
1478       if (existing_tab_id == command_tab_id &&
1479           existing_nav_index == command_nav_index) {
1480         // existing_command is an update for the same tab/index pair. Replace
1481         // it with the new one. We need to add to the end of the list just in
1482         // case there is a prune command after the update command.
1483         delete existing_command;
1484         pending_commands().erase(i.base() - 1);
1485         pending_commands().push_back(command);
1486         return true;
1487       }
1488       return false;
1489     }
1490     if (command->id() == kCommandSetActiveWindow &&
1491         existing_command->id() == kCommandSetActiveWindow) {
1492       *i = command;
1493       delete existing_command;
1494       return true;
1495     }
1496   }
1497   return false;
1498 }
1499
1500 void SessionService::ScheduleCommand(SessionCommand* command) {
1501   DCHECK(command);
1502   if (ReplacePendingCommand(command))
1503     return;
1504   BaseSessionService::ScheduleCommand(command);
1505   // Don't schedule a reset on tab closed/window closed. Otherwise we may
1506   // lose tabs/windows we want to restore from if we exit right after this.
1507   if (!pending_reset() && pending_window_close_ids_.empty() &&
1508       commands_since_reset() >= kWritesPerReset &&
1509       (command->id() != kCommandTabClosed &&
1510        command->id() != kCommandWindowClosed)) {
1511     ScheduleReset();
1512   }
1513 }
1514
1515 void SessionService::CommitPendingCloses() {
1516   for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
1517        i != pending_tab_close_ids_.end(); ++i) {
1518     ScheduleCommand(CreateTabClosedCommand(*i));
1519   }
1520   pending_tab_close_ids_.clear();
1521
1522   for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
1523        i != pending_window_close_ids_.end(); ++i) {
1524     ScheduleCommand(CreateWindowClosedCommand(*i));
1525   }
1526   pending_window_close_ids_.clear();
1527 }
1528
1529 bool SessionService::IsOnlyOneTabLeft() const {
1530   if (!profile() || profile()->AsTestingProfile()) {
1531     // We're testing, always return false.
1532     return false;
1533   }
1534
1535   int window_count = 0;
1536   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1537     Browser* browser = *it;
1538     const SessionID::id_type window_id = browser->session_id().id();
1539     if (ShouldTrackBrowser(browser) &&
1540         window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
1541       if (++window_count > 1)
1542         return false;
1543       // By the time this is invoked the tab has been removed. As such, we use
1544       // > 0 here rather than > 1.
1545       if (browser->tab_strip_model()->count() > 0)
1546         return false;
1547     }
1548   }
1549   return true;
1550 }
1551
1552 bool SessionService::HasOpenTrackableBrowsers(
1553     const SessionID& window_id) const {
1554   if (!profile() || profile()->AsTestingProfile()) {
1555     // We're testing, always return true.
1556     return true;
1557   }
1558
1559   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1560     Browser* browser = *it;
1561     const SessionID::id_type browser_id = browser->session_id().id();
1562     if (browser_id != window_id.id() &&
1563         window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
1564         ShouldTrackBrowser(browser)) {
1565       return true;
1566     }
1567   }
1568   return false;
1569 }
1570
1571 bool SessionService::ShouldTrackChangesToWindow(
1572     const SessionID& window_id) const {
1573   return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
1574 }
1575
1576 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
1577   if (browser->profile() != profile())
1578     return false;
1579   // Never track app popup windows that do not have a trusted source (i.e.
1580   // popup windows spawned by an app). If this logic changes, be sure to also
1581   // change SessionRestoreImpl::CreateRestoredBrowser().
1582   if (browser->is_app() && browser->is_type_popup() &&
1583       !browser->is_trusted_source()) {
1584     return false;
1585   }
1586   AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
1587   return should_track_changes_for_browser_type(browser->type(), app_type);
1588 }
1589
1590 bool SessionService::should_track_changes_for_browser_type(Browser::Type type,
1591                                                            AppType app_type) {
1592 #if defined(OS_CHROMEOS)
1593   // Restore app popups for chromeos alone.
1594   if (type == Browser::TYPE_POPUP && app_type == TYPE_APP)
1595     return true;
1596 #endif
1597
1598   return type == Browser::TYPE_TABBED;
1599 }
1600
1601 SessionService::WindowType SessionService::WindowTypeForBrowserType(
1602     Browser::Type type) {
1603   switch (type) {
1604     case Browser::TYPE_POPUP:
1605       return TYPE_POPUP;
1606     case Browser::TYPE_TABBED:
1607       return TYPE_TABBED;
1608     default:
1609       DCHECK(false);
1610       return TYPE_TABBED;
1611   }
1612 }
1613
1614 Browser::Type SessionService::BrowserTypeForWindowType(WindowType type) {
1615   switch (type) {
1616     case TYPE_POPUP:
1617       return Browser::TYPE_POPUP;
1618     case TYPE_TABBED:
1619     default:
1620       return Browser::TYPE_TABBED;
1621   }
1622 }
1623
1624 void SessionService::RecordSessionUpdateHistogramData(int type,
1625     base::TimeTicks* last_updated_time) {
1626   if (!last_updated_time->is_null()) {
1627     base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
1628     // We're interested in frequent updates periods longer than
1629     // 10 minutes.
1630     bool use_long_period = false;
1631     if (delta >= save_delay_in_mins_) {
1632       use_long_period = true;
1633     }
1634     switch (type) {
1635       case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
1636         RecordUpdatedSaveTime(delta, use_long_period);
1637         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1638         break;
1639       case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
1640         RecordUpdatedTabClosed(delta, use_long_period);
1641         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1642         break;
1643       case content::NOTIFICATION_NAV_LIST_PRUNED:
1644         RecordUpdatedNavListPruned(delta, use_long_period);
1645         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1646         break;
1647       case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
1648         RecordUpdatedNavEntryCommit(delta, use_long_period);
1649         RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1650         break;
1651       default:
1652         NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1653         break;
1654     }
1655   }
1656   (*last_updated_time) = base::TimeTicks::Now();
1657 }
1658
1659 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
1660                                             bool use_long_period) {
1661   std::string name("SessionRestore.TabClosedPeriod");
1662   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1663       delta,
1664       // 2500ms is the default save delay.
1665       save_delay_in_millis_,
1666       save_delay_in_mins_,
1667       50);
1668   if (use_long_period) {
1669     std::string long_name_("SessionRestore.TabClosedLongPeriod");
1670     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1671         delta,
1672         save_delay_in_mins_,
1673         save_delay_in_hrs_,
1674         50);
1675   }
1676 }
1677
1678 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1679                                                 bool use_long_period) {
1680   std::string name("SessionRestore.NavigationListPrunedPeriod");
1681   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1682       delta,
1683       // 2500ms is the default save delay.
1684       save_delay_in_millis_,
1685       save_delay_in_mins_,
1686       50);
1687   if (use_long_period) {
1688     std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1689     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1690         delta,
1691         save_delay_in_mins_,
1692         save_delay_in_hrs_,
1693         50);
1694   }
1695 }
1696
1697 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1698                                                  bool use_long_period) {
1699   std::string name("SessionRestore.NavEntryCommittedPeriod");
1700   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1701       delta,
1702       // 2500ms is the default save delay.
1703       save_delay_in_millis_,
1704       save_delay_in_mins_,
1705       50);
1706   if (use_long_period) {
1707     std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1708     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1709         delta,
1710         save_delay_in_mins_,
1711         save_delay_in_hrs_,
1712         50);
1713   }
1714 }
1715
1716 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1717                                                          bool use_long_period) {
1718   std::string name("SessionRestore.NavOrTabUpdatePeriod");
1719   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1720       delta,
1721       // 2500ms is the default save delay.
1722       save_delay_in_millis_,
1723       save_delay_in_mins_,
1724       50);
1725   if (use_long_period) {
1726     std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1727     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1728         delta,
1729         save_delay_in_mins_,
1730         save_delay_in_hrs_,
1731         50);
1732   }
1733 }
1734
1735 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1736                                            bool use_long_period) {
1737   std::string name("SessionRestore.SavePeriod");
1738   UMA_HISTOGRAM_CUSTOM_TIMES(name,
1739       delta,
1740       // 2500ms is the default save delay.
1741       save_delay_in_millis_,
1742       save_delay_in_mins_,
1743       50);
1744   if (use_long_period) {
1745     std::string long_name_("SessionRestore.SaveLongPeriod");
1746     UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1747         delta,
1748         save_delay_in_mins_,
1749         save_delay_in_hrs_,
1750         50);
1751   }
1752 }
1753
1754 void SessionService::TabInserted(WebContents* contents) {
1755   SessionTabHelper* session_tab_helper =
1756       SessionTabHelper::FromWebContents(contents);
1757   if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
1758     return;
1759   SetTabWindow(session_tab_helper->window_id(),
1760                session_tab_helper->session_id());
1761   extensions::TabHelper* extensions_tab_helper =
1762       extensions::TabHelper::FromWebContents(contents);
1763   if (extensions_tab_helper &&
1764       extensions_tab_helper->extension_app()) {
1765     SetTabExtensionAppID(
1766         session_tab_helper->window_id(),
1767         session_tab_helper->session_id(),
1768         extensions_tab_helper->extension_app()->id());
1769   }
1770
1771   // Record the association between the SessionStorageNamespace and the
1772   // tab.
1773   //
1774   // TODO(ajwong): This should be processing the whole map rather than
1775   // just the default. This in particular will not work for tabs with only
1776   // isolated apps which won't have a default partition.
1777   content::SessionStorageNamespace* session_storage_namespace =
1778       contents->GetController().GetDefaultSessionStorageNamespace();
1779   ScheduleCommand(CreateSessionStorageAssociatedCommand(
1780       session_tab_helper->session_id(),
1781       session_storage_namespace->persistent_id()));
1782   session_storage_namespace->SetShouldPersist(true);
1783 }
1784
1785 void SessionService::TabClosing(WebContents* contents) {
1786   // Allow the associated sessionStorage to get deleted; it won't be needed
1787   // in the session restore.
1788   content::SessionStorageNamespace* session_storage_namespace =
1789       contents->GetController().GetDefaultSessionStorageNamespace();
1790   session_storage_namespace->SetShouldPersist(false);
1791   SessionTabHelper* session_tab_helper =
1792       SessionTabHelper::FromWebContents(contents);
1793   TabClosed(session_tab_helper->window_id(),
1794             session_tab_helper->session_id(),
1795             contents->GetClosedByUserGesture());
1796   RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
1797                                    &last_updated_tab_closed_time_);
1798 }
1799
1800 void SessionService::MaybeDeleteSessionOnlyData() {
1801   // Don't try anything if we're testing.  The browser_process is not fully
1802   // created and DeleteSession will crash if we actually attempt it.
1803   if (!profile() || profile()->AsTestingProfile())
1804     return;
1805
1806   // Clear session data if the last window for a profile has been closed and
1807   // closing the last window would normally close Chrome, unless background mode
1808   // is active.  Tests don't have a background_mode_manager.
1809   if (has_open_trackable_browsers_ ||
1810       browser_defaults::kBrowserAliveWithNoWindows ||
1811       g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1812     return;
1813   }
1814
1815   // Check for any open windows for the current profile that we aren't tracking.
1816   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1817     if ((*it)->profile() == profile())
1818       return;
1819   }
1820   DeleteSessionOnlyData(profile());
1821 }