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/sessions/session_backend.h"
27 #include "chrome/browser/sessions/session_command.h"
28 #include "chrome/browser/sessions/session_data_deleter.h"
29 #include "chrome/browser/sessions/session_restore.h"
30 #include "chrome/browser/sessions/session_tab_helper.h"
31 #include "chrome/browser/sessions/session_types.h"
32 #include "chrome/browser/ui/browser_iterator.h"
33 #include "chrome/browser/ui/browser_list.h"
34 #include "chrome/browser/ui/browser_tabstrip.h"
35 #include "chrome/browser/ui/browser_window.h"
36 #include "chrome/browser/ui/host_desktop.h"
37 #include "chrome/browser/ui/startup/startup_browser_creator.h"
38 #include "chrome/browser/ui/tabs/tab_strip_model.h"
39 #include "chrome/common/extensions/extension.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"
48 #if defined(OS_MACOSX)
49 #include "chrome/browser/app_controller_mac.h"
53 using content::NavigationEntry;
54 using content::WebContents;
55 using sessions::SerializedNavigationEntry;
57 // Identifier for commands written to file.
58 static const SessionCommand::id_type kCommandSetTabWindow = 0;
59 // OBSOLETE Superseded by kCommandSetWindowBounds3.
60 // static const SessionCommand::id_type kCommandSetWindowBounds = 1;
61 static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2;
62 // Original kCommandTabClosed/kCommandWindowClosed. See comment in
63 // MigrateClosedPayload for details on why they were replaced.
64 static const SessionCommand::id_type kCommandTabClosedObsolete = 3;
65 static const SessionCommand::id_type kCommandWindowClosedObsolete = 4;
66 static const SessionCommand::id_type
67 kCommandTabNavigationPathPrunedFromBack = 5;
68 static const SessionCommand::id_type kCommandUpdateTabNavigation = 6;
69 static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7;
70 static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8;
71 static const SessionCommand::id_type kCommandSetWindowType = 9;
72 // OBSOLETE Superseded by kCommandSetWindowBounds3. Except for data migration.
73 // static const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
74 static const SessionCommand::id_type
75 kCommandTabNavigationPathPrunedFromFront = 11;
76 static const SessionCommand::id_type kCommandSetPinnedState = 12;
77 static const SessionCommand::id_type kCommandSetExtensionAppID = 13;
78 static const SessionCommand::id_type kCommandSetWindowBounds3 = 14;
79 static const SessionCommand::id_type kCommandSetWindowAppName = 15;
80 static const SessionCommand::id_type kCommandTabClosed = 16;
81 static const SessionCommand::id_type kCommandWindowClosed = 17;
82 static const SessionCommand::id_type kCommandSetTabUserAgentOverride = 18;
83 static const SessionCommand::id_type kCommandSessionStorageAssociated = 19;
84 static const SessionCommand::id_type kCommandSetActiveWindow = 20;
86 // Every kWritesPerReset commands triggers recreating the file.
87 static const int kWritesPerReset = 250;
91 // Various payload structures.
92 struct ClosedPayload {
93 SessionID::id_type id;
97 struct WindowBoundsPayload2 {
98 SessionID::id_type window_id;
106 struct WindowBoundsPayload3 {
107 SessionID::id_type window_id;
115 typedef SessionID::id_type ActiveWindowPayload;
117 struct IDAndIndexPayload {
118 SessionID::id_type id;
122 typedef IDAndIndexPayload TabIndexInWindowPayload;
124 typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload;
126 typedef IDAndIndexPayload SelectedNavigationIndexPayload;
128 typedef IDAndIndexPayload SelectedTabInIndexPayload;
130 typedef IDAndIndexPayload WindowTypePayload;
132 typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload;
134 struct PinnedStatePayload {
135 SessionID::id_type tab_id;
139 // Returns the show state to store to disk based |state|.
140 ui::WindowShowState AdjustShowState(ui::WindowShowState state) {
142 case ui::SHOW_STATE_NORMAL:
143 case ui::SHOW_STATE_MINIMIZED:
144 case ui::SHOW_STATE_MAXIMIZED:
145 case ui::SHOW_STATE_FULLSCREEN:
146 case ui::SHOW_STATE_DETACHED:
149 case ui::SHOW_STATE_DEFAULT:
150 case ui::SHOW_STATE_INACTIVE:
151 case ui::SHOW_STATE_END:
152 return ui::SHOW_STATE_NORMAL;
154 return ui::SHOW_STATE_NORMAL;
157 // Migrates a |ClosedPayload|, returning true on success (migration was
158 // necessary and happened), or false (migration was not necessary or was not
160 bool MigrateClosedPayload(const SessionCommand& command,
161 ClosedPayload* payload) {
162 #if defined(OS_CHROMEOS)
163 // Pre M17 versions of chromeos were 32bit. Post M17 is 64 bit. Apparently the
164 // 32 bit versions of chrome on pre M17 resulted in a sizeof 12 for the
165 // ClosedPayload, where as post M17 64-bit gives a sizeof 16 (presumably the
166 // struct is padded).
167 if ((command.id() == kCommandWindowClosedObsolete ||
168 command.id() == kCommandTabClosedObsolete) &&
169 command.size() == 12 && sizeof(payload->id) == 4 &&
170 sizeof(payload->close_time) == 8) {
171 memcpy(&payload->id, command.contents(), 4);
172 memcpy(&payload->close_time, command.contents() + 4, 8);
184 // SessionService -------------------------------------------------------------
186 SessionService::SessionService(Profile* profile)
187 : BaseSessionService(SESSION_RESTORE, profile, base::FilePath()),
188 has_open_trackable_browsers_(false),
189 move_on_new_browser_(false),
190 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
191 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
192 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
193 force_browser_not_alive_with_no_windows_(false) {
197 SessionService::SessionService(const base::FilePath& save_path)
198 : BaseSessionService(SESSION_RESTORE, NULL, save_path),
199 has_open_trackable_browsers_(false),
200 move_on_new_browser_(false),
201 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
202 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
203 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
204 force_browser_not_alive_with_no_windows_(false) {
208 SessionService::~SessionService() {
209 // The BrowserList should outlive the SessionService since it's static and
210 // the SessionService is a BrowserContextKeyedService.
211 BrowserList::RemoveObserver(this);
215 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
216 return RestoreIfNecessary(urls_to_open, NULL);
219 void SessionService::ResetFromCurrentBrowsers() {
223 void SessionService::MoveCurrentSessionToLastSession() {
224 pending_tab_close_ids_.clear();
225 window_closing_ids_.clear();
226 pending_window_close_ids_.clear();
230 RunTaskOnBackendThread(
231 FROM_HERE, base::Bind(&SessionBackend::MoveCurrentSessionToLastSession,
235 void SessionService::SetTabWindow(const SessionID& window_id,
236 const SessionID& tab_id) {
237 if (!ShouldTrackChangesToWindow(window_id))
240 ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id));
243 void SessionService::SetWindowBounds(const SessionID& window_id,
244 const gfx::Rect& bounds,
245 ui::WindowShowState show_state) {
246 if (!ShouldTrackChangesToWindow(window_id))
249 ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, show_state));
252 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
253 const SessionID& tab_id,
255 if (!ShouldTrackChangesToWindow(window_id))
258 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index));
261 void SessionService::SetPinnedState(const SessionID& window_id,
262 const SessionID& tab_id,
264 if (!ShouldTrackChangesToWindow(window_id))
267 ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned));
270 void SessionService::TabClosed(const SessionID& window_id,
271 const SessionID& tab_id,
272 bool closed_by_user_gesture) {
274 return; // Hapens when the tab is replaced.
276 if (!ShouldTrackChangesToWindow(window_id))
279 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
280 if (i != tab_to_available_range_.end())
281 tab_to_available_range_.erase(i);
283 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
284 window_id.id()) != pending_window_close_ids_.end()) {
285 // Tab is in last window. Don't commit it immediately, instead add it to the
286 // list of tabs to close. If the user creates another window, the close is
288 pending_tab_close_ids_.insert(tab_id.id());
289 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
290 window_id.id()) != window_closing_ids_.end() ||
291 !IsOnlyOneTabLeft() ||
292 closed_by_user_gesture) {
293 // Close is the result of one of the following:
294 // . window close (and it isn't the last window).
295 // . closing a tab and there are other windows/tabs open.
296 // . closed by a user gesture.
297 // In all cases we need to mark the tab as explicitly closed.
298 ScheduleCommand(CreateTabClosedCommand(tab_id.id()));
300 // User closed the last tab in the last tabbed browser. Don't mark the
302 pending_tab_close_ids_.insert(tab_id.id());
303 has_open_trackable_browsers_ = false;
307 void SessionService::WindowClosing(const SessionID& window_id) {
308 if (!ShouldTrackChangesToWindow(window_id))
311 // The window is about to close. If there are other tabbed browsers with the
312 // same original profile commit the close immediately.
314 // NOTE: if the user chooses the exit menu item session service is destroyed
315 // and this code isn't hit.
316 if (has_open_trackable_browsers_) {
317 // Closing a window can never make has_open_trackable_browsers_ go from
318 // false to true, so only update it if already true.
319 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
321 if (should_record_close_as_pending())
322 pending_window_close_ids_.insert(window_id.id());
324 window_closing_ids_.insert(window_id.id());
327 void SessionService::WindowClosed(const SessionID& window_id) {
328 if (!ShouldTrackChangesToWindow(window_id))
331 windows_tracking_.erase(window_id.id());
333 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
334 window_closing_ids_.erase(window_id.id());
335 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
336 } else if (pending_window_close_ids_.find(window_id.id()) ==
337 pending_window_close_ids_.end()) {
338 // We'll hit this if user closed the last tab in a window.
339 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
340 if (should_record_close_as_pending())
341 pending_window_close_ids_.insert(window_id.id());
343 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
345 // Clear session data if the last window for a profile has been closed and
346 // closing the last window would normally close Chrome, unless background mode
348 if (!has_open_trackable_browsers_ &&
349 !browser_defaults::kBrowserAliveWithNoWindows &&
350 !g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
351 DeleteSessionOnlyData(profile());
355 void SessionService::SetWindowType(const SessionID& window_id,
358 if (!should_track_changes_for_browser_type(type, app_type))
361 windows_tracking_.insert(window_id.id());
363 // The user created a new tabbed browser with our profile. Commit any
365 CommitPendingCloses();
367 has_open_trackable_browsers_ = true;
368 move_on_new_browser_ = true;
371 CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type)));
374 void SessionService::SetWindowAppName(
375 const SessionID& window_id,
376 const std::string& app_name) {
377 if (!ShouldTrackChangesToWindow(window_id))
380 ScheduleCommand(CreateSetTabExtensionAppIDCommand(
381 kCommandSetWindowAppName,
386 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
387 const SessionID& tab_id,
389 if (!ShouldTrackChangesToWindow(window_id))
392 TabNavigationPathPrunedFromBackPayload payload = { 0 };
393 payload.id = tab_id.id();
394 payload.index = count;
395 SessionCommand* command =
396 new SessionCommand(kCommandTabNavigationPathPrunedFromBack,
398 memcpy(command->contents(), &payload, sizeof(payload));
399 ScheduleCommand(command);
402 void SessionService::TabNavigationPathPrunedFromFront(
403 const SessionID& window_id,
404 const SessionID& tab_id,
406 if (!ShouldTrackChangesToWindow(window_id))
409 // Update the range of indices.
410 if (tab_to_available_range_.find(tab_id.id()) !=
411 tab_to_available_range_.end()) {
412 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
413 range.first = std::max(0, range.first - count);
414 range.second = std::max(0, range.second - count);
417 TabNavigationPathPrunedFromFrontPayload payload = { 0 };
418 payload.id = tab_id.id();
419 payload.index = count;
420 SessionCommand* command =
421 new SessionCommand(kCommandTabNavigationPathPrunedFromFront,
423 memcpy(command->contents(), &payload, sizeof(payload));
424 ScheduleCommand(command);
427 void SessionService::UpdateTabNavigation(
428 const SessionID& window_id,
429 const SessionID& tab_id,
430 const SerializedNavigationEntry& navigation) {
431 if (!ShouldTrackEntry(navigation.virtual_url()) ||
432 !ShouldTrackChangesToWindow(window_id)) {
436 if (tab_to_available_range_.find(tab_id.id()) !=
437 tab_to_available_range_.end()) {
438 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
439 range.first = std::min(navigation.index(), range.first);
440 range.second = std::max(navigation.index(), range.second);
442 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation,
443 tab_id.id(), navigation));
446 void SessionService::TabRestored(WebContents* tab, bool pinned) {
447 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
448 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
451 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1,
452 pinned, &pending_commands(), NULL);
456 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
457 const SessionID& tab_id,
459 if (!ShouldTrackChangesToWindow(window_id))
462 if (tab_to_available_range_.find(tab_id.id()) !=
463 tab_to_available_range_.end()) {
464 if (index < tab_to_available_range_[tab_id.id()].first ||
465 index > tab_to_available_range_[tab_id.id()].second) {
466 // The new index is outside the range of what we've archived, schedule
468 ResetFromCurrentBrowsers();
472 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index));
475 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
477 if (!ShouldTrackChangesToWindow(window_id))
480 ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index));
483 void SessionService::SetTabUserAgentOverride(
484 const SessionID& window_id,
485 const SessionID& tab_id,
486 const std::string& user_agent_override) {
487 if (!ShouldTrackChangesToWindow(window_id))
490 ScheduleCommand(CreateSetTabUserAgentOverrideCommand(
491 kCommandSetTabUserAgentOverride, tab_id.id(), user_agent_override));
494 CancelableTaskTracker::TaskId SessionService::GetLastSession(
495 const SessionCallback& callback,
496 CancelableTaskTracker* tracker) {
497 // OnGotSessionCommands maps the SessionCommands to browser state, then run
499 return ScheduleGetLastSessionCommands(
500 base::Bind(&SessionService::OnGotSessionCommands,
501 base::Unretained(this), callback),
505 void SessionService::Save() {
506 bool had_commands = !pending_commands().empty();
507 BaseSessionService::Save();
509 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
510 &last_updated_save_time_);
511 content::NotificationService::current()->Notify(
512 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
513 content::Source<Profile>(profile()),
514 content::NotificationService::NoDetails());
518 void SessionService::Init() {
519 // Register for the notifications we're interested in.
520 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
521 content::NotificationService::AllSources());
522 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
523 content::NotificationService::AllSources());
524 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
525 content::NotificationService::AllSources());
526 // Wait for NOTIFICATION_BROWSER_WINDOW_READY so that is_app() is set.
527 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_WINDOW_READY,
528 content::NotificationService::AllBrowserContextsAndSources());
530 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
531 content::NotificationService::AllSources());
533 BrowserList::AddObserver(this);
536 bool SessionService::processed_any_commands() {
537 return backend()->inited() || !pending_commands().empty();
540 bool SessionService::ShouldNewWindowStartSession() {
541 // ChromeOS and OSX have different ideas of application lifetime than
542 // the other platforms.
543 // On ChromeOS opening a new window should never start a new session.
544 #if defined(OS_CHROMEOS)
545 if (!force_browser_not_alive_with_no_windows_)
548 if (!has_open_trackable_browsers_ &&
549 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
550 !SessionRestore::IsRestoring(profile())
551 #if defined(OS_MACOSX)
552 // On OSX, a new window should not start a new session if it was opened
553 // from the dock or the menubar.
554 && !app_controller_mac::IsOpeningNewWindow()
562 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
564 if (ShouldNewWindowStartSession()) {
565 // We're going from no tabbed browsers to a tabbed browser (and not in
566 // process startup), restore the last session.
567 if (move_on_new_browser_) {
568 // Make the current session the last.
569 MoveCurrentSessionToLastSession();
570 move_on_new_browser_ = false;
572 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
573 *CommandLine::ForCurrentProcess(), profile());
574 if (pref.type == SessionStartupPref::LAST) {
575 SessionRestore::RestoreSession(
577 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
578 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
586 void SessionService::Observe(int type,
587 const content::NotificationSource& source,
588 const content::NotificationDetails& details) {
589 // All of our messages have the NavigationController as the source.
591 case chrome::NOTIFICATION_BROWSER_WINDOW_READY: {
592 Browser* browser = content::Source<Browser>(source).ptr();
593 if (!ShouldTrackBrowser(browser))
596 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
597 RestoreIfNecessary(std::vector<GURL>(), browser);
598 SetWindowType(browser->session_id(), browser->type(), app_type);
599 SetWindowAppName(browser->session_id(), browser->app_name());
603 case content::NOTIFICATION_NAV_LIST_PRUNED: {
604 WebContents* web_contents =
605 content::Source<content::NavigationController>(source).ptr()->
607 SessionTabHelper* session_tab_helper =
608 SessionTabHelper::FromWebContents(web_contents);
609 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
611 content::Details<content::PrunedDetails> pruned_details(details);
612 if (pruned_details->from_front) {
613 TabNavigationPathPrunedFromFront(
614 session_tab_helper->window_id(),
615 session_tab_helper->session_id(),
616 pruned_details->count);
618 TabNavigationPathPrunedFromBack(
619 session_tab_helper->window_id(),
620 session_tab_helper->session_id(),
621 web_contents->GetController().GetEntryCount());
623 RecordSessionUpdateHistogramData(type,
624 &last_updated_nav_list_pruned_time_);
628 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
629 WebContents* web_contents =
630 content::Source<content::NavigationController>(source).ptr()->
632 SessionTabHelper* session_tab_helper =
633 SessionTabHelper::FromWebContents(web_contents);
634 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
636 content::Details<content::EntryChangedDetails> changed(details);
637 const SerializedNavigationEntry navigation =
638 SerializedNavigationEntry::FromNavigationEntry(
639 changed->index, *changed->changed_entry);
640 UpdateTabNavigation(session_tab_helper->window_id(),
641 session_tab_helper->session_id(),
646 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
647 WebContents* web_contents =
648 content::Source<content::NavigationController>(source).ptr()->
650 SessionTabHelper* session_tab_helper =
651 SessionTabHelper::FromWebContents(web_contents);
652 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
654 int current_entry_index =
655 web_contents->GetController().GetCurrentEntryIndex();
656 SetSelectedNavigationIndex(
657 session_tab_helper->window_id(),
658 session_tab_helper->session_id(),
659 current_entry_index);
660 const SerializedNavigationEntry navigation =
661 SerializedNavigationEntry::FromNavigationEntry(
663 *web_contents->GetController().GetEntryAtIndex(
664 current_entry_index));
666 session_tab_helper->window_id(),
667 session_tab_helper->session_id(),
669 content::Details<content::LoadCommittedDetails> changed(details);
670 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
671 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
672 RecordSessionUpdateHistogramData(type,
673 &last_updated_nav_entry_commit_time_);
678 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
679 extensions::TabHelper* extension_tab_helper =
680 content::Source<extensions::TabHelper>(source).ptr();
681 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
685 if (extension_tab_helper->extension_app()) {
686 SessionTabHelper* session_tab_helper =
687 SessionTabHelper::FromWebContents(
688 extension_tab_helper->web_contents());
689 SetTabExtensionAppID(session_tab_helper->window_id(),
690 session_tab_helper->session_id(),
691 extension_tab_helper->extension_app()->id());
701 void SessionService::OnBrowserSetLastActive(Browser* browser) {
702 if (ShouldTrackBrowser(browser))
703 ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id()));
706 void SessionService::SetTabExtensionAppID(
707 const SessionID& window_id,
708 const SessionID& tab_id,
709 const std::string& extension_app_id) {
710 if (!ShouldTrackChangesToWindow(window_id))
713 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID,
714 tab_id.id(), extension_app_id));
717 SessionCommand* SessionService::CreateSetSelectedTabInWindow(
718 const SessionID& window_id,
720 SelectedTabInIndexPayload payload = { 0 };
721 payload.id = window_id.id();
722 payload.index = index;
723 SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex,
725 memcpy(command->contents(), &payload, sizeof(payload));
729 SessionCommand* SessionService::CreateSetTabWindowCommand(
730 const SessionID& window_id,
731 const SessionID& tab_id) {
732 SessionID::id_type payload[] = { window_id.id(), tab_id.id() };
733 SessionCommand* command =
734 new SessionCommand(kCommandSetTabWindow, sizeof(payload));
735 memcpy(command->contents(), payload, sizeof(payload));
739 SessionCommand* SessionService::CreateSetWindowBoundsCommand(
740 const SessionID& window_id,
741 const gfx::Rect& bounds,
742 ui::WindowShowState show_state) {
743 WindowBoundsPayload3 payload = { 0 };
744 payload.window_id = window_id.id();
745 payload.x = bounds.x();
746 payload.y = bounds.y();
747 payload.w = bounds.width();
748 payload.h = bounds.height();
749 payload.show_state = AdjustShowState(show_state);
750 SessionCommand* command = new SessionCommand(kCommandSetWindowBounds3,
752 memcpy(command->contents(), &payload, sizeof(payload));
756 SessionCommand* SessionService::CreateSetTabIndexInWindowCommand(
757 const SessionID& tab_id,
759 TabIndexInWindowPayload payload = { 0 };
760 payload.id = tab_id.id();
761 payload.index = new_index;
762 SessionCommand* command =
763 new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload));
764 memcpy(command->contents(), &payload, sizeof(payload));
768 SessionCommand* SessionService::CreateTabClosedCommand(
769 const SessionID::id_type tab_id) {
770 ClosedPayload payload;
771 // Because of what appears to be a compiler bug setting payload to {0} doesn't
772 // set the padding to 0, resulting in Purify reporting an UMR when we write
773 // the structure to disk. To avoid this we explicitly memset the struct.
774 memset(&payload, 0, sizeof(payload));
776 payload.close_time = Time::Now().ToInternalValue();
777 SessionCommand* command =
778 new SessionCommand(kCommandTabClosed, sizeof(payload));
779 memcpy(command->contents(), &payload, sizeof(payload));
783 SessionCommand* SessionService::CreateWindowClosedCommand(
784 const SessionID::id_type window_id) {
785 ClosedPayload payload;
786 // See comment in CreateTabClosedCommand as to why we do this.
787 memset(&payload, 0, sizeof(payload));
788 payload.id = window_id;
789 payload.close_time = Time::Now().ToInternalValue();
790 SessionCommand* command =
791 new SessionCommand(kCommandWindowClosed, sizeof(payload));
792 memcpy(command->contents(), &payload, sizeof(payload));
796 SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand(
797 const SessionID& tab_id,
799 SelectedNavigationIndexPayload payload = { 0 };
800 payload.id = tab_id.id();
801 payload.index = index;
802 SessionCommand* command = new SessionCommand(
803 kCommandSetSelectedNavigationIndex, sizeof(payload));
804 memcpy(command->contents(), &payload, sizeof(payload));
808 SessionCommand* SessionService::CreateSetWindowTypeCommand(
809 const SessionID& window_id,
811 WindowTypePayload payload = { 0 };
812 payload.id = window_id.id();
813 payload.index = static_cast<int32>(type);
814 SessionCommand* command = new SessionCommand(
815 kCommandSetWindowType, sizeof(payload));
816 memcpy(command->contents(), &payload, sizeof(payload));
820 SessionCommand* SessionService::CreatePinnedStateCommand(
821 const SessionID& tab_id,
823 PinnedStatePayload payload = { 0 };
824 payload.tab_id = tab_id.id();
825 payload.pinned_state = is_pinned;
826 SessionCommand* command =
827 new SessionCommand(kCommandSetPinnedState, sizeof(payload));
828 memcpy(command->contents(), &payload, sizeof(payload));
832 SessionCommand* SessionService::CreateSessionStorageAssociatedCommand(
833 const SessionID& tab_id,
834 const std::string& session_storage_persistent_id) {
836 pickle.WriteInt(tab_id.id());
837 pickle.WriteString(session_storage_persistent_id);
838 return new SessionCommand(kCommandSessionStorageAssociated, pickle);
841 SessionCommand* SessionService::CreateSetActiveWindowCommand(
842 const SessionID& window_id) {
843 ActiveWindowPayload payload = 0;
844 payload = window_id.id();
845 SessionCommand* command =
846 new SessionCommand(kCommandSetActiveWindow, sizeof(payload));
847 memcpy(command->contents(), &payload, sizeof(payload));
851 void SessionService::OnGotSessionCommands(
852 const SessionCallback& callback,
853 ScopedVector<SessionCommand> commands) {
854 ScopedVector<SessionWindow> valid_windows;
855 SessionID::id_type active_window_id = 0;
857 RestoreSessionFromCommands(
858 commands.get(), &valid_windows.get(), &active_window_id);
859 callback.Run(valid_windows.Pass(), active_window_id);
862 void SessionService::RestoreSessionFromCommands(
863 const std::vector<SessionCommand*>& commands,
864 std::vector<SessionWindow*>* valid_windows,
865 SessionID::id_type* active_window_id) {
866 std::map<int, SessionTab*> tabs;
867 std::map<int, SessionWindow*> windows;
869 VLOG(1) << "RestoreSessionFromCommands " << commands.size();
870 if (CreateTabsAndWindows(commands, &tabs, &windows, active_window_id)) {
871 AddTabsToWindows(&tabs, &windows);
872 SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows);
873 UpdateSelectedTabIndex(valid_windows);
875 STLDeleteValues(&tabs);
876 // Don't delete conents of windows, that is done by the caller as all
877 // valid windows are added to valid_windows.
880 void SessionService::UpdateSelectedTabIndex(
881 std::vector<SessionWindow*>* windows) {
882 for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
883 i != windows->end(); ++i) {
884 // See note in SessionWindow as to why we do this.
886 for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin();
887 j != (*i)->tabs.end(); ++j) {
888 if ((*j)->tab_visual_index == (*i)->selected_tab_index) {
889 new_index = static_cast<int>(j - (*i)->tabs.begin());
893 (*i)->selected_tab_index = new_index;
897 SessionWindow* SessionService::GetWindow(
898 SessionID::id_type window_id,
899 IdToSessionWindow* windows) {
900 std::map<int, SessionWindow*>::iterator i = windows->find(window_id);
901 if (i == windows->end()) {
902 SessionWindow* window = new SessionWindow();
903 window->window_id.set_id(window_id);
904 (*windows)[window_id] = window;
910 SessionTab* SessionService::GetTab(
911 SessionID::id_type tab_id,
912 IdToSessionTab* tabs) {
914 std::map<int, SessionTab*>::iterator i = tabs->find(tab_id);
915 if (i == tabs->end()) {
916 SessionTab* tab = new SessionTab();
917 tab->tab_id.set_id(tab_id);
918 (*tabs)[tab_id] = tab;
924 std::vector<SerializedNavigationEntry>::iterator
925 SessionService::FindClosestNavigationWithIndex(
926 std::vector<SerializedNavigationEntry>* navigations,
929 for (std::vector<SerializedNavigationEntry>::iterator
930 i = navigations->begin(); i != navigations->end(); ++i) {
931 if (i->index() >= index)
934 return navigations->end();
937 // Function used in sorting windows. Sorting is done based on window id. As
938 // window ids increment for each new window, this effectively sorts by creation
940 static bool WindowOrderSortFunction(const SessionWindow* w1,
941 const SessionWindow* w2) {
942 return w1->window_id.id() < w2->window_id.id();
945 // Compares the two tabs based on visual index.
946 static bool TabVisualIndexSortFunction(const SessionTab* t1,
947 const SessionTab* t2) {
948 const int delta = t1->tab_visual_index - t2->tab_visual_index;
949 return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0);
952 void SessionService::SortTabsBasedOnVisualOrderAndPrune(
953 std::map<int, SessionWindow*>* windows,
954 std::vector<SessionWindow*>* valid_windows) {
955 std::map<int, SessionWindow*>::iterator i = windows->begin();
956 while (i != windows->end()) {
957 SessionWindow* window = i->second;
958 AppType app_type = window->app_name.empty() ? TYPE_NORMAL : TYPE_APP;
959 if (window->tabs.empty() || window->is_constrained ||
960 !should_track_changes_for_browser_type(
961 static_cast<Browser::Type>(window->type),
966 // Valid window; sort the tabs and add it to the list of valid windows.
967 std::sort(window->tabs.begin(), window->tabs.end(),
968 &TabVisualIndexSortFunction);
969 // Otherwise, add the window such that older windows appear first.
970 if (valid_windows->empty()) {
971 valid_windows->push_back(window);
973 valid_windows->insert(
974 std::upper_bound(valid_windows->begin(), valid_windows->end(),
975 window, &WindowOrderSortFunction),
983 void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs,
984 std::map<int, SessionWindow*>* windows) {
985 VLOG(1) << "AddTabsToWindws";
986 VLOG(1) << "Tabs " << tabs->size() << ", windows " << windows->size();
987 std::map<int, SessionTab*>::iterator i = tabs->begin();
988 while (i != tabs->end()) {
989 SessionTab* tab = i->second;
990 if (tab->window_id.id() && !tab->navigations.empty()) {
991 SessionWindow* window = GetWindow(tab->window_id.id(), windows);
992 window->tabs.push_back(tab);
995 // See note in SessionTab as to why we do this.
996 std::vector<SerializedNavigationEntry>::iterator j =
997 FindClosestNavigationWithIndex(&(tab->navigations),
998 tab->current_navigation_index);
999 if (j == tab->navigations.end()) {
1000 tab->current_navigation_index =
1001 static_cast<int>(tab->navigations.size() - 1);
1003 tab->current_navigation_index =
1004 static_cast<int>(j - tab->navigations.begin());
1007 // Never got a set tab index in window, or tabs are empty, nothing
1014 bool SessionService::CreateTabsAndWindows(
1015 const std::vector<SessionCommand*>& data,
1016 std::map<int, SessionTab*>* tabs,
1017 std::map<int, SessionWindow*>* windows,
1018 SessionID::id_type* active_window_id) {
1019 // If the file is corrupt (command with wrong size, or unknown command), we
1020 // still return true and attempt to restore what we we can.
1021 VLOG(1) << "CreateTabsAndWindows";
1023 startup_metric_utils::ScopedSlowStartupUMA
1024 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
1026 for (std::vector<SessionCommand*>::const_iterator i = data.begin();
1027 i != data.end(); ++i) {
1028 const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
1029 const SessionCommand* command = *i;
1031 VLOG(1) << "Read command " << (int) command->id();
1032 switch (command->id()) {
1033 case kCommandSetTabWindow: {
1034 SessionID::id_type payload[2];
1035 if (!command->GetPayload(payload, sizeof(payload))) {
1036 VLOG(1) << "Failed reading command " << command->id();
1039 GetTab(payload[1], tabs)->window_id.set_id(payload[0]);
1043 // This is here for forward migration only. New data is saved with
1044 // |kCommandSetWindowBounds3|.
1045 case kCommandSetWindowBounds2: {
1046 WindowBoundsPayload2 payload;
1047 if (!command->GetPayload(&payload, sizeof(payload))) {
1048 VLOG(1) << "Failed reading command " << command->id();
1051 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1055 GetWindow(payload.window_id, windows)->show_state =
1056 payload.is_maximized ?
1057 ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL;
1061 case kCommandSetWindowBounds3: {
1062 WindowBoundsPayload3 payload;
1063 if (!command->GetPayload(&payload, sizeof(payload))) {
1064 VLOG(1) << "Failed reading command " << command->id();
1067 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1071 // SHOW_STATE_INACTIVE is not persisted.
1072 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
1073 if (payload.show_state > ui::SHOW_STATE_DEFAULT &&
1074 payload.show_state < ui::SHOW_STATE_END &&
1075 payload.show_state != ui::SHOW_STATE_INACTIVE) {
1076 show_state = static_cast<ui::WindowShowState>(payload.show_state);
1080 GetWindow(payload.window_id, windows)->show_state = show_state;
1084 case kCommandSetTabIndexInWindow: {
1085 TabIndexInWindowPayload payload;
1086 if (!command->GetPayload(&payload, sizeof(payload))) {
1087 VLOG(1) << "Failed reading command " << command->id();
1090 GetTab(payload.id, tabs)->tab_visual_index = payload.index;
1094 case kCommandTabClosedObsolete:
1095 case kCommandWindowClosedObsolete:
1096 case kCommandTabClosed:
1097 case kCommandWindowClosed: {
1098 ClosedPayload payload;
1099 if (!command->GetPayload(&payload, sizeof(payload)) &&
1100 !MigrateClosedPayload(*command, &payload)) {
1101 VLOG(1) << "Failed reading command " << command->id();
1104 if (command->id() == kCommandTabClosed ||
1105 command->id() == kCommandTabClosedObsolete) {
1106 delete GetTab(payload.id, tabs);
1107 tabs->erase(payload.id);
1109 delete GetWindow(payload.id, windows);
1110 windows->erase(payload.id);
1115 case kCommandTabNavigationPathPrunedFromBack: {
1116 TabNavigationPathPrunedFromBackPayload payload;
1117 if (!command->GetPayload(&payload, sizeof(payload))) {
1118 VLOG(1) << "Failed reading command " << command->id();
1121 SessionTab* tab = GetTab(payload.id, tabs);
1122 tab->navigations.erase(
1123 FindClosestNavigationWithIndex(&(tab->navigations), payload.index),
1124 tab->navigations.end());
1128 case kCommandTabNavigationPathPrunedFromFront: {
1129 TabNavigationPathPrunedFromFrontPayload payload;
1130 if (!command->GetPayload(&payload, sizeof(payload)) ||
1131 payload.index <= 0) {
1132 VLOG(1) << "Failed reading command " << command->id();
1135 SessionTab* tab = GetTab(payload.id, tabs);
1137 // Update the selected navigation index.
1138 tab->current_navigation_index =
1139 std::max(-1, tab->current_navigation_index - payload.index);
1141 // And update the index of existing navigations.
1142 for (std::vector<SerializedNavigationEntry>::iterator
1143 i = tab->navigations.begin();
1144 i != tab->navigations.end();) {
1145 i->set_index(i->index() - payload.index);
1147 i = tab->navigations.erase(i);
1154 case kCommandUpdateTabNavigation: {
1155 SerializedNavigationEntry navigation;
1156 SessionID::id_type tab_id;
1157 if (!RestoreUpdateTabNavigationCommand(
1158 *command, &navigation, &tab_id)) {
1159 VLOG(1) << "Failed reading command " << command->id();
1162 SessionTab* tab = GetTab(tab_id, tabs);
1163 std::vector<SerializedNavigationEntry>::iterator i =
1164 FindClosestNavigationWithIndex(&(tab->navigations),
1165 navigation.index());
1166 if (i != tab->navigations.end() && i->index() == navigation.index())
1169 tab->navigations.insert(i, navigation);
1173 case kCommandSetSelectedNavigationIndex: {
1174 SelectedNavigationIndexPayload payload;
1175 if (!command->GetPayload(&payload, sizeof(payload))) {
1176 VLOG(1) << "Failed reading command " << command->id();
1179 GetTab(payload.id, tabs)->current_navigation_index = payload.index;
1183 case kCommandSetSelectedTabInIndex: {
1184 SelectedTabInIndexPayload payload;
1185 if (!command->GetPayload(&payload, sizeof(payload))) {
1186 VLOG(1) << "Failed reading command " << command->id();
1189 GetWindow(payload.id, windows)->selected_tab_index = payload.index;
1193 case kCommandSetWindowType: {
1194 WindowTypePayload payload;
1195 if (!command->GetPayload(&payload, sizeof(payload))) {
1196 VLOG(1) << "Failed reading command " << command->id();
1199 GetWindow(payload.id, windows)->is_constrained = false;
1200 GetWindow(payload.id, windows)->type =
1201 BrowserTypeForWindowType(
1202 static_cast<WindowType>(payload.index));
1206 case kCommandSetPinnedState: {
1207 PinnedStatePayload payload;
1208 if (!command->GetPayload(&payload, sizeof(payload))) {
1209 VLOG(1) << "Failed reading command " << command->id();
1212 GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state;
1216 case kCommandSetWindowAppName: {
1217 SessionID::id_type window_id;
1218 std::string app_name;
1219 if (!RestoreSetWindowAppNameCommand(*command, &window_id, &app_name))
1222 GetWindow(window_id, windows)->app_name.swap(app_name);
1226 case kCommandSetExtensionAppID: {
1227 SessionID::id_type tab_id;
1228 std::string extension_app_id;
1229 if (!RestoreSetTabExtensionAppIDCommand(
1230 *command, &tab_id, &extension_app_id)) {
1231 VLOG(1) << "Failed reading command " << command->id();
1235 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id);
1239 case kCommandSetTabUserAgentOverride: {
1240 SessionID::id_type tab_id;
1241 std::string user_agent_override;
1242 if (!RestoreSetTabUserAgentOverrideCommand(
1243 *command, &tab_id, &user_agent_override)) {
1247 GetTab(tab_id, tabs)->user_agent_override.swap(user_agent_override);
1251 case kCommandSessionStorageAssociated: {
1252 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1253 SessionID::id_type command_tab_id;
1254 std::string session_storage_persistent_id;
1255 PickleIterator iter(*command_pickle.get());
1256 if (!command_pickle->ReadInt(&iter, &command_tab_id) ||
1257 !command_pickle->ReadString(&iter, &session_storage_persistent_id))
1259 // Associate the session storage back.
1260 GetTab(command_tab_id, tabs)->session_storage_persistent_id =
1261 session_storage_persistent_id;
1265 case kCommandSetActiveWindow: {
1266 ActiveWindowPayload payload;
1267 if (!command->GetPayload(&payload, sizeof(payload))) {
1268 VLOG(1) << "Failed reading command " << command->id();
1271 *active_window_id = payload;
1276 VLOG(1) << "Failed reading an unknown command " << command->id();
1283 void SessionService::BuildCommandsForTab(const SessionID& window_id,
1285 int index_in_window,
1287 std::vector<SessionCommand*>* commands,
1288 IdToRange* tab_to_available_range) {
1289 DCHECK(tab && commands && window_id.id());
1290 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
1291 const SessionID& session_id(session_tab_helper->session_id());
1292 commands->push_back(CreateSetTabWindowCommand(window_id, session_id));
1294 const int current_index = tab->GetController().GetCurrentEntryIndex();
1295 const int min_index = std::max(0,
1296 current_index - max_persist_navigation_count);
1297 const int max_index =
1298 std::min(current_index + max_persist_navigation_count,
1299 tab->GetController().GetEntryCount());
1300 const int pending_index = tab->GetController().GetPendingEntryIndex();
1301 if (tab_to_available_range) {
1302 (*tab_to_available_range)[session_id.id()] =
1303 std::pair<int, int>(min_index, max_index);
1307 commands->push_back(CreatePinnedStateCommand(session_id, true));
1310 extensions::TabHelper* extensions_tab_helper =
1311 extensions::TabHelper::FromWebContents(tab);
1312 if (extensions_tab_helper->extension_app()) {
1313 commands->push_back(
1314 CreateSetTabExtensionAppIDCommand(
1315 kCommandSetExtensionAppID, session_id.id(),
1316 extensions_tab_helper->extension_app()->id()));
1319 const std::string& ua_override = tab->GetUserAgentOverride();
1320 if (!ua_override.empty()) {
1321 commands->push_back(
1322 CreateSetTabUserAgentOverrideCommand(
1323 kCommandSetTabUserAgentOverride, session_id.id(), ua_override));
1326 for (int i = min_index; i < max_index; ++i) {
1327 const NavigationEntry* entry = (i == pending_index) ?
1328 tab->GetController().GetPendingEntry() :
1329 tab->GetController().GetEntryAtIndex(i);
1331 if (ShouldTrackEntry(entry->GetVirtualURL())) {
1332 const SerializedNavigationEntry navigation =
1333 SerializedNavigationEntry::FromNavigationEntry(i, *entry);
1334 commands->push_back(
1335 CreateUpdateTabNavigationCommand(
1336 kCommandUpdateTabNavigation, session_id.id(), navigation));
1339 commands->push_back(
1340 CreateSetSelectedNavigationIndexCommand(session_id, current_index));
1342 if (index_in_window != -1) {
1343 commands->push_back(
1344 CreateSetTabIndexInWindowCommand(session_id, index_in_window));
1347 // Record the association between the sessionStorage namespace and the tab.
1348 content::SessionStorageNamespace* session_storage_namespace =
1349 tab->GetController().GetDefaultSessionStorageNamespace();
1350 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1351 session_tab_helper->session_id(),
1352 session_storage_namespace->persistent_id()));
1355 void SessionService::BuildCommandsForBrowser(
1357 std::vector<SessionCommand*>* commands,
1358 IdToRange* tab_to_available_range,
1359 std::set<SessionID::id_type>* windows_to_track) {
1360 DCHECK(browser && commands);
1361 DCHECK(browser->session_id().id());
1363 commands->push_back(
1364 CreateSetWindowBoundsCommand(browser->session_id(),
1365 browser->window()->GetRestoredBounds(),
1366 browser->window()->GetRestoredState()));
1368 commands->push_back(CreateSetWindowTypeCommand(
1369 browser->session_id(), WindowTypeForBrowserType(browser->type())));
1371 if (!browser->app_name().empty()) {
1372 commands->push_back(CreateSetWindowAppNameCommand(
1373 kCommandSetWindowAppName,
1374 browser->session_id().id(),
1375 browser->app_name()));
1378 windows_to_track->insert(browser->session_id().id());
1379 TabStripModel* tab_strip = browser->tab_strip_model();
1380 for (int i = 0; i < tab_strip->count(); ++i) {
1381 WebContents* tab = tab_strip->GetWebContentsAt(i);
1383 BuildCommandsForTab(browser->session_id(), tab, i,
1384 tab_strip->IsTabPinned(i),
1385 commands, tab_to_available_range);
1388 commands->push_back(
1389 CreateSetSelectedTabInWindow(browser->session_id(),
1390 browser->tab_strip_model()->active_index()));
1393 void SessionService::BuildCommandsFromBrowsers(
1394 std::vector<SessionCommand*>* commands,
1395 IdToRange* tab_to_available_range,
1396 std::set<SessionID::id_type>* windows_to_track) {
1398 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1399 Browser* browser = *it;
1400 // Make sure the browser has tabs and a window. Browser's destructor
1401 // removes itself from the BrowserList. When a browser is closed the
1402 // destructor is not necessarily run immediately. This means it's possible
1403 // for us to get a handle to a browser that is about to be removed. If
1404 // the tab count is 0 or the window is NULL, the browser is about to be
1405 // deleted, so we ignore it.
1406 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
1407 browser->window()) {
1408 BuildCommandsForBrowser(browser, commands, tab_to_available_range,
1414 void SessionService::ScheduleReset() {
1415 set_pending_reset(true);
1416 STLDeleteElements(&pending_commands());
1417 tab_to_available_range_.clear();
1418 windows_tracking_.clear();
1419 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_,
1420 &windows_tracking_);
1421 if (!windows_tracking_.empty()) {
1422 // We're lazily created on startup and won't get an initial batch of
1423 // SetWindowType messages. Set these here to make sure our state is correct.
1424 has_open_trackable_browsers_ = true;
1425 move_on_new_browser_ = true;
1430 bool SessionService::ReplacePendingCommand(SessionCommand* command) {
1431 // We optimize page navigations, which can happen quite frequently and
1432 // are expensive. And activation is like Highlander, there can only be one!
1433 if (command->id() != kCommandUpdateTabNavigation &&
1434 command->id() != kCommandSetActiveWindow) {
1437 for (std::vector<SessionCommand*>::reverse_iterator i =
1438 pending_commands().rbegin(); i != pending_commands().rend(); ++i) {
1439 SessionCommand* existing_command = *i;
1440 if (command->id() == kCommandUpdateTabNavigation &&
1441 existing_command->id() == kCommandUpdateTabNavigation) {
1442 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1443 PickleIterator iterator(*command_pickle);
1444 SessionID::id_type command_tab_id;
1445 int command_nav_index;
1446 if (!command_pickle->ReadInt(&iterator, &command_tab_id) ||
1447 !command_pickle->ReadInt(&iterator, &command_nav_index)) {
1450 SessionID::id_type existing_tab_id;
1451 int existing_nav_index;
1453 // Creating a pickle like this means the Pickle references the data from
1454 // the command. Make sure we delete the pickle before the command, else
1455 // the pickle references deleted memory.
1456 scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle());
1457 iterator = PickleIterator(*existing_pickle);
1458 if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) ||
1459 !existing_pickle->ReadInt(&iterator, &existing_nav_index)) {
1463 if (existing_tab_id == command_tab_id &&
1464 existing_nav_index == command_nav_index) {
1465 // existing_command is an update for the same tab/index pair. Replace
1466 // it with the new one. We need to add to the end of the list just in
1467 // case there is a prune command after the update command.
1468 delete existing_command;
1469 pending_commands().erase(i.base() - 1);
1470 pending_commands().push_back(command);
1475 if (command->id() == kCommandSetActiveWindow &&
1476 existing_command->id() == kCommandSetActiveWindow) {
1478 delete existing_command;
1485 void SessionService::ScheduleCommand(SessionCommand* command) {
1487 if (ReplacePendingCommand(command))
1489 BaseSessionService::ScheduleCommand(command);
1490 // Don't schedule a reset on tab closed/window closed. Otherwise we may
1491 // lose tabs/windows we want to restore from if we exit right after this.
1492 if (!pending_reset() && pending_window_close_ids_.empty() &&
1493 commands_since_reset() >= kWritesPerReset &&
1494 (command->id() != kCommandTabClosed &&
1495 command->id() != kCommandWindowClosed)) {
1500 void SessionService::CommitPendingCloses() {
1501 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
1502 i != pending_tab_close_ids_.end(); ++i) {
1503 ScheduleCommand(CreateTabClosedCommand(*i));
1505 pending_tab_close_ids_.clear();
1507 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
1508 i != pending_window_close_ids_.end(); ++i) {
1509 ScheduleCommand(CreateWindowClosedCommand(*i));
1511 pending_window_close_ids_.clear();
1514 bool SessionService::IsOnlyOneTabLeft() const {
1516 // We're testing, always return false.
1520 int window_count = 0;
1521 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1522 Browser* browser = *it;
1523 const SessionID::id_type window_id = browser->session_id().id();
1524 if (ShouldTrackBrowser(browser) &&
1525 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
1526 if (++window_count > 1)
1528 // By the time this is invoked the tab has been removed. As such, we use
1529 // > 0 here rather than > 1.
1530 if (browser->tab_strip_model()->count() > 0)
1537 bool SessionService::HasOpenTrackableBrowsers(
1538 const SessionID& window_id) const {
1540 // We're testing, always return false.
1544 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1545 Browser* browser = *it;
1546 const SessionID::id_type browser_id = browser->session_id().id();
1547 if (browser_id != window_id.id() &&
1548 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
1549 ShouldTrackBrowser(browser)) {
1556 bool SessionService::ShouldTrackChangesToWindow(
1557 const SessionID& window_id) const {
1558 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
1561 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
1562 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
1563 return browser->profile() == profile() &&
1564 should_track_changes_for_browser_type(browser->type(), app_type);
1567 bool SessionService::should_track_changes_for_browser_type(Browser::Type type,
1569 #if defined(OS_CHROMEOS)
1570 // Restore app popups for chromeos alone.
1571 if (type == Browser::TYPE_POPUP && app_type == TYPE_APP)
1575 return type == Browser::TYPE_TABBED ||
1576 (type == Browser::TYPE_POPUP && browser_defaults::kRestorePopups);
1579 SessionService::WindowType SessionService::WindowTypeForBrowserType(
1580 Browser::Type type) {
1582 case Browser::TYPE_POPUP:
1584 case Browser::TYPE_TABBED:
1592 Browser::Type SessionService::BrowserTypeForWindowType(WindowType type) {
1595 return Browser::TYPE_POPUP;
1598 return Browser::TYPE_TABBED;
1602 void SessionService::RecordSessionUpdateHistogramData(int type,
1603 base::TimeTicks* last_updated_time) {
1604 if (!last_updated_time->is_null()) {
1605 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
1606 // We're interested in frequent updates periods longer than
1608 bool use_long_period = false;
1609 if (delta >= save_delay_in_mins_) {
1610 use_long_period = true;
1613 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
1614 RecordUpdatedSaveTime(delta, use_long_period);
1615 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1617 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
1618 RecordUpdatedTabClosed(delta, use_long_period);
1619 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1621 case content::NOTIFICATION_NAV_LIST_PRUNED:
1622 RecordUpdatedNavListPruned(delta, use_long_period);
1623 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1625 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
1626 RecordUpdatedNavEntryCommit(delta, use_long_period);
1627 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1630 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1634 (*last_updated_time) = base::TimeTicks::Now();
1637 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
1638 bool use_long_period) {
1639 std::string name("SessionRestore.TabClosedPeriod");
1640 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1642 // 2500ms is the default save delay.
1643 save_delay_in_millis_,
1644 save_delay_in_mins_,
1646 if (use_long_period) {
1647 std::string long_name_("SessionRestore.TabClosedLongPeriod");
1648 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1650 save_delay_in_mins_,
1656 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1657 bool use_long_period) {
1658 std::string name("SessionRestore.NavigationListPrunedPeriod");
1659 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1661 // 2500ms is the default save delay.
1662 save_delay_in_millis_,
1663 save_delay_in_mins_,
1665 if (use_long_period) {
1666 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1667 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1669 save_delay_in_mins_,
1675 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1676 bool use_long_period) {
1677 std::string name("SessionRestore.NavEntryCommittedPeriod");
1678 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1680 // 2500ms is the default save delay.
1681 save_delay_in_millis_,
1682 save_delay_in_mins_,
1684 if (use_long_period) {
1685 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1686 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1688 save_delay_in_mins_,
1694 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1695 bool use_long_period) {
1696 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1697 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1699 // 2500ms is the default save delay.
1700 save_delay_in_millis_,
1701 save_delay_in_mins_,
1703 if (use_long_period) {
1704 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1705 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1707 save_delay_in_mins_,
1713 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1714 bool use_long_period) {
1715 std::string name("SessionRestore.SavePeriod");
1716 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1718 // 2500ms is the default save delay.
1719 save_delay_in_millis_,
1720 save_delay_in_mins_,
1722 if (use_long_period) {
1723 std::string long_name_("SessionRestore.SaveLongPeriod");
1724 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1726 save_delay_in_mins_,
1732 void SessionService::TabInserted(WebContents* contents) {
1733 SessionTabHelper* session_tab_helper =
1734 SessionTabHelper::FromWebContents(contents);
1735 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
1737 SetTabWindow(session_tab_helper->window_id(),
1738 session_tab_helper->session_id());
1739 extensions::TabHelper* extensions_tab_helper =
1740 extensions::TabHelper::FromWebContents(contents);
1741 if (extensions_tab_helper &&
1742 extensions_tab_helper->extension_app()) {
1743 SetTabExtensionAppID(
1744 session_tab_helper->window_id(),
1745 session_tab_helper->session_id(),
1746 extensions_tab_helper->extension_app()->id());
1749 // Record the association between the SessionStorageNamespace and the
1752 // TODO(ajwong): This should be processing the whole map rather than
1753 // just the default. This in particular will not work for tabs with only
1754 // isolated apps which won't have a default partition.
1755 content::SessionStorageNamespace* session_storage_namespace =
1756 contents->GetController().GetDefaultSessionStorageNamespace();
1757 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1758 session_tab_helper->session_id(),
1759 session_storage_namespace->persistent_id()));
1760 session_storage_namespace->SetShouldPersist(true);
1763 void SessionService::TabClosing(WebContents* contents) {
1764 // Allow the associated sessionStorage to get deleted; it won't be needed
1765 // in the session restore.
1766 content::SessionStorageNamespace* session_storage_namespace =
1767 contents->GetController().GetDefaultSessionStorageNamespace();
1768 session_storage_namespace->SetShouldPersist(false);
1769 SessionTabHelper* session_tab_helper =
1770 SessionTabHelper::FromWebContents(contents);
1771 TabClosed(session_tab_helper->window_id(),
1772 session_tab_helper->session_id(),
1773 contents->GetClosedByUserGesture());
1774 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
1775 &last_updated_tab_closed_time_);