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