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