Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sessions / session_service.cc
blob75fcc7f0301ff35e8b00f98c1f84780a4b3db48d
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"
7 #include <algorithm>
8 #include <set>
9 #include <utility>
10 #include <vector>
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"
50 #endif
52 using base::Time;
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;
89 namespace {
91 // Various payload structures.
92 struct ClosedPayload {
93 SessionID::id_type id;
94 int64 close_time;
97 struct WindowBoundsPayload2 {
98 SessionID::id_type window_id;
99 int32 x;
100 int32 y;
101 int32 w;
102 int32 h;
103 bool is_maximized;
106 struct WindowBoundsPayload3 {
107 SessionID::id_type window_id;
108 int32 x;
109 int32 y;
110 int32 w;
111 int32 h;
112 int32 show_state;
115 typedef SessionID::id_type ActiveWindowPayload;
117 struct IDAndIndexPayload {
118 SessionID::id_type id;
119 int32 index;
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;
136 bool pinned_state;
139 // Returns the show state to store to disk based |state|.
140 ui::WindowShowState AdjustShowState(ui::WindowShowState state) {
141 switch (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:
147 return state;
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
159 // successful).
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);
173 return true;
174 } else {
175 return false;
177 #else
178 return false;
179 #endif
182 } // namespace
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) {
194 Init();
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) {
205 Init();
208 SessionService::~SessionService() {
209 // The BrowserList should outlive the SessionService since it's static and
210 // the SessionService is a BrowserContextKeyedService.
211 BrowserList::RemoveObserver(this);
212 Save();
215 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
216 return RestoreIfNecessary(urls_to_open, NULL);
219 void SessionService::ResetFromCurrentBrowsers() {
220 ScheduleReset();
223 void SessionService::MoveCurrentSessionToLastSession() {
224 pending_tab_close_ids_.clear();
225 window_closing_ids_.clear();
226 pending_window_close_ids_.clear();
228 Save();
230 RunTaskOnBackendThread(
231 FROM_HERE, base::Bind(&SessionBackend::MoveCurrentSessionToLastSession,
232 backend()));
235 void SessionService::SetTabWindow(const SessionID& window_id,
236 const SessionID& tab_id) {
237 if (!ShouldTrackChangesToWindow(window_id))
238 return;
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))
247 return;
249 ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, show_state));
252 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
253 const SessionID& tab_id,
254 int new_index) {
255 if (!ShouldTrackChangesToWindow(window_id))
256 return;
258 ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index));
261 void SessionService::SetPinnedState(const SessionID& window_id,
262 const SessionID& tab_id,
263 bool is_pinned) {
264 if (!ShouldTrackChangesToWindow(window_id))
265 return;
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) {
273 if (!tab_id.id())
274 return; // Hapens when the tab is replaced.
276 if (!ShouldTrackChangesToWindow(window_id))
277 return;
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
287 // committed.
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()));
299 } else {
300 // User closed the last tab in the last tabbed browser. Don't mark the
301 // tab closed.
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))
309 return;
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))
319 return;
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 (should_record_close_as_pending())
332 pending_window_close_ids_.insert(window_id.id());
333 else
334 window_closing_ids_.insert(window_id.id());
337 void SessionService::WindowClosed(const SessionID& window_id) {
338 if (!ShouldTrackChangesToWindow(window_id))
339 return;
341 windows_tracking_.erase(window_id.id());
343 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
344 window_closing_ids_.erase(window_id.id());
345 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
346 } else if (pending_window_close_ids_.find(window_id.id()) ==
347 pending_window_close_ids_.end()) {
348 // We'll hit this if user closed the last tab in a window.
349 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
350 if (should_record_close_as_pending())
351 pending_window_close_ids_.insert(window_id.id());
352 else
353 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
355 // Clear session data if the last window for a profile has been closed and
356 // closing the last window would normally close Chrome, unless background mode
357 // is active.
358 if (!has_open_trackable_browsers_ &&
359 !browser_defaults::kBrowserAliveWithNoWindows &&
360 !g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
361 DeleteSessionOnlyData(profile());
365 void SessionService::SetWindowType(const SessionID& window_id,
366 Browser::Type type,
367 AppType app_type) {
368 if (!should_track_changes_for_browser_type(type, app_type))
369 return;
371 windows_tracking_.insert(window_id.id());
373 // The user created a new tabbed browser with our profile. Commit any
374 // pending closes.
375 CommitPendingCloses();
377 has_open_trackable_browsers_ = true;
378 move_on_new_browser_ = true;
380 ScheduleCommand(
381 CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type)));
384 void SessionService::SetWindowAppName(
385 const SessionID& window_id,
386 const std::string& app_name) {
387 if (!ShouldTrackChangesToWindow(window_id))
388 return;
390 ScheduleCommand(CreateSetTabExtensionAppIDCommand(
391 kCommandSetWindowAppName,
392 window_id.id(),
393 app_name));
396 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
397 const SessionID& tab_id,
398 int count) {
399 if (!ShouldTrackChangesToWindow(window_id))
400 return;
402 TabNavigationPathPrunedFromBackPayload payload = { 0 };
403 payload.id = tab_id.id();
404 payload.index = count;
405 SessionCommand* command =
406 new SessionCommand(kCommandTabNavigationPathPrunedFromBack,
407 sizeof(payload));
408 memcpy(command->contents(), &payload, sizeof(payload));
409 ScheduleCommand(command);
412 void SessionService::TabNavigationPathPrunedFromFront(
413 const SessionID& window_id,
414 const SessionID& tab_id,
415 int count) {
416 if (!ShouldTrackChangesToWindow(window_id))
417 return;
419 // Update the range of indices.
420 if (tab_to_available_range_.find(tab_id.id()) !=
421 tab_to_available_range_.end()) {
422 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
423 range.first = std::max(0, range.first - count);
424 range.second = std::max(0, range.second - count);
427 TabNavigationPathPrunedFromFrontPayload payload = { 0 };
428 payload.id = tab_id.id();
429 payload.index = count;
430 SessionCommand* command =
431 new SessionCommand(kCommandTabNavigationPathPrunedFromFront,
432 sizeof(payload));
433 memcpy(command->contents(), &payload, sizeof(payload));
434 ScheduleCommand(command);
437 void SessionService::UpdateTabNavigation(
438 const SessionID& window_id,
439 const SessionID& tab_id,
440 const SerializedNavigationEntry& navigation) {
441 if (!ShouldTrackEntry(navigation.virtual_url()) ||
442 !ShouldTrackChangesToWindow(window_id)) {
443 return;
446 if (tab_to_available_range_.find(tab_id.id()) !=
447 tab_to_available_range_.end()) {
448 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
449 range.first = std::min(navigation.index(), range.first);
450 range.second = std::max(navigation.index(), range.second);
452 ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation,
453 tab_id.id(), navigation));
456 void SessionService::TabRestored(WebContents* tab, bool pinned) {
457 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
458 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
459 return;
461 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1,
462 pinned, &pending_commands(), NULL);
463 StartSaveTimer();
466 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
467 const SessionID& tab_id,
468 int index) {
469 if (!ShouldTrackChangesToWindow(window_id))
470 return;
472 if (tab_to_available_range_.find(tab_id.id()) !=
473 tab_to_available_range_.end()) {
474 if (index < tab_to_available_range_[tab_id.id()].first ||
475 index > tab_to_available_range_[tab_id.id()].second) {
476 // The new index is outside the range of what we've archived, schedule
477 // a reset.
478 ResetFromCurrentBrowsers();
479 return;
482 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index));
485 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
486 int index) {
487 if (!ShouldTrackChangesToWindow(window_id))
488 return;
490 ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index));
493 void SessionService::SetTabUserAgentOverride(
494 const SessionID& window_id,
495 const SessionID& tab_id,
496 const std::string& user_agent_override) {
497 if (!ShouldTrackChangesToWindow(window_id))
498 return;
500 ScheduleCommand(CreateSetTabUserAgentOverrideCommand(
501 kCommandSetTabUserAgentOverride, tab_id.id(), user_agent_override));
504 CancelableTaskTracker::TaskId SessionService::GetLastSession(
505 const SessionCallback& callback,
506 CancelableTaskTracker* tracker) {
507 // OnGotSessionCommands maps the SessionCommands to browser state, then run
508 // the callback.
509 return ScheduleGetLastSessionCommands(
510 base::Bind(&SessionService::OnGotSessionCommands,
511 base::Unretained(this), callback),
512 tracker);
515 void SessionService::Save() {
516 bool had_commands = !pending_commands().empty();
517 BaseSessionService::Save();
518 if (had_commands) {
519 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
520 &last_updated_save_time_);
521 content::NotificationService::current()->Notify(
522 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
523 content::Source<Profile>(profile()),
524 content::NotificationService::NoDetails());
528 void SessionService::Init() {
529 // Register for the notifications we're interested in.
530 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
531 content::NotificationService::AllSources());
532 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
533 content::NotificationService::AllSources());
534 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
535 content::NotificationService::AllSources());
536 registrar_.Add(
537 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
538 content::NotificationService::AllSources());
540 BrowserList::AddObserver(this);
543 bool SessionService::processed_any_commands() {
544 return backend()->inited() || !pending_commands().empty();
547 bool SessionService::ShouldNewWindowStartSession() {
548 // ChromeOS and OSX have different ideas of application lifetime than
549 // the other platforms.
550 // On ChromeOS opening a new window should never start a new session.
551 #if defined(OS_CHROMEOS)
552 if (!force_browser_not_alive_with_no_windows_)
553 return false;
554 #endif
555 if (!has_open_trackable_browsers_ &&
556 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
557 !SessionRestore::IsRestoring(profile())
558 #if defined(OS_MACOSX)
559 // On OSX, a new window should not start a new session if it was opened
560 // from the dock or the menubar.
561 && !app_controller_mac::IsOpeningNewWindow()
562 #endif // OS_MACOSX
564 return true;
566 return false;
569 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
570 Browser* browser) {
571 if (ShouldNewWindowStartSession()) {
572 // We're going from no tabbed browsers to a tabbed browser (and not in
573 // process startup), restore the last session.
574 if (move_on_new_browser_) {
575 // Make the current session the last.
576 MoveCurrentSessionToLastSession();
577 move_on_new_browser_ = false;
579 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
580 *CommandLine::ForCurrentProcess(), profile());
581 if (pref.type == SessionStartupPref::LAST) {
582 SessionRestore::RestoreSession(
583 profile(), browser,
584 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
585 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
586 urls_to_open);
587 return true;
590 return false;
593 void SessionService::Observe(int type,
594 const content::NotificationSource& source,
595 const content::NotificationDetails& details) {
596 // All of our messages have the NavigationController as the source.
597 switch (type) {
598 case content::NOTIFICATION_NAV_LIST_PRUNED: {
599 WebContents* web_contents =
600 content::Source<content::NavigationController>(source).ptr()->
601 GetWebContents();
602 SessionTabHelper* session_tab_helper =
603 SessionTabHelper::FromWebContents(web_contents);
604 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
605 return;
606 content::Details<content::PrunedDetails> pruned_details(details);
607 if (pruned_details->from_front) {
608 TabNavigationPathPrunedFromFront(
609 session_tab_helper->window_id(),
610 session_tab_helper->session_id(),
611 pruned_details->count);
612 } else {
613 TabNavigationPathPrunedFromBack(
614 session_tab_helper->window_id(),
615 session_tab_helper->session_id(),
616 web_contents->GetController().GetEntryCount());
618 RecordSessionUpdateHistogramData(type,
619 &last_updated_nav_list_pruned_time_);
620 break;
623 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
624 WebContents* web_contents =
625 content::Source<content::NavigationController>(source).ptr()->
626 GetWebContents();
627 SessionTabHelper* session_tab_helper =
628 SessionTabHelper::FromWebContents(web_contents);
629 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
630 return;
631 content::Details<content::EntryChangedDetails> changed(details);
632 const SerializedNavigationEntry navigation =
633 SerializedNavigationEntry::FromNavigationEntry(
634 changed->index, *changed->changed_entry);
635 UpdateTabNavigation(session_tab_helper->window_id(),
636 session_tab_helper->session_id(),
637 navigation);
638 break;
641 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
642 WebContents* web_contents =
643 content::Source<content::NavigationController>(source).ptr()->
644 GetWebContents();
645 SessionTabHelper* session_tab_helper =
646 SessionTabHelper::FromWebContents(web_contents);
647 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
648 return;
649 int current_entry_index =
650 web_contents->GetController().GetCurrentEntryIndex();
651 SetSelectedNavigationIndex(
652 session_tab_helper->window_id(),
653 session_tab_helper->session_id(),
654 current_entry_index);
655 const SerializedNavigationEntry navigation =
656 SerializedNavigationEntry::FromNavigationEntry(
657 current_entry_index,
658 *web_contents->GetController().GetEntryAtIndex(
659 current_entry_index));
660 UpdateTabNavigation(
661 session_tab_helper->window_id(),
662 session_tab_helper->session_id(),
663 navigation);
664 content::Details<content::LoadCommittedDetails> changed(details);
665 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
666 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
667 RecordSessionUpdateHistogramData(type,
668 &last_updated_nav_entry_commit_time_);
670 break;
673 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
674 extensions::TabHelper* extension_tab_helper =
675 content::Source<extensions::TabHelper>(source).ptr();
676 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
677 profile()) {
678 return;
680 if (extension_tab_helper->extension_app()) {
681 SessionTabHelper* session_tab_helper =
682 SessionTabHelper::FromWebContents(
683 extension_tab_helper->web_contents());
684 SetTabExtensionAppID(session_tab_helper->window_id(),
685 session_tab_helper->session_id(),
686 extension_tab_helper->extension_app()->id());
688 break;
691 default:
692 NOTREACHED();
696 void SessionService::OnBrowserSetLastActive(Browser* browser) {
697 if (ShouldTrackBrowser(browser))
698 ScheduleCommand(CreateSetActiveWindowCommand(browser->session_id()));
701 void SessionService::SetTabExtensionAppID(
702 const SessionID& window_id,
703 const SessionID& tab_id,
704 const std::string& extension_app_id) {
705 if (!ShouldTrackChangesToWindow(window_id))
706 return;
708 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID,
709 tab_id.id(), extension_app_id));
712 SessionCommand* SessionService::CreateSetSelectedTabInWindow(
713 const SessionID& window_id,
714 int index) {
715 SelectedTabInIndexPayload payload = { 0 };
716 payload.id = window_id.id();
717 payload.index = index;
718 SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex,
719 sizeof(payload));
720 memcpy(command->contents(), &payload, sizeof(payload));
721 return command;
724 SessionCommand* SessionService::CreateSetTabWindowCommand(
725 const SessionID& window_id,
726 const SessionID& tab_id) {
727 SessionID::id_type payload[] = { window_id.id(), tab_id.id() };
728 SessionCommand* command =
729 new SessionCommand(kCommandSetTabWindow, sizeof(payload));
730 memcpy(command->contents(), payload, sizeof(payload));
731 return command;
734 SessionCommand* SessionService::CreateSetWindowBoundsCommand(
735 const SessionID& window_id,
736 const gfx::Rect& bounds,
737 ui::WindowShowState show_state) {
738 WindowBoundsPayload3 payload = { 0 };
739 payload.window_id = window_id.id();
740 payload.x = bounds.x();
741 payload.y = bounds.y();
742 payload.w = bounds.width();
743 payload.h = bounds.height();
744 payload.show_state = AdjustShowState(show_state);
745 SessionCommand* command = new SessionCommand(kCommandSetWindowBounds3,
746 sizeof(payload));
747 memcpy(command->contents(), &payload, sizeof(payload));
748 return command;
751 SessionCommand* SessionService::CreateSetTabIndexInWindowCommand(
752 const SessionID& tab_id,
753 int new_index) {
754 TabIndexInWindowPayload payload = { 0 };
755 payload.id = tab_id.id();
756 payload.index = new_index;
757 SessionCommand* command =
758 new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload));
759 memcpy(command->contents(), &payload, sizeof(payload));
760 return command;
763 SessionCommand* SessionService::CreateTabClosedCommand(
764 const SessionID::id_type tab_id) {
765 ClosedPayload payload;
766 // Because of what appears to be a compiler bug setting payload to {0} doesn't
767 // set the padding to 0, resulting in Purify reporting an UMR when we write
768 // the structure to disk. To avoid this we explicitly memset the struct.
769 memset(&payload, 0, sizeof(payload));
770 payload.id = tab_id;
771 payload.close_time = Time::Now().ToInternalValue();
772 SessionCommand* command =
773 new SessionCommand(kCommandTabClosed, sizeof(payload));
774 memcpy(command->contents(), &payload, sizeof(payload));
775 return command;
778 SessionCommand* SessionService::CreateWindowClosedCommand(
779 const SessionID::id_type window_id) {
780 ClosedPayload payload;
781 // See comment in CreateTabClosedCommand as to why we do this.
782 memset(&payload, 0, sizeof(payload));
783 payload.id = window_id;
784 payload.close_time = Time::Now().ToInternalValue();
785 SessionCommand* command =
786 new SessionCommand(kCommandWindowClosed, sizeof(payload));
787 memcpy(command->contents(), &payload, sizeof(payload));
788 return command;
791 SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand(
792 const SessionID& tab_id,
793 int index) {
794 SelectedNavigationIndexPayload payload = { 0 };
795 payload.id = tab_id.id();
796 payload.index = index;
797 SessionCommand* command = new SessionCommand(
798 kCommandSetSelectedNavigationIndex, sizeof(payload));
799 memcpy(command->contents(), &payload, sizeof(payload));
800 return command;
803 SessionCommand* SessionService::CreateSetWindowTypeCommand(
804 const SessionID& window_id,
805 WindowType type) {
806 WindowTypePayload payload = { 0 };
807 payload.id = window_id.id();
808 payload.index = static_cast<int32>(type);
809 SessionCommand* command = new SessionCommand(
810 kCommandSetWindowType, sizeof(payload));
811 memcpy(command->contents(), &payload, sizeof(payload));
812 return command;
815 SessionCommand* SessionService::CreatePinnedStateCommand(
816 const SessionID& tab_id,
817 bool is_pinned) {
818 PinnedStatePayload payload = { 0 };
819 payload.tab_id = tab_id.id();
820 payload.pinned_state = is_pinned;
821 SessionCommand* command =
822 new SessionCommand(kCommandSetPinnedState, sizeof(payload));
823 memcpy(command->contents(), &payload, sizeof(payload));
824 return command;
827 SessionCommand* SessionService::CreateSessionStorageAssociatedCommand(
828 const SessionID& tab_id,
829 const std::string& session_storage_persistent_id) {
830 Pickle pickle;
831 pickle.WriteInt(tab_id.id());
832 pickle.WriteString(session_storage_persistent_id);
833 return new SessionCommand(kCommandSessionStorageAssociated, pickle);
836 SessionCommand* SessionService::CreateSetActiveWindowCommand(
837 const SessionID& window_id) {
838 ActiveWindowPayload payload = 0;
839 payload = window_id.id();
840 SessionCommand* command =
841 new SessionCommand(kCommandSetActiveWindow, sizeof(payload));
842 memcpy(command->contents(), &payload, sizeof(payload));
843 return command;
846 void SessionService::OnGotSessionCommands(
847 const SessionCallback& callback,
848 ScopedVector<SessionCommand> commands) {
849 ScopedVector<SessionWindow> valid_windows;
850 SessionID::id_type active_window_id = 0;
852 RestoreSessionFromCommands(
853 commands.get(), &valid_windows.get(), &active_window_id);
854 callback.Run(valid_windows.Pass(), active_window_id);
857 void SessionService::RestoreSessionFromCommands(
858 const std::vector<SessionCommand*>& commands,
859 std::vector<SessionWindow*>* valid_windows,
860 SessionID::id_type* active_window_id) {
861 std::map<int, SessionTab*> tabs;
862 std::map<int, SessionWindow*> windows;
864 VLOG(1) << "RestoreSessionFromCommands " << commands.size();
865 if (CreateTabsAndWindows(commands, &tabs, &windows, active_window_id)) {
866 AddTabsToWindows(&tabs, &windows);
867 SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows);
868 UpdateSelectedTabIndex(valid_windows);
870 STLDeleteValues(&tabs);
871 // Don't delete conents of windows, that is done by the caller as all
872 // valid windows are added to valid_windows.
875 void SessionService::UpdateSelectedTabIndex(
876 std::vector<SessionWindow*>* windows) {
877 for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
878 i != windows->end(); ++i) {
879 // See note in SessionWindow as to why we do this.
880 int new_index = 0;
881 for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin();
882 j != (*i)->tabs.end(); ++j) {
883 if ((*j)->tab_visual_index == (*i)->selected_tab_index) {
884 new_index = static_cast<int>(j - (*i)->tabs.begin());
885 break;
888 (*i)->selected_tab_index = new_index;
892 SessionWindow* SessionService::GetWindow(
893 SessionID::id_type window_id,
894 IdToSessionWindow* windows) {
895 std::map<int, SessionWindow*>::iterator i = windows->find(window_id);
896 if (i == windows->end()) {
897 SessionWindow* window = new SessionWindow();
898 window->window_id.set_id(window_id);
899 (*windows)[window_id] = window;
900 return window;
902 return i->second;
905 SessionTab* SessionService::GetTab(
906 SessionID::id_type tab_id,
907 IdToSessionTab* tabs) {
908 DCHECK(tabs);
909 std::map<int, SessionTab*>::iterator i = tabs->find(tab_id);
910 if (i == tabs->end()) {
911 SessionTab* tab = new SessionTab();
912 tab->tab_id.set_id(tab_id);
913 (*tabs)[tab_id] = tab;
914 return tab;
916 return i->second;
919 std::vector<SerializedNavigationEntry>::iterator
920 SessionService::FindClosestNavigationWithIndex(
921 std::vector<SerializedNavigationEntry>* navigations,
922 int index) {
923 DCHECK(navigations);
924 for (std::vector<SerializedNavigationEntry>::iterator
925 i = navigations->begin(); i != navigations->end(); ++i) {
926 if (i->index() >= index)
927 return i;
929 return navigations->end();
932 // Function used in sorting windows. Sorting is done based on window id. As
933 // window ids increment for each new window, this effectively sorts by creation
934 // time.
935 static bool WindowOrderSortFunction(const SessionWindow* w1,
936 const SessionWindow* w2) {
937 return w1->window_id.id() < w2->window_id.id();
940 // Compares the two tabs based on visual index.
941 static bool TabVisualIndexSortFunction(const SessionTab* t1,
942 const SessionTab* t2) {
943 const int delta = t1->tab_visual_index - t2->tab_visual_index;
944 return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0);
947 void SessionService::SortTabsBasedOnVisualOrderAndPrune(
948 std::map<int, SessionWindow*>* windows,
949 std::vector<SessionWindow*>* valid_windows) {
950 std::map<int, SessionWindow*>::iterator i = windows->begin();
951 while (i != windows->end()) {
952 SessionWindow* window = i->second;
953 AppType app_type = window->app_name.empty() ? TYPE_NORMAL : TYPE_APP;
954 if (window->tabs.empty() || window->is_constrained ||
955 !should_track_changes_for_browser_type(
956 static_cast<Browser::Type>(window->type),
957 app_type)) {
958 delete window;
959 windows->erase(i++);
960 } else {
961 // Valid window; sort the tabs and add it to the list of valid windows.
962 std::sort(window->tabs.begin(), window->tabs.end(),
963 &TabVisualIndexSortFunction);
964 // Otherwise, add the window such that older windows appear first.
965 if (valid_windows->empty()) {
966 valid_windows->push_back(window);
967 } else {
968 valid_windows->insert(
969 std::upper_bound(valid_windows->begin(), valid_windows->end(),
970 window, &WindowOrderSortFunction),
971 window);
973 ++i;
978 void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs,
979 std::map<int, SessionWindow*>* windows) {
980 VLOG(1) << "AddTabsToWindws";
981 VLOG(1) << "Tabs " << tabs->size() << ", windows " << windows->size();
982 std::map<int, SessionTab*>::iterator i = tabs->begin();
983 while (i != tabs->end()) {
984 SessionTab* tab = i->second;
985 if (tab->window_id.id() && !tab->navigations.empty()) {
986 SessionWindow* window = GetWindow(tab->window_id.id(), windows);
987 window->tabs.push_back(tab);
988 tabs->erase(i++);
990 // See note in SessionTab as to why we do this.
991 std::vector<SerializedNavigationEntry>::iterator j =
992 FindClosestNavigationWithIndex(&(tab->navigations),
993 tab->current_navigation_index);
994 if (j == tab->navigations.end()) {
995 tab->current_navigation_index =
996 static_cast<int>(tab->navigations.size() - 1);
997 } else {
998 tab->current_navigation_index =
999 static_cast<int>(j - tab->navigations.begin());
1001 } else {
1002 // Never got a set tab index in window, or tabs are empty, nothing
1003 // to do.
1004 ++i;
1009 bool SessionService::CreateTabsAndWindows(
1010 const std::vector<SessionCommand*>& data,
1011 std::map<int, SessionTab*>* tabs,
1012 std::map<int, SessionWindow*>* windows,
1013 SessionID::id_type* active_window_id) {
1014 // If the file is corrupt (command with wrong size, or unknown command), we
1015 // still return true and attempt to restore what we we can.
1016 VLOG(1) << "CreateTabsAndWindows";
1018 startup_metric_utils::ScopedSlowStartupUMA
1019 scoped_timer("Startup.SlowStartupSessionServiceCreateTabsAndWindows");
1021 for (std::vector<SessionCommand*>::const_iterator i = data.begin();
1022 i != data.end(); ++i) {
1023 const SessionCommand::id_type kCommandSetWindowBounds2 = 10;
1024 const SessionCommand* command = *i;
1026 VLOG(1) << "Read command " << (int) command->id();
1027 switch (command->id()) {
1028 case kCommandSetTabWindow: {
1029 SessionID::id_type payload[2];
1030 if (!command->GetPayload(payload, sizeof(payload))) {
1031 VLOG(1) << "Failed reading command " << command->id();
1032 return true;
1034 GetTab(payload[1], tabs)->window_id.set_id(payload[0]);
1035 break;
1038 // This is here for forward migration only. New data is saved with
1039 // |kCommandSetWindowBounds3|.
1040 case kCommandSetWindowBounds2: {
1041 WindowBoundsPayload2 payload;
1042 if (!command->GetPayload(&payload, sizeof(payload))) {
1043 VLOG(1) << "Failed reading command " << command->id();
1044 return true;
1046 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1047 payload.y,
1048 payload.w,
1049 payload.h);
1050 GetWindow(payload.window_id, windows)->show_state =
1051 payload.is_maximized ?
1052 ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL;
1053 break;
1056 case kCommandSetWindowBounds3: {
1057 WindowBoundsPayload3 payload;
1058 if (!command->GetPayload(&payload, sizeof(payload))) {
1059 VLOG(1) << "Failed reading command " << command->id();
1060 return true;
1062 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1063 payload.y,
1064 payload.w,
1065 payload.h);
1066 // SHOW_STATE_INACTIVE is not persisted.
1067 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
1068 if (payload.show_state > ui::SHOW_STATE_DEFAULT &&
1069 payload.show_state < ui::SHOW_STATE_END &&
1070 payload.show_state != ui::SHOW_STATE_INACTIVE) {
1071 show_state = static_cast<ui::WindowShowState>(payload.show_state);
1072 } else {
1073 NOTREACHED();
1075 GetWindow(payload.window_id, windows)->show_state = show_state;
1076 break;
1079 case kCommandSetTabIndexInWindow: {
1080 TabIndexInWindowPayload payload;
1081 if (!command->GetPayload(&payload, sizeof(payload))) {
1082 VLOG(1) << "Failed reading command " << command->id();
1083 return true;
1085 GetTab(payload.id, tabs)->tab_visual_index = payload.index;
1086 break;
1089 case kCommandTabClosedObsolete:
1090 case kCommandWindowClosedObsolete:
1091 case kCommandTabClosed:
1092 case kCommandWindowClosed: {
1093 ClosedPayload payload;
1094 if (!command->GetPayload(&payload, sizeof(payload)) &&
1095 !MigrateClosedPayload(*command, &payload)) {
1096 VLOG(1) << "Failed reading command " << command->id();
1097 return true;
1099 if (command->id() == kCommandTabClosed ||
1100 command->id() == kCommandTabClosedObsolete) {
1101 delete GetTab(payload.id, tabs);
1102 tabs->erase(payload.id);
1103 } else {
1104 delete GetWindow(payload.id, windows);
1105 windows->erase(payload.id);
1107 break;
1110 case kCommandTabNavigationPathPrunedFromBack: {
1111 TabNavigationPathPrunedFromBackPayload payload;
1112 if (!command->GetPayload(&payload, sizeof(payload))) {
1113 VLOG(1) << "Failed reading command " << command->id();
1114 return true;
1116 SessionTab* tab = GetTab(payload.id, tabs);
1117 tab->navigations.erase(
1118 FindClosestNavigationWithIndex(&(tab->navigations), payload.index),
1119 tab->navigations.end());
1120 break;
1123 case kCommandTabNavigationPathPrunedFromFront: {
1124 TabNavigationPathPrunedFromFrontPayload payload;
1125 if (!command->GetPayload(&payload, sizeof(payload)) ||
1126 payload.index <= 0) {
1127 VLOG(1) << "Failed reading command " << command->id();
1128 return true;
1130 SessionTab* tab = GetTab(payload.id, tabs);
1132 // Update the selected navigation index.
1133 tab->current_navigation_index =
1134 std::max(-1, tab->current_navigation_index - payload.index);
1136 // And update the index of existing navigations.
1137 for (std::vector<SerializedNavigationEntry>::iterator
1138 i = tab->navigations.begin();
1139 i != tab->navigations.end();) {
1140 i->set_index(i->index() - payload.index);
1141 if (i->index() < 0)
1142 i = tab->navigations.erase(i);
1143 else
1144 ++i;
1146 break;
1149 case kCommandUpdateTabNavigation: {
1150 SerializedNavigationEntry navigation;
1151 SessionID::id_type tab_id;
1152 if (!RestoreUpdateTabNavigationCommand(
1153 *command, &navigation, &tab_id)) {
1154 VLOG(1) << "Failed reading command " << command->id();
1155 return true;
1157 SessionTab* tab = GetTab(tab_id, tabs);
1158 std::vector<SerializedNavigationEntry>::iterator i =
1159 FindClosestNavigationWithIndex(&(tab->navigations),
1160 navigation.index());
1161 if (i != tab->navigations.end() && i->index() == navigation.index())
1162 *i = navigation;
1163 else
1164 tab->navigations.insert(i, navigation);
1165 break;
1168 case kCommandSetSelectedNavigationIndex: {
1169 SelectedNavigationIndexPayload payload;
1170 if (!command->GetPayload(&payload, sizeof(payload))) {
1171 VLOG(1) << "Failed reading command " << command->id();
1172 return true;
1174 GetTab(payload.id, tabs)->current_navigation_index = payload.index;
1175 break;
1178 case kCommandSetSelectedTabInIndex: {
1179 SelectedTabInIndexPayload payload;
1180 if (!command->GetPayload(&payload, sizeof(payload))) {
1181 VLOG(1) << "Failed reading command " << command->id();
1182 return true;
1184 GetWindow(payload.id, windows)->selected_tab_index = payload.index;
1185 break;
1188 case kCommandSetWindowType: {
1189 WindowTypePayload payload;
1190 if (!command->GetPayload(&payload, sizeof(payload))) {
1191 VLOG(1) << "Failed reading command " << command->id();
1192 return true;
1194 GetWindow(payload.id, windows)->is_constrained = false;
1195 GetWindow(payload.id, windows)->type =
1196 BrowserTypeForWindowType(
1197 static_cast<WindowType>(payload.index));
1198 break;
1201 case kCommandSetPinnedState: {
1202 PinnedStatePayload payload;
1203 if (!command->GetPayload(&payload, sizeof(payload))) {
1204 VLOG(1) << "Failed reading command " << command->id();
1205 return true;
1207 GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state;
1208 break;
1211 case kCommandSetWindowAppName: {
1212 SessionID::id_type window_id;
1213 std::string app_name;
1214 if (!RestoreSetWindowAppNameCommand(*command, &window_id, &app_name))
1215 return true;
1217 GetWindow(window_id, windows)->app_name.swap(app_name);
1218 break;
1221 case kCommandSetExtensionAppID: {
1222 SessionID::id_type tab_id;
1223 std::string extension_app_id;
1224 if (!RestoreSetTabExtensionAppIDCommand(
1225 *command, &tab_id, &extension_app_id)) {
1226 VLOG(1) << "Failed reading command " << command->id();
1227 return true;
1230 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id);
1231 break;
1234 case kCommandSetTabUserAgentOverride: {
1235 SessionID::id_type tab_id;
1236 std::string user_agent_override;
1237 if (!RestoreSetTabUserAgentOverrideCommand(
1238 *command, &tab_id, &user_agent_override)) {
1239 return true;
1242 GetTab(tab_id, tabs)->user_agent_override.swap(user_agent_override);
1243 break;
1246 case kCommandSessionStorageAssociated: {
1247 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1248 SessionID::id_type command_tab_id;
1249 std::string session_storage_persistent_id;
1250 PickleIterator iter(*command_pickle.get());
1251 if (!command_pickle->ReadInt(&iter, &command_tab_id) ||
1252 !command_pickle->ReadString(&iter, &session_storage_persistent_id))
1253 return true;
1254 // Associate the session storage back.
1255 GetTab(command_tab_id, tabs)->session_storage_persistent_id =
1256 session_storage_persistent_id;
1257 break;
1260 case kCommandSetActiveWindow: {
1261 ActiveWindowPayload payload;
1262 if (!command->GetPayload(&payload, sizeof(payload))) {
1263 VLOG(1) << "Failed reading command " << command->id();
1264 return true;
1266 *active_window_id = payload;
1267 break;
1270 default:
1271 VLOG(1) << "Failed reading an unknown command " << command->id();
1272 return true;
1275 return true;
1278 void SessionService::BuildCommandsForTab(const SessionID& window_id,
1279 WebContents* tab,
1280 int index_in_window,
1281 bool is_pinned,
1282 std::vector<SessionCommand*>* commands,
1283 IdToRange* tab_to_available_range) {
1284 DCHECK(tab && commands && window_id.id());
1285 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
1286 const SessionID& session_id(session_tab_helper->session_id());
1287 commands->push_back(CreateSetTabWindowCommand(window_id, session_id));
1289 const int current_index = tab->GetController().GetCurrentEntryIndex();
1290 const int min_index = std::max(0,
1291 current_index - max_persist_navigation_count);
1292 const int max_index =
1293 std::min(current_index + max_persist_navigation_count,
1294 tab->GetController().GetEntryCount());
1295 const int pending_index = tab->GetController().GetPendingEntryIndex();
1296 if (tab_to_available_range) {
1297 (*tab_to_available_range)[session_id.id()] =
1298 std::pair<int, int>(min_index, max_index);
1301 if (is_pinned) {
1302 commands->push_back(CreatePinnedStateCommand(session_id, true));
1305 extensions::TabHelper* extensions_tab_helper =
1306 extensions::TabHelper::FromWebContents(tab);
1307 if (extensions_tab_helper->extension_app()) {
1308 commands->push_back(
1309 CreateSetTabExtensionAppIDCommand(
1310 kCommandSetExtensionAppID, session_id.id(),
1311 extensions_tab_helper->extension_app()->id()));
1314 const std::string& ua_override = tab->GetUserAgentOverride();
1315 if (!ua_override.empty()) {
1316 commands->push_back(
1317 CreateSetTabUserAgentOverrideCommand(
1318 kCommandSetTabUserAgentOverride, session_id.id(), ua_override));
1321 for (int i = min_index; i < max_index; ++i) {
1322 const NavigationEntry* entry = (i == pending_index) ?
1323 tab->GetController().GetPendingEntry() :
1324 tab->GetController().GetEntryAtIndex(i);
1325 DCHECK(entry);
1326 if (ShouldTrackEntry(entry->GetVirtualURL())) {
1327 const SerializedNavigationEntry navigation =
1328 SerializedNavigationEntry::FromNavigationEntry(i, *entry);
1329 commands->push_back(
1330 CreateUpdateTabNavigationCommand(
1331 kCommandUpdateTabNavigation, session_id.id(), navigation));
1334 commands->push_back(
1335 CreateSetSelectedNavigationIndexCommand(session_id, current_index));
1337 if (index_in_window != -1) {
1338 commands->push_back(
1339 CreateSetTabIndexInWindowCommand(session_id, index_in_window));
1342 // Record the association between the sessionStorage namespace and the tab.
1343 content::SessionStorageNamespace* session_storage_namespace =
1344 tab->GetController().GetDefaultSessionStorageNamespace();
1345 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1346 session_tab_helper->session_id(),
1347 session_storage_namespace->persistent_id()));
1350 void SessionService::BuildCommandsForBrowser(
1351 Browser* browser,
1352 std::vector<SessionCommand*>* commands,
1353 IdToRange* tab_to_available_range,
1354 std::set<SessionID::id_type>* windows_to_track) {
1355 DCHECK(browser && commands);
1356 DCHECK(browser->session_id().id());
1358 commands->push_back(
1359 CreateSetWindowBoundsCommand(browser->session_id(),
1360 browser->window()->GetRestoredBounds(),
1361 browser->window()->GetRestoredState()));
1363 commands->push_back(CreateSetWindowTypeCommand(
1364 browser->session_id(), WindowTypeForBrowserType(browser->type())));
1366 if (!browser->app_name().empty()) {
1367 commands->push_back(CreateSetWindowAppNameCommand(
1368 kCommandSetWindowAppName,
1369 browser->session_id().id(),
1370 browser->app_name()));
1373 windows_to_track->insert(browser->session_id().id());
1374 TabStripModel* tab_strip = browser->tab_strip_model();
1375 for (int i = 0; i < tab_strip->count(); ++i) {
1376 WebContents* tab = tab_strip->GetWebContentsAt(i);
1377 DCHECK(tab);
1378 BuildCommandsForTab(browser->session_id(), tab, i,
1379 tab_strip->IsTabPinned(i),
1380 commands, tab_to_available_range);
1383 commands->push_back(
1384 CreateSetSelectedTabInWindow(browser->session_id(),
1385 browser->tab_strip_model()->active_index()));
1388 void SessionService::BuildCommandsFromBrowsers(
1389 std::vector<SessionCommand*>* commands,
1390 IdToRange* tab_to_available_range,
1391 std::set<SessionID::id_type>* windows_to_track) {
1392 DCHECK(commands);
1393 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1394 Browser* browser = *it;
1395 // Make sure the browser has tabs and a window. Browser's destructor
1396 // removes itself from the BrowserList. When a browser is closed the
1397 // destructor is not necessarily run immediately. This means it's possible
1398 // for us to get a handle to a browser that is about to be removed. If
1399 // the tab count is 0 or the window is NULL, the browser is about to be
1400 // deleted, so we ignore it.
1401 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
1402 browser->window()) {
1403 BuildCommandsForBrowser(browser, commands, tab_to_available_range,
1404 windows_to_track);
1409 void SessionService::ScheduleReset() {
1410 set_pending_reset(true);
1411 STLDeleteElements(&pending_commands());
1412 tab_to_available_range_.clear();
1413 windows_tracking_.clear();
1414 BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_,
1415 &windows_tracking_);
1416 if (!windows_tracking_.empty()) {
1417 // We're lazily created on startup and won't get an initial batch of
1418 // SetWindowType messages. Set these here to make sure our state is correct.
1419 has_open_trackable_browsers_ = true;
1420 move_on_new_browser_ = true;
1422 StartSaveTimer();
1425 bool SessionService::ReplacePendingCommand(SessionCommand* command) {
1426 // We optimize page navigations, which can happen quite frequently and
1427 // are expensive. And activation is like Highlander, there can only be one!
1428 if (command->id() != kCommandUpdateTabNavigation &&
1429 command->id() != kCommandSetActiveWindow) {
1430 return false;
1432 for (std::vector<SessionCommand*>::reverse_iterator i =
1433 pending_commands().rbegin(); i != pending_commands().rend(); ++i) {
1434 SessionCommand* existing_command = *i;
1435 if (command->id() == kCommandUpdateTabNavigation &&
1436 existing_command->id() == kCommandUpdateTabNavigation) {
1437 scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle());
1438 PickleIterator iterator(*command_pickle);
1439 SessionID::id_type command_tab_id;
1440 int command_nav_index;
1441 if (!command_pickle->ReadInt(&iterator, &command_tab_id) ||
1442 !command_pickle->ReadInt(&iterator, &command_nav_index)) {
1443 return false;
1445 SessionID::id_type existing_tab_id;
1446 int existing_nav_index;
1448 // Creating a pickle like this means the Pickle references the data from
1449 // the command. Make sure we delete the pickle before the command, else
1450 // the pickle references deleted memory.
1451 scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle());
1452 iterator = PickleIterator(*existing_pickle);
1453 if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) ||
1454 !existing_pickle->ReadInt(&iterator, &existing_nav_index)) {
1455 return false;
1458 if (existing_tab_id == command_tab_id &&
1459 existing_nav_index == command_nav_index) {
1460 // existing_command is an update for the same tab/index pair. Replace
1461 // it with the new one. We need to add to the end of the list just in
1462 // case there is a prune command after the update command.
1463 delete existing_command;
1464 pending_commands().erase(i.base() - 1);
1465 pending_commands().push_back(command);
1466 return true;
1468 return false;
1470 if (command->id() == kCommandSetActiveWindow &&
1471 existing_command->id() == kCommandSetActiveWindow) {
1472 *i = command;
1473 delete existing_command;
1474 return true;
1477 return false;
1480 void SessionService::ScheduleCommand(SessionCommand* command) {
1481 DCHECK(command);
1482 if (ReplacePendingCommand(command))
1483 return;
1484 BaseSessionService::ScheduleCommand(command);
1485 // Don't schedule a reset on tab closed/window closed. Otherwise we may
1486 // lose tabs/windows we want to restore from if we exit right after this.
1487 if (!pending_reset() && pending_window_close_ids_.empty() &&
1488 commands_since_reset() >= kWritesPerReset &&
1489 (command->id() != kCommandTabClosed &&
1490 command->id() != kCommandWindowClosed)) {
1491 ScheduleReset();
1495 void SessionService::CommitPendingCloses() {
1496 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
1497 i != pending_tab_close_ids_.end(); ++i) {
1498 ScheduleCommand(CreateTabClosedCommand(*i));
1500 pending_tab_close_ids_.clear();
1502 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
1503 i != pending_window_close_ids_.end(); ++i) {
1504 ScheduleCommand(CreateWindowClosedCommand(*i));
1506 pending_window_close_ids_.clear();
1509 bool SessionService::IsOnlyOneTabLeft() const {
1510 if (!profile()) {
1511 // We're testing, always return false.
1512 return false;
1515 int window_count = 0;
1516 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1517 Browser* browser = *it;
1518 const SessionID::id_type window_id = browser->session_id().id();
1519 if (ShouldTrackBrowser(browser) &&
1520 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
1521 if (++window_count > 1)
1522 return false;
1523 // By the time this is invoked the tab has been removed. As such, we use
1524 // > 0 here rather than > 1.
1525 if (browser->tab_strip_model()->count() > 0)
1526 return false;
1529 return true;
1532 bool SessionService::HasOpenTrackableBrowsers(
1533 const SessionID& window_id) const {
1534 if (!profile()) {
1535 // We're testing, always return false.
1536 return true;
1539 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1540 Browser* browser = *it;
1541 const SessionID::id_type browser_id = browser->session_id().id();
1542 if (browser_id != window_id.id() &&
1543 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
1544 ShouldTrackBrowser(browser)) {
1545 return true;
1548 return false;
1551 bool SessionService::ShouldTrackChangesToWindow(
1552 const SessionID& window_id) const {
1553 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
1556 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
1557 AppType app_type = browser->is_app() ? TYPE_APP : TYPE_NORMAL;
1558 return browser->profile() == profile() &&
1559 should_track_changes_for_browser_type(browser->type(), app_type);
1562 bool SessionService::should_track_changes_for_browser_type(Browser::Type type,
1563 AppType app_type) {
1564 #if defined(OS_CHROMEOS)
1565 // Restore app popups for chromeos alone.
1566 if (type == Browser::TYPE_POPUP && app_type == TYPE_APP)
1567 return true;
1568 #endif
1570 return type == Browser::TYPE_TABBED ||
1571 (type == Browser::TYPE_POPUP && browser_defaults::kRestorePopups);
1574 SessionService::WindowType SessionService::WindowTypeForBrowserType(
1575 Browser::Type type) {
1576 switch (type) {
1577 case Browser::TYPE_POPUP:
1578 return TYPE_POPUP;
1579 case Browser::TYPE_TABBED:
1580 return TYPE_TABBED;
1581 default:
1582 DCHECK(false);
1583 return TYPE_TABBED;
1587 Browser::Type SessionService::BrowserTypeForWindowType(WindowType type) {
1588 switch (type) {
1589 case TYPE_POPUP:
1590 return Browser::TYPE_POPUP;
1591 case TYPE_TABBED:
1592 default:
1593 return Browser::TYPE_TABBED;
1597 void SessionService::RecordSessionUpdateHistogramData(int type,
1598 base::TimeTicks* last_updated_time) {
1599 if (!last_updated_time->is_null()) {
1600 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
1601 // We're interested in frequent updates periods longer than
1602 // 10 minutes.
1603 bool use_long_period = false;
1604 if (delta >= save_delay_in_mins_) {
1605 use_long_period = true;
1607 switch (type) {
1608 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
1609 RecordUpdatedSaveTime(delta, use_long_period);
1610 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1611 break;
1612 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
1613 RecordUpdatedTabClosed(delta, use_long_period);
1614 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1615 break;
1616 case content::NOTIFICATION_NAV_LIST_PRUNED:
1617 RecordUpdatedNavListPruned(delta, use_long_period);
1618 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1619 break;
1620 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
1621 RecordUpdatedNavEntryCommit(delta, use_long_period);
1622 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1623 break;
1624 default:
1625 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1626 break;
1629 (*last_updated_time) = base::TimeTicks::Now();
1632 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
1633 bool use_long_period) {
1634 std::string name("SessionRestore.TabClosedPeriod");
1635 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1636 delta,
1637 // 2500ms is the default save delay.
1638 save_delay_in_millis_,
1639 save_delay_in_mins_,
1640 50);
1641 if (use_long_period) {
1642 std::string long_name_("SessionRestore.TabClosedLongPeriod");
1643 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1644 delta,
1645 save_delay_in_mins_,
1646 save_delay_in_hrs_,
1647 50);
1651 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1652 bool use_long_period) {
1653 std::string name("SessionRestore.NavigationListPrunedPeriod");
1654 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1655 delta,
1656 // 2500ms is the default save delay.
1657 save_delay_in_millis_,
1658 save_delay_in_mins_,
1659 50);
1660 if (use_long_period) {
1661 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1662 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1663 delta,
1664 save_delay_in_mins_,
1665 save_delay_in_hrs_,
1666 50);
1670 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1671 bool use_long_period) {
1672 std::string name("SessionRestore.NavEntryCommittedPeriod");
1673 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1674 delta,
1675 // 2500ms is the default save delay.
1676 save_delay_in_millis_,
1677 save_delay_in_mins_,
1678 50);
1679 if (use_long_period) {
1680 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1681 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1682 delta,
1683 save_delay_in_mins_,
1684 save_delay_in_hrs_,
1685 50);
1689 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1690 bool use_long_period) {
1691 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1692 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1693 delta,
1694 // 2500ms is the default save delay.
1695 save_delay_in_millis_,
1696 save_delay_in_mins_,
1697 50);
1698 if (use_long_period) {
1699 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1700 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1701 delta,
1702 save_delay_in_mins_,
1703 save_delay_in_hrs_,
1704 50);
1708 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1709 bool use_long_period) {
1710 std::string name("SessionRestore.SavePeriod");
1711 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1712 delta,
1713 // 2500ms is the default save delay.
1714 save_delay_in_millis_,
1715 save_delay_in_mins_,
1716 50);
1717 if (use_long_period) {
1718 std::string long_name_("SessionRestore.SaveLongPeriod");
1719 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1720 delta,
1721 save_delay_in_mins_,
1722 save_delay_in_hrs_,
1723 50);
1727 void SessionService::TabInserted(WebContents* contents) {
1728 SessionTabHelper* session_tab_helper =
1729 SessionTabHelper::FromWebContents(contents);
1730 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
1731 return;
1732 SetTabWindow(session_tab_helper->window_id(),
1733 session_tab_helper->session_id());
1734 extensions::TabHelper* extensions_tab_helper =
1735 extensions::TabHelper::FromWebContents(contents);
1736 if (extensions_tab_helper &&
1737 extensions_tab_helper->extension_app()) {
1738 SetTabExtensionAppID(
1739 session_tab_helper->window_id(),
1740 session_tab_helper->session_id(),
1741 extensions_tab_helper->extension_app()->id());
1744 // Record the association between the SessionStorageNamespace and the
1745 // tab.
1747 // TODO(ajwong): This should be processing the whole map rather than
1748 // just the default. This in particular will not work for tabs with only
1749 // isolated apps which won't have a default partition.
1750 content::SessionStorageNamespace* session_storage_namespace =
1751 contents->GetController().GetDefaultSessionStorageNamespace();
1752 ScheduleCommand(CreateSessionStorageAssociatedCommand(
1753 session_tab_helper->session_id(),
1754 session_storage_namespace->persistent_id()));
1755 session_storage_namespace->SetShouldPersist(true);
1758 void SessionService::TabClosing(WebContents* contents) {
1759 // Allow the associated sessionStorage to get deleted; it won't be needed
1760 // in the session restore.
1761 content::SessionStorageNamespace* session_storage_namespace =
1762 contents->GetController().GetDefaultSessionStorageNamespace();
1763 session_storage_namespace->SetShouldPersist(false);
1764 SessionTabHelper* session_tab_helper =
1765 SessionTabHelper::FromWebContents(contents);
1766 TabClosed(session_tab_helper->window_id(),
1767 session_tab_helper->session_id(),
1768 contents->GetClosedByUserGesture());
1769 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
1770 &last_updated_tab_closed_time_);