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 // Persisted versions of ui::WindowShowState that are written to disk and can
142 enum PersistedWindowShowState
{
143 // SHOW_STATE_DEFAULT (0) never persisted.
144 PERSISTED_SHOW_STATE_NORMAL
= 1,
145 PERSISTED_SHOW_STATE_MINIMIZED
= 2,
146 PERSISTED_SHOW_STATE_MAXIMIZED
= 3,
147 // SHOW_STATE_INACTIVE (4) never persisted.
148 PERSISTED_SHOW_STATE_FULLSCREEN
= 5,
149 PERSISTED_SHOW_STATE_DETACHED_DEPRECATED
= 6,
150 PERSISTED_SHOW_STATE_END
= 6
153 // Assert to ensure PersistedWindowShowState is updated if ui::WindowShowState
155 COMPILE_ASSERT(ui::SHOW_STATE_END
==
156 static_cast<ui::WindowShowState
>(PERSISTED_SHOW_STATE_END
),
157 persisted_show_state_mismatch
);
159 // Returns the show state to store to disk based |state|.
160 PersistedWindowShowState
ShowStateToPersistedShowState(
161 ui::WindowShowState state
) {
163 case ui::SHOW_STATE_NORMAL
:
164 return PERSISTED_SHOW_STATE_NORMAL
;
165 case ui::SHOW_STATE_MINIMIZED
:
166 return PERSISTED_SHOW_STATE_MINIMIZED
;
167 case ui::SHOW_STATE_MAXIMIZED
:
168 return PERSISTED_SHOW_STATE_MAXIMIZED
;
169 case ui::SHOW_STATE_FULLSCREEN
:
170 return PERSISTED_SHOW_STATE_FULLSCREEN
;
172 case ui::SHOW_STATE_DEFAULT
:
173 case ui::SHOW_STATE_INACTIVE
:
174 return PERSISTED_SHOW_STATE_NORMAL
;
176 case ui::SHOW_STATE_END
:
180 return PERSISTED_SHOW_STATE_NORMAL
;
183 // Lints show state values when read back from persited disk.
184 ui::WindowShowState
PersistedShowStateToShowState(int state
) {
186 case PERSISTED_SHOW_STATE_NORMAL
:
187 return ui::SHOW_STATE_NORMAL
;
188 case PERSISTED_SHOW_STATE_MINIMIZED
:
189 return ui::SHOW_STATE_MINIMIZED
;
190 case PERSISTED_SHOW_STATE_MAXIMIZED
:
191 return ui::SHOW_STATE_MAXIMIZED
;
192 case PERSISTED_SHOW_STATE_FULLSCREEN
:
193 return ui::SHOW_STATE_FULLSCREEN
;
194 case PERSISTED_SHOW_STATE_DETACHED_DEPRECATED
:
195 return ui::SHOW_STATE_NORMAL
;
198 return ui::SHOW_STATE_NORMAL
;
201 // Migrates a |ClosedPayload|, returning true on success (migration was
202 // necessary and happened), or false (migration was not necessary or was not
204 bool MigrateClosedPayload(const SessionCommand
& command
,
205 ClosedPayload
* payload
) {
206 #if defined(OS_CHROMEOS)
207 // Pre M17 versions of chromeos were 32bit. Post M17 is 64 bit. Apparently the
208 // 32 bit versions of chrome on pre M17 resulted in a sizeof 12 for the
209 // ClosedPayload, where as post M17 64-bit gives a sizeof 16 (presumably the
210 // struct is padded).
211 if ((command
.id() == kCommandWindowClosedObsolete
||
212 command
.id() == kCommandTabClosedObsolete
) &&
213 command
.size() == 12 && sizeof(payload
->id
) == 4 &&
214 sizeof(payload
->close_time
) == 8) {
215 memcpy(&payload
->id
, command
.contents(), 4);
216 memcpy(&payload
->close_time
, command
.contents() + 4, 8);
228 // SessionService -------------------------------------------------------------
230 SessionService::SessionService(Profile
* profile
)
231 : BaseSessionService(SESSION_RESTORE
, profile
, base::FilePath()),
232 has_open_trackable_browsers_(false),
233 move_on_new_browser_(false),
234 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
235 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
236 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
237 force_browser_not_alive_with_no_windows_(false),
238 weak_factory_(this) {
242 SessionService::SessionService(const base::FilePath
& save_path
)
243 : BaseSessionService(SESSION_RESTORE
, NULL
, save_path
),
244 has_open_trackable_browsers_(false),
245 move_on_new_browser_(false),
246 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
247 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
248 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
249 force_browser_not_alive_with_no_windows_(false),
250 weak_factory_(this) {
254 SessionService::~SessionService() {
255 // The BrowserList should outlive the SessionService since it's static and
256 // the SessionService is a KeyedService.
257 BrowserList::RemoveObserver(this);
261 bool SessionService::RestoreIfNecessary(const std::vector
<GURL
>& urls_to_open
) {
262 return RestoreIfNecessary(urls_to_open
, NULL
);
265 void SessionService::ResetFromCurrentBrowsers() {
269 void SessionService::MoveCurrentSessionToLastSession() {
270 pending_tab_close_ids_
.clear();
271 window_closing_ids_
.clear();
272 pending_window_close_ids_
.clear();
276 RunTaskOnBackendThread(
277 FROM_HERE
, base::Bind(&SessionBackend::MoveCurrentSessionToLastSession
,
281 void SessionService::SetTabWindow(const SessionID
& window_id
,
282 const SessionID
& tab_id
) {
283 if (!ShouldTrackChangesToWindow(window_id
))
286 ScheduleCommand(CreateSetTabWindowCommand(window_id
, tab_id
));
289 void SessionService::SetWindowBounds(const SessionID
& window_id
,
290 const gfx::Rect
& bounds
,
291 ui::WindowShowState show_state
) {
292 if (!ShouldTrackChangesToWindow(window_id
))
295 ScheduleCommand(CreateSetWindowBoundsCommand(window_id
, bounds
, show_state
));
298 void SessionService::SetTabIndexInWindow(const SessionID
& window_id
,
299 const SessionID
& tab_id
,
301 if (!ShouldTrackChangesToWindow(window_id
))
304 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id
, new_index
));
307 void SessionService::SetPinnedState(const SessionID
& window_id
,
308 const SessionID
& tab_id
,
310 if (!ShouldTrackChangesToWindow(window_id
))
313 ScheduleCommand(CreatePinnedStateCommand(tab_id
, is_pinned
));
316 void SessionService::TabClosed(const SessionID
& window_id
,
317 const SessionID
& tab_id
,
318 bool closed_by_user_gesture
) {
320 return; // Hapens when the tab is replaced.
322 if (!ShouldTrackChangesToWindow(window_id
))
325 IdToRange::iterator i
= tab_to_available_range_
.find(tab_id
.id());
326 if (i
!= tab_to_available_range_
.end())
327 tab_to_available_range_
.erase(i
);
329 if (find(pending_window_close_ids_
.begin(), pending_window_close_ids_
.end(),
330 window_id
.id()) != pending_window_close_ids_
.end()) {
331 // Tab is in last window. Don't commit it immediately, instead add it to the
332 // list of tabs to close. If the user creates another window, the close is
334 pending_tab_close_ids_
.insert(tab_id
.id());
335 } else if (find(window_closing_ids_
.begin(), window_closing_ids_
.end(),
336 window_id
.id()) != window_closing_ids_
.end() ||
337 !IsOnlyOneTabLeft() ||
338 closed_by_user_gesture
) {
339 // Close is the result of one of the following:
340 // . window close (and it isn't the last window).
341 // . closing a tab and there are other windows/tabs open.
342 // . closed by a user gesture.
343 // In all cases we need to mark the tab as explicitly closed.
344 ScheduleCommand(CreateTabClosedCommand(tab_id
.id()));
346 // User closed the last tab in the last tabbed browser. Don't mark the
348 pending_tab_close_ids_
.insert(tab_id
.id());
349 has_open_trackable_browsers_
= false;
353 void SessionService::WindowOpened(Browser
* browser
) {
354 if (!ShouldTrackBrowser(browser
))
357 AppType app_type
= browser
->is_app() ? TYPE_APP
: TYPE_NORMAL
;
358 RestoreIfNecessary(std::vector
<GURL
>(), browser
);
359 SetWindowType(browser
->session_id(), browser
->type(), app_type
);
360 SetWindowAppName(browser
->session_id(), browser
->app_name());
363 void SessionService::WindowClosing(const SessionID
& window_id
) {
364 if (!ShouldTrackChangesToWindow(window_id
))
367 // The window is about to close. If there are other tabbed browsers with the
368 // same original profile commit the close immediately.
370 // NOTE: if the user chooses the exit menu item session service is destroyed
371 // and this code isn't hit.
372 if (has_open_trackable_browsers_
) {
373 // Closing a window can never make has_open_trackable_browsers_ go from
374 // false to true, so only update it if already true.
375 has_open_trackable_browsers_
= HasOpenTrackableBrowsers(window_id
);
377 bool use_pending_close
= !has_open_trackable_browsers_
;
378 if (!use_pending_close
) {
379 // Somewhat outside of "normal behavior" is profile locking. In this case
380 // (when IsSiginRequired has already been set True), we're closing all
381 // browser windows in turn but want them all to be restored when the user
382 // unlocks. To accomplish this, we do a "pending close" on all windows
383 // instead of just the last one (which has no open_trackable_browsers).
384 // http://crbug.com/356818
386 // Some editions (like iOS) don't have a profile_manager and some tests
387 // don't supply one so be lenient.
388 if (g_browser_process
) {
389 ProfileManager
* profile_manager
= g_browser_process
->profile_manager();
390 if (profile_manager
) {
391 ProfileInfoCache
& profile_info
=
392 profile_manager
->GetProfileInfoCache();
393 size_t profile_index
= profile_info
.GetIndexOfProfileWithPath(
394 profile()->GetPath());
395 use_pending_close
= profile_index
!= std::string::npos
&&
396 profile_info
.ProfileIsSigninRequiredAtIndex(profile_index
);
400 if (use_pending_close
)
401 pending_window_close_ids_
.insert(window_id
.id());
403 window_closing_ids_
.insert(window_id
.id());
406 void SessionService::WindowClosed(const SessionID
& window_id
) {
407 if (!ShouldTrackChangesToWindow(window_id
)) {
408 // The last window may be one that is not tracked.
409 MaybeDeleteSessionOnlyData();
413 windows_tracking_
.erase(window_id
.id());
415 if (window_closing_ids_
.find(window_id
.id()) != window_closing_ids_
.end()) {
416 window_closing_ids_
.erase(window_id
.id());
417 ScheduleCommand(CreateWindowClosedCommand(window_id
.id()));
418 } else if (pending_window_close_ids_
.find(window_id
.id()) ==
419 pending_window_close_ids_
.end()) {
420 // We'll hit this if user closed the last tab in a window.
421 has_open_trackable_browsers_
= HasOpenTrackableBrowsers(window_id
);
422 if (!has_open_trackable_browsers_
)
423 pending_window_close_ids_
.insert(window_id
.id());
425 ScheduleCommand(CreateWindowClosedCommand(window_id
.id()));
427 MaybeDeleteSessionOnlyData();
430 void SessionService::SetWindowType(const SessionID
& window_id
,
433 if (!should_track_changes_for_browser_type(type
, app_type
))
436 windows_tracking_
.insert(window_id
.id());
438 // The user created a new tabbed browser with our profile. Commit any
440 CommitPendingCloses();
442 has_open_trackable_browsers_
= true;
443 move_on_new_browser_
= true;
446 CreateSetWindowTypeCommand(window_id
, WindowTypeForBrowserType(type
)));
449 void SessionService::SetWindowAppName(
450 const SessionID
& window_id
,
451 const std::string
& app_name
) {
452 if (!ShouldTrackChangesToWindow(window_id
))
455 ScheduleCommand(CreateSetTabExtensionAppIDCommand(
456 kCommandSetWindowAppName
,
461 void SessionService::TabNavigationPathPrunedFromBack(const SessionID
& window_id
,
462 const SessionID
& tab_id
,
464 if (!ShouldTrackChangesToWindow(window_id
))
467 TabNavigationPathPrunedFromBackPayload payload
= { 0 };
468 payload
.id
= tab_id
.id();
469 payload
.index
= count
;
470 SessionCommand
* command
=
471 new SessionCommand(kCommandTabNavigationPathPrunedFromBack
,
473 memcpy(command
->contents(), &payload
, sizeof(payload
));
474 ScheduleCommand(command
);
477 void SessionService::TabNavigationPathPrunedFromFront(
478 const SessionID
& window_id
,
479 const SessionID
& tab_id
,
481 if (!ShouldTrackChangesToWindow(window_id
))
484 // Update the range of indices.
485 if (tab_to_available_range_
.find(tab_id
.id()) !=
486 tab_to_available_range_
.end()) {
487 std::pair
<int, int>& range
= tab_to_available_range_
[tab_id
.id()];
488 range
.first
= std::max(0, range
.first
- count
);
489 range
.second
= std::max(0, range
.second
- count
);
492 TabNavigationPathPrunedFromFrontPayload payload
= { 0 };
493 payload
.id
= tab_id
.id();
494 payload
.index
= count
;
495 SessionCommand
* command
=
496 new SessionCommand(kCommandTabNavigationPathPrunedFromFront
,
498 memcpy(command
->contents(), &payload
, sizeof(payload
));
499 ScheduleCommand(command
);
502 void SessionService::UpdateTabNavigation(
503 const SessionID
& window_id
,
504 const SessionID
& tab_id
,
505 const SerializedNavigationEntry
& navigation
) {
506 if (!ShouldTrackEntry(navigation
.virtual_url()) ||
507 !ShouldTrackChangesToWindow(window_id
)) {
511 if (tab_to_available_range_
.find(tab_id
.id()) !=
512 tab_to_available_range_
.end()) {
513 std::pair
<int, int>& range
= tab_to_available_range_
[tab_id
.id()];
514 range
.first
= std::min(navigation
.index(), range
.first
);
515 range
.second
= std::max(navigation
.index(), range
.second
);
517 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation
,
518 tab_id
.id(), navigation
));
521 void SessionService::TabRestored(WebContents
* tab
, bool pinned
) {
522 SessionTabHelper
* session_tab_helper
= SessionTabHelper::FromWebContents(tab
);
523 if (!ShouldTrackChangesToWindow(session_tab_helper
->window_id()))
526 BuildCommandsForTab(session_tab_helper
->window_id(), tab
, -1,
527 pinned
, &pending_commands(), NULL
);
531 void SessionService::SetSelectedNavigationIndex(const SessionID
& window_id
,
532 const SessionID
& tab_id
,
534 if (!ShouldTrackChangesToWindow(window_id
))
537 if (tab_to_available_range_
.find(tab_id
.id()) !=
538 tab_to_available_range_
.end()) {
539 if (index
< tab_to_available_range_
[tab_id
.id()].first
||
540 index
> tab_to_available_range_
[tab_id
.id()].second
) {
541 // The new index is outside the range of what we've archived, schedule
543 ResetFromCurrentBrowsers();
547 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id
, index
));
550 void SessionService::SetSelectedTabInWindow(const SessionID
& window_id
,
552 if (!ShouldTrackChangesToWindow(window_id
))
555 ScheduleCommand(CreateSetSelectedTabInWindow(window_id
, index
));
558 void SessionService::SetTabUserAgentOverride(
559 const SessionID
& window_id
,
560 const SessionID
& tab_id
,
561 const std::string
& user_agent_override
) {
562 if (!ShouldTrackChangesToWindow(window_id
))
565 ScheduleCommand(CreateSetTabUserAgentOverrideCommand(
566 kCommandSetTabUserAgentOverride
, tab_id
.id(), user_agent_override
));
569 base::CancelableTaskTracker::TaskId
SessionService::GetLastSession(
570 const SessionCallback
& callback
,
571 base::CancelableTaskTracker
* tracker
) {
572 // OnGotSessionCommands maps the SessionCommands to browser state, then run
574 return ScheduleGetLastSessionCommands(
575 base::Bind(&SessionService::OnGotSessionCommands
,
576 weak_factory_
.GetWeakPtr(), callback
),
580 void SessionService::Save() {
581 bool had_commands
= !pending_commands().empty();
582 BaseSessionService::Save();
584 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED
,
585 &last_updated_save_time_
);
586 content::NotificationService::current()->Notify(
587 chrome::NOTIFICATION_SESSION_SERVICE_SAVED
,
588 content::Source
<Profile
>(profile()),
589 content::NotificationService::NoDetails());
593 void SessionService::Init() {
594 // Register for the notifications we're interested in.
595 registrar_
.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED
,
596 content::NotificationService::AllSources());
597 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED
,
598 content::NotificationService::AllSources());
599 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
600 content::NotificationService::AllSources());
602 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
,
603 content::NotificationService::AllSources());
605 BrowserList::AddObserver(this);
608 bool SessionService::processed_any_commands() {
609 return backend()->inited() || !pending_commands().empty();
612 bool SessionService::ShouldNewWindowStartSession() {
613 // ChromeOS and OSX have different ideas of application lifetime than
614 // the other platforms.
615 // On ChromeOS opening a new window should never start a new session.
616 #if defined(OS_CHROMEOS)
617 if (!force_browser_not_alive_with_no_windows_
)
620 if (!has_open_trackable_browsers_
&&
621 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
622 !SessionRestore::IsRestoring(profile())
623 #if defined(OS_MACOSX)
624 // On OSX, a new window should not start a new session if it was opened
625 // from the dock or the menubar.
626 && !app_controller_mac::IsOpeningNewWindow()
634 bool SessionService::RestoreIfNecessary(const std::vector
<GURL
>& urls_to_open
,
636 if (ShouldNewWindowStartSession()) {
637 // We're going from no tabbed browsers to a tabbed browser (and not in
638 // process startup), restore the last session.
639 if (move_on_new_browser_
) {
640 // Make the current session the last.
641 MoveCurrentSessionToLastSession();
642 move_on_new_browser_
= false;
644 SessionStartupPref pref
= StartupBrowserCreator::GetSessionStartupPref(
645 *CommandLine::ForCurrentProcess(), profile());
646 if (pref
.type
== SessionStartupPref::LAST
) {
647 SessionRestore::RestoreSession(
649 browser
? browser
->host_desktop_type() : chrome::GetActiveDesktop(),
650 browser
? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER
,
658 void SessionService::Observe(int type
,
659 const content::NotificationSource
& source
,
660 const content::NotificationDetails
& details
) {
661 // All of our messages have the NavigationController as the source.
663 case content::NOTIFICATION_NAV_LIST_PRUNED
: {
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 content::Details
<content::PrunedDetails
> pruned_details(details
);
672 if (pruned_details
->from_front
) {
673 TabNavigationPathPrunedFromFront(
674 session_tab_helper
->window_id(),
675 session_tab_helper
->session_id(),
676 pruned_details
->count
);
678 TabNavigationPathPrunedFromBack(
679 session_tab_helper
->window_id(),
680 session_tab_helper
->session_id(),
681 web_contents
->GetController().GetEntryCount());
683 RecordSessionUpdateHistogramData(type
,
684 &last_updated_nav_list_pruned_time_
);
688 case content::NOTIFICATION_NAV_ENTRY_CHANGED
: {
689 WebContents
* web_contents
=
690 content::Source
<content::NavigationController
>(source
).ptr()->
692 SessionTabHelper
* session_tab_helper
=
693 SessionTabHelper::FromWebContents(web_contents
);
694 if (!session_tab_helper
|| web_contents
->GetBrowserContext() != profile())
696 content::Details
<content::EntryChangedDetails
> changed(details
);
697 const SerializedNavigationEntry navigation
=
698 SerializedNavigationEntry::FromNavigationEntry(
699 changed
->index
, *changed
->changed_entry
);
700 UpdateTabNavigation(session_tab_helper
->window_id(),
701 session_tab_helper
->session_id(),
706 case content::NOTIFICATION_NAV_ENTRY_COMMITTED
: {
707 WebContents
* web_contents
=
708 content::Source
<content::NavigationController
>(source
).ptr()->
710 SessionTabHelper
* session_tab_helper
=
711 SessionTabHelper::FromWebContents(web_contents
);
712 if (!session_tab_helper
|| web_contents
->GetBrowserContext() != profile())
714 int current_entry_index
=
715 web_contents
->GetController().GetCurrentEntryIndex();
716 SetSelectedNavigationIndex(
717 session_tab_helper
->window_id(),
718 session_tab_helper
->session_id(),
719 current_entry_index
);
720 const SerializedNavigationEntry navigation
=
721 SerializedNavigationEntry::FromNavigationEntry(
723 *web_contents
->GetController().GetEntryAtIndex(
724 current_entry_index
));
726 session_tab_helper
->window_id(),
727 session_tab_helper
->session_id(),
729 content::Details
<content::LoadCommittedDetails
> changed(details
);
730 if (changed
->type
== content::NAVIGATION_TYPE_NEW_PAGE
||
731 changed
->type
== content::NAVIGATION_TYPE_EXISTING_PAGE
) {
732 RecordSessionUpdateHistogramData(type
,
733 &last_updated_nav_entry_commit_time_
);
738 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
: {
739 extensions::TabHelper
* extension_tab_helper
=
740 content::Source
<extensions::TabHelper
>(source
).ptr();
741 if (extension_tab_helper
->web_contents()->GetBrowserContext() !=
745 if (extension_tab_helper
->extension_app()) {
746 SessionTabHelper
* session_tab_helper
=
747 SessionTabHelper::FromWebContents(
748 extension_tab_helper
->web_contents());
749 SetTabExtensionAppID(session_tab_helper
->window_id(),
750 session_tab_helper
->session_id(),
751 extension_tab_helper
->extension_app()->id());
761 void SessionService::OnBrowserSetLastActive(Browser
* browser
) {
762 if (ShouldTrackBrowser(browser
))
763 ScheduleCommand(CreateSetActiveWindowCommand(browser
->session_id()));
766 void SessionService::SetTabExtensionAppID(
767 const SessionID
& window_id
,
768 const SessionID
& tab_id
,
769 const std::string
& extension_app_id
) {
770 if (!ShouldTrackChangesToWindow(window_id
))
773 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID
,
774 tab_id
.id(), extension_app_id
));
777 SessionCommand
* SessionService::CreateSetSelectedTabInWindow(
778 const SessionID
& window_id
,
780 SelectedTabInIndexPayload payload
= { 0 };
781 payload
.id
= window_id
.id();
782 payload
.index
= index
;
783 SessionCommand
* command
= new SessionCommand(kCommandSetSelectedTabInIndex
,
785 memcpy(command
->contents(), &payload
, sizeof(payload
));
789 SessionCommand
* SessionService::CreateSetTabWindowCommand(
790 const SessionID
& window_id
,
791 const SessionID
& tab_id
) {
792 SessionID::id_type payload
[] = { window_id
.id(), tab_id
.id() };
793 SessionCommand
* command
=
794 new SessionCommand(kCommandSetTabWindow
, sizeof(payload
));
795 memcpy(command
->contents(), payload
, sizeof(payload
));
799 SessionCommand
* SessionService::CreateSetWindowBoundsCommand(
800 const SessionID
& window_id
,
801 const gfx::Rect
& bounds
,
802 ui::WindowShowState show_state
) {
803 WindowBoundsPayload3 payload
= { 0 };
804 payload
.window_id
= window_id
.id();
805 payload
.x
= bounds
.x();
806 payload
.y
= bounds
.y();
807 payload
.w
= bounds
.width();
808 payload
.h
= bounds
.height();
809 payload
.show_state
= ShowStateToPersistedShowState(show_state
);
810 SessionCommand
* command
= new SessionCommand(kCommandSetWindowBounds3
,
812 memcpy(command
->contents(), &payload
, sizeof(payload
));
816 SessionCommand
* SessionService::CreateSetTabIndexInWindowCommand(
817 const SessionID
& tab_id
,
819 TabIndexInWindowPayload payload
= { 0 };
820 payload
.id
= tab_id
.id();
821 payload
.index
= new_index
;
822 SessionCommand
* command
=
823 new SessionCommand(kCommandSetTabIndexInWindow
, sizeof(payload
));
824 memcpy(command
->contents(), &payload
, sizeof(payload
));
828 SessionCommand
* SessionService::CreateTabClosedCommand(
829 const SessionID::id_type tab_id
) {
830 ClosedPayload payload
;
831 // Because of what appears to be a compiler bug setting payload to {0} doesn't
832 // set the padding to 0, resulting in Purify reporting an UMR when we write
833 // the structure to disk. To avoid this we explicitly memset the struct.
834 memset(&payload
, 0, sizeof(payload
));
836 payload
.close_time
= Time::Now().ToInternalValue();
837 SessionCommand
* command
=
838 new SessionCommand(kCommandTabClosed
, sizeof(payload
));
839 memcpy(command
->contents(), &payload
, sizeof(payload
));
843 SessionCommand
* SessionService::CreateWindowClosedCommand(
844 const SessionID::id_type window_id
) {
845 ClosedPayload payload
;
846 // See comment in CreateTabClosedCommand as to why we do this.
847 memset(&payload
, 0, sizeof(payload
));
848 payload
.id
= window_id
;
849 payload
.close_time
= Time::Now().ToInternalValue();
850 SessionCommand
* command
=
851 new SessionCommand(kCommandWindowClosed
, sizeof(payload
));
852 memcpy(command
->contents(), &payload
, sizeof(payload
));
856 SessionCommand
* SessionService::CreateSetSelectedNavigationIndexCommand(
857 const SessionID
& tab_id
,
859 SelectedNavigationIndexPayload payload
= { 0 };
860 payload
.id
= tab_id
.id();
861 payload
.index
= index
;
862 SessionCommand
* command
= new SessionCommand(
863 kCommandSetSelectedNavigationIndex
, sizeof(payload
));
864 memcpy(command
->contents(), &payload
, sizeof(payload
));
868 SessionCommand
* SessionService::CreateSetWindowTypeCommand(
869 const SessionID
& window_id
,
871 WindowTypePayload payload
= { 0 };
872 payload
.id
= window_id
.id();
873 payload
.index
= static_cast<int32
>(type
);
874 SessionCommand
* command
= new SessionCommand(
875 kCommandSetWindowType
, sizeof(payload
));
876 memcpy(command
->contents(), &payload
, sizeof(payload
));
880 SessionCommand
* SessionService::CreatePinnedStateCommand(
881 const SessionID
& tab_id
,
883 PinnedStatePayload payload
= { 0 };
884 payload
.tab_id
= tab_id
.id();
885 payload
.pinned_state
= is_pinned
;
886 SessionCommand
* command
=
887 new SessionCommand(kCommandSetPinnedState
, sizeof(payload
));
888 memcpy(command
->contents(), &payload
, sizeof(payload
));
892 SessionCommand
* SessionService::CreateSessionStorageAssociatedCommand(
893 const SessionID
& tab_id
,
894 const std::string
& session_storage_persistent_id
) {
896 pickle
.WriteInt(tab_id
.id());
897 pickle
.WriteString(session_storage_persistent_id
);
898 return new SessionCommand(kCommandSessionStorageAssociated
, pickle
);
901 SessionCommand
* SessionService::CreateSetActiveWindowCommand(
902 const SessionID
& window_id
) {
903 ActiveWindowPayload payload
= 0;
904 payload
= window_id
.id();
905 SessionCommand
* command
=
906 new SessionCommand(kCommandSetActiveWindow
, sizeof(payload
));
907 memcpy(command
->contents(), &payload
, sizeof(payload
));
911 void SessionService::OnGotSessionCommands(
912 const SessionCallback
& callback
,
913 ScopedVector
<SessionCommand
> commands
) {
914 ScopedVector
<SessionWindow
> valid_windows
;
915 SessionID::id_type active_window_id
= 0;
917 RestoreSessionFromCommands(
918 commands
.get(), &valid_windows
.get(), &active_window_id
);
919 callback
.Run(valid_windows
.Pass(), active_window_id
);
922 void SessionService::RestoreSessionFromCommands(
923 const std::vector
<SessionCommand
*>& commands
,
924 std::vector
<SessionWindow
*>* valid_windows
,
925 SessionID::id_type
* active_window_id
) {
926 std::map
<int, SessionTab
*> tabs
;
927 std::map
<int, SessionWindow
*> windows
;
929 VLOG(1) << "RestoreSessionFromCommands " << commands
.size();
930 if (CreateTabsAndWindows(commands
, &tabs
, &windows
, active_window_id
)) {
931 AddTabsToWindows(&tabs
, &windows
);
932 SortTabsBasedOnVisualOrderAndPrune(&windows
, valid_windows
);
933 UpdateSelectedTabIndex(valid_windows
);
935 STLDeleteValues(&tabs
);
936 // Don't delete conents of windows, that is done by the caller as all
937 // valid windows are added to valid_windows.
940 void SessionService::UpdateSelectedTabIndex(
941 std::vector
<SessionWindow
*>* windows
) {
942 for (std::vector
<SessionWindow
*>::const_iterator i
= windows
->begin();
943 i
!= windows
->end(); ++i
) {
944 // See note in SessionWindow as to why we do this.
946 for (std::vector
<SessionTab
*>::const_iterator j
= (*i
)->tabs
.begin();
947 j
!= (*i
)->tabs
.end(); ++j
) {
948 if ((*j
)->tab_visual_index
== (*i
)->selected_tab_index
) {
949 new_index
= static_cast<int>(j
- (*i
)->tabs
.begin());
953 (*i
)->selected_tab_index
= new_index
;
957 SessionWindow
* SessionService::GetWindow(
958 SessionID::id_type window_id
,
959 IdToSessionWindow
* windows
) {
960 std::map
<int, SessionWindow
*>::iterator i
= windows
->find(window_id
);
961 if (i
== windows
->end()) {
962 SessionWindow
* window
= new SessionWindow();
963 window
->window_id
.set_id(window_id
);
964 (*windows
)[window_id
] = window
;
970 SessionTab
* SessionService::GetTab(
971 SessionID::id_type tab_id
,
972 IdToSessionTab
* tabs
) {
974 std::map
<int, SessionTab
*>::iterator i
= tabs
->find(tab_id
);
975 if (i
== tabs
->end()) {
976 SessionTab
* tab
= new SessionTab();
977 tab
->tab_id
.set_id(tab_id
);
978 (*tabs
)[tab_id
] = tab
;
984 std::vector
<SerializedNavigationEntry
>::iterator
985 SessionService::FindClosestNavigationWithIndex(
986 std::vector
<SerializedNavigationEntry
>* navigations
,
989 for (std::vector
<SerializedNavigationEntry
>::iterator
990 i
= navigations
->begin(); i
!= navigations
->end(); ++i
) {
991 if (i
->index() >= index
)
994 return navigations
->end();
997 // Function used in sorting windows. Sorting is done based on window id. As
998 // window ids increment for each new window, this effectively sorts by creation
1000 static bool WindowOrderSortFunction(const SessionWindow
* w1
,
1001 const SessionWindow
* w2
) {
1002 return w1
->window_id
.id() < w2
->window_id
.id();
1005 // Compares the two tabs based on visual index.
1006 static bool TabVisualIndexSortFunction(const SessionTab
* t1
,
1007 const SessionTab
* t2
) {
1008 const int delta
= t1
->tab_visual_index
- t2
->tab_visual_index
;
1009 return delta
== 0 ? (t1
->tab_id
.id() < t2
->tab_id
.id()) : (delta
< 0);
1012 void SessionService::SortTabsBasedOnVisualOrderAndPrune(
1013 std::map
<int, SessionWindow
*>* windows
,
1014 std::vector
<SessionWindow
*>* valid_windows
) {
1015 std::map
<int, SessionWindow
*>::iterator i
= windows
->begin();
1016 while (i
!= windows
->end()) {
1017 SessionWindow
* window
= i
->second
;
1018 AppType app_type
= window
->app_name
.empty() ? TYPE_NORMAL
: TYPE_APP
;
1019 if (window
->tabs
.empty() || window
->is_constrained
||
1020 !should_track_changes_for_browser_type(
1021 static_cast<Browser::Type
>(window
->type
),
1024 windows
->erase(i
++);
1026 // Valid window; sort the tabs and add it to the list of valid windows.
1027 std::sort(window
->tabs
.begin(), window
->tabs
.end(),
1028 &TabVisualIndexSortFunction
);
1029 // Otherwise, add the window such that older windows appear first.
1030 if (valid_windows
->empty()) {
1031 valid_windows
->push_back(window
);
1033 valid_windows
->insert(
1034 std::upper_bound(valid_windows
->begin(), valid_windows
->end(),
1035 window
, &WindowOrderSortFunction
),
1043 void SessionService::AddTabsToWindows(std::map
<int, SessionTab
*>* tabs
,
1044 std::map
<int, SessionWindow
*>* windows
) {
1045 VLOG(1) << "AddTabsToWindws";
1046 VLOG(1) << "Tabs " << tabs
->size() << ", windows " << windows
->size();
1047 std::map
<int, SessionTab
*>::iterator i
= tabs
->begin();
1048 while (i
!= tabs
->end()) {
1049 SessionTab
* tab
= i
->second
;
1050 if (tab
->window_id
.id() && !tab
->navigations
.empty()) {
1051 SessionWindow
* window
= GetWindow(tab
->window_id
.id(), windows
);
1052 window
->tabs
.push_back(tab
);
1055 // See note in SessionTab as to why we do this.
1056 std::vector
<SerializedNavigationEntry
>::iterator j
=
1057 FindClosestNavigationWithIndex(&(tab
->navigations
),
1058 tab
->current_navigation_index
);
1059 if (j
== tab
->navigations
.end()) {
1060 tab
->current_navigation_index
=
1061 static_cast<int>(tab
->navigations
.size() - 1);
1063 tab
->current_navigation_index
=
1064 static_cast<int>(j
- tab
->navigations
.begin());
1067 // Never got a set tab index in window, or tabs are empty, nothing
1074 bool SessionService::CreateTabsAndWindows(
1075 const std::vector
<SessionCommand
*>& data
,
1076 std::map
<int, SessionTab
*>* tabs
,
1077 std::map
<int, SessionWindow
*>* windows
,
1078 SessionID::id_type
* active_window_id
) {
1079 // If the file is corrupt (command with wrong size, or unknown command), we
1080 // still return true and attempt to restore what we we can.
1081 VLOG(1) << "CreateTabsAndWindows";
1083 startup_metric_utils::ScopedSlowStartupUMA
1084 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
1086 for (std::vector
<SessionCommand
*>::const_iterator i
= data
.begin();
1087 i
!= data
.end(); ++i
) {
1088 const SessionCommand::id_type kCommandSetWindowBounds2
= 10;
1089 const SessionCommand
* command
= *i
;
1091 VLOG(1) << "Read command " << (int) command
->id();
1092 switch (command
->id()) {
1093 case kCommandSetTabWindow
: {
1094 SessionID::id_type payload
[2];
1095 if (!command
->GetPayload(payload
, sizeof(payload
))) {
1096 VLOG(1) << "Failed reading command " << command
->id();
1099 GetTab(payload
[1], tabs
)->window_id
.set_id(payload
[0]);
1103 // This is here for forward migration only. New data is saved with
1104 // |kCommandSetWindowBounds3|.
1105 case kCommandSetWindowBounds2
: {
1106 WindowBoundsPayload2 payload
;
1107 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1108 VLOG(1) << "Failed reading command " << command
->id();
1111 GetWindow(payload
.window_id
, windows
)->bounds
.SetRect(payload
.x
,
1115 GetWindow(payload
.window_id
, windows
)->show_state
=
1116 payload
.is_maximized
?
1117 ui::SHOW_STATE_MAXIMIZED
: ui::SHOW_STATE_NORMAL
;
1121 case kCommandSetWindowBounds3
: {
1122 WindowBoundsPayload3 payload
;
1123 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1124 VLOG(1) << "Failed reading command " << command
->id();
1127 GetWindow(payload
.window_id
, windows
)->bounds
.SetRect(payload
.x
,
1131 GetWindow(payload
.window_id
, windows
)->show_state
=
1132 PersistedShowStateToShowState(payload
.show_state
);
1136 case kCommandSetTabIndexInWindow
: {
1137 TabIndexInWindowPayload payload
;
1138 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1139 VLOG(1) << "Failed reading command " << command
->id();
1142 GetTab(payload
.id
, tabs
)->tab_visual_index
= payload
.index
;
1146 case kCommandTabClosedObsolete
:
1147 case kCommandWindowClosedObsolete
:
1148 case kCommandTabClosed
:
1149 case kCommandWindowClosed
: {
1150 ClosedPayload payload
;
1151 if (!command
->GetPayload(&payload
, sizeof(payload
)) &&
1152 !MigrateClosedPayload(*command
, &payload
)) {
1153 VLOG(1) << "Failed reading command " << command
->id();
1156 if (command
->id() == kCommandTabClosed
||
1157 command
->id() == kCommandTabClosedObsolete
) {
1158 delete GetTab(payload
.id
, tabs
);
1159 tabs
->erase(payload
.id
);
1161 delete GetWindow(payload
.id
, windows
);
1162 windows
->erase(payload
.id
);
1167 case kCommandTabNavigationPathPrunedFromBack
: {
1168 TabNavigationPathPrunedFromBackPayload payload
;
1169 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1170 VLOG(1) << "Failed reading command " << command
->id();
1173 SessionTab
* tab
= GetTab(payload
.id
, tabs
);
1174 tab
->navigations
.erase(
1175 FindClosestNavigationWithIndex(&(tab
->navigations
), payload
.index
),
1176 tab
->navigations
.end());
1180 case kCommandTabNavigationPathPrunedFromFront
: {
1181 TabNavigationPathPrunedFromFrontPayload payload
;
1182 if (!command
->GetPayload(&payload
, sizeof(payload
)) ||
1183 payload
.index
<= 0) {
1184 VLOG(1) << "Failed reading command " << command
->id();
1187 SessionTab
* tab
= GetTab(payload
.id
, tabs
);
1189 // Update the selected navigation index.
1190 tab
->current_navigation_index
=
1191 std::max(-1, tab
->current_navigation_index
- payload
.index
);
1193 // And update the index of existing navigations.
1194 for (std::vector
<SerializedNavigationEntry
>::iterator
1195 i
= tab
->navigations
.begin();
1196 i
!= tab
->navigations
.end();) {
1197 i
->set_index(i
->index() - payload
.index
);
1199 i
= tab
->navigations
.erase(i
);
1206 case kCommandUpdateTabNavigation
: {
1207 SerializedNavigationEntry navigation
;
1208 SessionID::id_type tab_id
;
1209 if (!RestoreUpdateTabNavigationCommand(
1210 *command
, &navigation
, &tab_id
)) {
1211 VLOG(1) << "Failed reading command " << command
->id();
1214 SessionTab
* tab
= GetTab(tab_id
, tabs
);
1215 std::vector
<SerializedNavigationEntry
>::iterator i
=
1216 FindClosestNavigationWithIndex(&(tab
->navigations
),
1217 navigation
.index());
1218 if (i
!= tab
->navigations
.end() && i
->index() == navigation
.index())
1221 tab
->navigations
.insert(i
, navigation
);
1225 case kCommandSetSelectedNavigationIndex
: {
1226 SelectedNavigationIndexPayload payload
;
1227 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1228 VLOG(1) << "Failed reading command " << command
->id();
1231 GetTab(payload
.id
, tabs
)->current_navigation_index
= payload
.index
;
1235 case kCommandSetSelectedTabInIndex
: {
1236 SelectedTabInIndexPayload payload
;
1237 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1238 VLOG(1) << "Failed reading command " << command
->id();
1241 GetWindow(payload
.id
, windows
)->selected_tab_index
= payload
.index
;
1245 case kCommandSetWindowType
: {
1246 WindowTypePayload payload
;
1247 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1248 VLOG(1) << "Failed reading command " << command
->id();
1251 GetWindow(payload
.id
, windows
)->is_constrained
= false;
1252 GetWindow(payload
.id
, windows
)->type
=
1253 BrowserTypeForWindowType(
1254 static_cast<WindowType
>(payload
.index
));
1258 case kCommandSetPinnedState
: {
1259 PinnedStatePayload payload
;
1260 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1261 VLOG(1) << "Failed reading command " << command
->id();
1264 GetTab(payload
.tab_id
, tabs
)->pinned
= payload
.pinned_state
;
1268 case kCommandSetWindowAppName
: {
1269 SessionID::id_type window_id
;
1270 std::string app_name
;
1271 if (!RestoreSetWindowAppNameCommand(*command
, &window_id
, &app_name
))
1274 GetWindow(window_id
, windows
)->app_name
.swap(app_name
);
1278 case kCommandSetExtensionAppID
: {
1279 SessionID::id_type tab_id
;
1280 std::string extension_app_id
;
1281 if (!RestoreSetTabExtensionAppIDCommand(
1282 *command
, &tab_id
, &extension_app_id
)) {
1283 VLOG(1) << "Failed reading command " << command
->id();
1287 GetTab(tab_id
, tabs
)->extension_app_id
.swap(extension_app_id
);
1291 case kCommandSetTabUserAgentOverride
: {
1292 SessionID::id_type tab_id
;
1293 std::string user_agent_override
;
1294 if (!RestoreSetTabUserAgentOverrideCommand(
1295 *command
, &tab_id
, &user_agent_override
)) {
1299 GetTab(tab_id
, tabs
)->user_agent_override
.swap(user_agent_override
);
1303 case kCommandSessionStorageAssociated
: {
1304 scoped_ptr
<Pickle
> command_pickle(command
->PayloadAsPickle());
1305 SessionID::id_type command_tab_id
;
1306 std::string session_storage_persistent_id
;
1307 PickleIterator
iter(*command_pickle
.get());
1308 if (!command_pickle
->ReadInt(&iter
, &command_tab_id
) ||
1309 !command_pickle
->ReadString(&iter
, &session_storage_persistent_id
))
1311 // Associate the session storage back.
1312 GetTab(command_tab_id
, tabs
)->session_storage_persistent_id
=
1313 session_storage_persistent_id
;
1317 case kCommandSetActiveWindow
: {
1318 ActiveWindowPayload payload
;
1319 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1320 VLOG(1) << "Failed reading command " << command
->id();
1323 *active_window_id
= payload
;
1328 VLOG(1) << "Failed reading an unknown command " << command
->id();
1335 void SessionService::BuildCommandsForTab(const SessionID
& window_id
,
1337 int index_in_window
,
1339 std::vector
<SessionCommand
*>* commands
,
1340 IdToRange
* tab_to_available_range
) {
1341 DCHECK(tab
&& commands
&& window_id
.id());
1342 SessionTabHelper
* session_tab_helper
= SessionTabHelper::FromWebContents(tab
);
1343 const SessionID
& session_id(session_tab_helper
->session_id());
1344 commands
->push_back(CreateSetTabWindowCommand(window_id
, session_id
));
1346 const int current_index
= tab
->GetController().GetCurrentEntryIndex();
1347 const int min_index
= std::max(0,
1348 current_index
- max_persist_navigation_count
);
1349 const int max_index
=
1350 std::min(current_index
+ max_persist_navigation_count
,
1351 tab
->GetController().GetEntryCount());
1352 const int pending_index
= tab
->GetController().GetPendingEntryIndex();
1353 if (tab_to_available_range
) {
1354 (*tab_to_available_range
)[session_id
.id()] =
1355 std::pair
<int, int>(min_index
, max_index
);
1359 commands
->push_back(CreatePinnedStateCommand(session_id
, true));
1362 extensions::TabHelper
* extensions_tab_helper
=
1363 extensions::TabHelper::FromWebContents(tab
);
1364 if (extensions_tab_helper
->extension_app()) {
1365 commands
->push_back(
1366 CreateSetTabExtensionAppIDCommand(
1367 kCommandSetExtensionAppID
, session_id
.id(),
1368 extensions_tab_helper
->extension_app()->id()));
1371 const std::string
& ua_override
= tab
->GetUserAgentOverride();
1372 if (!ua_override
.empty()) {
1373 commands
->push_back(
1374 CreateSetTabUserAgentOverrideCommand(
1375 kCommandSetTabUserAgentOverride
, session_id
.id(), ua_override
));
1378 for (int i
= min_index
; i
< max_index
; ++i
) {
1379 const NavigationEntry
* entry
= (i
== pending_index
) ?
1380 tab
->GetController().GetPendingEntry() :
1381 tab
->GetController().GetEntryAtIndex(i
);
1383 if (ShouldTrackEntry(entry
->GetVirtualURL())) {
1384 const SerializedNavigationEntry navigation
=
1385 SerializedNavigationEntry::FromNavigationEntry(i
, *entry
);
1386 commands
->push_back(
1387 CreateUpdateTabNavigationCommand(
1388 kCommandUpdateTabNavigation
, session_id
.id(), navigation
));
1391 commands
->push_back(
1392 CreateSetSelectedNavigationIndexCommand(session_id
, current_index
));
1394 if (index_in_window
!= -1) {
1395 commands
->push_back(
1396 CreateSetTabIndexInWindowCommand(session_id
, index_in_window
));
1399 // Record the association between the sessionStorage namespace and the tab.
1400 content::SessionStorageNamespace
* session_storage_namespace
=
1401 tab
->GetController().GetDefaultSessionStorageNamespace();
1402 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1403 session_tab_helper
->session_id(),
1404 session_storage_namespace
->persistent_id()));
1407 void SessionService::BuildCommandsForBrowser(
1409 std::vector
<SessionCommand
*>* commands
,
1410 IdToRange
* tab_to_available_range
,
1411 std::set
<SessionID::id_type
>* windows_to_track
) {
1412 DCHECK(browser
&& commands
);
1413 DCHECK(browser
->session_id().id());
1415 commands
->push_back(
1416 CreateSetWindowBoundsCommand(browser
->session_id(),
1417 browser
->window()->GetRestoredBounds(),
1418 browser
->window()->GetRestoredState()));
1420 commands
->push_back(CreateSetWindowTypeCommand(
1421 browser
->session_id(), WindowTypeForBrowserType(browser
->type())));
1423 if (!browser
->app_name().empty()) {
1424 commands
->push_back(CreateSetWindowAppNameCommand(
1425 kCommandSetWindowAppName
,
1426 browser
->session_id().id(),
1427 browser
->app_name()));
1430 windows_to_track
->insert(browser
->session_id().id());
1431 TabStripModel
* tab_strip
= browser
->tab_strip_model();
1432 for (int i
= 0; i
< tab_strip
->count(); ++i
) {
1433 WebContents
* tab
= tab_strip
->GetWebContentsAt(i
);
1435 BuildCommandsForTab(browser
->session_id(), tab
, i
,
1436 tab_strip
->IsTabPinned(i
),
1437 commands
, tab_to_available_range
);
1440 commands
->push_back(
1441 CreateSetSelectedTabInWindow(browser
->session_id(),
1442 browser
->tab_strip_model()->active_index()));
1445 void SessionService::BuildCommandsFromBrowsers(
1446 std::vector
<SessionCommand
*>* commands
,
1447 IdToRange
* tab_to_available_range
,
1448 std::set
<SessionID::id_type
>* windows_to_track
) {
1450 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1451 Browser
* browser
= *it
;
1452 // Make sure the browser has tabs and a window. Browser's destructor
1453 // removes itself from the BrowserList. When a browser is closed the
1454 // destructor is not necessarily run immediately. This means it's possible
1455 // for us to get a handle to a browser that is about to be removed. If
1456 // the tab count is 0 or the window is NULL, the browser is about to be
1457 // deleted, so we ignore it.
1458 if (ShouldTrackBrowser(browser
) && browser
->tab_strip_model()->count() &&
1459 browser
->window()) {
1460 BuildCommandsForBrowser(browser
, commands
, tab_to_available_range
,
1466 void SessionService::ScheduleReset() {
1467 set_pending_reset(true);
1468 STLDeleteElements(&pending_commands());
1469 tab_to_available_range_
.clear();
1470 windows_tracking_
.clear();
1471 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_
,
1472 &windows_tracking_
);
1473 if (!windows_tracking_
.empty()) {
1474 // We're lazily created on startup and won't get an initial batch of
1475 // SetWindowType messages. Set these here to make sure our state is correct.
1476 has_open_trackable_browsers_
= true;
1477 move_on_new_browser_
= true;
1482 bool SessionService::ReplacePendingCommand(SessionCommand
* command
) {
1483 // We optimize page navigations, which can happen quite frequently and
1484 // are expensive. And activation is like Highlander, there can only be one!
1485 if (command
->id() != kCommandUpdateTabNavigation
&&
1486 command
->id() != kCommandSetActiveWindow
) {
1489 for (std::vector
<SessionCommand
*>::reverse_iterator i
=
1490 pending_commands().rbegin(); i
!= pending_commands().rend(); ++i
) {
1491 SessionCommand
* existing_command
= *i
;
1492 if (command
->id() == kCommandUpdateTabNavigation
&&
1493 existing_command
->id() == kCommandUpdateTabNavigation
) {
1494 scoped_ptr
<Pickle
> command_pickle(command
->PayloadAsPickle());
1495 PickleIterator
iterator(*command_pickle
);
1496 SessionID::id_type command_tab_id
;
1497 int command_nav_index
;
1498 if (!command_pickle
->ReadInt(&iterator
, &command_tab_id
) ||
1499 !command_pickle
->ReadInt(&iterator
, &command_nav_index
)) {
1502 SessionID::id_type existing_tab_id
;
1503 int existing_nav_index
;
1505 // Creating a pickle like this means the Pickle references the data from
1506 // the command. Make sure we delete the pickle before the command, else
1507 // the pickle references deleted memory.
1508 scoped_ptr
<Pickle
> existing_pickle(existing_command
->PayloadAsPickle());
1509 iterator
= PickleIterator(*existing_pickle
);
1510 if (!existing_pickle
->ReadInt(&iterator
, &existing_tab_id
) ||
1511 !existing_pickle
->ReadInt(&iterator
, &existing_nav_index
)) {
1515 if (existing_tab_id
== command_tab_id
&&
1516 existing_nav_index
== command_nav_index
) {
1517 // existing_command is an update for the same tab/index pair. Replace
1518 // it with the new one. We need to add to the end of the list just in
1519 // case there is a prune command after the update command.
1520 delete existing_command
;
1521 pending_commands().erase(i
.base() - 1);
1522 pending_commands().push_back(command
);
1527 if (command
->id() == kCommandSetActiveWindow
&&
1528 existing_command
->id() == kCommandSetActiveWindow
) {
1530 delete existing_command
;
1537 void SessionService::ScheduleCommand(SessionCommand
* command
) {
1539 if (ReplacePendingCommand(command
))
1541 BaseSessionService::ScheduleCommand(command
);
1542 // Don't schedule a reset on tab closed/window closed. Otherwise we may
1543 // lose tabs/windows we want to restore from if we exit right after this.
1544 if (!pending_reset() && pending_window_close_ids_
.empty() &&
1545 commands_since_reset() >= kWritesPerReset
&&
1546 (command
->id() != kCommandTabClosed
&&
1547 command
->id() != kCommandWindowClosed
)) {
1552 void SessionService::CommitPendingCloses() {
1553 for (PendingTabCloseIDs::iterator i
= pending_tab_close_ids_
.begin();
1554 i
!= pending_tab_close_ids_
.end(); ++i
) {
1555 ScheduleCommand(CreateTabClosedCommand(*i
));
1557 pending_tab_close_ids_
.clear();
1559 for (PendingWindowCloseIDs::iterator i
= pending_window_close_ids_
.begin();
1560 i
!= pending_window_close_ids_
.end(); ++i
) {
1561 ScheduleCommand(CreateWindowClosedCommand(*i
));
1563 pending_window_close_ids_
.clear();
1566 bool SessionService::IsOnlyOneTabLeft() const {
1567 if (!profile() || profile()->AsTestingProfile()) {
1568 // We're testing, always return false.
1572 int window_count
= 0;
1573 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1574 Browser
* browser
= *it
;
1575 const SessionID::id_type window_id
= browser
->session_id().id();
1576 if (ShouldTrackBrowser(browser
) &&
1577 window_closing_ids_
.find(window_id
) == window_closing_ids_
.end()) {
1578 if (++window_count
> 1)
1580 // By the time this is invoked the tab has been removed. As such, we use
1581 // > 0 here rather than > 1.
1582 if (browser
->tab_strip_model()->count() > 0)
1589 bool SessionService::HasOpenTrackableBrowsers(
1590 const SessionID
& window_id
) const {
1591 if (!profile() || profile()->AsTestingProfile()) {
1592 // We're testing, always return true.
1596 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1597 Browser
* browser
= *it
;
1598 const SessionID::id_type browser_id
= browser
->session_id().id();
1599 if (browser_id
!= window_id
.id() &&
1600 window_closing_ids_
.find(browser_id
) == window_closing_ids_
.end() &&
1601 ShouldTrackBrowser(browser
)) {
1608 bool SessionService::ShouldTrackChangesToWindow(
1609 const SessionID
& window_id
) const {
1610 return windows_tracking_
.find(window_id
.id()) != windows_tracking_
.end();
1613 bool SessionService::ShouldTrackBrowser(Browser
* browser
) const {
1614 if (browser
->profile() != profile())
1616 // Never track app popup windows that do not have a trusted source (i.e.
1617 // popup windows spawned by an app). If this logic changes, be sure to also
1618 // change SessionRestoreImpl::CreateRestoredBrowser().
1619 if (browser
->is_app() && browser
->is_type_popup() &&
1620 !browser
->is_trusted_source()) {
1623 AppType app_type
= browser
->is_app() ? TYPE_APP
: TYPE_NORMAL
;
1624 return should_track_changes_for_browser_type(browser
->type(), app_type
);
1627 bool SessionService::should_track_changes_for_browser_type(Browser::Type type
,
1629 #if defined(OS_CHROMEOS)
1630 // Restore app popups for chromeos alone.
1631 if (type
== Browser::TYPE_POPUP
&& app_type
== TYPE_APP
)
1635 return type
== Browser::TYPE_TABBED
;
1638 SessionService::WindowType
SessionService::WindowTypeForBrowserType(
1639 Browser::Type type
) {
1641 case Browser::TYPE_POPUP
:
1643 case Browser::TYPE_TABBED
:
1651 Browser::Type
SessionService::BrowserTypeForWindowType(WindowType type
) {
1654 return Browser::TYPE_POPUP
;
1657 return Browser::TYPE_TABBED
;
1661 void SessionService::RecordSessionUpdateHistogramData(int type
,
1662 base::TimeTicks
* last_updated_time
) {
1663 if (!last_updated_time
->is_null()) {
1664 base::TimeDelta delta
= base::TimeTicks::Now() - *last_updated_time
;
1665 // We're interested in frequent updates periods longer than
1667 bool use_long_period
= false;
1668 if (delta
>= save_delay_in_mins_
) {
1669 use_long_period
= true;
1672 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED
:
1673 RecordUpdatedSaveTime(delta
, use_long_period
);
1674 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1676 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
:
1677 RecordUpdatedTabClosed(delta
, use_long_period
);
1678 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1680 case content::NOTIFICATION_NAV_LIST_PRUNED
:
1681 RecordUpdatedNavListPruned(delta
, use_long_period
);
1682 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1684 case content::NOTIFICATION_NAV_ENTRY_COMMITTED
:
1685 RecordUpdatedNavEntryCommit(delta
, use_long_period
);
1686 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1689 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1693 (*last_updated_time
) = base::TimeTicks::Now();
1696 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta
,
1697 bool use_long_period
) {
1698 std::string
name("SessionRestore.TabClosedPeriod");
1699 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1701 // 2500ms is the default save delay.
1702 save_delay_in_millis_
,
1703 save_delay_in_mins_
,
1705 if (use_long_period
) {
1706 std::string
long_name_("SessionRestore.TabClosedLongPeriod");
1707 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1709 save_delay_in_mins_
,
1715 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta
,
1716 bool use_long_period
) {
1717 std::string
name("SessionRestore.NavigationListPrunedPeriod");
1718 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1720 // 2500ms is the default save delay.
1721 save_delay_in_millis_
,
1722 save_delay_in_mins_
,
1724 if (use_long_period
) {
1725 std::string
long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1726 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1728 save_delay_in_mins_
,
1734 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta
,
1735 bool use_long_period
) {
1736 std::string
name("SessionRestore.NavEntryCommittedPeriod");
1737 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1739 // 2500ms is the default save delay.
1740 save_delay_in_millis_
,
1741 save_delay_in_mins_
,
1743 if (use_long_period
) {
1744 std::string
long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1745 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1747 save_delay_in_mins_
,
1753 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta
,
1754 bool use_long_period
) {
1755 std::string
name("SessionRestore.NavOrTabUpdatePeriod");
1756 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1758 // 2500ms is the default save delay.
1759 save_delay_in_millis_
,
1760 save_delay_in_mins_
,
1762 if (use_long_period
) {
1763 std::string
long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1764 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1766 save_delay_in_mins_
,
1772 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta
,
1773 bool use_long_period
) {
1774 std::string
name("SessionRestore.SavePeriod");
1775 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1777 // 2500ms is the default save delay.
1778 save_delay_in_millis_
,
1779 save_delay_in_mins_
,
1781 if (use_long_period
) {
1782 std::string
long_name_("SessionRestore.SaveLongPeriod");
1783 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1785 save_delay_in_mins_
,
1791 void SessionService::TabInserted(WebContents
* contents
) {
1792 SessionTabHelper
* session_tab_helper
=
1793 SessionTabHelper::FromWebContents(contents
);
1794 if (!ShouldTrackChangesToWindow(session_tab_helper
->window_id()))
1796 SetTabWindow(session_tab_helper
->window_id(),
1797 session_tab_helper
->session_id());
1798 extensions::TabHelper
* extensions_tab_helper
=
1799 extensions::TabHelper::FromWebContents(contents
);
1800 if (extensions_tab_helper
&&
1801 extensions_tab_helper
->extension_app()) {
1802 SetTabExtensionAppID(
1803 session_tab_helper
->window_id(),
1804 session_tab_helper
->session_id(),
1805 extensions_tab_helper
->extension_app()->id());
1808 // Record the association between the SessionStorageNamespace and the
1811 // TODO(ajwong): This should be processing the whole map rather than
1812 // just the default. This in particular will not work for tabs with only
1813 // isolated apps which won't have a default partition.
1814 content::SessionStorageNamespace
* session_storage_namespace
=
1815 contents
->GetController().GetDefaultSessionStorageNamespace();
1816 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1817 session_tab_helper
->session_id(),
1818 session_storage_namespace
->persistent_id()));
1819 session_storage_namespace
->SetShouldPersist(true);
1822 void SessionService::TabClosing(WebContents
* contents
) {
1823 // Allow the associated sessionStorage to get deleted; it won't be needed
1824 // in the session restore.
1825 content::SessionStorageNamespace
* session_storage_namespace
=
1826 contents
->GetController().GetDefaultSessionStorageNamespace();
1827 session_storage_namespace
->SetShouldPersist(false);
1828 SessionTabHelper
* session_tab_helper
=
1829 SessionTabHelper::FromWebContents(contents
);
1830 TabClosed(session_tab_helper
->window_id(),
1831 session_tab_helper
->session_id(),
1832 contents
->GetClosedByUserGesture());
1833 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
1834 &last_updated_tab_closed_time_
);
1837 void SessionService::MaybeDeleteSessionOnlyData() {
1838 // Don't try anything if we're testing. The browser_process is not fully
1839 // created and DeleteSession will crash if we actually attempt it.
1840 if (!profile() || profile()->AsTestingProfile())
1843 // Clear session data if the last window for a profile has been closed and
1844 // closing the last window would normally close Chrome, unless background mode
1845 // is active. Tests don't have a background_mode_manager.
1846 if (has_open_trackable_browsers_
||
1847 browser_defaults::kBrowserAliveWithNoWindows
||
1848 g_browser_process
->background_mode_manager()->IsBackgroundModeActive()) {
1852 // Check for any open windows for the current profile that we aren't tracking.
1853 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1854 if ((*it
)->profile() == profile())
1857 DeleteSessionOnlyData(profile());