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