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