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 "components/startup_metric_utils/startup_metric_utils.h"
40 #include "content/public/browser/navigation_details.h"
41 #include "content/public/browser/navigation_entry.h"
42 #include "content/public/browser/notification_details.h"
43 #include "content/public/browser/notification_service.h"
44 #include "content/public/browser/session_storage_namespace.h"
45 #include "content/public/browser/web_contents.h"
46 #include "extensions/common/extension.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 KeyedService.
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::WindowOpened(Browser
* browser
) {
308 if (!ShouldTrackBrowser(browser
))
311 AppType app_type
= browser
->is_app() ? TYPE_APP
: TYPE_NORMAL
;
312 RestoreIfNecessary(std::vector
<GURL
>(), browser
);
313 SetWindowType(browser
->session_id(), browser
->type(), app_type
);
314 SetWindowAppName(browser
->session_id(), browser
->app_name());
317 void SessionService::WindowClosing(const SessionID
& window_id
) {
318 if (!ShouldTrackChangesToWindow(window_id
))
321 // The window is about to close. If there are other tabbed browsers with the
322 // same original profile commit the close immediately.
324 // NOTE: if the user chooses the exit menu item session service is destroyed
325 // and this code isn't hit.
326 if (has_open_trackable_browsers_
) {
327 // Closing a window can never make has_open_trackable_browsers_ go from
328 // false to true, so only update it if already true.
329 has_open_trackable_browsers_
= HasOpenTrackableBrowsers(window_id
);
331 if (!has_open_trackable_browsers_
)
332 pending_window_close_ids_
.insert(window_id
.id());
334 window_closing_ids_
.insert(window_id
.id());
337 void SessionService::WindowClosed(const SessionID
& window_id
) {
338 if (!ShouldTrackChangesToWindow(window_id
)) {
339 // The last window may be one that is not tracked.
340 MaybeDeleteSessionOnlyData();
344 windows_tracking_
.erase(window_id
.id());
346 if (window_closing_ids_
.find(window_id
.id()) != window_closing_ids_
.end()) {
347 window_closing_ids_
.erase(window_id
.id());
348 ScheduleCommand(CreateWindowClosedCommand(window_id
.id()));
349 } else if (pending_window_close_ids_
.find(window_id
.id()) ==
350 pending_window_close_ids_
.end()) {
351 // We'll hit this if user closed the last tab in a window.
352 has_open_trackable_browsers_
= HasOpenTrackableBrowsers(window_id
);
353 if (!has_open_trackable_browsers_
)
354 pending_window_close_ids_
.insert(window_id
.id());
356 ScheduleCommand(CreateWindowClosedCommand(window_id
.id()));
358 MaybeDeleteSessionOnlyData();
361 void SessionService::SetWindowType(const SessionID
& window_id
,
364 if (!should_track_changes_for_browser_type(type
, app_type
))
367 windows_tracking_
.insert(window_id
.id());
369 // The user created a new tabbed browser with our profile. Commit any
371 CommitPendingCloses();
373 has_open_trackable_browsers_
= true;
374 move_on_new_browser_
= true;
377 CreateSetWindowTypeCommand(window_id
, WindowTypeForBrowserType(type
)));
380 void SessionService::SetWindowAppName(
381 const SessionID
& window_id
,
382 const std::string
& app_name
) {
383 if (!ShouldTrackChangesToWindow(window_id
))
386 ScheduleCommand(CreateSetTabExtensionAppIDCommand(
387 kCommandSetWindowAppName
,
392 void SessionService::TabNavigationPathPrunedFromBack(const SessionID
& window_id
,
393 const SessionID
& tab_id
,
395 if (!ShouldTrackChangesToWindow(window_id
))
398 TabNavigationPathPrunedFromBackPayload payload
= { 0 };
399 payload
.id
= tab_id
.id();
400 payload
.index
= count
;
401 SessionCommand
* command
=
402 new SessionCommand(kCommandTabNavigationPathPrunedFromBack
,
404 memcpy(command
->contents(), &payload
, sizeof(payload
));
405 ScheduleCommand(command
);
408 void SessionService::TabNavigationPathPrunedFromFront(
409 const SessionID
& window_id
,
410 const SessionID
& tab_id
,
412 if (!ShouldTrackChangesToWindow(window_id
))
415 // Update the range of indices.
416 if (tab_to_available_range_
.find(tab_id
.id()) !=
417 tab_to_available_range_
.end()) {
418 std::pair
<int, int>& range
= tab_to_available_range_
[tab_id
.id()];
419 range
.first
= std::max(0, range
.first
- count
);
420 range
.second
= std::max(0, range
.second
- count
);
423 TabNavigationPathPrunedFromFrontPayload payload
= { 0 };
424 payload
.id
= tab_id
.id();
425 payload
.index
= count
;
426 SessionCommand
* command
=
427 new SessionCommand(kCommandTabNavigationPathPrunedFromFront
,
429 memcpy(command
->contents(), &payload
, sizeof(payload
));
430 ScheduleCommand(command
);
433 void SessionService::UpdateTabNavigation(
434 const SessionID
& window_id
,
435 const SessionID
& tab_id
,
436 const SerializedNavigationEntry
& navigation
) {
437 if (!ShouldTrackEntry(navigation
.virtual_url()) ||
438 !ShouldTrackChangesToWindow(window_id
)) {
442 if (tab_to_available_range_
.find(tab_id
.id()) !=
443 tab_to_available_range_
.end()) {
444 std::pair
<int, int>& range
= tab_to_available_range_
[tab_id
.id()];
445 range
.first
= std::min(navigation
.index(), range
.first
);
446 range
.second
= std::max(navigation
.index(), range
.second
);
448 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation
,
449 tab_id
.id(), navigation
));
452 void SessionService::TabRestored(WebContents
* tab
, bool pinned
) {
453 SessionTabHelper
* session_tab_helper
= SessionTabHelper::FromWebContents(tab
);
454 if (!ShouldTrackChangesToWindow(session_tab_helper
->window_id()))
457 BuildCommandsForTab(session_tab_helper
->window_id(), tab
, -1,
458 pinned
, &pending_commands(), NULL
);
462 void SessionService::SetSelectedNavigationIndex(const SessionID
& window_id
,
463 const SessionID
& tab_id
,
465 if (!ShouldTrackChangesToWindow(window_id
))
468 if (tab_to_available_range_
.find(tab_id
.id()) !=
469 tab_to_available_range_
.end()) {
470 if (index
< tab_to_available_range_
[tab_id
.id()].first
||
471 index
> tab_to_available_range_
[tab_id
.id()].second
) {
472 // The new index is outside the range of what we've archived, schedule
474 ResetFromCurrentBrowsers();
478 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id
, index
));
481 void SessionService::SetSelectedTabInWindow(const SessionID
& window_id
,
483 if (!ShouldTrackChangesToWindow(window_id
))
486 ScheduleCommand(CreateSetSelectedTabInWindow(window_id
, index
));
489 void SessionService::SetTabUserAgentOverride(
490 const SessionID
& window_id
,
491 const SessionID
& tab_id
,
492 const std::string
& user_agent_override
) {
493 if (!ShouldTrackChangesToWindow(window_id
))
496 ScheduleCommand(CreateSetTabUserAgentOverrideCommand(
497 kCommandSetTabUserAgentOverride
, tab_id
.id(), user_agent_override
));
500 base::CancelableTaskTracker::TaskId
SessionService::GetLastSession(
501 const SessionCallback
& callback
,
502 base::CancelableTaskTracker
* tracker
) {
503 // OnGotSessionCommands maps the SessionCommands to browser state, then run
505 return ScheduleGetLastSessionCommands(
506 base::Bind(&SessionService::OnGotSessionCommands
,
507 base::Unretained(this), callback
),
511 void SessionService::Save() {
512 bool had_commands
= !pending_commands().empty();
513 BaseSessionService::Save();
515 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED
,
516 &last_updated_save_time_
);
517 content::NotificationService::current()->Notify(
518 chrome::NOTIFICATION_SESSION_SERVICE_SAVED
,
519 content::Source
<Profile
>(profile()),
520 content::NotificationService::NoDetails());
524 void SessionService::Init() {
525 // Register for the notifications we're interested in.
526 registrar_
.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED
,
527 content::NotificationService::AllSources());
528 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED
,
529 content::NotificationService::AllSources());
530 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
531 content::NotificationService::AllSources());
533 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
,
534 content::NotificationService::AllSources());
536 BrowserList::AddObserver(this);
539 bool SessionService::processed_any_commands() {
540 return backend()->inited() || !pending_commands().empty();
543 bool SessionService::ShouldNewWindowStartSession() {
544 // ChromeOS and OSX have different ideas of application lifetime than
545 // the other platforms.
546 // On ChromeOS opening a new window should never start a new session.
547 #if defined(OS_CHROMEOS)
548 if (!force_browser_not_alive_with_no_windows_
)
551 if (!has_open_trackable_browsers_
&&
552 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
553 !SessionRestore::IsRestoring(profile())
554 #if defined(OS_MACOSX)
555 // On OSX, a new window should not start a new session if it was opened
556 // from the dock or the menubar.
557 && !app_controller_mac::IsOpeningNewWindow()
565 bool SessionService::RestoreIfNecessary(const std::vector
<GURL
>& urls_to_open
,
567 if (ShouldNewWindowStartSession()) {
568 // We're going from no tabbed browsers to a tabbed browser (and not in
569 // process startup), restore the last session.
570 if (move_on_new_browser_
) {
571 // Make the current session the last.
572 MoveCurrentSessionToLastSession();
573 move_on_new_browser_
= false;
575 SessionStartupPref pref
= StartupBrowserCreator::GetSessionStartupPref(
576 *CommandLine::ForCurrentProcess(), profile());
577 if (pref
.type
== SessionStartupPref::LAST
) {
578 SessionRestore::RestoreSession(
580 browser
? browser
->host_desktop_type() : chrome::GetActiveDesktop(),
581 browser
? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER
,
589 void SessionService::Observe(int type
,
590 const content::NotificationSource
& source
,
591 const content::NotificationDetails
& details
) {
592 // All of our messages have the NavigationController as the source.
594 case content::NOTIFICATION_NAV_LIST_PRUNED
: {
595 WebContents
* web_contents
=
596 content::Source
<content::NavigationController
>(source
).ptr()->
598 SessionTabHelper
* session_tab_helper
=
599 SessionTabHelper::FromWebContents(web_contents
);
600 if (!session_tab_helper
|| web_contents
->GetBrowserContext() != profile())
602 content::Details
<content::PrunedDetails
> pruned_details(details
);
603 if (pruned_details
->from_front
) {
604 TabNavigationPathPrunedFromFront(
605 session_tab_helper
->window_id(),
606 session_tab_helper
->session_id(),
607 pruned_details
->count
);
609 TabNavigationPathPrunedFromBack(
610 session_tab_helper
->window_id(),
611 session_tab_helper
->session_id(),
612 web_contents
->GetController().GetEntryCount());
614 RecordSessionUpdateHistogramData(type
,
615 &last_updated_nav_list_pruned_time_
);
619 case content::NOTIFICATION_NAV_ENTRY_CHANGED
: {
620 WebContents
* web_contents
=
621 content::Source
<content::NavigationController
>(source
).ptr()->
623 SessionTabHelper
* session_tab_helper
=
624 SessionTabHelper::FromWebContents(web_contents
);
625 if (!session_tab_helper
|| web_contents
->GetBrowserContext() != profile())
627 content::Details
<content::EntryChangedDetails
> changed(details
);
628 const SerializedNavigationEntry navigation
=
629 SerializedNavigationEntry::FromNavigationEntry(
630 changed
->index
, *changed
->changed_entry
);
631 UpdateTabNavigation(session_tab_helper
->window_id(),
632 session_tab_helper
->session_id(),
637 case content::NOTIFICATION_NAV_ENTRY_COMMITTED
: {
638 WebContents
* web_contents
=
639 content::Source
<content::NavigationController
>(source
).ptr()->
641 SessionTabHelper
* session_tab_helper
=
642 SessionTabHelper::FromWebContents(web_contents
);
643 if (!session_tab_helper
|| web_contents
->GetBrowserContext() != profile())
645 int current_entry_index
=
646 web_contents
->GetController().GetCurrentEntryIndex();
647 SetSelectedNavigationIndex(
648 session_tab_helper
->window_id(),
649 session_tab_helper
->session_id(),
650 current_entry_index
);
651 const SerializedNavigationEntry navigation
=
652 SerializedNavigationEntry::FromNavigationEntry(
654 *web_contents
->GetController().GetEntryAtIndex(
655 current_entry_index
));
657 session_tab_helper
->window_id(),
658 session_tab_helper
->session_id(),
660 content::Details
<content::LoadCommittedDetails
> changed(details
);
661 if (changed
->type
== content::NAVIGATION_TYPE_NEW_PAGE
||
662 changed
->type
== content::NAVIGATION_TYPE_EXISTING_PAGE
) {
663 RecordSessionUpdateHistogramData(type
,
664 &last_updated_nav_entry_commit_time_
);
669 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
: {
670 extensions::TabHelper
* extension_tab_helper
=
671 content::Source
<extensions::TabHelper
>(source
).ptr();
672 if (extension_tab_helper
->web_contents()->GetBrowserContext() !=
676 if (extension_tab_helper
->extension_app()) {
677 SessionTabHelper
* session_tab_helper
=
678 SessionTabHelper::FromWebContents(
679 extension_tab_helper
->web_contents());
680 SetTabExtensionAppID(session_tab_helper
->window_id(),
681 session_tab_helper
->session_id(),
682 extension_tab_helper
->extension_app()->id());
692 void SessionService::OnBrowserSetLastActive(Browser
* browser
) {
693 if (ShouldTrackBrowser(browser
))
694 ScheduleCommand(CreateSetActiveWindowCommand(browser
->session_id()));
697 void SessionService::SetTabExtensionAppID(
698 const SessionID
& window_id
,
699 const SessionID
& tab_id
,
700 const std::string
& extension_app_id
) {
701 if (!ShouldTrackChangesToWindow(window_id
))
704 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID
,
705 tab_id
.id(), extension_app_id
));
708 SessionCommand
* SessionService::CreateSetSelectedTabInWindow(
709 const SessionID
& window_id
,
711 SelectedTabInIndexPayload payload
= { 0 };
712 payload
.id
= window_id
.id();
713 payload
.index
= index
;
714 SessionCommand
* command
= new SessionCommand(kCommandSetSelectedTabInIndex
,
716 memcpy(command
->contents(), &payload
, sizeof(payload
));
720 SessionCommand
* SessionService::CreateSetTabWindowCommand(
721 const SessionID
& window_id
,
722 const SessionID
& tab_id
) {
723 SessionID::id_type payload
[] = { window_id
.id(), tab_id
.id() };
724 SessionCommand
* command
=
725 new SessionCommand(kCommandSetTabWindow
, sizeof(payload
));
726 memcpy(command
->contents(), payload
, sizeof(payload
));
730 SessionCommand
* SessionService::CreateSetWindowBoundsCommand(
731 const SessionID
& window_id
,
732 const gfx::Rect
& bounds
,
733 ui::WindowShowState show_state
) {
734 WindowBoundsPayload3 payload
= { 0 };
735 payload
.window_id
= window_id
.id();
736 payload
.x
= bounds
.x();
737 payload
.y
= bounds
.y();
738 payload
.w
= bounds
.width();
739 payload
.h
= bounds
.height();
740 payload
.show_state
= AdjustShowState(show_state
);
741 SessionCommand
* command
= new SessionCommand(kCommandSetWindowBounds3
,
743 memcpy(command
->contents(), &payload
, sizeof(payload
));
747 SessionCommand
* SessionService::CreateSetTabIndexInWindowCommand(
748 const SessionID
& tab_id
,
750 TabIndexInWindowPayload payload
= { 0 };
751 payload
.id
= tab_id
.id();
752 payload
.index
= new_index
;
753 SessionCommand
* command
=
754 new SessionCommand(kCommandSetTabIndexInWindow
, sizeof(payload
));
755 memcpy(command
->contents(), &payload
, sizeof(payload
));
759 SessionCommand
* SessionService::CreateTabClosedCommand(
760 const SessionID::id_type tab_id
) {
761 ClosedPayload payload
;
762 // Because of what appears to be a compiler bug setting payload to {0} doesn't
763 // set the padding to 0, resulting in Purify reporting an UMR when we write
764 // the structure to disk. To avoid this we explicitly memset the struct.
765 memset(&payload
, 0, sizeof(payload
));
767 payload
.close_time
= Time::Now().ToInternalValue();
768 SessionCommand
* command
=
769 new SessionCommand(kCommandTabClosed
, sizeof(payload
));
770 memcpy(command
->contents(), &payload
, sizeof(payload
));
774 SessionCommand
* SessionService::CreateWindowClosedCommand(
775 const SessionID::id_type window_id
) {
776 ClosedPayload payload
;
777 // See comment in CreateTabClosedCommand as to why we do this.
778 memset(&payload
, 0, sizeof(payload
));
779 payload
.id
= window_id
;
780 payload
.close_time
= Time::Now().ToInternalValue();
781 SessionCommand
* command
=
782 new SessionCommand(kCommandWindowClosed
, sizeof(payload
));
783 memcpy(command
->contents(), &payload
, sizeof(payload
));
787 SessionCommand
* SessionService::CreateSetSelectedNavigationIndexCommand(
788 const SessionID
& tab_id
,
790 SelectedNavigationIndexPayload payload
= { 0 };
791 payload
.id
= tab_id
.id();
792 payload
.index
= index
;
793 SessionCommand
* command
= new SessionCommand(
794 kCommandSetSelectedNavigationIndex
, sizeof(payload
));
795 memcpy(command
->contents(), &payload
, sizeof(payload
));
799 SessionCommand
* SessionService::CreateSetWindowTypeCommand(
800 const SessionID
& window_id
,
802 WindowTypePayload payload
= { 0 };
803 payload
.id
= window_id
.id();
804 payload
.index
= static_cast<int32
>(type
);
805 SessionCommand
* command
= new SessionCommand(
806 kCommandSetWindowType
, sizeof(payload
));
807 memcpy(command
->contents(), &payload
, sizeof(payload
));
811 SessionCommand
* SessionService::CreatePinnedStateCommand(
812 const SessionID
& tab_id
,
814 PinnedStatePayload payload
= { 0 };
815 payload
.tab_id
= tab_id
.id();
816 payload
.pinned_state
= is_pinned
;
817 SessionCommand
* command
=
818 new SessionCommand(kCommandSetPinnedState
, sizeof(payload
));
819 memcpy(command
->contents(), &payload
, sizeof(payload
));
823 SessionCommand
* SessionService::CreateSessionStorageAssociatedCommand(
824 const SessionID
& tab_id
,
825 const std::string
& session_storage_persistent_id
) {
827 pickle
.WriteInt(tab_id
.id());
828 pickle
.WriteString(session_storage_persistent_id
);
829 return new SessionCommand(kCommandSessionStorageAssociated
, pickle
);
832 SessionCommand
* SessionService::CreateSetActiveWindowCommand(
833 const SessionID
& window_id
) {
834 ActiveWindowPayload payload
= 0;
835 payload
= window_id
.id();
836 SessionCommand
* command
=
837 new SessionCommand(kCommandSetActiveWindow
, sizeof(payload
));
838 memcpy(command
->contents(), &payload
, sizeof(payload
));
842 void SessionService::OnGotSessionCommands(
843 const SessionCallback
& callback
,
844 ScopedVector
<SessionCommand
> commands
) {
845 ScopedVector
<SessionWindow
> valid_windows
;
846 SessionID::id_type active_window_id
= 0;
848 RestoreSessionFromCommands(
849 commands
.get(), &valid_windows
.get(), &active_window_id
);
850 callback
.Run(valid_windows
.Pass(), active_window_id
);
853 void SessionService::RestoreSessionFromCommands(
854 const std::vector
<SessionCommand
*>& commands
,
855 std::vector
<SessionWindow
*>* valid_windows
,
856 SessionID::id_type
* active_window_id
) {
857 std::map
<int, SessionTab
*> tabs
;
858 std::map
<int, SessionWindow
*> windows
;
860 VLOG(1) << "RestoreSessionFromCommands " << commands
.size();
861 if (CreateTabsAndWindows(commands
, &tabs
, &windows
, active_window_id
)) {
862 AddTabsToWindows(&tabs
, &windows
);
863 SortTabsBasedOnVisualOrderAndPrune(&windows
, valid_windows
);
864 UpdateSelectedTabIndex(valid_windows
);
866 STLDeleteValues(&tabs
);
867 // Don't delete conents of windows, that is done by the caller as all
868 // valid windows are added to valid_windows.
871 void SessionService::UpdateSelectedTabIndex(
872 std::vector
<SessionWindow
*>* windows
) {
873 for (std::vector
<SessionWindow
*>::const_iterator i
= windows
->begin();
874 i
!= windows
->end(); ++i
) {
875 // See note in SessionWindow as to why we do this.
877 for (std::vector
<SessionTab
*>::const_iterator j
= (*i
)->tabs
.begin();
878 j
!= (*i
)->tabs
.end(); ++j
) {
879 if ((*j
)->tab_visual_index
== (*i
)->selected_tab_index
) {
880 new_index
= static_cast<int>(j
- (*i
)->tabs
.begin());
884 (*i
)->selected_tab_index
= new_index
;
888 SessionWindow
* SessionService::GetWindow(
889 SessionID::id_type window_id
,
890 IdToSessionWindow
* windows
) {
891 std::map
<int, SessionWindow
*>::iterator i
= windows
->find(window_id
);
892 if (i
== windows
->end()) {
893 SessionWindow
* window
= new SessionWindow();
894 window
->window_id
.set_id(window_id
);
895 (*windows
)[window_id
] = window
;
901 SessionTab
* SessionService::GetTab(
902 SessionID::id_type tab_id
,
903 IdToSessionTab
* tabs
) {
905 std::map
<int, SessionTab
*>::iterator i
= tabs
->find(tab_id
);
906 if (i
== tabs
->end()) {
907 SessionTab
* tab
= new SessionTab();
908 tab
->tab_id
.set_id(tab_id
);
909 (*tabs
)[tab_id
] = tab
;
915 std::vector
<SerializedNavigationEntry
>::iterator
916 SessionService::FindClosestNavigationWithIndex(
917 std::vector
<SerializedNavigationEntry
>* navigations
,
920 for (std::vector
<SerializedNavigationEntry
>::iterator
921 i
= navigations
->begin(); i
!= navigations
->end(); ++i
) {
922 if (i
->index() >= index
)
925 return navigations
->end();
928 // Function used in sorting windows. Sorting is done based on window id. As
929 // window ids increment for each new window, this effectively sorts by creation
931 static bool WindowOrderSortFunction(const SessionWindow
* w1
,
932 const SessionWindow
* w2
) {
933 return w1
->window_id
.id() < w2
->window_id
.id();
936 // Compares the two tabs based on visual index.
937 static bool TabVisualIndexSortFunction(const SessionTab
* t1
,
938 const SessionTab
* t2
) {
939 const int delta
= t1
->tab_visual_index
- t2
->tab_visual_index
;
940 return delta
== 0 ? (t1
->tab_id
.id() < t2
->tab_id
.id()) : (delta
< 0);
943 void SessionService::SortTabsBasedOnVisualOrderAndPrune(
944 std::map
<int, SessionWindow
*>* windows
,
945 std::vector
<SessionWindow
*>* valid_windows
) {
946 std::map
<int, SessionWindow
*>::iterator i
= windows
->begin();
947 while (i
!= windows
->end()) {
948 SessionWindow
* window
= i
->second
;
949 AppType app_type
= window
->app_name
.empty() ? TYPE_NORMAL
: TYPE_APP
;
950 if (window
->tabs
.empty() || window
->is_constrained
||
951 !should_track_changes_for_browser_type(
952 static_cast<Browser::Type
>(window
->type
),
957 // Valid window; sort the tabs and add it to the list of valid windows.
958 std::sort(window
->tabs
.begin(), window
->tabs
.end(),
959 &TabVisualIndexSortFunction
);
960 // Otherwise, add the window such that older windows appear first.
961 if (valid_windows
->empty()) {
962 valid_windows
->push_back(window
);
964 valid_windows
->insert(
965 std::upper_bound(valid_windows
->begin(), valid_windows
->end(),
966 window
, &WindowOrderSortFunction
),
974 void SessionService::AddTabsToWindows(std::map
<int, SessionTab
*>* tabs
,
975 std::map
<int, SessionWindow
*>* windows
) {
976 VLOG(1) << "AddTabsToWindws";
977 VLOG(1) << "Tabs " << tabs
->size() << ", windows " << windows
->size();
978 std::map
<int, SessionTab
*>::iterator i
= tabs
->begin();
979 while (i
!= tabs
->end()) {
980 SessionTab
* tab
= i
->second
;
981 if (tab
->window_id
.id() && !tab
->navigations
.empty()) {
982 SessionWindow
* window
= GetWindow(tab
->window_id
.id(), windows
);
983 window
->tabs
.push_back(tab
);
986 // See note in SessionTab as to why we do this.
987 std::vector
<SerializedNavigationEntry
>::iterator j
=
988 FindClosestNavigationWithIndex(&(tab
->navigations
),
989 tab
->current_navigation_index
);
990 if (j
== tab
->navigations
.end()) {
991 tab
->current_navigation_index
=
992 static_cast<int>(tab
->navigations
.size() - 1);
994 tab
->current_navigation_index
=
995 static_cast<int>(j
- tab
->navigations
.begin());
998 // Never got a set tab index in window, or tabs are empty, nothing
1005 bool SessionService::CreateTabsAndWindows(
1006 const std::vector
<SessionCommand
*>& data
,
1007 std::map
<int, SessionTab
*>* tabs
,
1008 std::map
<int, SessionWindow
*>* windows
,
1009 SessionID::id_type
* active_window_id
) {
1010 // If the file is corrupt (command with wrong size, or unknown command), we
1011 // still return true and attempt to restore what we we can.
1012 VLOG(1) << "CreateTabsAndWindows";
1014 startup_metric_utils::ScopedSlowStartupUMA
1015 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
1017 for (std::vector
<SessionCommand
*>::const_iterator i
= data
.begin();
1018 i
!= data
.end(); ++i
) {
1019 const SessionCommand::id_type kCommandSetWindowBounds2
= 10;
1020 const SessionCommand
* command
= *i
;
1022 VLOG(1) << "Read command " << (int) command
->id();
1023 switch (command
->id()) {
1024 case kCommandSetTabWindow
: {
1025 SessionID::id_type payload
[2];
1026 if (!command
->GetPayload(payload
, sizeof(payload
))) {
1027 VLOG(1) << "Failed reading command " << command
->id();
1030 GetTab(payload
[1], tabs
)->window_id
.set_id(payload
[0]);
1034 // This is here for forward migration only. New data is saved with
1035 // |kCommandSetWindowBounds3|.
1036 case kCommandSetWindowBounds2
: {
1037 WindowBoundsPayload2 payload
;
1038 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1039 VLOG(1) << "Failed reading command " << command
->id();
1042 GetWindow(payload
.window_id
, windows
)->bounds
.SetRect(payload
.x
,
1046 GetWindow(payload
.window_id
, windows
)->show_state
=
1047 payload
.is_maximized
?
1048 ui::SHOW_STATE_MAXIMIZED
: ui::SHOW_STATE_NORMAL
;
1052 case kCommandSetWindowBounds3
: {
1053 WindowBoundsPayload3 payload
;
1054 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1055 VLOG(1) << "Failed reading command " << command
->id();
1058 GetWindow(payload
.window_id
, windows
)->bounds
.SetRect(payload
.x
,
1062 // SHOW_STATE_INACTIVE is not persisted.
1063 ui::WindowShowState show_state
= ui::SHOW_STATE_NORMAL
;
1064 if (payload
.show_state
> ui::SHOW_STATE_DEFAULT
&&
1065 payload
.show_state
< ui::SHOW_STATE_END
&&
1066 payload
.show_state
!= ui::SHOW_STATE_INACTIVE
) {
1067 show_state
= static_cast<ui::WindowShowState
>(payload
.show_state
);
1071 GetWindow(payload
.window_id
, windows
)->show_state
= show_state
;
1075 case kCommandSetTabIndexInWindow
: {
1076 TabIndexInWindowPayload payload
;
1077 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1078 VLOG(1) << "Failed reading command " << command
->id();
1081 GetTab(payload
.id
, tabs
)->tab_visual_index
= payload
.index
;
1085 case kCommandTabClosedObsolete
:
1086 case kCommandWindowClosedObsolete
:
1087 case kCommandTabClosed
:
1088 case kCommandWindowClosed
: {
1089 ClosedPayload payload
;
1090 if (!command
->GetPayload(&payload
, sizeof(payload
)) &&
1091 !MigrateClosedPayload(*command
, &payload
)) {
1092 VLOG(1) << "Failed reading command " << command
->id();
1095 if (command
->id() == kCommandTabClosed
||
1096 command
->id() == kCommandTabClosedObsolete
) {
1097 delete GetTab(payload
.id
, tabs
);
1098 tabs
->erase(payload
.id
);
1100 delete GetWindow(payload
.id
, windows
);
1101 windows
->erase(payload
.id
);
1106 case kCommandTabNavigationPathPrunedFromBack
: {
1107 TabNavigationPathPrunedFromBackPayload payload
;
1108 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1109 VLOG(1) << "Failed reading command " << command
->id();
1112 SessionTab
* tab
= GetTab(payload
.id
, tabs
);
1113 tab
->navigations
.erase(
1114 FindClosestNavigationWithIndex(&(tab
->navigations
), payload
.index
),
1115 tab
->navigations
.end());
1119 case kCommandTabNavigationPathPrunedFromFront
: {
1120 TabNavigationPathPrunedFromFrontPayload payload
;
1121 if (!command
->GetPayload(&payload
, sizeof(payload
)) ||
1122 payload
.index
<= 0) {
1123 VLOG(1) << "Failed reading command " << command
->id();
1126 SessionTab
* tab
= GetTab(payload
.id
, tabs
);
1128 // Update the selected navigation index.
1129 tab
->current_navigation_index
=
1130 std::max(-1, tab
->current_navigation_index
- payload
.index
);
1132 // And update the index of existing navigations.
1133 for (std::vector
<SerializedNavigationEntry
>::iterator
1134 i
= tab
->navigations
.begin();
1135 i
!= tab
->navigations
.end();) {
1136 i
->set_index(i
->index() - payload
.index
);
1138 i
= tab
->navigations
.erase(i
);
1145 case kCommandUpdateTabNavigation
: {
1146 SerializedNavigationEntry navigation
;
1147 SessionID::id_type tab_id
;
1148 if (!RestoreUpdateTabNavigationCommand(
1149 *command
, &navigation
, &tab_id
)) {
1150 VLOG(1) << "Failed reading command " << command
->id();
1153 SessionTab
* tab
= GetTab(tab_id
, tabs
);
1154 std::vector
<SerializedNavigationEntry
>::iterator i
=
1155 FindClosestNavigationWithIndex(&(tab
->navigations
),
1156 navigation
.index());
1157 if (i
!= tab
->navigations
.end() && i
->index() == navigation
.index())
1160 tab
->navigations
.insert(i
, navigation
);
1164 case kCommandSetSelectedNavigationIndex
: {
1165 SelectedNavigationIndexPayload payload
;
1166 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1167 VLOG(1) << "Failed reading command " << command
->id();
1170 GetTab(payload
.id
, tabs
)->current_navigation_index
= payload
.index
;
1174 case kCommandSetSelectedTabInIndex
: {
1175 SelectedTabInIndexPayload payload
;
1176 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1177 VLOG(1) << "Failed reading command " << command
->id();
1180 GetWindow(payload
.id
, windows
)->selected_tab_index
= payload
.index
;
1184 case kCommandSetWindowType
: {
1185 WindowTypePayload payload
;
1186 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1187 VLOG(1) << "Failed reading command " << command
->id();
1190 GetWindow(payload
.id
, windows
)->is_constrained
= false;
1191 GetWindow(payload
.id
, windows
)->type
=
1192 BrowserTypeForWindowType(
1193 static_cast<WindowType
>(payload
.index
));
1197 case kCommandSetPinnedState
: {
1198 PinnedStatePayload payload
;
1199 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1200 VLOG(1) << "Failed reading command " << command
->id();
1203 GetTab(payload
.tab_id
, tabs
)->pinned
= payload
.pinned_state
;
1207 case kCommandSetWindowAppName
: {
1208 SessionID::id_type window_id
;
1209 std::string app_name
;
1210 if (!RestoreSetWindowAppNameCommand(*command
, &window_id
, &app_name
))
1213 GetWindow(window_id
, windows
)->app_name
.swap(app_name
);
1217 case kCommandSetExtensionAppID
: {
1218 SessionID::id_type tab_id
;
1219 std::string extension_app_id
;
1220 if (!RestoreSetTabExtensionAppIDCommand(
1221 *command
, &tab_id
, &extension_app_id
)) {
1222 VLOG(1) << "Failed reading command " << command
->id();
1226 GetTab(tab_id
, tabs
)->extension_app_id
.swap(extension_app_id
);
1230 case kCommandSetTabUserAgentOverride
: {
1231 SessionID::id_type tab_id
;
1232 std::string user_agent_override
;
1233 if (!RestoreSetTabUserAgentOverrideCommand(
1234 *command
, &tab_id
, &user_agent_override
)) {
1238 GetTab(tab_id
, tabs
)->user_agent_override
.swap(user_agent_override
);
1242 case kCommandSessionStorageAssociated
: {
1243 scoped_ptr
<Pickle
> command_pickle(command
->PayloadAsPickle());
1244 SessionID::id_type command_tab_id
;
1245 std::string session_storage_persistent_id
;
1246 PickleIterator
iter(*command_pickle
.get());
1247 if (!command_pickle
->ReadInt(&iter
, &command_tab_id
) ||
1248 !command_pickle
->ReadString(&iter
, &session_storage_persistent_id
))
1250 // Associate the session storage back.
1251 GetTab(command_tab_id
, tabs
)->session_storage_persistent_id
=
1252 session_storage_persistent_id
;
1256 case kCommandSetActiveWindow
: {
1257 ActiveWindowPayload payload
;
1258 if (!command
->GetPayload(&payload
, sizeof(payload
))) {
1259 VLOG(1) << "Failed reading command " << command
->id();
1262 *active_window_id
= payload
;
1267 VLOG(1) << "Failed reading an unknown command " << command
->id();
1274 void SessionService::BuildCommandsForTab(const SessionID
& window_id
,
1276 int index_in_window
,
1278 std::vector
<SessionCommand
*>* commands
,
1279 IdToRange
* tab_to_available_range
) {
1280 DCHECK(tab
&& commands
&& window_id
.id());
1281 SessionTabHelper
* session_tab_helper
= SessionTabHelper::FromWebContents(tab
);
1282 const SessionID
& session_id(session_tab_helper
->session_id());
1283 commands
->push_back(CreateSetTabWindowCommand(window_id
, session_id
));
1285 const int current_index
= tab
->GetController().GetCurrentEntryIndex();
1286 const int min_index
= std::max(0,
1287 current_index
- max_persist_navigation_count
);
1288 const int max_index
=
1289 std::min(current_index
+ max_persist_navigation_count
,
1290 tab
->GetController().GetEntryCount());
1291 const int pending_index
= tab
->GetController().GetPendingEntryIndex();
1292 if (tab_to_available_range
) {
1293 (*tab_to_available_range
)[session_id
.id()] =
1294 std::pair
<int, int>(min_index
, max_index
);
1298 commands
->push_back(CreatePinnedStateCommand(session_id
, true));
1301 extensions::TabHelper
* extensions_tab_helper
=
1302 extensions::TabHelper::FromWebContents(tab
);
1303 if (extensions_tab_helper
->extension_app()) {
1304 commands
->push_back(
1305 CreateSetTabExtensionAppIDCommand(
1306 kCommandSetExtensionAppID
, session_id
.id(),
1307 extensions_tab_helper
->extension_app()->id()));
1310 const std::string
& ua_override
= tab
->GetUserAgentOverride();
1311 if (!ua_override
.empty()) {
1312 commands
->push_back(
1313 CreateSetTabUserAgentOverrideCommand(
1314 kCommandSetTabUserAgentOverride
, session_id
.id(), ua_override
));
1317 for (int i
= min_index
; i
< max_index
; ++i
) {
1318 const NavigationEntry
* entry
= (i
== pending_index
) ?
1319 tab
->GetController().GetPendingEntry() :
1320 tab
->GetController().GetEntryAtIndex(i
);
1322 if (ShouldTrackEntry(entry
->GetVirtualURL())) {
1323 const SerializedNavigationEntry navigation
=
1324 SerializedNavigationEntry::FromNavigationEntry(i
, *entry
);
1325 commands
->push_back(
1326 CreateUpdateTabNavigationCommand(
1327 kCommandUpdateTabNavigation
, session_id
.id(), navigation
));
1330 commands
->push_back(
1331 CreateSetSelectedNavigationIndexCommand(session_id
, current_index
));
1333 if (index_in_window
!= -1) {
1334 commands
->push_back(
1335 CreateSetTabIndexInWindowCommand(session_id
, index_in_window
));
1338 // Record the association between the sessionStorage namespace and the tab.
1339 content::SessionStorageNamespace
* session_storage_namespace
=
1340 tab
->GetController().GetDefaultSessionStorageNamespace();
1341 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1342 session_tab_helper
->session_id(),
1343 session_storage_namespace
->persistent_id()));
1346 void SessionService::BuildCommandsForBrowser(
1348 std::vector
<SessionCommand
*>* commands
,
1349 IdToRange
* tab_to_available_range
,
1350 std::set
<SessionID::id_type
>* windows_to_track
) {
1351 DCHECK(browser
&& commands
);
1352 DCHECK(browser
->session_id().id());
1354 commands
->push_back(
1355 CreateSetWindowBoundsCommand(browser
->session_id(),
1356 browser
->window()->GetRestoredBounds(),
1357 browser
->window()->GetRestoredState()));
1359 commands
->push_back(CreateSetWindowTypeCommand(
1360 browser
->session_id(), WindowTypeForBrowserType(browser
->type())));
1362 if (!browser
->app_name().empty()) {
1363 commands
->push_back(CreateSetWindowAppNameCommand(
1364 kCommandSetWindowAppName
,
1365 browser
->session_id().id(),
1366 browser
->app_name()));
1369 windows_to_track
->insert(browser
->session_id().id());
1370 TabStripModel
* tab_strip
= browser
->tab_strip_model();
1371 for (int i
= 0; i
< tab_strip
->count(); ++i
) {
1372 WebContents
* tab
= tab_strip
->GetWebContentsAt(i
);
1374 BuildCommandsForTab(browser
->session_id(), tab
, i
,
1375 tab_strip
->IsTabPinned(i
),
1376 commands
, tab_to_available_range
);
1379 commands
->push_back(
1380 CreateSetSelectedTabInWindow(browser
->session_id(),
1381 browser
->tab_strip_model()->active_index()));
1384 void SessionService::BuildCommandsFromBrowsers(
1385 std::vector
<SessionCommand
*>* commands
,
1386 IdToRange
* tab_to_available_range
,
1387 std::set
<SessionID::id_type
>* windows_to_track
) {
1389 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1390 Browser
* browser
= *it
;
1391 // Make sure the browser has tabs and a window. Browser's destructor
1392 // removes itself from the BrowserList. When a browser is closed the
1393 // destructor is not necessarily run immediately. This means it's possible
1394 // for us to get a handle to a browser that is about to be removed. If
1395 // the tab count is 0 or the window is NULL, the browser is about to be
1396 // deleted, so we ignore it.
1397 if (ShouldTrackBrowser(browser
) && browser
->tab_strip_model()->count() &&
1398 browser
->window()) {
1399 BuildCommandsForBrowser(browser
, commands
, tab_to_available_range
,
1405 void SessionService::ScheduleReset() {
1406 set_pending_reset(true);
1407 STLDeleteElements(&pending_commands());
1408 tab_to_available_range_
.clear();
1409 windows_tracking_
.clear();
1410 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_
,
1411 &windows_tracking_
);
1412 if (!windows_tracking_
.empty()) {
1413 // We're lazily created on startup and won't get an initial batch of
1414 // SetWindowType messages. Set these here to make sure our state is correct.
1415 has_open_trackable_browsers_
= true;
1416 move_on_new_browser_
= true;
1421 bool SessionService::ReplacePendingCommand(SessionCommand
* command
) {
1422 // We optimize page navigations, which can happen quite frequently and
1423 // are expensive. And activation is like Highlander, there can only be one!
1424 if (command
->id() != kCommandUpdateTabNavigation
&&
1425 command
->id() != kCommandSetActiveWindow
) {
1428 for (std::vector
<SessionCommand
*>::reverse_iterator i
=
1429 pending_commands().rbegin(); i
!= pending_commands().rend(); ++i
) {
1430 SessionCommand
* existing_command
= *i
;
1431 if (command
->id() == kCommandUpdateTabNavigation
&&
1432 existing_command
->id() == kCommandUpdateTabNavigation
) {
1433 scoped_ptr
<Pickle
> command_pickle(command
->PayloadAsPickle());
1434 PickleIterator
iterator(*command_pickle
);
1435 SessionID::id_type command_tab_id
;
1436 int command_nav_index
;
1437 if (!command_pickle
->ReadInt(&iterator
, &command_tab_id
) ||
1438 !command_pickle
->ReadInt(&iterator
, &command_nav_index
)) {
1441 SessionID::id_type existing_tab_id
;
1442 int existing_nav_index
;
1444 // Creating a pickle like this means the Pickle references the data from
1445 // the command. Make sure we delete the pickle before the command, else
1446 // the pickle references deleted memory.
1447 scoped_ptr
<Pickle
> existing_pickle(existing_command
->PayloadAsPickle());
1448 iterator
= PickleIterator(*existing_pickle
);
1449 if (!existing_pickle
->ReadInt(&iterator
, &existing_tab_id
) ||
1450 !existing_pickle
->ReadInt(&iterator
, &existing_nav_index
)) {
1454 if (existing_tab_id
== command_tab_id
&&
1455 existing_nav_index
== command_nav_index
) {
1456 // existing_command is an update for the same tab/index pair. Replace
1457 // it with the new one. We need to add to the end of the list just in
1458 // case there is a prune command after the update command.
1459 delete existing_command
;
1460 pending_commands().erase(i
.base() - 1);
1461 pending_commands().push_back(command
);
1466 if (command
->id() == kCommandSetActiveWindow
&&
1467 existing_command
->id() == kCommandSetActiveWindow
) {
1469 delete existing_command
;
1476 void SessionService::ScheduleCommand(SessionCommand
* command
) {
1478 if (ReplacePendingCommand(command
))
1480 BaseSessionService::ScheduleCommand(command
);
1481 // Don't schedule a reset on tab closed/window closed. Otherwise we may
1482 // lose tabs/windows we want to restore from if we exit right after this.
1483 if (!pending_reset() && pending_window_close_ids_
.empty() &&
1484 commands_since_reset() >= kWritesPerReset
&&
1485 (command
->id() != kCommandTabClosed
&&
1486 command
->id() != kCommandWindowClosed
)) {
1491 void SessionService::CommitPendingCloses() {
1492 for (PendingTabCloseIDs::iterator i
= pending_tab_close_ids_
.begin();
1493 i
!= pending_tab_close_ids_
.end(); ++i
) {
1494 ScheduleCommand(CreateTabClosedCommand(*i
));
1496 pending_tab_close_ids_
.clear();
1498 for (PendingWindowCloseIDs::iterator i
= pending_window_close_ids_
.begin();
1499 i
!= pending_window_close_ids_
.end(); ++i
) {
1500 ScheduleCommand(CreateWindowClosedCommand(*i
));
1502 pending_window_close_ids_
.clear();
1505 bool SessionService::IsOnlyOneTabLeft() const {
1507 // We're testing, always return false.
1511 int window_count
= 0;
1512 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1513 Browser
* browser
= *it
;
1514 const SessionID::id_type window_id
= browser
->session_id().id();
1515 if (ShouldTrackBrowser(browser
) &&
1516 window_closing_ids_
.find(window_id
) == window_closing_ids_
.end()) {
1517 if (++window_count
> 1)
1519 // By the time this is invoked the tab has been removed. As such, we use
1520 // > 0 here rather than > 1.
1521 if (browser
->tab_strip_model()->count() > 0)
1528 bool SessionService::HasOpenTrackableBrowsers(
1529 const SessionID
& window_id
) const {
1531 // We're testing, always return false.
1535 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1536 Browser
* browser
= *it
;
1537 const SessionID::id_type browser_id
= browser
->session_id().id();
1538 if (browser_id
!= window_id
.id() &&
1539 window_closing_ids_
.find(browser_id
) == window_closing_ids_
.end() &&
1540 ShouldTrackBrowser(browser
)) {
1547 bool SessionService::ShouldTrackChangesToWindow(
1548 const SessionID
& window_id
) const {
1549 return windows_tracking_
.find(window_id
.id()) != windows_tracking_
.end();
1552 bool SessionService::ShouldTrackBrowser(Browser
* browser
) const {
1553 AppType app_type
= browser
->is_app() ? TYPE_APP
: TYPE_NORMAL
;
1554 return browser
->profile() == profile() &&
1555 should_track_changes_for_browser_type(browser
->type(), app_type
);
1558 bool SessionService::should_track_changes_for_browser_type(Browser::Type type
,
1560 #if defined(OS_CHROMEOS)
1561 // Restore app popups for chromeos alone.
1562 if (type
== Browser::TYPE_POPUP
&& app_type
== TYPE_APP
)
1566 return type
== Browser::TYPE_TABBED
||
1567 (type
== Browser::TYPE_POPUP
&& browser_defaults::kRestorePopups
);
1570 SessionService::WindowType
SessionService::WindowTypeForBrowserType(
1571 Browser::Type type
) {
1573 case Browser::TYPE_POPUP
:
1575 case Browser::TYPE_TABBED
:
1583 Browser::Type
SessionService::BrowserTypeForWindowType(WindowType type
) {
1586 return Browser::TYPE_POPUP
;
1589 return Browser::TYPE_TABBED
;
1593 void SessionService::RecordSessionUpdateHistogramData(int type
,
1594 base::TimeTicks
* last_updated_time
) {
1595 if (!last_updated_time
->is_null()) {
1596 base::TimeDelta delta
= base::TimeTicks::Now() - *last_updated_time
;
1597 // We're interested in frequent updates periods longer than
1599 bool use_long_period
= false;
1600 if (delta
>= save_delay_in_mins_
) {
1601 use_long_period
= true;
1604 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED
:
1605 RecordUpdatedSaveTime(delta
, use_long_period
);
1606 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1608 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
:
1609 RecordUpdatedTabClosed(delta
, use_long_period
);
1610 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1612 case content::NOTIFICATION_NAV_LIST_PRUNED
:
1613 RecordUpdatedNavListPruned(delta
, use_long_period
);
1614 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1616 case content::NOTIFICATION_NAV_ENTRY_COMMITTED
:
1617 RecordUpdatedNavEntryCommit(delta
, use_long_period
);
1618 RecordUpdatedSessionNavigationOrTab(delta
, use_long_period
);
1621 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1625 (*last_updated_time
) = base::TimeTicks::Now();
1628 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta
,
1629 bool use_long_period
) {
1630 std::string
name("SessionRestore.TabClosedPeriod");
1631 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1633 // 2500ms is the default save delay.
1634 save_delay_in_millis_
,
1635 save_delay_in_mins_
,
1637 if (use_long_period
) {
1638 std::string
long_name_("SessionRestore.TabClosedLongPeriod");
1639 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1641 save_delay_in_mins_
,
1647 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta
,
1648 bool use_long_period
) {
1649 std::string
name("SessionRestore.NavigationListPrunedPeriod");
1650 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1652 // 2500ms is the default save delay.
1653 save_delay_in_millis_
,
1654 save_delay_in_mins_
,
1656 if (use_long_period
) {
1657 std::string
long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1658 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1660 save_delay_in_mins_
,
1666 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta
,
1667 bool use_long_period
) {
1668 std::string
name("SessionRestore.NavEntryCommittedPeriod");
1669 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1671 // 2500ms is the default save delay.
1672 save_delay_in_millis_
,
1673 save_delay_in_mins_
,
1675 if (use_long_period
) {
1676 std::string
long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1677 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1679 save_delay_in_mins_
,
1685 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta
,
1686 bool use_long_period
) {
1687 std::string
name("SessionRestore.NavOrTabUpdatePeriod");
1688 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1690 // 2500ms is the default save delay.
1691 save_delay_in_millis_
,
1692 save_delay_in_mins_
,
1694 if (use_long_period
) {
1695 std::string
long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1696 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1698 save_delay_in_mins_
,
1704 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta
,
1705 bool use_long_period
) {
1706 std::string
name("SessionRestore.SavePeriod");
1707 UMA_HISTOGRAM_CUSTOM_TIMES(name
,
1709 // 2500ms is the default save delay.
1710 save_delay_in_millis_
,
1711 save_delay_in_mins_
,
1713 if (use_long_period
) {
1714 std::string
long_name_("SessionRestore.SaveLongPeriod");
1715 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_
,
1717 save_delay_in_mins_
,
1723 void SessionService::TabInserted(WebContents
* contents
) {
1724 SessionTabHelper
* session_tab_helper
=
1725 SessionTabHelper::FromWebContents(contents
);
1726 if (!ShouldTrackChangesToWindow(session_tab_helper
->window_id()))
1728 SetTabWindow(session_tab_helper
->window_id(),
1729 session_tab_helper
->session_id());
1730 extensions::TabHelper
* extensions_tab_helper
=
1731 extensions::TabHelper::FromWebContents(contents
);
1732 if (extensions_tab_helper
&&
1733 extensions_tab_helper
->extension_app()) {
1734 SetTabExtensionAppID(
1735 session_tab_helper
->window_id(),
1736 session_tab_helper
->session_id(),
1737 extensions_tab_helper
->extension_app()->id());
1740 // Record the association between the SessionStorageNamespace and the
1743 // TODO(ajwong): This should be processing the whole map rather than
1744 // just the default. This in particular will not work for tabs with only
1745 // isolated apps which won't have a default partition.
1746 content::SessionStorageNamespace
* session_storage_namespace
=
1747 contents
->GetController().GetDefaultSessionStorageNamespace();
1748 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1749 session_tab_helper
->session_id(),
1750 session_storage_namespace
->persistent_id()));
1751 session_storage_namespace
->SetShouldPersist(true);
1754 void SessionService::TabClosing(WebContents
* contents
) {
1755 // Allow the associated sessionStorage to get deleted; it won't be needed
1756 // in the session restore.
1757 content::SessionStorageNamespace
* session_storage_namespace
=
1758 contents
->GetController().GetDefaultSessionStorageNamespace();
1759 session_storage_namespace
->SetShouldPersist(false);
1760 SessionTabHelper
* session_tab_helper
=
1761 SessionTabHelper::FromWebContents(contents
);
1762 TabClosed(session_tab_helper
->window_id(),
1763 session_tab_helper
->session_id(),
1764 contents
->GetClosedByUserGesture());
1765 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
1766 &last_updated_tab_closed_time_
);
1769 void SessionService::MaybeDeleteSessionOnlyData() {
1770 // Clear session data if the last window for a profile has been closed and
1771 // closing the last window would normally close Chrome, unless background mode
1773 if (has_open_trackable_browsers_
||
1774 browser_defaults::kBrowserAliveWithNoWindows
||
1775 g_browser_process
->background_mode_manager()->IsBackgroundModeActive()) {
1779 // Check for any open windows for the current profile that we aren't tracking.
1780 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1781 if ((*it
)->profile() == profile())
1784 DeleteSessionOnlyData(profile());