Fix infinite recursion on hiding panel when created during fullscreen mode.
[chromium-blink-merge.git] / chrome / browser / sessions / session_service.cc
blob298206a71f1ec2b4380bde4e97eac3ba937de4d4
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 KeyedService.
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 (!has_open_trackable_browsers_)
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 // The last window may be one that is not tracked.
340 MaybeDeleteSessionOnlyData();
341 return;
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());
355 else
356 ScheduleCommand(CreateWindowClosedCommand(window_id.id()));
358 MaybeDeleteSessionOnlyData();
361 void SessionService::SetWindowType(const SessionID& window_id,
362 Browser::Type type,
363 AppType app_type) {
364 if (!should_track_changes_for_browser_type(type, app_type))
365 return;
367 windows_tracking_.insert(window_id.id());
369 // The user created a new tabbed browser with our profile. Commit any
370 // pending closes.
371 CommitPendingCloses();
373 has_open_trackable_browsers_ = true;
374 move_on_new_browser_ = true;
376 ScheduleCommand(
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))
384 return;
386 ScheduleCommand(CreateSetTabExtensionAppIDCommand(
387 kCommandSetWindowAppName,
388 window_id.id(),
389 app_name));
392 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
393 const SessionID& tab_id,
394 int count) {
395 if (!ShouldTrackChangesToWindow(window_id))
396 return;
398 TabNavigationPathPrunedFromBackPayload payload = { 0 };
399 payload.id = tab_id.id();
400 payload.index = count;
401 SessionCommand* command =
402 new SessionCommand(kCommandTabNavigationPathPrunedFromBack,
403 sizeof(payload));
404 memcpy(command->contents(), &payload, sizeof(payload));
405 ScheduleCommand(command);
408 void SessionService::TabNavigationPathPrunedFromFront(
409 const SessionID& window_id,
410 const SessionID& tab_id,
411 int count) {
412 if (!ShouldTrackChangesToWindow(window_id))
413 return;
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,
428 sizeof(payload));
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)) {
439 return;
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()))
455 return;
457 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1,
458 pinned, &pending_commands(), NULL);
459 StartSaveTimer();
462 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
463 const SessionID& tab_id,
464 int index) {
465 if (!ShouldTrackChangesToWindow(window_id))
466 return;
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
473 // a reset.
474 ResetFromCurrentBrowsers();
475 return;
478 ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index));
481 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
482 int index) {
483 if (!ShouldTrackChangesToWindow(window_id))
484 return;
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))
494 return;
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
504 // the callback.
505 return ScheduleGetLastSessionCommands(
506 base::Bind(&SessionService::OnGotSessionCommands,
507 base::Unretained(this), callback),
508 tracker);
511 void SessionService::Save() {
512 bool had_commands = !pending_commands().empty();
513 BaseSessionService::Save();
514 if (had_commands) {
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());
532 registrar_.Add(
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_)
549 return false;
550 #endif
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()
558 #endif // OS_MACOSX
560 return true;
562 return false;
565 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
566 Browser* browser) {
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(
579 profile(), browser,
580 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
581 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
582 urls_to_open);
583 return true;
586 return false;
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.
593 switch (type) {
594 case content::NOTIFICATION_NAV_LIST_PRUNED: {
595 WebContents* web_contents =
596 content::Source<content::NavigationController>(source).ptr()->
597 GetWebContents();
598 SessionTabHelper* session_tab_helper =
599 SessionTabHelper::FromWebContents(web_contents);
600 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
601 return;
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);
608 } else {
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_);
616 break;
619 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
620 WebContents* web_contents =
621 content::Source<content::NavigationController>(source).ptr()->
622 GetWebContents();
623 SessionTabHelper* session_tab_helper =
624 SessionTabHelper::FromWebContents(web_contents);
625 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
626 return;
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(),
633 navigation);
634 break;
637 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
638 WebContents* web_contents =
639 content::Source<content::NavigationController>(source).ptr()->
640 GetWebContents();
641 SessionTabHelper* session_tab_helper =
642 SessionTabHelper::FromWebContents(web_contents);
643 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
644 return;
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(
653 current_entry_index,
654 *web_contents->GetController().GetEntryAtIndex(
655 current_entry_index));
656 UpdateTabNavigation(
657 session_tab_helper->window_id(),
658 session_tab_helper->session_id(),
659 navigation);
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_);
666 break;
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() !=
673 profile()) {
674 return;
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());
684 break;
687 default:
688 NOTREACHED();
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))
702 return;
704 ScheduleCommand(CreateSetTabExtensionAppIDCommand(kCommandSetExtensionAppID,
705 tab_id.id(), extension_app_id));
708 SessionCommand* SessionService::CreateSetSelectedTabInWindow(
709 const SessionID& window_id,
710 int index) {
711 SelectedTabInIndexPayload payload = { 0 };
712 payload.id = window_id.id();
713 payload.index = index;
714 SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex,
715 sizeof(payload));
716 memcpy(command->contents(), &payload, sizeof(payload));
717 return command;
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));
727 return command;
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,
742 sizeof(payload));
743 memcpy(command->contents(), &payload, sizeof(payload));
744 return command;
747 SessionCommand* SessionService::CreateSetTabIndexInWindowCommand(
748 const SessionID& tab_id,
749 int new_index) {
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));
756 return command;
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));
766 payload.id = tab_id;
767 payload.close_time = Time::Now().ToInternalValue();
768 SessionCommand* command =
769 new SessionCommand(kCommandTabClosed, sizeof(payload));
770 memcpy(command->contents(), &payload, sizeof(payload));
771 return command;
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));
784 return command;
787 SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand(
788 const SessionID& tab_id,
789 int index) {
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));
796 return command;
799 SessionCommand* SessionService::CreateSetWindowTypeCommand(
800 const SessionID& window_id,
801 WindowType type) {
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));
808 return command;
811 SessionCommand* SessionService::CreatePinnedStateCommand(
812 const SessionID& tab_id,
813 bool is_pinned) {
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));
820 return command;
823 SessionCommand* SessionService::CreateSessionStorageAssociatedCommand(
824 const SessionID& tab_id,
825 const std::string& session_storage_persistent_id) {
826 Pickle pickle;
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));
839 return command;
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.
876 int new_index = 0;
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());
881 break;
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;
896 return window;
898 return i->second;
901 SessionTab* SessionService::GetTab(
902 SessionID::id_type tab_id,
903 IdToSessionTab* tabs) {
904 DCHECK(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;
910 return tab;
912 return i->second;
915 std::vector<SerializedNavigationEntry>::iterator
916 SessionService::FindClosestNavigationWithIndex(
917 std::vector<SerializedNavigationEntry>* navigations,
918 int index) {
919 DCHECK(navigations);
920 for (std::vector<SerializedNavigationEntry>::iterator
921 i = navigations->begin(); i != navigations->end(); ++i) {
922 if (i->index() >= index)
923 return i;
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
930 // time.
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),
953 app_type)) {
954 delete window;
955 windows->erase(i++);
956 } else {
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);
963 } else {
964 valid_windows->insert(
965 std::upper_bound(valid_windows->begin(), valid_windows->end(),
966 window, &WindowOrderSortFunction),
967 window);
969 ++i;
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);
984 tabs->erase(i++);
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);
993 } else {
994 tab->current_navigation_index =
995 static_cast<int>(j - tab->navigations.begin());
997 } else {
998 // Never got a set tab index in window, or tabs are empty, nothing
999 // to do.
1000 ++i;
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();
1028 return true;
1030 GetTab(payload[1], tabs)->window_id.set_id(payload[0]);
1031 break;
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();
1040 return true;
1042 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1043 payload.y,
1044 payload.w,
1045 payload.h);
1046 GetWindow(payload.window_id, windows)->show_state =
1047 payload.is_maximized ?
1048 ui::SHOW_STATE_MAXIMIZED : ui::SHOW_STATE_NORMAL;
1049 break;
1052 case kCommandSetWindowBounds3: {
1053 WindowBoundsPayload3 payload;
1054 if (!command->GetPayload(&payload, sizeof(payload))) {
1055 VLOG(1) << "Failed reading command " << command->id();
1056 return true;
1058 GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x,
1059 payload.y,
1060 payload.w,
1061 payload.h);
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);
1068 } else {
1069 NOTREACHED();
1071 GetWindow(payload.window_id, windows)->show_state = show_state;
1072 break;
1075 case kCommandSetTabIndexInWindow: {
1076 TabIndexInWindowPayload payload;
1077 if (!command->GetPayload(&payload, sizeof(payload))) {
1078 VLOG(1) << "Failed reading command " << command->id();
1079 return true;
1081 GetTab(payload.id, tabs)->tab_visual_index = payload.index;
1082 break;
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();
1093 return true;
1095 if (command->id() == kCommandTabClosed ||
1096 command->id() == kCommandTabClosedObsolete) {
1097 delete GetTab(payload.id, tabs);
1098 tabs->erase(payload.id);
1099 } else {
1100 delete GetWindow(payload.id, windows);
1101 windows->erase(payload.id);
1103 break;
1106 case kCommandTabNavigationPathPrunedFromBack: {
1107 TabNavigationPathPrunedFromBackPayload payload;
1108 if (!command->GetPayload(&payload, sizeof(payload))) {
1109 VLOG(1) << "Failed reading command " << command->id();
1110 return true;
1112 SessionTab* tab = GetTab(payload.id, tabs);
1113 tab->navigations.erase(
1114 FindClosestNavigationWithIndex(&(tab->navigations), payload.index),
1115 tab->navigations.end());
1116 break;
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();
1124 return true;
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);
1137 if (i->index() < 0)
1138 i = tab->navigations.erase(i);
1139 else
1140 ++i;
1142 break;
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();
1151 return true;
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())
1158 *i = navigation;
1159 else
1160 tab->navigations.insert(i, navigation);
1161 break;
1164 case kCommandSetSelectedNavigationIndex: {
1165 SelectedNavigationIndexPayload payload;
1166 if (!command->GetPayload(&payload, sizeof(payload))) {
1167 VLOG(1) << "Failed reading command " << command->id();
1168 return true;
1170 GetTab(payload.id, tabs)->current_navigation_index = payload.index;
1171 break;
1174 case kCommandSetSelectedTabInIndex: {
1175 SelectedTabInIndexPayload payload;
1176 if (!command->GetPayload(&payload, sizeof(payload))) {
1177 VLOG(1) << "Failed reading command " << command->id();
1178 return true;
1180 GetWindow(payload.id, windows)->selected_tab_index = payload.index;
1181 break;
1184 case kCommandSetWindowType: {
1185 WindowTypePayload payload;
1186 if (!command->GetPayload(&payload, sizeof(payload))) {
1187 VLOG(1) << "Failed reading command " << command->id();
1188 return true;
1190 GetWindow(payload.id, windows)->is_constrained = false;
1191 GetWindow(payload.id, windows)->type =
1192 BrowserTypeForWindowType(
1193 static_cast<WindowType>(payload.index));
1194 break;
1197 case kCommandSetPinnedState: {
1198 PinnedStatePayload payload;
1199 if (!command->GetPayload(&payload, sizeof(payload))) {
1200 VLOG(1) << "Failed reading command " << command->id();
1201 return true;
1203 GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state;
1204 break;
1207 case kCommandSetWindowAppName: {
1208 SessionID::id_type window_id;
1209 std::string app_name;
1210 if (!RestoreSetWindowAppNameCommand(*command, &window_id, &app_name))
1211 return true;
1213 GetWindow(window_id, windows)->app_name.swap(app_name);
1214 break;
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();
1223 return true;
1226 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id);
1227 break;
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)) {
1235 return true;
1238 GetTab(tab_id, tabs)->user_agent_override.swap(user_agent_override);
1239 break;
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))
1249 return true;
1250 // Associate the session storage back.
1251 GetTab(command_tab_id, tabs)->session_storage_persistent_id =
1252 session_storage_persistent_id;
1253 break;
1256 case kCommandSetActiveWindow: {
1257 ActiveWindowPayload payload;
1258 if (!command->GetPayload(&payload, sizeof(payload))) {
1259 VLOG(1) << "Failed reading command " << command->id();
1260 return true;
1262 *active_window_id = payload;
1263 break;
1266 default:
1267 VLOG(1) << "Failed reading an unknown command " << command->id();
1268 return true;
1271 return true;
1274 void SessionService::BuildCommandsForTab(const SessionID& window_id,
1275 WebContents* tab,
1276 int index_in_window,
1277 bool is_pinned,
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);
1297 if (is_pinned) {
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);
1321 DCHECK(entry);
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(
1347 Browser* browser,
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);
1373 DCHECK(tab);
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) {
1388 DCHECK(commands);
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,
1400 windows_to_track);
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;
1418 StartSaveTimer();
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) {
1426 return false;
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)) {
1439 return false;
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)) {
1451 return false;
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);
1462 return true;
1464 return false;
1466 if (command->id() == kCommandSetActiveWindow &&
1467 existing_command->id() == kCommandSetActiveWindow) {
1468 *i = command;
1469 delete existing_command;
1470 return true;
1473 return false;
1476 void SessionService::ScheduleCommand(SessionCommand* command) {
1477 DCHECK(command);
1478 if (ReplacePendingCommand(command))
1479 return;
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)) {
1487 ScheduleReset();
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 {
1506 if (!profile()) {
1507 // We're testing, always return false.
1508 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)
1518 return false;
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)
1522 return false;
1525 return true;
1528 bool SessionService::HasOpenTrackableBrowsers(
1529 const SessionID& window_id) const {
1530 if (!profile()) {
1531 // We're testing, always return false.
1532 return true;
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)) {
1541 return true;
1544 return false;
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,
1559 AppType app_type) {
1560 #if defined(OS_CHROMEOS)
1561 // Restore app popups for chromeos alone.
1562 if (type == Browser::TYPE_POPUP && app_type == TYPE_APP)
1563 return true;
1564 #endif
1566 return type == Browser::TYPE_TABBED ||
1567 (type == Browser::TYPE_POPUP && browser_defaults::kRestorePopups);
1570 SessionService::WindowType SessionService::WindowTypeForBrowserType(
1571 Browser::Type type) {
1572 switch (type) {
1573 case Browser::TYPE_POPUP:
1574 return TYPE_POPUP;
1575 case Browser::TYPE_TABBED:
1576 return TYPE_TABBED;
1577 default:
1578 DCHECK(false);
1579 return TYPE_TABBED;
1583 Browser::Type SessionService::BrowserTypeForWindowType(WindowType type) {
1584 switch (type) {
1585 case TYPE_POPUP:
1586 return Browser::TYPE_POPUP;
1587 case TYPE_TABBED:
1588 default:
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
1598 // 10 minutes.
1599 bool use_long_period = false;
1600 if (delta >= save_delay_in_mins_) {
1601 use_long_period = true;
1603 switch (type) {
1604 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
1605 RecordUpdatedSaveTime(delta, use_long_period);
1606 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1607 break;
1608 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
1609 RecordUpdatedTabClosed(delta, use_long_period);
1610 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1611 break;
1612 case content::NOTIFICATION_NAV_LIST_PRUNED:
1613 RecordUpdatedNavListPruned(delta, use_long_period);
1614 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1615 break;
1616 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
1617 RecordUpdatedNavEntryCommit(delta, use_long_period);
1618 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
1619 break;
1620 default:
1621 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
1622 break;
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,
1632 delta,
1633 // 2500ms is the default save delay.
1634 save_delay_in_millis_,
1635 save_delay_in_mins_,
1636 50);
1637 if (use_long_period) {
1638 std::string long_name_("SessionRestore.TabClosedLongPeriod");
1639 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1640 delta,
1641 save_delay_in_mins_,
1642 save_delay_in_hrs_,
1643 50);
1647 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1648 bool use_long_period) {
1649 std::string name("SessionRestore.NavigationListPrunedPeriod");
1650 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1651 delta,
1652 // 2500ms is the default save delay.
1653 save_delay_in_millis_,
1654 save_delay_in_mins_,
1655 50);
1656 if (use_long_period) {
1657 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1658 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1659 delta,
1660 save_delay_in_mins_,
1661 save_delay_in_hrs_,
1662 50);
1666 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1667 bool use_long_period) {
1668 std::string name("SessionRestore.NavEntryCommittedPeriod");
1669 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1670 delta,
1671 // 2500ms is the default save delay.
1672 save_delay_in_millis_,
1673 save_delay_in_mins_,
1674 50);
1675 if (use_long_period) {
1676 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1677 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1678 delta,
1679 save_delay_in_mins_,
1680 save_delay_in_hrs_,
1681 50);
1685 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1686 bool use_long_period) {
1687 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1688 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1689 delta,
1690 // 2500ms is the default save delay.
1691 save_delay_in_millis_,
1692 save_delay_in_mins_,
1693 50);
1694 if (use_long_period) {
1695 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1696 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1697 delta,
1698 save_delay_in_mins_,
1699 save_delay_in_hrs_,
1700 50);
1704 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1705 bool use_long_period) {
1706 std::string name("SessionRestore.SavePeriod");
1707 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1708 delta,
1709 // 2500ms is the default save delay.
1710 save_delay_in_millis_,
1711 save_delay_in_mins_,
1712 50);
1713 if (use_long_period) {
1714 std::string long_name_("SessionRestore.SaveLongPeriod");
1715 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1716 delta,
1717 save_delay_in_mins_,
1718 save_delay_in_hrs_,
1719 50);
1723 void SessionService::TabInserted(WebContents* contents) {
1724 SessionTabHelper* session_tab_helper =
1725 SessionTabHelper::FromWebContents(contents);
1726 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
1727 return;
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
1741 // tab.
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
1772 // is active.
1773 if (has_open_trackable_browsers_ ||
1774 browser_defaults::kBrowserAliveWithNoWindows ||
1775 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1776 return;
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())
1782 return;
1784 DeleteSessionOnlyData(profile());