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