1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sessions/session_service.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/command_line.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/metrics/histogram.h"
17 #include "base/pickle.h"
18 #include "base/threading/thread.h"
19 #include "chrome/browser/background/background_mode_manager.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/defaults.h"
23 #include "chrome/browser/extensions/tab_helper.h"
24 #include "chrome/browser/prefs/session_startup_pref.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/profiles/profile_manager.h"
27 #include "chrome/browser/sessions/session_backend.h"
28 #include "chrome/browser/sessions/session_command.h"
29 #include "chrome/browser/sessions/session_data_deleter.h"
30 #include "chrome/browser/sessions/session_restore.h"
31 #include "chrome/browser/sessions/session_tab_helper.h"
32 #include "chrome/browser/sessions/session_types.h"
33 #include "chrome/browser/ui/browser_iterator.h"
34 #include "chrome/browser/ui/browser_list.h"
35 #include "chrome/browser/ui/browser_tabstrip.h"
36 #include "chrome/browser/ui/browser_window.h"
37 #include "chrome/browser/ui/host_desktop.h"
38 #include "chrome/browser/ui/startup/startup_browser_creator.h"
39 #include "chrome/browser/ui/tabs/tab_strip_model.h"
40 #include "components/startup_metric_utils/startup_metric_utils.h"
41 #include "content/public/browser/navigation_details.h"
42 #include "content/public/browser/navigation_entry.h"
43 #include "content/public/browser/notification_details.h"
44 #include "content/public/browser/notification_service.h"
45 #include "content/public/browser/session_storage_namespace.h"
46 #include "content/public/browser/web_contents.h"
47 #include "extensions/common/extension.h"
49 #if defined(OS_MACOSX)
50 #include "chrome/browser/app_controller_mac.h"
54 using content::NavigationEntry;
55 using content::WebContents;
56 using sessions::SerializedNavigationEntry;
58 // Identifier for commands written to file.
59 static const SessionCommand::id_type kCommandSetTabWindow = 0;
60 // OBSOLETE Superseded by kCommandSetWindowBounds3.
61 // static const SessionCommand::id_type kCommandSetWindowBounds = 1;
62 static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2;
63 // Original kCommandTabClosed/kCommandWindowClosed. See comment in
64 // MigrateClosedPayload for details on why they were replaced.
65 static const SessionCommand::id_type kCommandTabClosedObsolete = 3;
66 static const SessionCommand::id_type kCommandWindowClosedObsolete = 4;
67 static const SessionCommand::id_type
68 kCommandTabNavigationPathPrunedFromBack = 5;
69 static const SessionCommand::id_type kCommandUpdateTabNavigation = 6;
70 static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7;
71 static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8;
72 static const SessionCommand::id_type kCommandSetWindowType = 9;
73 // OBSOLETE Superseded by kCommandSetWindowBounds3. Except for data migration.
74 // static const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
75 static const SessionCommand::id_type
76 kCommandTabNavigationPathPrunedFromFront = 11;
77 static const SessionCommand::id_type kCommandSetPinnedState = 12;
78 static const SessionCommand::id_type kCommandSetExtensionAppID = 13;
79 static const SessionCommand::id_type kCommandSetWindowBounds3 = 14;
80 static const SessionCommand::id_type kCommandSetWindowAppName = 15;
81 static const SessionCommand::id_type kCommandTabClosed = 16;
82 static const SessionCommand::id_type kCommandWindowClosed = 17;
83 static const SessionCommand::id_type kCommandSetTabUserAgentOverride = 18;
84 static const SessionCommand::id_type kCommandSessionStorageAssociated = 19;
85 static const SessionCommand::id_type kCommandSetActiveWindow = 20;
87 // Every kWritesPerReset commands triggers recreating the file.
88 static const int kWritesPerReset = 250;
92 // Various payload structures.
93 struct ClosedPayload {
94 SessionID::id_type id;
98 struct WindowBoundsPayload2 {
99 SessionID::id_type window_id;
107 struct WindowBoundsPayload3 {
108 SessionID::id_type window_id;
116 typedef SessionID::id_type ActiveWindowPayload;
118 struct IDAndIndexPayload {
119 SessionID::id_type id;
123 typedef IDAndIndexPayload TabIndexInWindowPayload;
125 typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload;
127 typedef IDAndIndexPayload SelectedNavigationIndexPayload;
129 typedef IDAndIndexPayload SelectedTabInIndexPayload;
131 typedef IDAndIndexPayload WindowTypePayload;
133 typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload;
135 struct PinnedStatePayload {
136 SessionID::id_type tab_id;
140 // Returns the show state to store to disk based |state|.
141 ui::WindowShowState AdjustShowState(ui::WindowShowState state) {
143 case ui::SHOW_STATE_NORMAL:
144 case ui::SHOW_STATE_MINIMIZED:
145 case ui::SHOW_STATE_MAXIMIZED:
146 case ui::SHOW_STATE_FULLSCREEN:
147 case ui::SHOW_STATE_DETACHED:
150 case ui::SHOW_STATE_DEFAULT:
151 case ui::SHOW_STATE_INACTIVE:
152 case ui::SHOW_STATE_END:
153 return ui::SHOW_STATE_NORMAL;
155 return ui::SHOW_STATE_NORMAL;
158 // Migrates a |ClosedPayload|, returning true on success (migration was
159 // necessary and happened), or false (migration was not necessary or was not
161 bool MigrateClosedPayload(const SessionCommand& command,
162 ClosedPayload* payload) {
163 #if defined(OS_CHROMEOS)
164 // Pre M17 versions of chromeos were 32bit. Post M17 is 64 bit. Apparently the
165 // 32 bit versions of chrome on pre M17 resulted in a sizeof 12 for the
166 // ClosedPayload, where as post M17 64-bit gives a sizeof 16 (presumably the
167 // struct is padded).
168 if ((command.id() == kCommandWindowClosedObsolete ||
169 command.id() == kCommandTabClosedObsolete) &&
170 command.size() == 12 && sizeof(payload->id) == 4 &&
171 sizeof(payload->close_time) == 8) {
172 memcpy(&payload->id, command.contents(), 4);
173 memcpy(&payload->close_time, command.contents() + 4, 8);
185 // SessionService -------------------------------------------------------------
187 SessionService::SessionService(Profile* profile)
188 : BaseSessionService(SESSION_RESTORE, profile, base::FilePath()),
189 has_open_trackable_browsers_(false),
190 move_on_new_browser_(false),
191 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
192 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
193 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
194 force_browser_not_alive_with_no_windows_(false),
195 weak_factory_(this) {
199 SessionService::SessionService(const base::FilePath& save_path)
200 : BaseSessionService(SESSION_RESTORE, NULL, save_path),
201 has_open_trackable_browsers_(false),
202 move_on_new_browser_(false),
203 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
204 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
205 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
206 force_browser_not_alive_with_no_windows_(false),
207 weak_factory_(this) {
211 SessionService::~SessionService() {
212 // The BrowserList should outlive the SessionService since it's static and
213 // the SessionService is a KeyedService.
214 BrowserList::RemoveObserver(this);
218 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
219 return RestoreIfNecessary(urls_to_open, NULL);
222 void SessionService::ResetFromCurrentBrowsers() {
226 void SessionService::MoveCurrentSessionToLastSession() {
227 pending_tab_close_ids_.clear();
228 window_closing_ids_.clear();
229 pending_window_close_ids_.clear();
233 RunTaskOnBackendThread(
234 FROM_HERE, base::Bind(&SessionBackend::MoveCurrentSessionToLastSession,
238 void SessionService::SetTabWindow(const SessionID& window_id,
239 const SessionID& tab_id) {
240 if (!ShouldTrackChangesToWindow(window_id))
243 ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id));
246 void SessionService::SetWindowBounds(const SessionID& window_id,
247 const gfx::Rect& bounds,
248 ui::WindowShowState show_state) {
249 if (!ShouldTrackChangesToWindow(window_id))
252 ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, show_state));
255 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
256 const SessionID& tab_id,
258 if (!ShouldTrackChangesToWindow(window_id))
261 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index));
264 void SessionService::SetPinnedState(const SessionID& window_id,
265 const SessionID& tab_id,
267 if (!ShouldTrackChangesToWindow(window_id))
270 ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned));
273 void SessionService::TabClosed(const SessionID& window_id,
274 const SessionID& tab_id,
275 bool closed_by_user_gesture) {
277 return; // Hapens when the tab is replaced.
279 if (!ShouldTrackChangesToWindow(window_id))
282 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
283 if (i != tab_to_available_range_.end())
284 tab_to_available_range_.erase(i);
286 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
287 window_id.id()) != pending_window_close_ids_.end()) {
288 // Tab is in last window. Don't commit it immediately, instead add it to the
289 // list of tabs to close. If the user creates another window, the close is
291 pending_tab_close_ids_.insert(tab_id.id());
292 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
293 window_id.id()) != window_closing_ids_.end() ||
294 !IsOnlyOneTabLeft() ||
295 closed_by_user_gesture) {
296 // Close is the result of one of the following:
297 // . window close (and it isn't the last window).
298 // . closing a tab and there are other windows/tabs open.
299 // . closed by a user gesture.
300 // In all cases we need to mark the tab as explicitly closed.
301 ScheduleCommand(CreateTabClosedCommand(tab_id.id()));
303 // User closed the last tab in the last tabbed browser. Don't mark the
305 pending_tab_close_ids_.insert(tab_id.id());
306 has_open_trackable_browsers_ = false;
310 void SessionService::WindowOpened(Browser* browser) {
311 if (!ShouldTrackBrowser(browser))
314 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
315 RestoreIfNecessary(std::vector<GURL>(), browser);
316 SetWindowType(browser->session_id(), browser->type(), app_type);
317 SetWindowAppName(browser->session_id(), browser->app_name());
320 void SessionService::WindowClosing(const SessionID& window_id) {
321 if (!ShouldTrackChangesToWindow(window_id))
324 // The window is about to close. If there are other tabbed browsers with the
325 // same original profile commit the close immediately.
327 // NOTE: if the user chooses the exit menu item session service is destroyed
328 // and this code isn't hit.
329 if (has_open_trackable_browsers_) {
330 // Closing a window can never make has_open_trackable_browsers_ go from
331 // false to true, so only update it if already true.
332 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
334 bool use_pending_close = !has_open_trackable_browsers_;
335 if (!use_pending_close) {
336 // Somewhat outside of "normal behavior" is profile locking. In this case
337 // (when IsSiginRequired has already been set True), we're closing all
338 // browser windows in turn but want them all to be restored when the user
339 // unlocks. To accomplish this, we do a "pending close" on all windows
340 // instead of just the last one (which has no open_trackable_browsers).
341 // http://crbug.com/356818
343 // Some editions (like iOS) don't have a profile_manager and some tests
344 // don't supply one so be lenient.
345 if (g_browser_process) {
346 ProfileManager* profile_manager = g_browser_process->profile_manager();
347 if (profile_manager) {
348 ProfileInfoCache& profile_info =
349 profile_manager->GetProfileInfoCache();
350 size_t profile_index = profile_info.GetIndexOfProfileWithPath(
351 profile()->GetPath());
352 use_pending_close = profile_index != std::string::npos &&
353 profile_info.ProfileIsSigninRequiredAtIndex(profile_index);
357 if (use_pending_close)
358 pending_window_close_ids_.insert(window_id.id());
360 window_closing_ids_.insert(window_id.id());
363 void SessionService::WindowClosed(const SessionID& window_id) {
364 if (!ShouldTrackChangesToWindow(window_id)) {
365 // The last window may be one that is not tracked.
366 MaybeDeleteSessionOnlyData();
370 windows_tracking_.erase(window_id.id());
372 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
373 window_closing_ids_.erase(window_id.id());
374 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
375 } else if (pending_window_close_ids_.find(window_id.id()) ==
376 pending_window_close_ids_.end()) {
377 // We'll hit this if user closed the last tab in a window.
378 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
379 if (!has_open_trackable_browsers_)
380 pending_window_close_ids_.insert(window_id.id());
382 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
384 MaybeDeleteSessionOnlyData();
387 void SessionService::SetWindowType(const SessionID& window_id,
390 if (!should_track_changes_for_browser_type(type, app_type))
393 windows_tracking_.insert(window_id.id());
395 // The user created a new tabbed browser with our profile. Commit any
397 CommitPendingCloses();
399 has_open_trackable_browsers_ = true;
400 move_on_new_browser_ = true;
403 CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type)));
406 void SessionService::SetWindowAppName(
407 const SessionID& window_id,
408 const std::string& app_name) {
409 if (!ShouldTrackChangesToWindow(window_id))
412 ScheduleCommand(CreateSetTabExtensionAppIDCommand(
413 kCommandSetWindowAppName,
418 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
419 const SessionID& tab_id,
421 if (!ShouldTrackChangesToWindow(window_id))
424 TabNavigationPathPrunedFromBackPayload payload = { 0 };
425 payload.id = tab_id.id();
426 payload.index = count;
427 SessionCommand* command =
428 new SessionCommand(kCommandTabNavigationPathPrunedFromBack,
430 memcpy(command->contents(), &payload, sizeof(payload));
431 ScheduleCommand(command);
434 void SessionService::TabNavigationPathPrunedFromFront(
435 const SessionID& window_id,
436 const SessionID& tab_id,
438 if (!ShouldTrackChangesToWindow(window_id))
441 // Update the range of indices.
442 if (tab_to_available_range_.find(tab_id.id()) !=
443 tab_to_available_range_.end()) {
444 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
445 range.first = std::max(0, range.first - count);
446 range.second = std::max(0, range.second - count);
449 TabNavigationPathPrunedFromFrontPayload payload = { 0 };
450 payload.id = tab_id.id();
451 payload.index = count;
452 SessionCommand* command =
453 new SessionCommand(kCommandTabNavigationPathPrunedFromFront,
455 memcpy(command->contents(), &payload, sizeof(payload));
456 ScheduleCommand(command);
459 void SessionService::UpdateTabNavigation(
460 const SessionID& window_id,
461 const SessionID& tab_id,
462 const SerializedNavigationEntry& navigation) {
463 if (!ShouldTrackEntry(navigation.virtual_url()) ||
464 !ShouldTrackChangesToWindow(window_id)) {
468 if (tab_to_available_range_.find(tab_id.id()) !=
469 tab_to_available_range_.end()) {
470 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
471 range.first = std::min(navigation.index(), range.first);
472 range.second = std::max(navigation.index(), range.second);
474 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation,
475 tab_id.id(), navigation));
478 void SessionService::TabRestored(WebContents* tab, bool pinned) {
479 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
480 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
483 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1,
484 pinned, &pending_commands(), NULL);
488 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
489 const SessionID& tab_id,
491 if (!ShouldTrackChangesToWindow(window_id))
494 if (tab_to_available_range_.find(tab_id.id()) !=
495 tab_to_available_range_.end()) {
496 if (index < tab_to_available_range_[tab_id.id()].first ||
497 index > tab_to_available_range_[tab_id.id()].second) {
498 // The new index is outside the range of what we've archived, schedule
500 ResetFromCurrentBrowsers();
504 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index));
507 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
509 if (!ShouldTrackChangesToWindow(window_id))
512 ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index));
515 void SessionService::SetTabUserAgentOverride(
516 const SessionID& window_id,
517 const SessionID& tab_id,
518 const std::string& user_agent_override) {
519 if (!ShouldTrackChangesToWindow(window_id))
522 ScheduleCommand(CreateSetTabUserAgentOverrideCommand(
523 kCommandSetTabUserAgentOverride, tab_id.id(), user_agent_override));
526 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
527 const SessionCallback& callback,
528 base::CancelableTaskTracker* tracker) {
529 // OnGotSessionCommands maps the SessionCommands to browser state, then run
531 return ScheduleGetLastSessionCommands(
532 base::Bind(&SessionService::OnGotSessionCommands,
533 weak_factory_.GetWeakPtr(), callback),
537 void SessionService::Save() {
538 bool had_commands = !pending_commands().empty();
539 BaseSessionService::Save();
541 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
542 &last_updated_save_time_);
543 content::NotificationService::current()->Notify(
544 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
545 content::Source<Profile>(profile()),
546 content::NotificationService::NoDetails());
550 void SessionService::Init() {
551 // Register for the notifications we're interested in.
552 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
553 content::NotificationService::AllSources());
554 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
555 content::NotificationService::AllSources());
556 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
557 content::NotificationService::AllSources());
559 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
560 content::NotificationService::AllSources());
562 BrowserList::AddObserver(this);
565 bool SessionService::processed_any_commands() {
566 return backend()->inited() || !pending_commands().empty();
569 bool SessionService::ShouldNewWindowStartSession() {
570 // ChromeOS and OSX have different ideas of application lifetime than
571 // the other platforms.
572 // On ChromeOS opening a new window should never start a new session.
573 #if defined(OS_CHROMEOS)
574 if (!force_browser_not_alive_with_no_windows_)
577 if (!has_open_trackable_browsers_ &&
578 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
579 !SessionRestore::IsRestoring(profile())
580 #if defined(OS_MACOSX)
581 // On OSX, a new window should not start a new session if it was opened
582 // from the dock or the menubar.
583 && !app_controller_mac::IsOpeningNewWindow()
591 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
593 if (ShouldNewWindowStartSession()) {
594 // We're going from no tabbed browsers to a tabbed browser (and not in
595 // process startup), restore the last session.
596 if (move_on_new_browser_) {
597 // Make the current session the last.
598 MoveCurrentSessionToLastSession();
599 move_on_new_browser_ = false;
601 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
602 *CommandLine::ForCurrentProcess(), profile());
603 if (pref.type == SessionStartupPref::LAST) {
604 SessionRestore::RestoreSession(
606 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
607 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
615 void SessionService::Observe(int type,
616 const content::NotificationSource& source,
617 const content::NotificationDetails& details) {
618 // All of our messages have the NavigationController as the source.
620 case content::NOTIFICATION_NAV_LIST_PRUNED: {
621 WebContents* web_contents =
622 content::Source<content::NavigationController>(source).ptr()->
624 SessionTabHelper* session_tab_helper =
625 SessionTabHelper::FromWebContents(web_contents);
626 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
628 content::Details<content::PrunedDetails> pruned_details(details);
629 if (pruned_details->from_front) {
630 TabNavigationPathPrunedFromFront(
631 session_tab_helper->window_id(),
632 session_tab_helper->session_id(),
633 pruned_details->count);
635 TabNavigationPathPrunedFromBack(
636 session_tab_helper->window_id(),
637 session_tab_helper->session_id(),
638 web_contents->GetController().GetEntryCount());
640 RecordSessionUpdateHistogramData(type,
641 &last_updated_nav_list_pruned_time_);
645 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
646 WebContents* web_contents =
647 content::Source<content::NavigationController>(source).ptr()->
649 SessionTabHelper* session_tab_helper =
650 SessionTabHelper::FromWebContents(web_contents);
651 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
653 content::Details<content::EntryChangedDetails> changed(details);
654 const SerializedNavigationEntry navigation =
655 SerializedNavigationEntry::FromNavigationEntry(
656 changed->index, *changed->changed_entry);
657 UpdateTabNavigation(session_tab_helper->window_id(),
658 session_tab_helper->session_id(),
663 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
664 WebContents* web_contents =
665 content::Source<content::NavigationController>(source).ptr()->
667 SessionTabHelper* session_tab_helper =
668 SessionTabHelper::FromWebContents(web_contents);
669 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
671 int current_entry_index =
672 web_contents->GetController().GetCurrentEntryIndex();
673 SetSelectedNavigationIndex(
674 session_tab_helper->window_id(),
675 session_tab_helper->session_id(),
676 current_entry_index);
677 const SerializedNavigationEntry navigation =
678 SerializedNavigationEntry::FromNavigationEntry(
680 *web_contents->GetController().GetEntryAtIndex(
681 current_entry_index));
683 session_tab_helper->window_id(),
684 session_tab_helper->session_id(),
686 content::Details<content::LoadCommittedDetails> changed(details);
687 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
688 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
689 RecordSessionUpdateHistogramData(type,
690 &last_updated_nav_entry_commit_time_);
695 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
696 extensions::TabHelper* extension_tab_helper =
697 content::Source<extensions::TabHelper>(source).ptr();
698 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
702 if (extension_tab_helper->extension_app()) {
703 SessionTabHelper* session_tab_helper =
704 SessionTabHelper::FromWebContents(
705 extension_tab_helper->web_contents());
706 SetTabExtensionAppID(session_tab_helper->window_id(),
707 session_tab_helper->session_id(),
708 extension_tab_helper->extension_app()->id());
718 void SessionService::OnBrowserSetLastActive(Browser* browser) {
719 if (ShouldTrackBrowser(browser))
720 ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id()));
723 void SessionService::SetTabExtensionAppID(
724 const SessionID& window_id,
725 const SessionID& tab_id,
726 const std::string& extension_app_id) {
727 if (!ShouldTrackChangesToWindow(window_id))
730 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID,
731 tab_id.id(), extension_app_id));
734 SessionCommand* SessionService::CreateSetSelectedTabInWindow(
735 const SessionID& window_id,
737 SelectedTabInIndexPayload payload = { 0 };
738 payload.id = window_id.id();
739 payload.index = index;
740 SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex,
742 memcpy(command->contents(), &payload, sizeof(payload));
746 SessionCommand* SessionService::CreateSetTabWindowCommand(
747 const SessionID& window_id,
748 const SessionID& tab_id) {
749 SessionID::id_type payload[] = { window_id.id(), tab_id.id() };
750 SessionCommand* command =
751 new SessionCommand(kCommandSetTabWindow, sizeof(payload));
752 memcpy(command->contents(), payload, sizeof(payload));
756 SessionCommand* SessionService::CreateSetWindowBoundsCommand(
757 const SessionID& window_id,
758 const gfx::Rect& bounds,
759 ui::WindowShowState show_state) {
760 WindowBoundsPayload3 payload = { 0 };
761 payload.window_id = window_id.id();
762 payload.x = bounds.x();
763 payload.y = bounds.y();
764 payload.w = bounds.width();
765 payload.h = bounds.height();
766 payload.show_state = AdjustShowState(show_state);
767 SessionCommand* command = new SessionCommand(kCommandSetWindowBounds3,
769 memcpy(command->contents(), &payload, sizeof(payload));
773 SessionCommand* SessionService::CreateSetTabIndexInWindowCommand(
774 const SessionID& tab_id,
776 TabIndexInWindowPayload payload = { 0 };
777 payload.id = tab_id.id();
778 payload.index = new_index;
779 SessionCommand* command =
780 new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload));
781 memcpy(command->contents(), &payload, sizeof(payload));
785 SessionCommand* SessionService::CreateTabClosedCommand(
786 const SessionID::id_type tab_id) {
787 ClosedPayload payload;
788 // Because of what appears to be a compiler bug setting payload to {0} doesn't
789 // set the padding to 0, resulting in Purify reporting an UMR when we write
790 // the structure to disk. To avoid this we explicitly memset the struct.
791 memset(&payload, 0, sizeof(payload));
793 payload.close_time = Time::Now().ToInternalValue();
794 SessionCommand* command =
795 new SessionCommand(kCommandTabClosed, sizeof(payload));
796 memcpy(command->contents(), &payload, sizeof(payload));
800 SessionCommand* SessionService::CreateWindowClosedCommand(
801 const SessionID::id_type window_id) {
802 ClosedPayload payload;
803 // See comment in CreateTabClosedCommand as to why we do this.
804 memset(&payload, 0, sizeof(payload));
805 payload.id = window_id;
806 payload.close_time = Time::Now().ToInternalValue();
807 SessionCommand* command =
808 new SessionCommand(kCommandWindowClosed, sizeof(payload));
809 memcpy(command->contents(), &payload, sizeof(payload));
813 SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand(
814 const SessionID& tab_id,
816 SelectedNavigationIndexPayload payload = { 0 };
817 payload.id = tab_id.id();
818 payload.index = index;
819 SessionCommand* command = new SessionCommand(
820 kCommandSetSelectedNavigationIndex, sizeof(payload));
821 memcpy(command->contents(), &payload, sizeof(payload));
825 SessionCommand* SessionService::CreateSetWindowTypeCommand(
826 const SessionID& window_id,
828 WindowTypePayload payload = { 0 };
829 payload.id = window_id.id();
830 payload.index = static_cast<int32>(type);
831 SessionCommand* command = new SessionCommand(
832 kCommandSetWindowType, sizeof(payload));
833 memcpy(command->contents(), &payload, sizeof(payload));
837 SessionCommand* SessionService::CreatePinnedStateCommand(
838 const SessionID& tab_id,
840 PinnedStatePayload payload = { 0 };
841 payload.tab_id = tab_id.id();
842 payload.pinned_state = is_pinned;
843 SessionCommand* command =
844 new SessionCommand(kCommandSetPinnedState, sizeof(payload));
845 memcpy(command->contents(), &payload, sizeof(payload));
849 SessionCommand* SessionService::CreateSessionStorageAssociatedCommand(
850 const SessionID& tab_id,
851 const std::string& session_storage_persistent_id) {
853 pickle.WriteInt(tab_id.id());
854 pickle.WriteString(session_storage_persistent_id);
855 return new SessionCommand(kCommandSessionStorageAssociated, pickle);
858 SessionCommand* SessionService::CreateSetActiveWindowCommand(
859 const SessionID& window_id) {
860 ActiveWindowPayload payload = 0;
861 payload = window_id.id();
862 SessionCommand* command =
863 new SessionCommand(kCommandSetActiveWindow, sizeof(payload));
864 memcpy(command->contents(), &payload, sizeof(payload));
868 void SessionService::OnGotSessionCommands(
869 const SessionCallback& callback,
870 ScopedVector<SessionCommand> commands) {
871 ScopedVector<SessionWindow> valid_windows;
872 SessionID::id_type active_window_id = 0;
874 RestoreSessionFromCommands(
875 commands.get(), &valid_windows.get(), &active_window_id);
876 callback.Run(valid_windows.Pass(), active_window_id);
879 void SessionService::RestoreSessionFromCommands(
880 const std::vector<SessionCommand*>& commands,
881 std::vector<SessionWindow*>* valid_windows,
882 SessionID::id_type* active_window_id) {
883 std::map<int, SessionTab*> tabs;
884 std::map<int, SessionWindow*> windows;
886 VLOG(1) << "RestoreSessionFromCommands " << commands.size();
887 if (CreateTabsAndWindows(commands, &tabs, &windows, active_window_id)) {
888 AddTabsToWindows(&tabs, &windows);
889 SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows);
890 UpdateSelectedTabIndex(valid_windows);
892 STLDeleteValues(&tabs);
893 // Don't delete conents of windows, that is done by the caller as all
894 // valid windows are added to valid_windows.
897 void SessionService::UpdateSelectedTabIndex(
898 std::vector<SessionWindow*>* windows) {
899 for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
900 i != windows->end(); ++i) {
901 // See note in SessionWindow as to why we do this.
903 for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin();
904 j != (*i)->tabs.end(); ++j) {
905 if ((*j)->tab_visual_index == (*i)->selected_tab_index) {
906 new_index = static_cast<int>(j - (*i)->tabs.begin());
910 (*i)->selected_tab_index = new_index;
914 SessionWindow* SessionService::GetWindow(
915 SessionID::id_type window_id,
916 IdToSessionWindow* windows) {
917 std::map<int, SessionWindow*>::iterator i = windows->find(window_id);
918 if (i == windows->end()) {
919 SessionWindow* window = new SessionWindow();
920 window->window_id.set_id(window_id);
921 (*windows)[window_id] = window;
927 SessionTab* SessionService::GetTab(
928 SessionID::id_type tab_id,
929 IdToSessionTab* tabs) {
931 std::map<int, SessionTab*>::iterator i = tabs->find(tab_id);
932 if (i == tabs->end()) {
933 SessionTab* tab = new SessionTab();
934 tab->tab_id.set_id(tab_id);
935 (*tabs)[tab_id] = tab;
941 std::vector<SerializedNavigationEntry>::iterator
942 SessionService::FindClosestNavigationWithIndex(
943 std::vector<SerializedNavigationEntry>* navigations,
946 for (std::vector<SerializedNavigationEntry>::iterator
947 i = navigations->begin(); i != navigations->end(); ++i) {
948 if (i->index() >= index)
951 return navigations->end();
954 // Function used in sorting windows. Sorting is done based on window id. As
955 // window ids increment for each new window, this effectively sorts by creation
957 static bool WindowOrderSortFunction(const SessionWindow* w1,
958 const SessionWindow* w2) {
959 return w1->window_id.id() < w2->window_id.id();
962 // Compares the two tabs based on visual index.
963 static bool TabVisualIndexSortFunction(const SessionTab* t1,
964 const SessionTab* t2) {
965 const int delta = t1->tab_visual_index - t2->tab_visual_index;
966 return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0);
969 void SessionService::SortTabsBasedOnVisualOrderAndPrune(
970 std::map<int, SessionWindow*>* windows,
971 std::vector<SessionWindow*>* valid_windows) {
972 std::map<int, SessionWindow*>::iterator i = windows->begin();
973 while (i != windows->end()) {
974 SessionWindow* window = i->second;
975 AppType app_type = window->app_name.empty() ? TYPE_NORMAL : TYPE_APP;
976 if (window->tabs.empty() || window->is_constrained ||
977 !should_track_changes_for_browser_type(
978 static_cast<Browser::Type>(window->type),
983 // Valid window; sort the tabs and add it to the list of valid windows.
984 std::sort(window->tabs.begin(), window->tabs.end(),
985 &TabVisualIndexSortFunction);
986 // Otherwise, add the window such that older windows appear first.
987 if (valid_windows->empty()) {
988 valid_windows->push_back(window);
990 valid_windows->insert(
991 std::upper_bound(valid_windows->begin(), valid_windows->end(),
992 window, &WindowOrderSortFunction),
1000 void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs,
1001 std::map<int, SessionWindow*>* windows) {
1002 VLOG(1) << "AddTabsToWindws";
1003 VLOG(1) << "Tabs " << tabs->size() << ", windows " << windows->size();
1004 std::map<int, SessionTab*>::iterator i = tabs->begin();
1005 while (i != tabs->end()) {
1006 SessionTab* tab = i->second;
1007 if (tab->window_id.id() && !tab->navigations.empty()) {
1008 SessionWindow* window = GetWindow(tab->window_id.id(), windows);
1009 window->tabs.push_back(tab);
1012 // See note in SessionTab as to why we do this.
1013 std::vector<SerializedNavigationEntry>::iterator j =
1014 FindClosestNavigationWithIndex(&(tab->navigations),
1015 tab->current_navigation_index);
1016 if (j == tab->navigations.end()) {
1017 tab->current_navigation_index =
1018 static_cast<int>(tab->navigations.size() - 1);
1020 tab->current_navigation_index =
1021 static_cast<int>(j - tab->navigations.begin());
1024 // Never got a set tab index in window, or tabs are empty, nothing
1031 bool SessionService::CreateTabsAndWindows(
1032 const std::vector<SessionCommand*>& data,
1033 std::map<int, SessionTab*>* tabs,
1034 std::map<int, SessionWindow*>* windows,
1035 SessionID::id_type* active_window_id) {
1036 // If the file is corrupt (command with wrong size, or unknown command), we
1037 // still return true and attempt to restore what we we can.
1038 VLOG(1) << "CreateTabsAndWindows";
1040 startup_metric_utils::ScopedSlowStartupUMA
1041 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
1043 for (std::vector<SessionCommand*>::const_iterator i = data.begin();
1044 i != data.end(); ++i) {
1045 const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
1046 const SessionCommand* command = *i;
1048 VLOG(1) << "Read command " << (int) command->id();
1049 switch (command->id()) {
1050 case kCommandSetTabWindow: {
1051 SessionID::id_type payload[2];
1052 if (!command->GetPayload(payload, sizeof(payload))) {
1053 VLOG(1) << "Failed reading command " << command->id();
1056 GetTab(payload[1], tabs)->window_id.set_id(payload[0]);
1060 // This is here for forward migration only. New data is saved with
1061 // |kCommandSetWindowBounds3|.
1062 case kCommandSetWindowBounds2: {
1063 WindowBoundsPayload2 payload;
1064 if (!command->GetPayload(&payload, sizeof(payload))) {
1065 VLOG(1) << "Failed reading command " << command->id();
1068 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1072 GetWindow(payload.window_id, windows)->show_state =
1073 payload.is_maximized ?
1074 ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL;
1078 case kCommandSetWindowBounds3: {
1079 WindowBoundsPayload3 payload;
1080 if (!command->GetPayload(&payload, sizeof(payload))) {
1081 VLOG(1) << "Failed reading command " << command->id();
1084 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1088 // SHOW_STATE_INACTIVE is not persisted.
1089 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
1090 if (payload.show_state > ui::SHOW_STATE_DEFAULT &&
1091 payload.show_state < ui::SHOW_STATE_END &&
1092 payload.show_state != ui::SHOW_STATE_INACTIVE) {
1093 show_state = static_cast<ui::WindowShowState>(payload.show_state);
1097 GetWindow(payload.window_id, windows)->show_state = show_state;
1101 case kCommandSetTabIndexInWindow: {
1102 TabIndexInWindowPayload payload;
1103 if (!command->GetPayload(&payload, sizeof(payload))) {
1104 VLOG(1) << "Failed reading command " << command->id();
1107 GetTab(payload.id, tabs)->tab_visual_index = payload.index;
1111 case kCommandTabClosedObsolete:
1112 case kCommandWindowClosedObsolete:
1113 case kCommandTabClosed:
1114 case kCommandWindowClosed: {
1115 ClosedPayload payload;
1116 if (!command->GetPayload(&payload, sizeof(payload)) &&
1117 !MigrateClosedPayload(*command, &payload)) {
1118 VLOG(1) << "Failed reading command " << command->id();
1121 if (command->id() == kCommandTabClosed ||
1122 command->id() == kCommandTabClosedObsolete) {
1123 delete GetTab(payload.id, tabs);
1124 tabs->erase(payload.id);
1126 delete GetWindow(payload.id, windows);
1127 windows->erase(payload.id);
1132 case kCommandTabNavigationPathPrunedFromBack: {
1133 TabNavigationPathPrunedFromBackPayload payload;
1134 if (!command->GetPayload(&payload, sizeof(payload))) {
1135 VLOG(1) << "Failed reading command " << command->id();
1138 SessionTab* tab = GetTab(payload.id, tabs);
1139 tab->navigations.erase(
1140 FindClosestNavigationWithIndex(&(tab->navigations), payload.index),
1141 tab->navigations.end());
1145 case kCommandTabNavigationPathPrunedFromFront: {
1146 TabNavigationPathPrunedFromFrontPayload payload;
1147 if (!command->GetPayload(&payload, sizeof(payload)) ||
1148 payload.index <= 0) {
1149 VLOG(1) << "Failed reading command " << command->id();
1152 SessionTab* tab = GetTab(payload.id, tabs);
1154 // Update the selected navigation index.
1155 tab->current_navigation_index =
1156 std::max(-1, tab->current_navigation_index - payload.index);
1158 // And update the index of existing navigations.
1159 for (std::vector<SerializedNavigationEntry>::iterator
1160 i = tab->navigations.begin();
1161 i != tab->navigations.end();) {
1162 i->set_index(i->index() - payload.index);
1164 i = tab->navigations.erase(i);
1171 case kCommandUpdateTabNavigation: {
1172 SerializedNavigationEntry navigation;
1173 SessionID::id_type tab_id;
1174 if (!RestoreUpdateTabNavigationCommand(
1175 *command, &navigation, &tab_id)) {
1176 VLOG(1) << "Failed reading command " << command->id();
1179 SessionTab* tab = GetTab(tab_id, tabs);
1180 std::vector<SerializedNavigationEntry>::iterator i =
1181 FindClosestNavigationWithIndex(&(tab->navigations),
1182 navigation.index());
1183 if (i != tab->navigations.end() && i->index() == navigation.index())
1186 tab->navigations.insert(i, navigation);
1190 case kCommandSetSelectedNavigationIndex: {
1191 SelectedNavigationIndexPayload payload;
1192 if (!command->GetPayload(&payload, sizeof(payload))) {
1193 VLOG(1) << "Failed reading command " << command->id();
1196 GetTab(payload.id, tabs)->current_navigation_index = payload.index;
1200 case kCommandSetSelectedTabInIndex: {
1201 SelectedTabInIndexPayload payload;
1202 if (!command->GetPayload(&payload, sizeof(payload))) {
1203 VLOG(1) << "Failed reading command " << command->id();
1206 GetWindow(payload.id, windows)->selected_tab_index = payload.index;
1210 case kCommandSetWindowType: {
1211 WindowTypePayload payload;
1212 if (!command->GetPayload(&payload, sizeof(payload))) {
1213 VLOG(1) << "Failed reading command " << command->id();
1216 GetWindow(payload.id, windows)->is_constrained = false;
1217 GetWindow(payload.id, windows)->type =
1218 BrowserTypeForWindowType(
1219 static_cast<WindowType>(payload.index));
1223 case kCommandSetPinnedState: {
1224 PinnedStatePayload payload;
1225 if (!command->GetPayload(&payload, sizeof(payload))) {
1226 VLOG(1) << "Failed reading command " << command->id();
1229 GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state;
1233 case kCommandSetWindowAppName: {
1234 SessionID::id_type window_id;
1235 std::string app_name;
1236 if (!RestoreSetWindowAppNameCommand(*command, &window_id, &app_name))
1239 GetWindow(window_id, windows)->app_name.swap(app_name);
1243 case kCommandSetExtensionAppID: {
1244 SessionID::id_type tab_id;
1245 std::string extension_app_id;
1246 if (!RestoreSetTabExtensionAppIDCommand(
1247 *command, &tab_id, &extension_app_id)) {
1248 VLOG(1) << "Failed reading command " << command->id();
1252 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id);
1256 case kCommandSetTabUserAgentOverride: {
1257 SessionID::id_type tab_id;
1258 std::string user_agent_override;
1259 if (!RestoreSetTabUserAgentOverrideCommand(
1260 *command, &tab_id, &user_agent_override)) {
1264 GetTab(tab_id, tabs)->user_agent_override.swap(user_agent_override);
1268 case kCommandSessionStorageAssociated: {
1269 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1270 SessionID::id_type command_tab_id;
1271 std::string session_storage_persistent_id;
1272 PickleIterator iter(*command_pickle.get());
1273 if (!command_pickle->ReadInt(&iter, &command_tab_id) ||
1274 !command_pickle->ReadString(&iter, &session_storage_persistent_id))
1276 // Associate the session storage back.
1277 GetTab(command_tab_id, tabs)->session_storage_persistent_id =
1278 session_storage_persistent_id;
1282 case kCommandSetActiveWindow: {
1283 ActiveWindowPayload payload;
1284 if (!command->GetPayload(&payload, sizeof(payload))) {
1285 VLOG(1) << "Failed reading command " << command->id();
1288 *active_window_id = payload;
1293 VLOG(1) << "Failed reading an unknown command " << command->id();
1300 void SessionService::BuildCommandsForTab(const SessionID& window_id,
1302 int index_in_window,
1304 std::vector<SessionCommand*>* commands,
1305 IdToRange* tab_to_available_range) {
1306 DCHECK(tab && commands && window_id.id());
1307 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
1308 const SessionID& session_id(session_tab_helper->session_id());
1309 commands->push_back(CreateSetTabWindowCommand(window_id, session_id));
1311 const int current_index = tab->GetController().GetCurrentEntryIndex();
1312 const int min_index = std::max(0,
1313 current_index - max_persist_navigation_count);
1314 const int max_index =
1315 std::min(current_index + max_persist_navigation_count,
1316 tab->GetController().GetEntryCount());
1317 const int pending_index = tab->GetController().GetPendingEntryIndex();
1318 if (tab_to_available_range) {
1319 (*tab_to_available_range)[session_id.id()] =
1320 std::pair<int, int>(min_index, max_index);
1324 commands->push_back(CreatePinnedStateCommand(session_id, true));
1327 extensions::TabHelper* extensions_tab_helper =
1328 extensions::TabHelper::FromWebContents(tab);
1329 if (extensions_tab_helper->extension_app()) {
1330 commands->push_back(
1331 CreateSetTabExtensionAppIDCommand(
1332 kCommandSetExtensionAppID, session_id.id(),
1333 extensions_tab_helper->extension_app()->id()));
1336 const std::string& ua_override = tab->GetUserAgentOverride();
1337 if (!ua_override.empty()) {
1338 commands->push_back(
1339 CreateSetTabUserAgentOverrideCommand(
1340 kCommandSetTabUserAgentOverride, session_id.id(), ua_override));
1343 for (int i = min_index; i < max_index; ++i) {
1344 const NavigationEntry* entry = (i == pending_index) ?
1345 tab->GetController().GetPendingEntry() :
1346 tab->GetController().GetEntryAtIndex(i);
1348 if (ShouldTrackEntry(entry->GetVirtualURL())) {
1349 const SerializedNavigationEntry navigation =
1350 SerializedNavigationEntry::FromNavigationEntry(i, *entry);
1351 commands->push_back(
1352 CreateUpdateTabNavigationCommand(
1353 kCommandUpdateTabNavigation, session_id.id(), navigation));
1356 commands->push_back(
1357 CreateSetSelectedNavigationIndexCommand(session_id, current_index));
1359 if (index_in_window != -1) {
1360 commands->push_back(
1361 CreateSetTabIndexInWindowCommand(session_id, index_in_window));
1364 // Record the association between the sessionStorage namespace and the tab.
1365 content::SessionStorageNamespace* session_storage_namespace =
1366 tab->GetController().GetDefaultSessionStorageNamespace();
1367 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1368 session_tab_helper->session_id(),
1369 session_storage_namespace->persistent_id()));
1372 void SessionService::BuildCommandsForBrowser(
1374 std::vector<SessionCommand*>* commands,
1375 IdToRange* tab_to_available_range,
1376 std::set<SessionID::id_type>* windows_to_track) {
1377 DCHECK(browser && commands);
1378 DCHECK(browser->session_id().id());
1380 commands->push_back(
1381 CreateSetWindowBoundsCommand(browser->session_id(),
1382 browser->window()->GetRestoredBounds(),
1383 browser->window()->GetRestoredState()));
1385 commands->push_back(CreateSetWindowTypeCommand(
1386 browser->session_id(), WindowTypeForBrowserType(browser->type())));
1388 if (!browser->app_name().empty()) {
1389 commands->push_back(CreateSetWindowAppNameCommand(
1390 kCommandSetWindowAppName,
1391 browser->session_id().id(),
1392 browser->app_name()));
1395 windows_to_track->insert(browser->session_id().id());
1396 TabStripModel* tab_strip = browser->tab_strip_model();
1397 for (int i = 0; i < tab_strip->count(); ++i) {
1398 WebContents* tab = tab_strip->GetWebContentsAt(i);
1400 BuildCommandsForTab(browser->session_id(), tab, i,
1401 tab_strip->IsTabPinned(i),
1402 commands, tab_to_available_range);
1405 commands->push_back(
1406 CreateSetSelectedTabInWindow(browser->session_id(),
1407 browser->tab_strip_model()->active_index()));
1410 void SessionService::BuildCommandsFromBrowsers(
1411 std::vector<SessionCommand*>* commands,
1412 IdToRange* tab_to_available_range,
1413 std::set<SessionID::id_type>* windows_to_track) {
1415 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1416 Browser* browser = *it;
1417 // Make sure the browser has tabs and a window. Browser's destructor
1418 // removes itself from the BrowserList. When a browser is closed the
1419 // destructor is not necessarily run immediately. This means it's possible
1420 // for us to get a handle to a browser that is about to be removed. If
1421 // the tab count is 0 or the window is NULL, the browser is about to be
1422 // deleted, so we ignore it.
1423 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
1424 browser->window()) {
1425 BuildCommandsForBrowser(browser, commands, tab_to_available_range,
1431 void SessionService::ScheduleReset() {
1432 set_pending_reset(true);
1433 STLDeleteElements(&pending_commands());
1434 tab_to_available_range_.clear();
1435 windows_tracking_.clear();
1436 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_,
1437 &windows_tracking_);
1438 if (!windows_tracking_.empty()) {
1439 // We're lazily created on startup and won't get an initial batch of
1440 // SetWindowType messages. Set these here to make sure our state is correct.
1441 has_open_trackable_browsers_ = true;
1442 move_on_new_browser_ = true;
1447 bool SessionService::ReplacePendingCommand(SessionCommand* command) {
1448 // We optimize page navigations, which can happen quite frequently and
1449 // are expensive. And activation is like Highlander, there can only be one!
1450 if (command->id() != kCommandUpdateTabNavigation &&
1451 command->id() != kCommandSetActiveWindow) {
1454 for (std::vector<SessionCommand*>::reverse_iterator i =
1455 pending_commands().rbegin(); i != pending_commands().rend(); ++i) {
1456 SessionCommand* existing_command = *i;
1457 if (command->id() == kCommandUpdateTabNavigation &&
1458 existing_command->id() == kCommandUpdateTabNavigation) {
1459 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1460 PickleIterator iterator(*command_pickle);
1461 SessionID::id_type command_tab_id;
1462 int command_nav_index;
1463 if (!command_pickle->ReadInt(&iterator, &command_tab_id) ||
1464 !command_pickle->ReadInt(&iterator, &command_nav_index)) {
1467 SessionID::id_type existing_tab_id;
1468 int existing_nav_index;
1470 // Creating a pickle like this means the Pickle references the data from
1471 // the command. Make sure we delete the pickle before the command, else
1472 // the pickle references deleted memory.
1473 scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle());
1474 iterator = PickleIterator(*existing_pickle);
1475 if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) ||
1476 !existing_pickle->ReadInt(&iterator, &existing_nav_index)) {
1480 if (existing_tab_id == command_tab_id &&
1481 existing_nav_index == command_nav_index) {
1482 // existing_command is an update for the same tab/index pair. Replace
1483 // it with the new one. We need to add to the end of the list just in
1484 // case there is a prune command after the update command.
1485 delete existing_command;
1486 pending_commands().erase(i.base() - 1);
1487 pending_commands().push_back(command);
1492 if (command->id() == kCommandSetActiveWindow &&
1493 existing_command->id() == kCommandSetActiveWindow) {
1495 delete existing_command;
1502 void SessionService::ScheduleCommand(SessionCommand* command) {
1504 if (ReplacePendingCommand(command))
1506 BaseSessionService::ScheduleCommand(command);
1507 // Don't schedule a reset on tab closed/window closed. Otherwise we may
1508 // lose tabs/windows we want to restore from if we exit right after this.
1509 if (!pending_reset() && pending_window_close_ids_.empty() &&
1510 commands_since_reset() >= kWritesPerReset &&
1511 (command->id() != kCommandTabClosed &&
1512 command->id() != kCommandWindowClosed)) {
1517 void SessionService::CommitPendingCloses() {
1518 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
1519 i != pending_tab_close_ids_.end(); ++i) {
1520 ScheduleCommand(CreateTabClosedCommand(*i));
1522 pending_tab_close_ids_.clear();
1524 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
1525 i != pending_window_close_ids_.end(); ++i) {
1526 ScheduleCommand(CreateWindowClosedCommand(*i));
1528 pending_window_close_ids_.clear();
1531 bool SessionService::IsOnlyOneTabLeft() const {
1532 if (!profile() || profile()->AsTestingProfile()) {
1533 // We're testing, always return false.
1537 int window_count = 0;
1538 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1539 Browser* browser = *it;
1540 const SessionID::id_type window_id = browser->session_id().id();
1541 if (ShouldTrackBrowser(browser) &&
1542 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
1543 if (++window_count > 1)
1545 // By the time this is invoked the tab has been removed. As such, we use
1546 // > 0 here rather than > 1.
1547 if (browser->tab_strip_model()->count() > 0)
1554 bool SessionService::HasOpenTrackableBrowsers(
1555 const SessionID& window_id) const {
1556 if (!profile() || profile()->AsTestingProfile()) {
1557 // We're testing, always return true.
1561 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1562 Browser* browser = *it;
1563 const SessionID::id_type browser_id = browser->session_id().id();
1564 if (browser_id != window_id.id() &&
1565 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
1566 ShouldTrackBrowser(browser)) {
1573 bool SessionService::ShouldTrackChangesToWindow(
1574 const SessionID& window_id) const {
1575 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
1578 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
1579 if (browser->profile() != profile())
1581 // Never track app popup windows that do not have a trusted source (i.e.
1582 // popup windows spawned by an app). If this logic changes, be sure to also
1583 // change SessionRestoreImpl::CreateRestoredBrowser().
1584 if (browser->is_app() && browser->is_type_popup() &&
1585 !browser->is_trusted_source()) {
1588 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
1589 return should_track_changes_for_browser_type(browser->type(), app_type);
1592 bool SessionService::should_track_changes_for_browser_type(Browser::Type type,
1594 #if defined(OS_CHROMEOS)
1595 // Restore app popups for chromeos alone.
1596 if (type == Browser::TYPE_POPUP && app_type == TYPE_APP)
1600 return type == Browser::TYPE_TABBED;
1603 SessionService::WindowType SessionService::WindowTypeForBrowserType(
1604 Browser::Type type) {
1606 case Browser::TYPE_POPUP:
1608 case Browser::TYPE_TABBED:
1616 Browser::Type SessionService::BrowserTypeForWindowType(WindowType type) {
1619 return Browser::TYPE_POPUP;
1622 return Browser::TYPE_TABBED;
1626 void SessionService::RecordSessionUpdateHistogramData(int type,
1627 base::TimeTicks* last_updated_time) {
1628 if (!last_updated_time->is_null()) {
1629 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
1630 // We're interested in frequent updates periods longer than
1632 bool use_long_period = false;
1633 if (delta >= save_delay_in_mins_) {
1634 use_long_period = true;
1637 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
1638 RecordUpdatedSaveTime(delta, use_long_period);
1639 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1641 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
1642 RecordUpdatedTabClosed(delta, use_long_period);
1643 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1645 case content::NOTIFICATION_NAV_LIST_PRUNED:
1646 RecordUpdatedNavListPruned(delta, use_long_period);
1647 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1649 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
1650 RecordUpdatedNavEntryCommit(delta, use_long_period);
1651 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1654 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1658 (*last_updated_time) = base::TimeTicks::Now();
1661 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
1662 bool use_long_period) {
1663 std::string name("SessionRestore.TabClosedPeriod");
1664 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1666 // 2500ms is the default save delay.
1667 save_delay_in_millis_,
1668 save_delay_in_mins_,
1670 if (use_long_period) {
1671 std::string long_name_("SessionRestore.TabClosedLongPeriod");
1672 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1674 save_delay_in_mins_,
1680 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1681 bool use_long_period) {
1682 std::string name("SessionRestore.NavigationListPrunedPeriod");
1683 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1685 // 2500ms is the default save delay.
1686 save_delay_in_millis_,
1687 save_delay_in_mins_,
1689 if (use_long_period) {
1690 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1691 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1693 save_delay_in_mins_,
1699 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1700 bool use_long_period) {
1701 std::string name("SessionRestore.NavEntryCommittedPeriod");
1702 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1704 // 2500ms is the default save delay.
1705 save_delay_in_millis_,
1706 save_delay_in_mins_,
1708 if (use_long_period) {
1709 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1710 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1712 save_delay_in_mins_,
1718 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1719 bool use_long_period) {
1720 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1721 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1723 // 2500ms is the default save delay.
1724 save_delay_in_millis_,
1725 save_delay_in_mins_,
1727 if (use_long_period) {
1728 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1729 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1731 save_delay_in_mins_,
1737 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1738 bool use_long_period) {
1739 std::string name("SessionRestore.SavePeriod");
1740 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1742 // 2500ms is the default save delay.
1743 save_delay_in_millis_,
1744 save_delay_in_mins_,
1746 if (use_long_period) {
1747 std::string long_name_("SessionRestore.SaveLongPeriod");
1748 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1750 save_delay_in_mins_,
1756 void SessionService::TabInserted(WebContents* contents) {
1757 SessionTabHelper* session_tab_helper =
1758 SessionTabHelper::FromWebContents(contents);
1759 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
1761 SetTabWindow(session_tab_helper->window_id(),
1762 session_tab_helper->session_id());
1763 extensions::TabHelper* extensions_tab_helper =
1764 extensions::TabHelper::FromWebContents(contents);
1765 if (extensions_tab_helper &&
1766 extensions_tab_helper->extension_app()) {
1767 SetTabExtensionAppID(
1768 session_tab_helper->window_id(),
1769 session_tab_helper->session_id(),
1770 extensions_tab_helper->extension_app()->id());
1773 // Record the association between the SessionStorageNamespace and the
1776 // TODO(ajwong): This should be processing the whole map rather than
1777 // just the default. This in particular will not work for tabs with only
1778 // isolated apps which won't have a default partition.
1779 content::SessionStorageNamespace* session_storage_namespace =
1780 contents->GetController().GetDefaultSessionStorageNamespace();
1781 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1782 session_tab_helper->session_id(),
1783 session_storage_namespace->persistent_id()));
1784 session_storage_namespace->SetShouldPersist(true);
1787 void SessionService::TabClosing(WebContents* contents) {
1788 // Allow the associated sessionStorage to get deleted; it won't be needed
1789 // in the session restore.
1790 content::SessionStorageNamespace* session_storage_namespace =
1791 contents->GetController().GetDefaultSessionStorageNamespace();
1792 session_storage_namespace->SetShouldPersist(false);
1793 SessionTabHelper* session_tab_helper =
1794 SessionTabHelper::FromWebContents(contents);
1795 TabClosed(session_tab_helper->window_id(),
1796 session_tab_helper->session_id(),
1797 contents->GetClosedByUserGesture());
1798 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
1799 &last_updated_tab_closed_time_);
1802 void SessionService::MaybeDeleteSessionOnlyData() {
1803 // Don't try anything if we're testing. The browser_process is not fully
1804 // created and DeleteSession will crash if we actually attempt it.
1805 if (!profile() || profile()->AsTestingProfile())
1808 // Clear session data if the last window for a profile has been closed and
1809 // closing the last window would normally close Chrome, unless background mode
1810 // is active. Tests don't have a background_mode_manager.
1811 if (has_open_trackable_browsers_ ||
1812 browser_defaults::kBrowserAliveWithNoWindows ||
1813 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1817 // Check for any open windows for the current profile that we aren't tracking.
1818 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1819 if ((*it)->profile() == profile())
1822 DeleteSessionOnlyData(profile());