• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
InternalSessionRequest(CallbackType * callback,SessionService::SessionCallback * real_callback)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:
~InternalSessionRequest()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 
SessionService(Profile * profile)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 
SessionService(const FilePath & save_path)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 
~SessionService()152 SessionService::~SessionService() {
153   Save();
154 }
155 
RestoreIfNecessary(const std::vector<GURL> & urls_to_open)156 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
157   return RestoreIfNecessary(urls_to_open, NULL);
158 }
159 
ResetFromCurrentBrowsers()160 void SessionService::ResetFromCurrentBrowsers() {
161   ScheduleReset();
162 }
163 
MoveCurrentSessionToLastSession()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 
SetTabWindow(const SessionID & window_id,const SessionID & tab_id)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 
SetWindowBounds(const SessionID & window_id,const gfx::Rect & bounds,bool is_maximized)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 
SetTabIndexInWindow(const SessionID & window_id,const SessionID & tab_id,int new_index)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 
SetPinnedState(const SessionID & window_id,const SessionID & tab_id,bool is_pinned)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 
TabClosed(const SessionID & window_id,const SessionID & tab_id,bool closed_by_user_gesture)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 
WindowClosing(const SessionID & window_id)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 
WindowClosed(const SessionID & window_id)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 
SetWindowType(const SessionID & window_id,Browser::Type type)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 
TabNavigationPathPrunedFromBack(const SessionID & window_id,const SessionID & tab_id,int count)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 
TabNavigationPathPrunedFromFront(const SessionID & window_id,const SessionID & tab_id,int count)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 
UpdateTabNavigation(const SessionID & window_id,const SessionID & tab_id,int index,const NavigationEntry & entry)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 
TabRestored(NavigationController * controller,bool pinned)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 
SetSelectedNavigationIndex(const SessionID & window_id,const SessionID & tab_id,int index)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 
SetSelectedTabInWindow(const SessionID & window_id,int index)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 
GetLastSession(CancelableRequestConsumerBase * consumer,SessionCallback * callback)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 
GetCurrentSession(CancelableRequestConsumerBase * consumer,SessionCallback * callback)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 
Save()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 
Init()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 
RestoreIfNecessary(const std::vector<GURL> & urls_to_open,Browser * browser)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
SetTabExtensionAppID(const SessionID & window_id,const SessionID & tab_id,const std::string & extension_app_id)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 
CreateSetSelectedTabInWindow(const SessionID & window_id,int index)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 
CreateSetTabWindowCommand(const SessionID & window_id,const SessionID & tab_id)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 
CreateSetWindowBoundsCommand(const SessionID & window_id,const gfx::Rect & bounds,bool is_maximized)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 
CreateSetTabIndexInWindowCommand(const SessionID & tab_id,int new_index)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 
CreateTabClosedCommand(const SessionID::id_type tab_id)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 
CreateWindowClosedCommand(const SessionID::id_type window_id)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 
CreateSetSelectedNavigationIndexCommand(const SessionID & tab_id,int index)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 
CreateSetWindowTypeCommand(const SessionID & window_id,WindowType type)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 
CreatePinnedStateCommand(const SessionID & tab_id,bool is_pinned)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 
OnGotSessionCommands(Handle handle,scoped_refptr<InternalGetCommandsRequest> request)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 
RestoreSessionFromCommands(const std::vector<SessionCommand * > & commands,std::vector<SessionWindow * > * valid_windows)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 
UpdateSelectedTabIndex(std::vector<SessionWindow * > * windows)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 
GetWindow(SessionID::id_type window_id,IdToSessionWindow * windows)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 
GetTab(SessionID::id_type tab_id,IdToSessionTab * tabs)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
FindClosestNavigationWithIndex(std::vector<TabNavigation> * navigations,int index)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.
WindowOrderSortFunction(const SessionWindow * w1,const SessionWindow * w2)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.
TabVisualIndexSortFunction(const SessionTab * t1,const SessionTab * t2)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 
SortTabsBasedOnVisualOrderAndPrune(std::map<int,SessionWindow * > * windows,std::vector<SessionWindow * > * valid_windows)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 
AddTabsToWindows(std::map<int,SessionTab * > * tabs,std::map<int,SessionWindow * > * windows)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 
CreateTabsAndWindows(const std::vector<SessionCommand * > & data,std::map<int,SessionTab * > * tabs,std::map<int,SessionWindow * > * windows)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 
BuildCommandsForTab(const SessionID & window_id,NavigationController * controller,int index_in_window,bool is_pinned,std::vector<SessionCommand * > * commands,IdToRange * tab_to_available_range)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 
BuildCommandsForBrowser(Browser * browser,std::vector<SessionCommand * > * commands,IdToRange * tab_to_available_range,std::set<SessionID::id_type> * windows_to_track)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 
BuildCommandsFromBrowsers(std::vector<SessionCommand * > * commands,IdToRange * tab_to_available_range,std::set<SessionID::id_type> * windows_to_track)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 
ScheduleReset()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 
ReplacePendingCommand(SessionCommand * command)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 
ScheduleCommand(SessionCommand * command)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 
CommitPendingCloses()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 
IsOnlyOneTabLeft()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 
HasOpenTrackableBrowsers(const SessionID & window_id)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 
ShouldTrackChangesToWindow(const SessionID & window_id)1306 bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) {
1307   return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
1308 }
1309 
1310 
WindowTypeForBrowserType(Browser::Type type)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 
BrowserTypeForWindowType(SessionService::WindowType type)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 
RecordSessionUpdateHistogramData(NotificationType type,base::TimeTicks * last_updated_time)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 
RecordUpdatedTabClosed(base::TimeDelta delta,bool use_long_period)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 
RecordUpdatedNavListPruned(base::TimeDelta delta,bool use_long_period)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 
RecordUpdatedNavEntryCommit(base::TimeDelta delta,bool use_long_period)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 
RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,bool use_long_period)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 
RecordUpdatedSaveTime(base::TimeDelta delta,bool use_long_period)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