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