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