Home | History | Annotate | Download | only in sessions
      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 
     41 using base::Time;
     42 
     43 // Identifier for commands written to file.
     44 static 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;
     48 static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2;
     49 static const SessionCommand::id_type kCommandTabClosed = 3;
     50 static const SessionCommand::id_type kCommandWindowClosed = 4;
     51 static const SessionCommand::id_type
     52     kCommandTabNavigationPathPrunedFromBack = 5;
     53 static const SessionCommand::id_type kCommandUpdateTabNavigation = 6;
     54 static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7;
     55 static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8;
     56 static const SessionCommand::id_type kCommandSetWindowType = 9;
     57 static const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
     58 static const SessionCommand::id_type
     59     kCommandTabNavigationPathPrunedFromFront = 11;
     60 static const SessionCommand::id_type kCommandSetPinnedState = 12;
     61 static const SessionCommand::id_type kCommandSetExtensionAppID = 13;
     62 
     63 // Every kWritesPerReset commands triggers recreating the file.
     64 static const int kWritesPerReset = 250;
     65 
     66 namespace {
     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.
     72 class 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.
     92 struct ClosedPayload {
     93   SessionID::id_type id;
     94   int64 close_time;
     95 };
     96 
     97 struct 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 
    106 struct IDAndIndexPayload {
    107   SessionID::id_type id;
    108   int32 index;
    109 };
    110 
    111 typedef IDAndIndexPayload TabIndexInWindowPayload;
    112 
    113 typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload;
    114 
    115 typedef IDAndIndexPayload SelectedNavigationIndexPayload;
    116 
    117 typedef IDAndIndexPayload SelectedTabInIndexPayload;
    118 
    119 typedef IDAndIndexPayload WindowTypePayload;
    120 
    121 typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload;
    122 
    123 struct PinnedStatePayload {
    124   SessionID::id_type tab_id;
    125   bool pinned_state;
    126 };
    127 
    128 }  // namespace
    129 
    130 // SessionService -------------------------------------------------------------
    131 
    132 SessionService::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 
    142 SessionService::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 
    152 SessionService::~SessionService() {
    153   Save();
    154 }
    155 
    156 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
    157   return RestoreIfNecessary(urls_to_open, NULL);
    158 }
    159 
    160 void SessionService::ResetFromCurrentBrowsers() {
    161   ScheduleReset();
    162 }
    163 
    164 void 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 
    179 void 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 
    187 void 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 
    197 void 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 
    206 void 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 
    215 void 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 
    252 void 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 
    272 void 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 
    292 void 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 
    310 void 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 
    326 void 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 
    351 void 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 
    368 void 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 
    378 void 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 
    397 void 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 
    405 SessionService::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 
    414 SessionService::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 
    442 void 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 
    455 void 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 
    474 bool 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 
    502 void 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 
    608 void 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 
    621 SessionCommand* 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 
    633 SessionCommand* 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 
    643 SessionCommand* 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 
    660 SessionCommand* 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 
    672 SessionCommand* 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 
    687 SessionCommand* 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 
    700 SessionCommand* 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 
    712 SessionCommand* 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 
    724 SessionCommand* 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 
    736 void 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 
    750 void 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 
    766 void 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 
    783 SessionWindow* 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 
    796 SessionTab* 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 
    810 std::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.
    826 static 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.
    832 static 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 
    838 void 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 
    866 void 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 
    895 bool 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 
   1057 void 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 
   1114 void 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 
   1149 void 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 
   1170 void 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 
   1186 bool 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 
   1233 void 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 
   1248 void 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 
   1262 bool 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 
   1286 bool 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 
   1306 bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) {
   1307   return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
   1308 }
   1309 
   1310 
   1311 SessionService::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 
   1331 Browser::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 
   1350 void 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 
   1385 void 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 
   1404 void 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 
   1423 void 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 
   1442 void 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 
   1461 void 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