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