1// Copyright (c) 2011 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 <limits>
9#include <set>
10#include <vector>
11
12#include "base/file_util.h"
13#include "base/memory/scoped_vector.h"
14#include "base/message_loop.h"
15#include "base/metrics/histogram.h"
16#include "base/pickle.h"
17#include "base/threading/thread.h"
18#include "chrome/browser/extensions/extension_tab_helper.h"
19#include "chrome/browser/prefs/session_startup_pref.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/sessions/session_backend.h"
22#include "chrome/browser/sessions/session_command.h"
23#include "chrome/browser/sessions/session_restore.h"
24#include "chrome/browser/sessions/session_types.h"
25#include "chrome/browser/tabs/tab_strip_model.h"
26#include "chrome/browser/ui/browser_init.h"
27#include "chrome/browser/ui/browser_list.h"
28#include "chrome/browser/ui/browser_window.h"
29#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
30#include "chrome/common/extensions/extension.h"
31#include "content/browser/tab_contents/navigation_controller.h"
32#include "content/browser/tab_contents/navigation_entry.h"
33#include "content/browser/tab_contents/tab_contents.h"
34#include "content/common/notification_details.h"
35#include "content/common/notification_service.h"
36
37#if defined(OS_MACOSX)
38#include "chrome/browser/app_controller_cppsafe_mac.h"
39#endif
40
41using base::Time;
42
43// Identifier for commands written to file.
44static const SessionCommand::id_type kCommandSetTabWindow = 0;
45// kCommandSetWindowBounds is no longer used (it's superseded by
46// kCommandSetWindowBounds2). I leave it here to document what it was.
47// static const SessionCommand::id_type kCommandSetWindowBounds = 1;
48static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2;
49static const SessionCommand::id_type kCommandTabClosed = 3;
50static const SessionCommand::id_type kCommandWindowClosed = 4;
51static const SessionCommand::id_type
52    kCommandTabNavigationPathPrunedFromBack = 5;
53static const SessionCommand::id_type kCommandUpdateTabNavigation = 6;
54static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7;
55static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8;
56static const SessionCommand::id_type kCommandSetWindowType = 9;
57static const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
58static const SessionCommand::id_type
59    kCommandTabNavigationPathPrunedFromFront = 11;
60static const SessionCommand::id_type kCommandSetPinnedState = 12;
61static const SessionCommand::id_type kCommandSetExtensionAppID = 13;
62
63// Every kWritesPerReset commands triggers recreating the file.
64static const int kWritesPerReset = 250;
65
66namespace {
67
68// The callback from GetLastSession is internally routed to SessionService
69// first and then the caller. This is done so that the SessionWindows can be
70// recreated from the SessionCommands and the SessionWindows passed to the
71// caller. The following class is used for this.
72class InternalSessionRequest
73    : public BaseSessionService::InternalGetCommandsRequest {
74 public:
75  InternalSessionRequest(
76      CallbackType* callback,
77      SessionService::SessionCallback* real_callback)
78      : BaseSessionService::InternalGetCommandsRequest(callback),
79        real_callback(real_callback) {
80  }
81
82  // The callback supplied to GetLastSession and GetCurrentSession.
83  scoped_ptr<SessionService::SessionCallback> real_callback;
84
85 private:
86  ~InternalSessionRequest() {}
87
88  DISALLOW_COPY_AND_ASSIGN(InternalSessionRequest);
89};
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 IDAndIndexPayload {
107  SessionID::id_type id;
108  int32 index;
109};
110
111typedef IDAndIndexPayload TabIndexInWindowPayload;
112
113typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload;
114
115typedef IDAndIndexPayload SelectedNavigationIndexPayload;
116
117typedef IDAndIndexPayload SelectedTabInIndexPayload;
118
119typedef IDAndIndexPayload WindowTypePayload;
120
121typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload;
122
123struct PinnedStatePayload {
124  SessionID::id_type tab_id;
125  bool pinned_state;
126};
127
128}  // namespace
129
130// SessionService -------------------------------------------------------------
131
132SessionService::SessionService(Profile* profile)
133    : BaseSessionService(SESSION_RESTORE, profile, FilePath()),
134      has_open_trackable_browsers_(false),
135      move_on_new_browser_(false),
136      save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
137      save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
138      save_delay_in_hrs_(base::TimeDelta::FromHours(8)) {
139  Init();
140}
141
142SessionService::SessionService(const FilePath& save_path)
143    : BaseSessionService(SESSION_RESTORE, NULL, save_path),
144      has_open_trackable_browsers_(false),
145      move_on_new_browser_(false),
146      save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
147      save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
148      save_delay_in_hrs_(base::TimeDelta::FromHours(8)) {
149  Init();
150}
151
152SessionService::~SessionService() {
153  Save();
154}
155
156bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
157  return RestoreIfNecessary(urls_to_open, NULL);
158}
159
160void SessionService::ResetFromCurrentBrowsers() {
161  ScheduleReset();
162}
163
164void SessionService::MoveCurrentSessionToLastSession() {
165  pending_tab_close_ids_.clear();
166  window_closing_ids_.clear();
167  pending_window_close_ids_.clear();
168
169  Save();
170
171  if (!backend_thread()) {
172    backend()->MoveCurrentSessionToLastSession();
173  } else {
174    backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
175        backend(), &SessionBackend::MoveCurrentSessionToLastSession));
176  }
177}
178
179void SessionService::SetTabWindow(const SessionID& window_id,
180                                  const SessionID& tab_id) {
181  if (!ShouldTrackChangesToWindow(window_id))
182    return;
183
184  ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id));
185}
186
187void SessionService::SetWindowBounds(const SessionID& window_id,
188                                     const gfx::Rect& bounds,
189                                     bool is_maximized) {
190  if (!ShouldTrackChangesToWindow(window_id))
191    return;
192
193  ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds,
194                                               is_maximized));
195}
196
197void SessionService::SetTabIndexInWindow(const SessionID& window_id,
198                                         const SessionID& tab_id,
199                                         int new_index) {
200  if (!ShouldTrackChangesToWindow(window_id))
201    return;
202
203  ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index));
204}
205
206void SessionService::SetPinnedState(const SessionID& window_id,
207                                    const SessionID& tab_id,
208                                    bool is_pinned) {
209  if (!ShouldTrackChangesToWindow(window_id))
210    return;
211
212  ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned));
213}
214
215void SessionService::TabClosed(const SessionID& window_id,
216                               const SessionID& tab_id,
217                               bool closed_by_user_gesture) {
218  if (!tab_id.id())
219    return;  // Hapens when the tab is replaced.
220
221  if (!ShouldTrackChangesToWindow(window_id))
222    return;
223
224  IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
225  if (i != tab_to_available_range_.end())
226    tab_to_available_range_.erase(i);
227
228  if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
229           window_id.id()) != pending_window_close_ids_.end()) {
230    // Tab is in last window. Don't commit it immediately, instead add it to the
231    // list of tabs to close. If the user creates another window, the close is
232    // committed.
233    pending_tab_close_ids_.insert(tab_id.id());
234  } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
235                  window_id.id()) != window_closing_ids_.end() ||
236             !IsOnlyOneTabLeft() ||
237             closed_by_user_gesture) {
238    // Close is the result of one of the following:
239    // . window close (and it isn't the last window).
240    // . closing a tab and there are other windows/tabs open.
241    // . closed by a user gesture.
242    // In all cases we need to mark the tab as explicitly closed.
243    ScheduleCommand(CreateTabClosedCommand(tab_id.id()));
244  } else {
245    // User closed the last tab in the last tabbed browser. Don't mark the
246    // tab closed.
247    pending_tab_close_ids_.insert(tab_id.id());
248    has_open_trackable_browsers_ = false;
249  }
250}
251
252void SessionService::WindowClosing(const SessionID& window_id) {
253  if (!ShouldTrackChangesToWindow(window_id))
254    return;
255
256  // The window is about to close. If there are other tabbed browsers with the
257  // same original profile commit the close immediately.
258  //
259  // NOTE: if the user chooses the exit menu item session service is destroyed
260  // and this code isn't hit.
261  if (has_open_trackable_browsers_) {
262    // Closing a window can never make has_open_trackable_browsers_ go from
263    // false to true, so only update it if already true.
264    has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
265  }
266  if (should_record_close_as_pending())
267    pending_window_close_ids_.insert(window_id.id());
268  else
269    window_closing_ids_.insert(window_id.id());
270}
271
272void SessionService::WindowClosed(const SessionID& window_id) {
273  if (!ShouldTrackChangesToWindow(window_id))
274    return;
275
276  windows_tracking_.erase(window_id.id());
277
278  if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
279    window_closing_ids_.erase(window_id.id());
280    ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
281  } else if (pending_window_close_ids_.find(window_id.id()) ==
282             pending_window_close_ids_.end()) {
283    // We'll hit this if user closed the last tab in a window.
284    has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
285    if (should_record_close_as_pending())
286      pending_window_close_ids_.insert(window_id.id());
287    else
288      ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
289  }
290}
291
292void SessionService::SetWindowType(const SessionID& window_id,
293                                   Browser::Type type) {
294  if (!should_track_changes_for_browser_type(type))
295    return;
296
297  windows_tracking_.insert(window_id.id());
298
299  // The user created a new tabbed browser with our profile. Commit any
300  // pending closes.
301  CommitPendingCloses();
302
303  has_open_trackable_browsers_ = true;
304  move_on_new_browser_ = true;
305
306  ScheduleCommand(
307      CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type)));
308}
309
310void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
311                                                     const SessionID& tab_id,
312                                                     int count) {
313  if (!ShouldTrackChangesToWindow(window_id))
314    return;
315
316  TabNavigationPathPrunedFromBackPayload payload = { 0 };
317  payload.id = tab_id.id();
318  payload.index = count;
319  SessionCommand* command =
320      new SessionCommand(kCommandTabNavigationPathPrunedFromBack,
321                         sizeof(payload));
322  memcpy(command->contents(), &payload, sizeof(payload));
323  ScheduleCommand(command);
324}
325
326void SessionService::TabNavigationPathPrunedFromFront(
327    const SessionID& window_id,
328    const SessionID& tab_id,
329    int count) {
330  if (!ShouldTrackChangesToWindow(window_id))
331    return;
332
333  // Update the range of indices.
334  if (tab_to_available_range_.find(tab_id.id()) !=
335      tab_to_available_range_.end()) {
336    std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
337    range.first = std::max(0, range.first - count);
338    range.second = std::max(0, range.second - count);
339  }
340
341  TabNavigationPathPrunedFromFrontPayload payload = { 0 };
342  payload.id = tab_id.id();
343  payload.index = count;
344  SessionCommand* command =
345      new SessionCommand(kCommandTabNavigationPathPrunedFromFront,
346                         sizeof(payload));
347  memcpy(command->contents(), &payload, sizeof(payload));
348  ScheduleCommand(command);
349}
350
351void SessionService::UpdateTabNavigation(const SessionID& window_id,
352                                         const SessionID& tab_id,
353                                         int index,
354                                         const NavigationEntry& entry) {
355  if (!ShouldTrackEntry(entry) || !ShouldTrackChangesToWindow(window_id))
356    return;
357
358  if (tab_to_available_range_.find(tab_id.id()) !=
359      tab_to_available_range_.end()) {
360    std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
361    range.first = std::min(index, range.first);
362    range.second = std::max(index, range.second);
363  }
364  ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation,
365                                                   tab_id.id(), index, entry));
366}
367
368void SessionService::TabRestored(NavigationController* controller,
369                                 bool pinned) {
370  if (!ShouldTrackChangesToWindow(controller->window_id()))
371    return;
372
373  BuildCommandsForTab(controller->window_id(), controller, -1,
374                      pinned, &pending_commands(), NULL);
375  StartSaveTimer();
376}
377
378void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
379                                                const SessionID& tab_id,
380                                                int index) {
381  if (!ShouldTrackChangesToWindow(window_id))
382    return;
383
384  if (tab_to_available_range_.find(tab_id.id()) !=
385      tab_to_available_range_.end()) {
386    if (index < tab_to_available_range_[tab_id.id()].first ||
387        index > tab_to_available_range_[tab_id.id()].second) {
388      // The new index is outside the range of what we've archived, schedule
389      // a reset.
390      ResetFromCurrentBrowsers();
391      return;
392    }
393  }
394  ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index));
395}
396
397void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
398                                            int index) {
399  if (!ShouldTrackChangesToWindow(window_id))
400    return;
401
402  ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index));
403}
404
405SessionService::Handle SessionService::GetLastSession(
406    CancelableRequestConsumerBase* consumer,
407    SessionCallback* callback) {
408  return ScheduleGetLastSessionCommands(
409      new InternalSessionRequest(
410          NewCallback(this, &SessionService::OnGotSessionCommands),
411          callback), consumer);
412}
413
414SessionService::Handle SessionService::GetCurrentSession(
415    CancelableRequestConsumerBase* consumer,
416    SessionCallback* callback) {
417  if (pending_window_close_ids_.empty()) {
418    // If there are no pending window closes, we can get the current session
419    // from memory.
420    scoped_refptr<InternalSessionRequest> request(new InternalSessionRequest(
421        NewCallback(this, &SessionService::OnGotSessionCommands),
422        callback));
423    AddRequest(request, consumer);
424    IdToRange tab_to_available_range;
425    std::set<SessionID::id_type> windows_to_track;
426    BuildCommandsFromBrowsers(&(request->commands),
427                              &tab_to_available_range,
428                              &windows_to_track);
429    request->ForwardResult(
430        BaseSessionService::InternalGetCommandsRequest::TupleType(
431            request->handle(), request));
432    return request->handle();
433  } else {
434    // If there are pending window closes, read the current session from disk.
435    return ScheduleGetCurrentSessionCommands(
436        new InternalSessionRequest(
437            NewCallback(this, &SessionService::OnGotSessionCommands),
438            callback), consumer);
439  }
440}
441
442void SessionService::Save() {
443  bool had_commands = !pending_commands().empty();
444  BaseSessionService::Save();
445  if (had_commands) {
446    RecordSessionUpdateHistogramData(NotificationType::SESSION_SERVICE_SAVED,
447        &last_updated_save_time_);
448    NotificationService::current()->Notify(
449        NotificationType::SESSION_SERVICE_SAVED,
450        Source<Profile>(profile()),
451        NotificationService::NoDetails());
452  }
453}
454
455void SessionService::Init() {
456  // Register for the notifications we're interested in.
457  registrar_.Add(this, NotificationType::TAB_PARENTED,
458                 NotificationService::AllSources());
459  registrar_.Add(this, NotificationType::TAB_CLOSED,
460                 NotificationService::AllSources());
461  registrar_.Add(this, NotificationType::NAV_LIST_PRUNED,
462                 NotificationService::AllSources());
463  registrar_.Add(this, NotificationType::NAV_ENTRY_CHANGED,
464                 NotificationService::AllSources());
465  registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
466                 NotificationService::AllSources());
467  registrar_.Add(this, NotificationType::BROWSER_OPENED,
468                 NotificationService::AllSources());
469  registrar_.Add(this,
470                 NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
471                 NotificationService::AllSources());
472}
473
474bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
475                                        Browser* browser) {
476  if (!has_open_trackable_browsers_ && !BrowserInit::InProcessStartup() &&
477      !SessionRestore::IsRestoring()
478#if defined(OS_MACOSX)
479      // OSX has a fairly different idea of application lifetime than the
480      // other platforms. We need to check that we aren't opening a window
481      // from the dock or the menubar.
482      && !app_controller_mac::IsOpeningNewWindow()
483#endif
484      ) {
485    // We're going from no tabbed browsers to a tabbed browser (and not in
486    // process startup), restore the last session.
487    if (move_on_new_browser_) {
488      // Make the current session the last.
489      MoveCurrentSessionToLastSession();
490      move_on_new_browser_ = false;
491    }
492    SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile());
493    if (pref.type == SessionStartupPref::LAST) {
494      SessionRestore::RestoreSession(
495          profile(), browser, false, browser ? false : true, urls_to_open);
496      return true;
497    }
498  }
499  return false;
500}
501
502void SessionService::Observe(NotificationType type,
503                             const NotificationSource& source,
504                             const NotificationDetails& details) {
505  // All of our messages have the NavigationController as the source.
506  switch (type.value) {
507    case NotificationType::BROWSER_OPENED: {
508      Browser* browser = Source<Browser>(source).ptr();
509      if (browser->profile() != profile() ||
510          !should_track_changes_for_browser_type(browser->type())) {
511        return;
512      }
513
514      RestoreIfNecessary(std::vector<GURL>(), browser);
515      SetWindowType(browser->session_id(), browser->type());
516      break;
517    }
518
519    case NotificationType::TAB_PARENTED: {
520      NavigationController* controller =
521          Source<NavigationController>(source).ptr();
522      SetTabWindow(controller->window_id(), controller->session_id());
523      TabContentsWrapper* wrapper =
524          TabContentsWrapper::GetCurrentWrapperForContents(
525              controller->tab_contents());
526      if (wrapper->extension_tab_helper()->extension_app()) {
527        SetTabExtensionAppID(
528            controller->window_id(),
529            controller->session_id(),
530            wrapper->extension_tab_helper()->extension_app()->id());
531      }
532      break;
533    }
534
535    case NotificationType::TAB_CLOSED: {
536      NavigationController* controller =
537          Source<NavigationController>(source).ptr();
538      TabClosed(controller->window_id(), controller->session_id(),
539                controller->tab_contents()->closed_by_user_gesture());
540      RecordSessionUpdateHistogramData(NotificationType::TAB_CLOSED,
541          &last_updated_tab_closed_time_);
542      break;
543    }
544
545    case NotificationType::NAV_LIST_PRUNED: {
546      NavigationController* controller =
547          Source<NavigationController>(source).ptr();
548      Details<NavigationController::PrunedDetails> pruned_details(details);
549      if (pruned_details->from_front) {
550        TabNavigationPathPrunedFromFront(controller->window_id(),
551                                         controller->session_id(),
552                                         pruned_details->count);
553      } else {
554        TabNavigationPathPrunedFromBack(controller->window_id(),
555                                        controller->session_id(),
556                                        controller->entry_count());
557      }
558      RecordSessionUpdateHistogramData(NotificationType::NAV_LIST_PRUNED,
559          &last_updated_nav_list_pruned_time_);
560      break;
561    }
562
563    case NotificationType::NAV_ENTRY_CHANGED: {
564      NavigationController* controller =
565          Source<NavigationController>(source).ptr();
566      Details<NavigationController::EntryChangedDetails> changed(details);
567      UpdateTabNavigation(controller->window_id(), controller->session_id(),
568                          changed->index, *changed->changed_entry);
569      break;
570    }
571
572    case NotificationType::NAV_ENTRY_COMMITTED: {
573      NavigationController* controller =
574          Source<NavigationController>(source).ptr();
575      int current_entry_index = controller->GetCurrentEntryIndex();
576      SetSelectedNavigationIndex(controller->window_id(),
577                                 controller->session_id(),
578                                 current_entry_index);
579      UpdateTabNavigation(controller->window_id(), controller->session_id(),
580                          current_entry_index,
581                          *controller->GetEntryAtIndex(current_entry_index));
582      Details<NavigationController::LoadCommittedDetails> changed(details);
583      if (changed->type == NavigationType::NEW_PAGE ||
584        changed->type == NavigationType::EXISTING_PAGE) {
585        RecordSessionUpdateHistogramData(NotificationType::NAV_ENTRY_COMMITTED,
586            &last_updated_nav_entry_commit_time_);
587      }
588      break;
589    }
590
591    case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
592      ExtensionTabHelper* extension_tab_helper =
593          Source<ExtensionTabHelper>(source).ptr();
594      if (extension_tab_helper->extension_app()) {
595        SetTabExtensionAppID(
596            extension_tab_helper->tab_contents()->controller().window_id(),
597            extension_tab_helper->tab_contents()->controller().session_id(),
598            extension_tab_helper->extension_app()->id());
599      }
600      break;
601    }
602
603    default:
604      NOTREACHED();
605  }
606}
607
608void SessionService::SetTabExtensionAppID(
609    const SessionID& window_id,
610    const SessionID& tab_id,
611    const std::string& extension_app_id) {
612  if (!ShouldTrackChangesToWindow(window_id))
613    return;
614
615  ScheduleCommand(CreateSetTabExtensionAppIDCommand(
616                      kCommandSetExtensionAppID,
617                      tab_id.id(),
618                      extension_app_id));
619}
620
621SessionCommand* SessionService::CreateSetSelectedTabInWindow(
622    const SessionID& window_id,
623    int index) {
624  SelectedTabInIndexPayload payload = { 0 };
625  payload.id = window_id.id();
626  payload.index = index;
627  SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex,
628                                 sizeof(payload));
629  memcpy(command->contents(), &payload, sizeof(payload));
630  return command;
631}
632
633SessionCommand* SessionService::CreateSetTabWindowCommand(
634    const SessionID& window_id,
635    const SessionID& tab_id) {
636  SessionID::id_type payload[] = { window_id.id(), tab_id.id() };
637  SessionCommand* command =
638      new SessionCommand(kCommandSetTabWindow, sizeof(payload));
639  memcpy(command->contents(), payload, sizeof(payload));
640  return command;
641}
642
643SessionCommand* SessionService::CreateSetWindowBoundsCommand(
644    const SessionID& window_id,
645    const gfx::Rect& bounds,
646    bool is_maximized) {
647  WindowBoundsPayload2 payload = { 0 };
648  payload.window_id = window_id.id();
649  payload.x = bounds.x();
650  payload.y = bounds.y();
651  payload.w = bounds.width();
652  payload.h = bounds.height();
653  payload.is_maximized = is_maximized;
654  SessionCommand* command = new SessionCommand(kCommandSetWindowBounds2,
655                                               sizeof(payload));
656  memcpy(command->contents(), &payload, sizeof(payload));
657  return command;
658}
659
660SessionCommand* SessionService::CreateSetTabIndexInWindowCommand(
661    const SessionID& tab_id,
662    int new_index) {
663  TabIndexInWindowPayload payload = { 0 };
664  payload.id = tab_id.id();
665  payload.index = new_index;
666  SessionCommand* command =
667      new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload));
668  memcpy(command->contents(), &payload, sizeof(payload));
669  return command;
670}
671
672SessionCommand* SessionService::CreateTabClosedCommand(
673    const SessionID::id_type tab_id) {
674  ClosedPayload payload;
675  // Because of what appears to be a compiler bug setting payload to {0} doesn't
676  // set the padding to 0, resulting in Purify reporting an UMR when we write
677  // the structure to disk. To avoid this we explicitly memset the struct.
678  memset(&payload, 0, sizeof(payload));
679  payload.id = tab_id;
680  payload.close_time = Time::Now().ToInternalValue();
681  SessionCommand* command =
682      new SessionCommand(kCommandTabClosed, sizeof(payload));
683  memcpy(command->contents(), &payload, sizeof(payload));
684  return command;
685}
686
687SessionCommand* SessionService::CreateWindowClosedCommand(
688    const SessionID::id_type window_id) {
689  ClosedPayload payload;
690  // See comment in CreateTabClosedCommand as to why we do this.
691  memset(&payload, 0, sizeof(payload));
692  payload.id = window_id;
693  payload.close_time = Time::Now().ToInternalValue();
694  SessionCommand* command =
695      new SessionCommand(kCommandWindowClosed, sizeof(payload));
696  memcpy(command->contents(), &payload, sizeof(payload));
697  return command;
698}
699
700SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand(
701    const SessionID& tab_id,
702    int index) {
703  SelectedNavigationIndexPayload payload = { 0 };
704  payload.id = tab_id.id();
705  payload.index = index;
706  SessionCommand* command = new SessionCommand(
707      kCommandSetSelectedNavigationIndex, sizeof(payload));
708  memcpy(command->contents(), &payload, sizeof(payload));
709  return command;
710}
711
712SessionCommand* SessionService::CreateSetWindowTypeCommand(
713    const SessionID& window_id,
714    WindowType type) {
715  WindowTypePayload payload = { 0 };
716  payload.id = window_id.id();
717  payload.index = static_cast<int32>(type);
718  SessionCommand* command = new SessionCommand(
719      kCommandSetWindowType, sizeof(payload));
720  memcpy(command->contents(), &payload, sizeof(payload));
721  return command;
722}
723
724SessionCommand* SessionService::CreatePinnedStateCommand(
725    const SessionID& tab_id,
726    bool is_pinned) {
727  PinnedStatePayload payload = { 0 };
728  payload.tab_id = tab_id.id();
729  payload.pinned_state = is_pinned;
730  SessionCommand* command =
731      new SessionCommand(kCommandSetPinnedState, sizeof(payload));
732  memcpy(command->contents(), &payload, sizeof(payload));
733  return command;
734}
735
736void SessionService::OnGotSessionCommands(
737    Handle handle,
738    scoped_refptr<InternalGetCommandsRequest> request) {
739  if (request->canceled())
740    return;
741  ScopedVector<SessionWindow> valid_windows;
742  RestoreSessionFromCommands(
743      request->commands, &(valid_windows.get()));
744  static_cast<InternalSessionRequest*>(request.get())->
745      real_callback->RunWithParams(
746          SessionCallback::TupleType(request->handle(),
747                                     &(valid_windows.get())));
748}
749
750void SessionService::RestoreSessionFromCommands(
751    const std::vector<SessionCommand*>& commands,
752    std::vector<SessionWindow*>* valid_windows) {
753  std::map<int, SessionTab*> tabs;
754  std::map<int, SessionWindow*> windows;
755
756  if (CreateTabsAndWindows(commands, &tabs, &windows)) {
757    AddTabsToWindows(&tabs, &windows);
758    SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows);
759    UpdateSelectedTabIndex(valid_windows);
760  }
761  STLDeleteValues(&tabs);
762  // Don't delete conents of windows, that is done by the caller as all
763  // valid windows are added to valid_windows.
764}
765
766void SessionService::UpdateSelectedTabIndex(
767    std::vector<SessionWindow*>* windows) {
768  for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
769       i != windows->end(); ++i) {
770    // See note in SessionWindow as to why we do this.
771    int new_index = 0;
772    for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin();
773         j != (*i)->tabs.end(); ++j) {
774      if ((*j)->tab_visual_index == (*i)->selected_tab_index) {
775        new_index = static_cast<int>(j - (*i)->tabs.begin());
776        break;
777      }
778    }
779    (*i)->selected_tab_index = new_index;
780  }
781}
782
783SessionWindow* SessionService::GetWindow(
784    SessionID::id_type window_id,
785    IdToSessionWindow* windows) {
786  std::map<int, SessionWindow*>::iterator i = windows->find(window_id);
787  if (i == windows->end()) {
788    SessionWindow* window = new SessionWindow();
789    window->window_id.set_id(window_id);
790    (*windows)[window_id] = window;
791    return window;
792  }
793  return i->second;
794}
795
796SessionTab* SessionService::GetTab(
797    SessionID::id_type tab_id,
798    IdToSessionTab* tabs) {
799  DCHECK(tabs);
800  std::map<int, SessionTab*>::iterator i = tabs->find(tab_id);
801  if (i == tabs->end()) {
802    SessionTab* tab = new SessionTab();
803    tab->tab_id.set_id(tab_id);
804    (*tabs)[tab_id] = tab;
805    return tab;
806  }
807  return i->second;
808}
809
810std::vector<TabNavigation>::iterator
811  SessionService::FindClosestNavigationWithIndex(
812    std::vector<TabNavigation>* navigations,
813    int index) {
814  DCHECK(navigations);
815  for (std::vector<TabNavigation>::iterator i = navigations->begin();
816       i != navigations->end(); ++i) {
817    if (i->index() >= index)
818      return i;
819  }
820  return navigations->end();
821}
822
823// Function used in sorting windows. Sorting is done based on window id. As
824// window ids increment for each new window, this effectively sorts by creation
825// time.
826static bool WindowOrderSortFunction(const SessionWindow* w1,
827                                    const SessionWindow* w2) {
828  return w1->window_id.id() < w2->window_id.id();
829}
830
831// Compares the two tabs based on visual index.
832static bool TabVisualIndexSortFunction(const SessionTab* t1,
833                                       const SessionTab* t2) {
834  const int delta = t1->tab_visual_index - t2->tab_visual_index;
835  return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0);
836}
837
838void SessionService::SortTabsBasedOnVisualOrderAndPrune(
839    std::map<int, SessionWindow*>* windows,
840    std::vector<SessionWindow*>* valid_windows) {
841  std::map<int, SessionWindow*>::iterator i = windows->begin();
842  while (i != windows->end()) {
843    if (i->second->tabs.empty() || i->second->is_constrained ||
844        !should_track_changes_for_browser_type(
845            static_cast<Browser::Type>(i->second->type))) {
846      delete i->second;
847      windows->erase(i++);
848    } else {
849      // Valid window; sort the tabs and add it to the list of valid windows.
850      std::sort(i->second->tabs.begin(), i->second->tabs.end(),
851                &TabVisualIndexSortFunction);
852      // Add the window such that older windows appear first.
853      if (valid_windows->empty()) {
854        valid_windows->push_back(i->second);
855      } else {
856        valid_windows->insert(
857            std::upper_bound(valid_windows->begin(), valid_windows->end(),
858                             i->second, &WindowOrderSortFunction),
859            i->second);
860      }
861      ++i;
862    }
863  }
864}
865
866void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs,
867                                      std::map<int, SessionWindow*>* windows) {
868  std::map<int, SessionTab*>::iterator i = tabs->begin();
869  while (i != tabs->end()) {
870    SessionTab* tab = i->second;
871    if (tab->window_id.id() && !tab->navigations.empty()) {
872      SessionWindow* window = GetWindow(tab->window_id.id(), windows);
873      window->tabs.push_back(tab);
874      tabs->erase(i++);
875
876      // See note in SessionTab as to why we do this.
877      std::vector<TabNavigation>::iterator j =
878          FindClosestNavigationWithIndex(&(tab->navigations),
879                                         tab->current_navigation_index);
880      if (j == tab->navigations.end()) {
881        tab->current_navigation_index =
882            static_cast<int>(tab->navigations.size() - 1);
883      } else {
884        tab->current_navigation_index =
885            static_cast<int>(j - tab->navigations.begin());
886      }
887    } else {
888      // Never got a set tab index in window, or tabs are empty, nothing
889      // to do.
890      ++i;
891    }
892  }
893}
894
895bool SessionService::CreateTabsAndWindows(
896    const std::vector<SessionCommand*>& data,
897    std::map<int, SessionTab*>* tabs,
898    std::map<int, SessionWindow*>* windows) {
899  // If the file is corrupt (command with wrong size, or unknown command), we
900  // still return true and attempt to restore what we we can.
901
902  for (std::vector<SessionCommand*>::const_iterator i = data.begin();
903       i != data.end(); ++i) {
904    const SessionCommand* command = *i;
905
906    switch (command->id()) {
907      case kCommandSetTabWindow: {
908        SessionID::id_type payload[2];
909        if (!command->GetPayload(payload, sizeof(payload)))
910          return true;
911        GetTab(payload[1], tabs)->window_id.set_id(payload[0]);
912        break;
913      }
914
915      case kCommandSetWindowBounds2: {
916        WindowBoundsPayload2 payload;
917        if (!command->GetPayload(&payload, sizeof(payload)))
918          return true;
919        GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
920                                                              payload.y,
921                                                              payload.w,
922                                                              payload.h);
923        GetWindow(payload.window_id, windows)->is_maximized =
924            payload.is_maximized;
925        break;
926      }
927
928      case kCommandSetTabIndexInWindow: {
929        TabIndexInWindowPayload payload;
930        if (!command->GetPayload(&payload, sizeof(payload)))
931          return true;
932        GetTab(payload.id, tabs)->tab_visual_index = payload.index;
933        break;
934      }
935
936      case kCommandTabClosed:
937      case kCommandWindowClosed: {
938        ClosedPayload payload;
939        if (!command->GetPayload(&payload, sizeof(payload)))
940          return true;
941        if (command->id() == kCommandTabClosed) {
942          delete GetTab(payload.id, tabs);
943          tabs->erase(payload.id);
944        } else {
945          delete GetWindow(payload.id, windows);
946          windows->erase(payload.id);
947        }
948        break;
949      }
950
951      case kCommandTabNavigationPathPrunedFromBack: {
952        TabNavigationPathPrunedFromBackPayload payload;
953        if (!command->GetPayload(&payload, sizeof(payload)))
954          return true;
955        SessionTab* tab = GetTab(payload.id, tabs);
956        tab->navigations.erase(
957            FindClosestNavigationWithIndex(&(tab->navigations), payload.index),
958            tab->navigations.end());
959        break;
960      }
961
962      case kCommandTabNavigationPathPrunedFromFront: {
963        TabNavigationPathPrunedFromFrontPayload payload;
964        if (!command->GetPayload(&payload, sizeof(payload)) ||
965            payload.index <= 0) {
966          return true;
967        }
968        SessionTab* tab = GetTab(payload.id, tabs);
969
970        // Update the selected navigation index.
971        tab->current_navigation_index =
972            std::max(-1, tab->current_navigation_index - payload.index);
973
974        // And update the index of existing navigations.
975        for (std::vector<TabNavigation>::iterator i = tab->navigations.begin();
976             i != tab->navigations.end();) {
977          i->set_index(i->index() - payload.index);
978          if (i->index() < 0)
979            i = tab->navigations.erase(i);
980          else
981            ++i;
982        }
983        break;
984      }
985
986      case kCommandUpdateTabNavigation: {
987        TabNavigation navigation;
988        SessionID::id_type tab_id;
989        if (!RestoreUpdateTabNavigationCommand(*command, &navigation, &tab_id))
990          return true;
991
992        SessionTab* tab = GetTab(tab_id, tabs);
993        std::vector<TabNavigation>::iterator i =
994            FindClosestNavigationWithIndex(&(tab->navigations),
995                                           navigation.index());
996        if (i != tab->navigations.end() && i->index() == navigation.index())
997          *i = navigation;
998        else
999          tab->navigations.insert(i, navigation);
1000        break;
1001      }
1002
1003      case kCommandSetSelectedNavigationIndex: {
1004        SelectedNavigationIndexPayload payload;
1005        if (!command->GetPayload(&payload, sizeof(payload)))
1006          return true;
1007        GetTab(payload.id, tabs)->current_navigation_index = payload.index;
1008        break;
1009      }
1010
1011      case kCommandSetSelectedTabInIndex: {
1012        SelectedTabInIndexPayload payload;
1013        if (!command->GetPayload(&payload, sizeof(payload)))
1014          return true;
1015        GetWindow(payload.id, windows)->selected_tab_index = payload.index;
1016        break;
1017      }
1018
1019      case kCommandSetWindowType: {
1020        WindowTypePayload payload;
1021        if (!command->GetPayload(&payload, sizeof(payload)))
1022          return true;
1023        GetWindow(payload.id, windows)->is_constrained = false;
1024        GetWindow(payload.id, windows)->type =
1025            BrowserTypeForWindowType(
1026                static_cast<WindowType>(payload.index));
1027        break;
1028      }
1029
1030      case kCommandSetPinnedState: {
1031        PinnedStatePayload payload;
1032        if (!command->GetPayload(&payload, sizeof(payload)))
1033          return true;
1034        GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state;
1035        break;
1036      }
1037
1038      case kCommandSetExtensionAppID: {
1039        SessionID::id_type tab_id;
1040        std::string extension_app_id;
1041        if (!RestoreSetTabExtensionAppIDCommand(
1042                *command, &tab_id, &extension_app_id)) {
1043          return true;
1044        }
1045
1046        GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id);
1047        break;
1048      }
1049
1050      default:
1051        return true;
1052    }
1053  }
1054  return true;
1055}
1056
1057void SessionService::BuildCommandsForTab(
1058    const SessionID& window_id,
1059    NavigationController* controller,
1060    int index_in_window,
1061    bool is_pinned,
1062    std::vector<SessionCommand*>* commands,
1063    IdToRange* tab_to_available_range) {
1064  DCHECK(controller && commands && window_id.id());
1065  commands->push_back(
1066      CreateSetTabWindowCommand(window_id, controller->session_id()));
1067  const int current_index = controller->GetCurrentEntryIndex();
1068  const int min_index = std::max(0,
1069                                 current_index - max_persist_navigation_count);
1070  const int max_index = std::min(current_index + max_persist_navigation_count,
1071                                 controller->entry_count());
1072  const int pending_index = controller->pending_entry_index();
1073  if (tab_to_available_range) {
1074    (*tab_to_available_range)[controller->session_id().id()] =
1075        std::pair<int, int>(min_index, max_index);
1076  }
1077  if (is_pinned) {
1078    commands->push_back(
1079        CreatePinnedStateCommand(controller->session_id(), true));
1080  }
1081  TabContentsWrapper* wrapper =
1082      TabContentsWrapper::GetCurrentWrapperForContents(
1083          controller->tab_contents());
1084  if (wrapper->extension_tab_helper()->extension_app()) {
1085    commands->push_back(
1086        CreateSetTabExtensionAppIDCommand(
1087            kCommandSetExtensionAppID,
1088            controller->session_id().id(),
1089            wrapper->extension_tab_helper()->extension_app()->id()));
1090  }
1091  for (int i = min_index; i < max_index; ++i) {
1092    const NavigationEntry* entry = (i == pending_index) ?
1093        controller->pending_entry() : controller->GetEntryAtIndex(i);
1094    DCHECK(entry);
1095    if (ShouldTrackEntry(*entry)) {
1096      commands->push_back(
1097          CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation,
1098                                           controller->session_id().id(),
1099                                           i,
1100                                           *entry));
1101    }
1102  }
1103  commands->push_back(
1104      CreateSetSelectedNavigationIndexCommand(controller->session_id(),
1105                                              current_index));
1106
1107  if (index_in_window != -1) {
1108    commands->push_back(
1109        CreateSetTabIndexInWindowCommand(controller->session_id(),
1110                                         index_in_window));
1111  }
1112}
1113
1114void SessionService::BuildCommandsForBrowser(
1115    Browser* browser,
1116    std::vector<SessionCommand*>* commands,
1117    IdToRange* tab_to_available_range,
1118    std::set<SessionID::id_type>* windows_to_track) {
1119  DCHECK(browser && commands);
1120  DCHECK(browser->session_id().id());
1121
1122  commands->push_back(
1123      CreateSetWindowBoundsCommand(browser->session_id(),
1124                                   browser->window()->GetRestoredBounds(),
1125                                   browser->window()->IsMaximized()));
1126
1127  commands->push_back(CreateSetWindowTypeCommand(
1128      browser->session_id(), WindowTypeForBrowserType(browser->type())));
1129
1130  bool added_to_windows_to_track = false;
1131  for (int i = 0; i < browser->tab_count(); ++i) {
1132    TabContents* tab = browser->GetTabContentsAt(i);
1133    DCHECK(tab);
1134    if (tab->profile() == profile() || profile() == NULL) {
1135      BuildCommandsForTab(browser->session_id(), &tab->controller(), i,
1136                          browser->tabstrip_model()->IsTabPinned(i),
1137                          commands, tab_to_available_range);
1138      if (windows_to_track && !added_to_windows_to_track) {
1139        windows_to_track->insert(browser->session_id().id());
1140        added_to_windows_to_track = true;
1141      }
1142    }
1143  }
1144  commands->push_back(
1145      CreateSetSelectedTabInWindow(browser->session_id(),
1146                                   browser->active_index()));
1147}
1148
1149void SessionService::BuildCommandsFromBrowsers(
1150    std::vector<SessionCommand*>* commands,
1151    IdToRange* tab_to_available_range,
1152    std::set<SessionID::id_type>* windows_to_track) {
1153  DCHECK(commands);
1154  for (BrowserList::const_iterator i = BrowserList::begin();
1155       i != BrowserList::end(); ++i) {
1156    // Make sure the browser has tabs and a window. Browsers destructor
1157    // removes itself from the BrowserList. When a browser is closed the
1158    // destructor is not necessarily run immediately. This means its possible
1159    // for us to get a handle to a browser that is about to be removed. If
1160    // the tab count is 0 or the window is NULL, the browser is about to be
1161    // deleted, so we ignore it.
1162    if (should_track_changes_for_browser_type((*i)->type()) &&
1163        (*i)->tab_count() && (*i)->window()) {
1164      BuildCommandsForBrowser(*i, commands, tab_to_available_range,
1165                              windows_to_track);
1166    }
1167  }
1168}
1169
1170void SessionService::ScheduleReset() {
1171  set_pending_reset(true);
1172  STLDeleteElements(&pending_commands());
1173  tab_to_available_range_.clear();
1174  windows_tracking_.clear();
1175  BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_,
1176                            &windows_tracking_);
1177  if (!windows_tracking_.empty()) {
1178    // We're lazily created on startup and won't get an initial batch of
1179    // SetWindowType messages. Set these here to make sure our state is correct.
1180    has_open_trackable_browsers_ = true;
1181    move_on_new_browser_ = true;
1182  }
1183  StartSaveTimer();
1184}
1185
1186bool SessionService::ReplacePendingCommand(SessionCommand* command) {
1187  // We only optimize page navigations, which can happen quite frequently and
1188  // are expensive. If necessary, other commands could be searched for as
1189  // well.
1190  if (command->id() != kCommandUpdateTabNavigation)
1191    return false;
1192  void* iterator = NULL;
1193  scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1194  SessionID::id_type command_tab_id;
1195  int command_nav_index;
1196  if (!command_pickle->ReadInt(&iterator, &command_tab_id) ||
1197      !command_pickle->ReadInt(&iterator, &command_nav_index)) {
1198    return false;
1199  }
1200  for (std::vector<SessionCommand*>::reverse_iterator i =
1201       pending_commands().rbegin(); i != pending_commands().rend(); ++i) {
1202    SessionCommand* existing_command = *i;
1203    if (existing_command->id() == kCommandUpdateTabNavigation) {
1204      SessionID::id_type existing_tab_id;
1205      int existing_nav_index;
1206      {
1207        // Creating a pickle like this means the Pickle references the data from
1208        // the command. Make sure we delete the pickle before the command, else
1209        // the pickle references deleted memory.
1210        scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle());
1211        iterator = NULL;
1212        if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) ||
1213            !existing_pickle->ReadInt(&iterator, &existing_nav_index)) {
1214          return false;
1215        }
1216      }
1217      if (existing_tab_id == command_tab_id &&
1218          existing_nav_index == command_nav_index) {
1219        // existing_command is an update for the same tab/index pair. Replace
1220        // it with the new one. We need to add to the end of the list just in
1221        // case there is a prune command after the update command.
1222        delete existing_command;
1223        pending_commands().erase(i.base() - 1);
1224        pending_commands().push_back(command);
1225        return true;
1226      }
1227      return false;
1228    }
1229  }
1230  return false;
1231}
1232
1233void SessionService::ScheduleCommand(SessionCommand* command) {
1234  DCHECK(command);
1235  if (ReplacePendingCommand(command))
1236    return;
1237  BaseSessionService::ScheduleCommand(command);
1238  // Don't schedule a reset on tab closed/window closed. Otherwise we may
1239  // lose tabs/windows we want to restore from if we exit right after this.
1240  if (!pending_reset() && pending_window_close_ids_.empty() &&
1241      commands_since_reset() >= kWritesPerReset &&
1242      (command->id() != kCommandTabClosed &&
1243       command->id() != kCommandWindowClosed)) {
1244    ScheduleReset();
1245  }
1246}
1247
1248void SessionService::CommitPendingCloses() {
1249  for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
1250       i != pending_tab_close_ids_.end(); ++i) {
1251    ScheduleCommand(CreateTabClosedCommand(*i));
1252  }
1253  pending_tab_close_ids_.clear();
1254
1255  for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
1256       i != pending_window_close_ids_.end(); ++i) {
1257    ScheduleCommand(CreateWindowClosedCommand(*i));
1258  }
1259  pending_window_close_ids_.clear();
1260}
1261
1262bool SessionService::IsOnlyOneTabLeft() {
1263  if (!profile()) {
1264    // We're testing, always return false.
1265    return false;
1266  }
1267
1268  int window_count = 0;
1269  for (BrowserList::const_iterator i = BrowserList::begin();
1270       i != BrowserList::end(); ++i) {
1271    const SessionID::id_type window_id = (*i)->session_id().id();
1272    if (should_track_changes_for_browser_type((*i)->type()) &&
1273        (*i)->profile() == profile() &&
1274        window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
1275      if (++window_count > 1)
1276        return false;
1277      // By the time this is invoked the tab has been removed. As such, we use
1278      // > 0 here rather than > 1.
1279      if ((*i)->tab_count() > 0)
1280        return false;
1281    }
1282  }
1283  return true;
1284}
1285
1286bool SessionService::HasOpenTrackableBrowsers(const SessionID& window_id) {
1287  if (!profile()) {
1288    // We're testing, always return false.
1289    return true;
1290  }
1291
1292  for (BrowserList::const_iterator i = BrowserList::begin();
1293       i != BrowserList::end(); ++i) {
1294    Browser* browser = *i;
1295    const SessionID::id_type browser_id = browser->session_id().id();
1296    if (browser_id != window_id.id() &&
1297        window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
1298        should_track_changes_for_browser_type(browser->type()) &&
1299        browser->profile() == profile()) {
1300      return true;
1301    }
1302  }
1303  return false;
1304}
1305
1306bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) {
1307  return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
1308}
1309
1310
1311SessionService::WindowType SessionService::WindowTypeForBrowserType(
1312    Browser::Type type) {
1313  // We don't support masks here, only discrete types.
1314  switch (type) {
1315    case Browser::TYPE_POPUP:
1316      return TYPE_POPUP;
1317    case Browser::TYPE_APP:
1318      return TYPE_APP;
1319    case Browser::TYPE_APP_POPUP:
1320      return TYPE_APP_POPUP;
1321    case Browser::TYPE_DEVTOOLS:
1322      return TYPE_DEVTOOLS;
1323    case Browser::TYPE_APP_PANEL:
1324      return TYPE_APP_PANEL;
1325    case Browser::TYPE_NORMAL:
1326    default:
1327      return TYPE_NORMAL;
1328  }
1329}
1330
1331Browser::Type SessionService::BrowserTypeForWindowType(
1332    SessionService::WindowType type) {
1333  switch (type) {
1334    case TYPE_POPUP:
1335      return Browser::TYPE_POPUP;
1336    case TYPE_APP:
1337      return Browser::TYPE_APP;
1338    case TYPE_APP_POPUP:
1339      return Browser::TYPE_APP_POPUP;
1340    case TYPE_DEVTOOLS:
1341      return Browser::TYPE_DEVTOOLS;
1342    case TYPE_APP_PANEL:
1343      return Browser::TYPE_APP_PANEL;
1344    case TYPE_NORMAL:
1345    default:
1346      return Browser::TYPE_NORMAL;
1347  }
1348}
1349
1350void SessionService::RecordSessionUpdateHistogramData(NotificationType type,
1351    base::TimeTicks* last_updated_time) {
1352  if (!last_updated_time->is_null()) {
1353    base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
1354    // We're interested in frequent updates periods longer than
1355    // 10 minutes.
1356    bool use_long_period = false;
1357    if (delta >= save_delay_in_mins_) {
1358      use_long_period = true;
1359    }
1360    switch (type.value) {
1361      case NotificationType::SESSION_SERVICE_SAVED :
1362        RecordUpdatedSaveTime(delta, use_long_period);
1363        RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1364        break;
1365      case NotificationType::TAB_CLOSED:
1366        RecordUpdatedTabClosed(delta, use_long_period);
1367        RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1368        break;
1369      case NotificationType::NAV_LIST_PRUNED:
1370        RecordUpdatedNavListPruned(delta, use_long_period);
1371        RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1372        break;
1373      case NotificationType::NAV_ENTRY_COMMITTED:
1374        RecordUpdatedNavEntryCommit(delta, use_long_period);
1375        RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1376        break;
1377      default:
1378        NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1379        break;
1380    }
1381  }
1382  (*last_updated_time) = base::TimeTicks::Now();
1383}
1384
1385void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
1386                                            bool use_long_period) {
1387  std::string name("SessionRestore.TabClosedPeriod");
1388  UMA_HISTOGRAM_CUSTOM_TIMES(name,
1389      delta,
1390      // 2500ms is the default save delay.
1391      save_delay_in_millis_,
1392      save_delay_in_mins_,
1393      50);
1394  if (use_long_period) {
1395    std::string long_name_("SessionRestore.TabClosedLongPeriod");
1396    UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1397        delta,
1398        save_delay_in_mins_,
1399        save_delay_in_hrs_,
1400        50);
1401  }
1402}
1403
1404void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1405                                                bool use_long_period) {
1406  std::string name("SessionRestore.NavigationListPrunedPeriod");
1407  UMA_HISTOGRAM_CUSTOM_TIMES(name,
1408      delta,
1409      // 2500ms is the default save delay.
1410      save_delay_in_millis_,
1411      save_delay_in_mins_,
1412      50);
1413  if (use_long_period) {
1414    std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1415    UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1416        delta,
1417        save_delay_in_mins_,
1418        save_delay_in_hrs_,
1419        50);
1420  }
1421}
1422
1423void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1424                                                 bool use_long_period) {
1425  std::string name("SessionRestore.NavEntryCommittedPeriod");
1426  UMA_HISTOGRAM_CUSTOM_TIMES(name,
1427      delta,
1428      // 2500ms is the default save delay.
1429      save_delay_in_millis_,
1430      save_delay_in_mins_,
1431      50);
1432  if (use_long_period) {
1433    std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1434    UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1435        delta,
1436        save_delay_in_mins_,
1437        save_delay_in_hrs_,
1438        50);
1439  }
1440}
1441
1442void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1443                                                         bool use_long_period) {
1444  std::string name("SessionRestore.NavOrTabUpdatePeriod");
1445  UMA_HISTOGRAM_CUSTOM_TIMES(name,
1446      delta,
1447      // 2500ms is the default save delay.
1448      save_delay_in_millis_,
1449      save_delay_in_mins_,
1450      50);
1451  if (use_long_period) {
1452    std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1453    UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1454        delta,
1455        save_delay_in_mins_,
1456        save_delay_in_hrs_,
1457        50);
1458  }
1459}
1460
1461void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1462                                           bool use_long_period) {
1463  std::string name("SessionRestore.SavePeriod");
1464  UMA_HISTOGRAM_CUSTOM_TIMES(name,
1465      delta,
1466      // 2500ms is the default save delay.
1467      save_delay_in_millis_,
1468      save_delay_in_mins_,
1469      50);
1470  if (use_long_period) {
1471    std::string long_name_("SessionRestore.SaveLongPeriod");
1472    UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1473        delta,
1474        save_delay_in_mins_,
1475        save_delay_in_hrs_,
1476        50);
1477  }
1478}
1479