Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / sessions / session_service.cc
blobe8403ec0221b89d676bb23f680c4bf488f5707ca
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_common_utils.h"
28 #include "chrome/browser/sessions/session_data_deleter.h"
29 #include "chrome/browser/sessions/session_restore.h"
30 #include "chrome/browser/sessions/session_service_utils.h"
31 #include "chrome/browser/sessions/session_tab_helper.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/sessions/content/content_serialized_navigation_builder.h"
40 #include "components/sessions/core/session_constants.h"
41 #include "components/sessions/session_command.h"
42 #include "components/sessions/session_types.h"
43 #include "content/public/browser/navigation_details.h"
44 #include "content/public/browser/navigation_entry.h"
45 #include "content/public/browser/notification_details.h"
46 #include "content/public/browser/notification_service.h"
47 #include "content/public/browser/session_storage_namespace.h"
48 #include "content/public/browser/web_contents.h"
49 #include "extensions/common/extension.h"
51 #if defined(OS_MACOSX)
52 #include "chrome/browser/app_controller_mac.h"
53 #endif
55 using base::Time;
56 using content::NavigationEntry;
57 using content::WebContents;
58 using sessions::ContentSerializedNavigationBuilder;
59 using sessions::SerializedNavigationEntry;
61 // Every kWritesPerReset commands triggers recreating the file.
62 static const int kWritesPerReset = 250;
64 // SessionService -------------------------------------------------------------
66 SessionService::SessionService(Profile* profile)
67 : profile_(profile),
68 should_use_delayed_save_(true),
69 base_session_service_(new sessions::BaseSessionService(
70 sessions::BaseSessionService::SESSION_RESTORE,
71 profile->GetPath(),
72 this)),
73 has_open_trackable_browsers_(false),
74 move_on_new_browser_(false),
75 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
76 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
77 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
78 force_browser_not_alive_with_no_windows_(false),
79 weak_factory_(this) {
80 // We should never be created when incognito.
81 DCHECK(!profile->IsOffTheRecord());
82 Init();
85 SessionService::SessionService(const base::FilePath& save_path)
86 : profile_(NULL),
87 should_use_delayed_save_(false),
88 base_session_service_(new sessions::BaseSessionService(
89 sessions::BaseSessionService::SESSION_RESTORE,
90 save_path,
91 this)),
92 has_open_trackable_browsers_(false),
93 move_on_new_browser_(false),
94 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)),
95 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)),
96 save_delay_in_hrs_(base::TimeDelta::FromHours(8)),
97 force_browser_not_alive_with_no_windows_(false),
98 weak_factory_(this) {
99 Init();
102 SessionService::~SessionService() {
103 // The BrowserList should outlive the SessionService since it's static and
104 // the SessionService is a KeyedService.
105 BrowserList::RemoveObserver(this);
106 base_session_service_->Save();
109 bool SessionService::ShouldNewWindowStartSession() {
110 // ChromeOS and OSX have different ideas of application lifetime than
111 // the other platforms.
112 // On ChromeOS opening a new window should never start a new session.
113 #if defined(OS_CHROMEOS)
114 if (!force_browser_not_alive_with_no_windows_)
115 return false;
116 #endif
117 if (!has_open_trackable_browsers_ &&
118 !StartupBrowserCreator::InSynchronousProfileLaunch() &&
119 !SessionRestore::IsRestoring(profile())
120 #if defined(OS_MACOSX)
121 // On OSX, a new window should not start a new session if it was opened
122 // from the dock or the menubar.
123 && !app_controller_mac::IsOpeningNewWindow()
124 #endif // OS_MACOSX
126 return true;
128 return false;
131 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) {
132 return RestoreIfNecessary(urls_to_open, NULL);
135 void SessionService::ResetFromCurrentBrowsers() {
136 ScheduleResetCommands();
139 void SessionService::MoveCurrentSessionToLastSession() {
140 pending_tab_close_ids_.clear();
141 window_closing_ids_.clear();
142 pending_window_close_ids_.clear();
144 base_session_service_->MoveCurrentSessionToLastSession();
147 void SessionService::DeleteLastSession() {
148 base_session_service_->DeleteLastSession();
151 void SessionService::SetTabWindow(const SessionID& window_id,
152 const SessionID& tab_id) {
153 if (!ShouldTrackChangesToWindow(window_id))
154 return;
156 ScheduleCommand(sessions::CreateSetTabWindowCommand(window_id,
157 tab_id).Pass());
160 void SessionService::SetWindowBounds(const SessionID& window_id,
161 const gfx::Rect& bounds,
162 ui::WindowShowState show_state) {
163 if (!ShouldTrackChangesToWindow(window_id))
164 return;
166 ScheduleCommand(sessions::CreateSetWindowBoundsCommand(
167 window_id, bounds, show_state).Pass());
170 void SessionService::SetTabIndexInWindow(const SessionID& window_id,
171 const SessionID& tab_id,
172 int new_index) {
173 if (!ShouldTrackChangesToWindow(window_id))
174 return;
176 ScheduleCommand(sessions::CreateSetTabIndexInWindowCommand(tab_id,
177 new_index).Pass());
180 void SessionService::SetPinnedState(const SessionID& window_id,
181 const SessionID& tab_id,
182 bool is_pinned) {
183 if (!ShouldTrackChangesToWindow(window_id))
184 return;
186 ScheduleCommand(sessions::CreatePinnedStateCommand(tab_id, is_pinned).Pass());
189 void SessionService::TabClosed(const SessionID& window_id,
190 const SessionID& tab_id,
191 bool closed_by_user_gesture) {
192 if (!tab_id.id())
193 return; // Hapens when the tab is replaced.
195 if (!ShouldTrackChangesToWindow(window_id))
196 return;
198 IdToRange::iterator i = tab_to_available_range_.find(tab_id.id());
199 if (i != tab_to_available_range_.end())
200 tab_to_available_range_.erase(i);
202 if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(),
203 window_id.id()) != pending_window_close_ids_.end()) {
204 // Tab is in last window. Don't commit it immediately, instead add it to the
205 // list of tabs to close. If the user creates another window, the close is
206 // committed.
207 pending_tab_close_ids_.insert(tab_id.id());
208 } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(),
209 window_id.id()) != window_closing_ids_.end() ||
210 !IsOnlyOneTabLeft() ||
211 closed_by_user_gesture) {
212 // Close is the result of one of the following:
213 // . window close (and it isn't the last window).
214 // . closing a tab and there are other windows/tabs open.
215 // . closed by a user gesture.
216 // In all cases we need to mark the tab as explicitly closed.
217 ScheduleCommand(sessions::CreateTabClosedCommand(tab_id.id()).Pass());
218 } else {
219 // User closed the last tab in the last tabbed browser. Don't mark the
220 // tab closed.
221 pending_tab_close_ids_.insert(tab_id.id());
222 has_open_trackable_browsers_ = false;
226 void SessionService::WindowOpened(Browser* browser) {
227 if (!ShouldTrackBrowser(browser))
228 return;
230 RestoreIfNecessary(std::vector<GURL>(), browser);
231 SetWindowType(browser->session_id(),
232 browser->type(),
233 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
234 SetWindowAppName(browser->session_id(), browser->app_name());
237 void SessionService::WindowClosing(const SessionID& window_id) {
238 if (!ShouldTrackChangesToWindow(window_id))
239 return;
241 // The window is about to close. If there are other tabbed browsers with the
242 // same original profile commit the close immediately.
244 // NOTE: if the user chooses the exit menu item session service is destroyed
245 // and this code isn't hit.
246 if (has_open_trackable_browsers_) {
247 // Closing a window can never make has_open_trackable_browsers_ go from
248 // false to true, so only update it if already true.
249 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
251 bool use_pending_close = !has_open_trackable_browsers_;
252 if (!use_pending_close) {
253 // Somewhat outside of "normal behavior" is profile locking. In this case
254 // (when IsSiginRequired has already been set True), we're closing all
255 // browser windows in turn but want them all to be restored when the user
256 // unlocks. To accomplish this, we do a "pending close" on all windows
257 // instead of just the last one (which has no open_trackable_browsers).
258 // http://crbug.com/356818
260 // Some editions (like iOS) don't have a profile_manager and some tests
261 // don't supply one so be lenient.
262 if (g_browser_process) {
263 ProfileManager* profile_manager = g_browser_process->profile_manager();
264 if (profile_manager) {
265 ProfileInfoCache& profile_info =
266 profile_manager->GetProfileInfoCache();
267 size_t profile_index = profile_info.GetIndexOfProfileWithPath(
268 profile()->GetPath());
269 use_pending_close = profile_index != std::string::npos &&
270 profile_info.ProfileIsSigninRequiredAtIndex(profile_index);
274 if (use_pending_close)
275 pending_window_close_ids_.insert(window_id.id());
276 else
277 window_closing_ids_.insert(window_id.id());
280 void SessionService::WindowClosed(const SessionID& window_id) {
281 if (!ShouldTrackChangesToWindow(window_id)) {
282 // The last window may be one that is not tracked.
283 MaybeDeleteSessionOnlyData();
284 return;
287 windows_tracking_.erase(window_id.id());
289 if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) {
290 window_closing_ids_.erase(window_id.id());
291 ScheduleCommand(sessions::CreateWindowClosedCommand(window_id.id()).Pass());
292 } else if (pending_window_close_ids_.find(window_id.id()) ==
293 pending_window_close_ids_.end()) {
294 // We'll hit this if user closed the last tab in a window.
295 has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id);
296 if (!has_open_trackable_browsers_)
297 pending_window_close_ids_.insert(window_id.id());
298 else
299 ScheduleCommand(sessions::CreateWindowClosedCommand(
300 window_id.id()).Pass());
302 MaybeDeleteSessionOnlyData();
305 void SessionService::TabInserted(WebContents* contents) {
306 SessionTabHelper* session_tab_helper =
307 SessionTabHelper::FromWebContents(contents);
308 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
309 return;
310 SetTabWindow(session_tab_helper->window_id(),
311 session_tab_helper->session_id());
312 extensions::TabHelper* extensions_tab_helper =
313 extensions::TabHelper::FromWebContents(contents);
314 if (extensions_tab_helper &&
315 extensions_tab_helper->extension_app()) {
316 SetTabExtensionAppID(
317 session_tab_helper->window_id(),
318 session_tab_helper->session_id(),
319 extensions_tab_helper->extension_app()->id());
322 // Record the association between the SessionStorageNamespace and the
323 // tab.
325 // TODO(ajwong): This should be processing the whole map rather than
326 // just the default. This in particular will not work for tabs with only
327 // isolated apps which won't have a default partition.
328 content::SessionStorageNamespace* session_storage_namespace =
329 contents->GetController().GetDefaultSessionStorageNamespace();
330 ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
331 session_tab_helper->session_id(),
332 session_storage_namespace->persistent_id()).Pass());
333 session_storage_namespace->SetShouldPersist(true);
336 void SessionService::TabClosing(WebContents* contents) {
337 // Allow the associated sessionStorage to get deleted; it won't be needed
338 // in the session restore.
339 content::SessionStorageNamespace* session_storage_namespace =
340 contents->GetController().GetDefaultSessionStorageNamespace();
341 session_storage_namespace->SetShouldPersist(false);
342 SessionTabHelper* session_tab_helper =
343 SessionTabHelper::FromWebContents(contents);
344 TabClosed(session_tab_helper->window_id(),
345 session_tab_helper->session_id(),
346 contents->GetClosedByUserGesture());
347 RecordSessionUpdateHistogramData(content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
348 &last_updated_tab_closed_time_);
351 void SessionService::SetWindowType(const SessionID& window_id,
352 Browser::Type type,
353 AppType app_type) {
354 sessions::SessionWindow::WindowType window_type =
355 WindowTypeForBrowserType(type);
356 if (!ShouldRestoreWindowOfType(window_type, app_type))
357 return;
359 windows_tracking_.insert(window_id.id());
361 // The user created a new tabbed browser with our profile. Commit any
362 // pending closes.
363 CommitPendingCloses();
365 has_open_trackable_browsers_ = true;
366 move_on_new_browser_ = true;
368 ScheduleCommand(CreateSetWindowTypeCommand(window_id, window_type).Pass());
371 void SessionService::SetWindowAppName(
372 const SessionID& window_id,
373 const std::string& app_name) {
374 if (!ShouldTrackChangesToWindow(window_id))
375 return;
377 ScheduleCommand(sessions::CreateSetWindowAppNameCommand(window_id,
378 app_name).Pass());
381 void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id,
382 const SessionID& tab_id,
383 int count) {
384 if (!ShouldTrackChangesToWindow(window_id))
385 return;
387 ScheduleCommand(sessions::CreateTabNavigationPathPrunedFromBackCommand(
388 tab_id, count).Pass());
391 void SessionService::TabNavigationPathPrunedFromFront(
392 const SessionID& window_id,
393 const SessionID& tab_id,
394 int count) {
395 if (!ShouldTrackChangesToWindow(window_id))
396 return;
398 // Update the range of indices.
399 if (tab_to_available_range_.find(tab_id.id()) !=
400 tab_to_available_range_.end()) {
401 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
402 range.first = std::max(0, range.first - count);
403 range.second = std::max(0, range.second - count);
406 ScheduleCommand(sessions::CreateTabNavigationPathPrunedFromFrontCommand(
407 tab_id, count).Pass());
410 void SessionService::UpdateTabNavigation(
411 const SessionID& window_id,
412 const SessionID& tab_id,
413 const SerializedNavigationEntry& navigation) {
414 if (!ShouldTrackURLForRestore(navigation.virtual_url()) ||
415 !ShouldTrackChangesToWindow(window_id)) {
416 return;
419 if (tab_to_available_range_.find(tab_id.id()) !=
420 tab_to_available_range_.end()) {
421 std::pair<int, int>& range = tab_to_available_range_[tab_id.id()];
422 range.first = std::min(navigation.index(), range.first);
423 range.second = std::max(navigation.index(), range.second);
425 ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, navigation).Pass());
428 void SessionService::TabRestored(WebContents* tab, bool pinned) {
429 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
430 if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
431 return;
433 BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, pinned, NULL);
434 base_session_service_->StartSaveTimer();
437 void SessionService::SetSelectedNavigationIndex(const SessionID& window_id,
438 const SessionID& tab_id,
439 int index) {
440 if (!ShouldTrackChangesToWindow(window_id))
441 return;
443 if (tab_to_available_range_.find(tab_id.id()) !=
444 tab_to_available_range_.end()) {
445 if (index < tab_to_available_range_[tab_id.id()].first ||
446 index > tab_to_available_range_[tab_id.id()].second) {
447 // The new index is outside the range of what we've archived, schedule
448 // a reset.
449 ResetFromCurrentBrowsers();
450 return;
453 ScheduleCommand(
454 sessions::CreateSetSelectedNavigationIndexCommand(tab_id, index).Pass());
457 void SessionService::SetSelectedTabInWindow(const SessionID& window_id,
458 int index) {
459 if (!ShouldTrackChangesToWindow(window_id))
460 return;
462 ScheduleCommand(
463 sessions::CreateSetSelectedTabInWindowCommand(window_id, index).Pass());
466 void SessionService::SetTabUserAgentOverride(
467 const SessionID& window_id,
468 const SessionID& tab_id,
469 const std::string& user_agent_override) {
470 if (!ShouldTrackChangesToWindow(window_id))
471 return;
473 ScheduleCommand(sessions::CreateSetTabUserAgentOverrideCommand(
474 tab_id, user_agent_override).Pass());
477 void SessionService::SetTabExtensionAppID(
478 const SessionID& window_id,
479 const SessionID& tab_id,
480 const std::string& extension_app_id) {
481 if (!ShouldTrackChangesToWindow(window_id))
482 return;
484 ScheduleCommand(sessions::CreateSetTabExtensionAppIDCommand(
485 tab_id, extension_app_id).Pass());
488 void SessionService::SetLastActiveTime(const SessionID& window_id,
489 const SessionID& tab_id,
490 base::TimeTicks last_active_time) {
491 if (!ShouldTrackChangesToWindow(window_id))
492 return;
494 ScheduleCommand(
495 sessions::CreateLastActiveTimeCommand(tab_id, last_active_time).Pass());
498 base::CancelableTaskTracker::TaskId SessionService::GetLastSession(
499 const sessions::GetLastSessionCallback& callback,
500 base::CancelableTaskTracker* tracker) {
501 // OnGotSessionCommands maps the SessionCommands to browser state, then run
502 // the callback.
503 return base_session_service_->ScheduleGetLastSessionCommands(
504 base::Bind(&SessionService::OnGotSessionCommands,
505 weak_factory_.GetWeakPtr(),
506 callback),
507 tracker);
510 base::SequencedWorkerPool* SessionService::GetBlockingPool() {
511 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
512 return content::BrowserThread::GetBlockingPool();
515 bool SessionService::ShouldUseDelayedSave() {
516 return should_use_delayed_save_;
519 void SessionService::OnSavedCommands() {
520 RecordSessionUpdateHistogramData(chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
521 &last_updated_save_time_);
522 content::NotificationService::current()->Notify(
523 chrome::NOTIFICATION_SESSION_SERVICE_SAVED,
524 content::Source<Profile>(profile()),
525 content::NotificationService::NoDetails());
528 void SessionService::Init() {
529 // Register for the notifications we're interested in.
530 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
531 content::NotificationService::AllSources());
532 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
533 content::NotificationService::AllSources());
534 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
535 content::NotificationService::AllSources());
536 registrar_.Add(
537 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
538 content::NotificationService::AllSources());
540 BrowserList::AddObserver(this);
543 bool SessionService::ShouldRestoreWindowOfType(
544 sessions::SessionWindow::WindowType window_type,
545 AppType app_type) const {
546 #if defined(OS_CHROMEOS)
547 // Restore app popups for ChromeOS alone.
548 if (window_type == sessions::SessionWindow::TYPE_POPUP &&
549 app_type == TYPE_APP)
550 return true;
551 #endif
553 return window_type == sessions::SessionWindow::TYPE_TABBED;
556 void SessionService::RemoveUnusedRestoreWindows(
557 std::vector<sessions::SessionWindow*>* window_list) {
558 std::vector<sessions::SessionWindow*>::iterator i = window_list->begin();
559 while (i != window_list->end()) {
560 sessions::SessionWindow* window = *i;
561 if (!ShouldRestoreWindowOfType(window->type,
562 window->app_name.empty() ? TYPE_NORMAL :
563 TYPE_APP)) {
564 delete window;
565 i = window_list->erase(i);
566 } else {
567 ++i;
572 bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open,
573 Browser* browser) {
574 if (ShouldNewWindowStartSession()) {
575 // We're going from no tabbed browsers to a tabbed browser (and not in
576 // process startup), restore the last session.
577 if (move_on_new_browser_) {
578 // Make the current session the last.
579 MoveCurrentSessionToLastSession();
580 move_on_new_browser_ = false;
582 SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref(
583 *base::CommandLine::ForCurrentProcess(), profile());
584 if (pref.type == SessionStartupPref::LAST) {
585 SessionRestore::RestoreSession(
586 profile(), browser,
587 browser ? browser->host_desktop_type() : chrome::GetActiveDesktop(),
588 browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER,
589 urls_to_open);
590 return true;
593 return false;
596 void SessionService::Observe(int type,
597 const content::NotificationSource& source,
598 const content::NotificationDetails& details) {
599 // All of our messages have the NavigationController as the source.
600 switch (type) {
601 case content::NOTIFICATION_NAV_LIST_PRUNED: {
602 WebContents* web_contents =
603 content::Source<content::NavigationController>(source).ptr()->
604 GetWebContents();
605 SessionTabHelper* session_tab_helper =
606 SessionTabHelper::FromWebContents(web_contents);
607 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
608 return;
609 content::Details<content::PrunedDetails> pruned_details(details);
610 if (pruned_details->from_front) {
611 TabNavigationPathPrunedFromFront(
612 session_tab_helper->window_id(),
613 session_tab_helper->session_id(),
614 pruned_details->count);
615 } else {
616 TabNavigationPathPrunedFromBack(
617 session_tab_helper->window_id(),
618 session_tab_helper->session_id(),
619 web_contents->GetController().GetEntryCount());
621 RecordSessionUpdateHistogramData(type,
622 &last_updated_nav_list_pruned_time_);
623 break;
626 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
627 WebContents* web_contents =
628 content::Source<content::NavigationController>(source).ptr()->
629 GetWebContents();
630 SessionTabHelper* session_tab_helper =
631 SessionTabHelper::FromWebContents(web_contents);
632 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
633 return;
634 content::Details<content::EntryChangedDetails> changed(details);
635 const SerializedNavigationEntry navigation =
636 ContentSerializedNavigationBuilder::FromNavigationEntry(
637 changed->index, *changed->changed_entry);
638 UpdateTabNavigation(session_tab_helper->window_id(),
639 session_tab_helper->session_id(),
640 navigation);
641 break;
644 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
645 WebContents* web_contents =
646 content::Source<content::NavigationController>(source).ptr()->
647 GetWebContents();
648 SessionTabHelper* session_tab_helper =
649 SessionTabHelper::FromWebContents(web_contents);
650 if (!session_tab_helper || web_contents->GetBrowserContext() != profile())
651 return;
652 int current_entry_index =
653 web_contents->GetController().GetCurrentEntryIndex();
654 SetSelectedNavigationIndex(
655 session_tab_helper->window_id(),
656 session_tab_helper->session_id(),
657 current_entry_index);
658 const SerializedNavigationEntry navigation =
659 ContentSerializedNavigationBuilder::FromNavigationEntry(
660 current_entry_index,
661 *web_contents->GetController().GetEntryAtIndex(
662 current_entry_index));
663 UpdateTabNavigation(
664 session_tab_helper->window_id(),
665 session_tab_helper->session_id(),
666 navigation);
667 content::Details<content::LoadCommittedDetails> changed(details);
668 if (changed->type == content::NAVIGATION_TYPE_NEW_PAGE ||
669 changed->type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
670 RecordSessionUpdateHistogramData(type,
671 &last_updated_nav_entry_commit_time_);
673 break;
676 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
677 extensions::TabHelper* extension_tab_helper =
678 content::Source<extensions::TabHelper>(source).ptr();
679 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
680 profile()) {
681 return;
683 if (extension_tab_helper->extension_app()) {
684 SessionTabHelper* session_tab_helper =
685 SessionTabHelper::FromWebContents(
686 extension_tab_helper->web_contents());
687 SetTabExtensionAppID(session_tab_helper->window_id(),
688 session_tab_helper->session_id(),
689 extension_tab_helper->extension_app()->id());
691 break;
694 default:
695 NOTREACHED();
699 void SessionService::OnBrowserSetLastActive(Browser* browser) {
700 if (ShouldTrackBrowser(browser))
701 ScheduleCommand(sessions::CreateSetActiveWindowCommand(
702 browser->session_id()).Pass());
705 void SessionService::OnGotSessionCommands(
706 const sessions::GetLastSessionCallback& callback,
707 ScopedVector<sessions::SessionCommand> commands) {
708 ScopedVector<sessions::SessionWindow> valid_windows;
709 SessionID::id_type active_window_id = 0;
711 sessions::RestoreSessionFromCommands(
712 commands, &valid_windows.get(), &active_window_id);
713 RemoveUnusedRestoreWindows(&valid_windows.get());
715 callback.Run(valid_windows.Pass(), active_window_id);
718 void SessionService::BuildCommandsForTab(const SessionID& window_id,
719 WebContents* tab,
720 int index_in_window,
721 bool is_pinned,
722 IdToRange* tab_to_available_range) {
723 DCHECK(tab && window_id.id());
724 SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(tab);
725 const SessionID& session_id(session_tab_helper->session_id());
726 base_session_service_->AppendRebuildCommand(
727 sessions::CreateSetTabWindowCommand(window_id, session_id));
729 const int current_index = tab->GetController().GetCurrentEntryIndex();
730 const int min_index =
731 std::max(current_index - sessions::gMaxPersistNavigationCount, 0);
732 const int max_index =
733 std::min(current_index + sessions::gMaxPersistNavigationCount,
734 tab->GetController().GetEntryCount());
735 const int pending_index = tab->GetController().GetPendingEntryIndex();
736 if (tab_to_available_range) {
737 (*tab_to_available_range)[session_id.id()] =
738 std::pair<int, int>(min_index, max_index);
741 if (is_pinned) {
742 base_session_service_->AppendRebuildCommand(
743 sessions::CreatePinnedStateCommand(session_id, true));
746 if (SessionRestore::GetSmartRestoreMode() ==
747 SessionRestore::SMART_RESTORE_MODE_MRU) {
748 base_session_service_->AppendRebuildCommand(
749 sessions::CreateLastActiveTimeCommand(session_id,
750 tab->GetLastActiveTime()));
753 extensions::TabHelper* extensions_tab_helper =
754 extensions::TabHelper::FromWebContents(tab);
755 if (extensions_tab_helper->extension_app()) {
756 base_session_service_->AppendRebuildCommand(
757 sessions::CreateSetTabExtensionAppIDCommand(
758 session_id,
759 extensions_tab_helper->extension_app()->id()));
762 const std::string& ua_override = tab->GetUserAgentOverride();
763 if (!ua_override.empty()) {
764 base_session_service_->AppendRebuildCommand(
765 sessions::CreateSetTabUserAgentOverrideCommand(session_id,
766 ua_override));
769 for (int i = min_index; i < max_index; ++i) {
770 const NavigationEntry* entry = (i == pending_index) ?
771 tab->GetController().GetPendingEntry() :
772 tab->GetController().GetEntryAtIndex(i);
773 DCHECK(entry);
774 if (ShouldTrackURLForRestore(entry->GetVirtualURL())) {
775 const SerializedNavigationEntry navigation =
776 ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry);
777 base_session_service_->AppendRebuildCommand(
778 CreateUpdateTabNavigationCommand(session_id, navigation));
781 base_session_service_->AppendRebuildCommand(
782 sessions::CreateSetSelectedNavigationIndexCommand(session_id,
783 current_index));
785 if (index_in_window != -1) {
786 base_session_service_->AppendRebuildCommand(
787 sessions::CreateSetTabIndexInWindowCommand(session_id,
788 index_in_window));
791 // Record the association between the sessionStorage namespace and the tab.
792 content::SessionStorageNamespace* session_storage_namespace =
793 tab->GetController().GetDefaultSessionStorageNamespace();
794 ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
795 session_tab_helper->session_id(),
796 session_storage_namespace->persistent_id()).Pass());
799 void SessionService::BuildCommandsForBrowser(
800 Browser* browser,
801 IdToRange* tab_to_available_range,
802 std::set<SessionID::id_type>* windows_to_track) {
803 DCHECK(browser);
804 DCHECK(browser->session_id().id());
806 base_session_service_->AppendRebuildCommand(
807 sessions::CreateSetWindowBoundsCommand(
808 browser->session_id(),
809 browser->window()->GetRestoredBounds(),
810 browser->window()->GetRestoredState()));
812 base_session_service_->AppendRebuildCommand(
813 sessions::CreateSetWindowTypeCommand(
814 browser->session_id(),
815 WindowTypeForBrowserType(browser->type())));
817 if (!browser->app_name().empty()) {
818 base_session_service_->AppendRebuildCommand(
819 sessions::CreateSetWindowAppNameCommand(
820 browser->session_id(),
821 browser->app_name()));
824 windows_to_track->insert(browser->session_id().id());
825 TabStripModel* tab_strip = browser->tab_strip_model();
826 for (int i = 0; i < tab_strip->count(); ++i) {
827 WebContents* tab = tab_strip->GetWebContentsAt(i);
828 DCHECK(tab);
829 BuildCommandsForTab(browser->session_id(),
830 tab,
832 tab_strip->IsTabPinned(i),
833 tab_to_available_range);
836 base_session_service_->AppendRebuildCommand(
837 sessions::CreateSetSelectedTabInWindowCommand(
838 browser->session_id(),
839 browser->tab_strip_model()->active_index()));
842 void SessionService::BuildCommandsFromBrowsers(
843 IdToRange* tab_to_available_range,
844 std::set<SessionID::id_type>* windows_to_track) {
845 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
846 Browser* browser = *it;
847 // Make sure the browser has tabs and a window. Browser's destructor
848 // removes itself from the BrowserList. When a browser is closed the
849 // destructor is not necessarily run immediately. This means it's possible
850 // for us to get a handle to a browser that is about to be removed. If
851 // the tab count is 0 or the window is NULL, the browser is about to be
852 // deleted, so we ignore it.
853 if (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
854 browser->window()) {
855 BuildCommandsForBrowser(browser,
856 tab_to_available_range,
857 windows_to_track);
862 void SessionService::ScheduleResetCommands() {
863 base_session_service_->set_pending_reset(true);
864 base_session_service_->ClearPendingCommands();
865 tab_to_available_range_.clear();
866 windows_tracking_.clear();
867 BuildCommandsFromBrowsers(&tab_to_available_range_,
868 &windows_tracking_);
869 if (!windows_tracking_.empty()) {
870 // We're lazily created on startup and won't get an initial batch of
871 // SetWindowType messages. Set these here to make sure our state is correct.
872 has_open_trackable_browsers_ = true;
873 move_on_new_browser_ = true;
875 base_session_service_->StartSaveTimer();
878 void SessionService::ScheduleCommand(
879 scoped_ptr<sessions::SessionCommand> command) {
880 DCHECK(command);
881 if (ReplacePendingCommand(base_session_service_.get(), &command))
882 return;
883 bool is_closing_command = IsClosingCommand(command.get());
884 base_session_service_->ScheduleCommand(command.Pass());
885 // Don't schedule a reset on tab closed/window closed. Otherwise we may
886 // lose tabs/windows we want to restore from if we exit right after this.
887 if (!base_session_service_->pending_reset() &&
888 pending_window_close_ids_.empty() &&
889 base_session_service_->commands_since_reset() >= kWritesPerReset &&
890 !is_closing_command) {
891 ScheduleResetCommands();
895 void SessionService::CommitPendingCloses() {
896 for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin();
897 i != pending_tab_close_ids_.end(); ++i) {
898 ScheduleCommand(sessions::CreateTabClosedCommand(*i).Pass());
900 pending_tab_close_ids_.clear();
902 for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin();
903 i != pending_window_close_ids_.end(); ++i) {
904 ScheduleCommand(sessions::CreateWindowClosedCommand(*i).Pass());
906 pending_window_close_ids_.clear();
909 bool SessionService::IsOnlyOneTabLeft() const {
910 if (!profile() || profile()->AsTestingProfile()) {
911 // We're testing, always return false.
912 return false;
915 int window_count = 0;
916 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
917 Browser* browser = *it;
918 const SessionID::id_type window_id = browser->session_id().id();
919 if (ShouldTrackBrowser(browser) &&
920 window_closing_ids_.find(window_id) == window_closing_ids_.end()) {
921 if (++window_count > 1)
922 return false;
923 // By the time this is invoked the tab has been removed. As such, we use
924 // > 0 here rather than > 1.
925 if (browser->tab_strip_model()->count() > 0)
926 return false;
929 return true;
932 bool SessionService::HasOpenTrackableBrowsers(
933 const SessionID& window_id) const {
934 if (!profile() || profile()->AsTestingProfile()) {
935 // We're testing, always return true.
936 return true;
939 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
940 Browser* browser = *it;
941 const SessionID::id_type browser_id = browser->session_id().id();
942 if (browser_id != window_id.id() &&
943 window_closing_ids_.find(browser_id) == window_closing_ids_.end() &&
944 ShouldTrackBrowser(browser)) {
945 return true;
948 return false;
951 bool SessionService::ShouldTrackChangesToWindow(
952 const SessionID& window_id) const {
953 return windows_tracking_.find(window_id.id()) != windows_tracking_.end();
956 bool SessionService::ShouldTrackBrowser(Browser* browser) const {
957 if (browser->profile() != profile())
958 return false;
959 // Never track app popup windows that do not have a trusted source (i.e.
960 // popup windows spawned by an app). If this logic changes, be sure to also
961 // change SessionRestoreImpl::CreateRestoredBrowser().
962 if (browser->is_app() && browser->is_type_popup() &&
963 !browser->is_trusted_source()) {
964 return false;
966 return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()),
967 browser->is_app() ? TYPE_APP : TYPE_NORMAL);
970 void SessionService::RecordSessionUpdateHistogramData(int type,
971 base::TimeTicks* last_updated_time) {
972 if (!last_updated_time->is_null()) {
973 base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time;
974 // We're interested in frequent updates periods longer than
975 // 10 minutes.
976 bool use_long_period = false;
977 if (delta >= save_delay_in_mins_) {
978 use_long_period = true;
980 switch (type) {
981 case chrome::NOTIFICATION_SESSION_SERVICE_SAVED :
982 RecordUpdatedSaveTime(delta, use_long_period);
983 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
984 break;
985 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED:
986 RecordUpdatedTabClosed(delta, use_long_period);
987 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
988 break;
989 case content::NOTIFICATION_NAV_LIST_PRUNED:
990 RecordUpdatedNavListPruned(delta, use_long_period);
991 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
992 break;
993 case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
994 RecordUpdatedNavEntryCommit(delta, use_long_period);
995 RecordUpdatedSessionNavigationOrTab(delta, use_long_period);
996 break;
997 default:
998 NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData";
999 break;
1002 (*last_updated_time) = base::TimeTicks::Now();
1005 void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta,
1006 bool use_long_period) {
1007 std::string name("SessionRestore.TabClosedPeriod");
1008 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1009 delta,
1010 // 2500ms is the default save delay.
1011 save_delay_in_millis_,
1012 save_delay_in_mins_,
1013 50);
1014 if (use_long_period) {
1015 std::string long_name_("SessionRestore.TabClosedLongPeriod");
1016 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1017 delta,
1018 save_delay_in_mins_,
1019 save_delay_in_hrs_,
1020 50);
1024 void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta,
1025 bool use_long_period) {
1026 std::string name("SessionRestore.NavigationListPrunedPeriod");
1027 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1028 delta,
1029 // 2500ms is the default save delay.
1030 save_delay_in_millis_,
1031 save_delay_in_mins_,
1032 50);
1033 if (use_long_period) {
1034 std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod");
1035 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1036 delta,
1037 save_delay_in_mins_,
1038 save_delay_in_hrs_,
1039 50);
1043 void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta,
1044 bool use_long_period) {
1045 std::string name("SessionRestore.NavEntryCommittedPeriod");
1046 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1047 delta,
1048 // 2500ms is the default save delay.
1049 save_delay_in_millis_,
1050 save_delay_in_mins_,
1051 50);
1052 if (use_long_period) {
1053 std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod");
1054 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1055 delta,
1056 save_delay_in_mins_,
1057 save_delay_in_hrs_,
1058 50);
1062 void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta,
1063 bool use_long_period) {
1064 std::string name("SessionRestore.SavePeriod");
1065 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1066 delta,
1067 // 2500ms is the default save delay.
1068 save_delay_in_millis_,
1069 save_delay_in_mins_,
1070 50);
1071 if (use_long_period) {
1072 std::string long_name_("SessionRestore.SaveLongPeriod");
1073 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1074 delta,
1075 save_delay_in_mins_,
1076 save_delay_in_hrs_,
1077 50);
1081 void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta,
1082 bool use_long_period) {
1083 std::string name("SessionRestore.NavOrTabUpdatePeriod");
1084 UMA_HISTOGRAM_CUSTOM_TIMES(name,
1085 delta,
1086 // 2500ms is the default save delay.
1087 save_delay_in_millis_,
1088 save_delay_in_mins_,
1089 50);
1090 if (use_long_period) {
1091 std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod");
1092 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_,
1093 delta,
1094 save_delay_in_mins_,
1095 save_delay_in_hrs_,
1096 50);
1100 void SessionService::MaybeDeleteSessionOnlyData() {
1101 // Don't try anything if we're testing. The browser_process is not fully
1102 // created and DeleteSession will crash if we actually attempt it.
1103 if (!profile() || profile()->AsTestingProfile())
1104 return;
1106 // Clear session data if the last window for a profile has been closed and
1107 // closing the last window would normally close Chrome, unless background mode
1108 // is active. Tests don't have a background_mode_manager.
1109 if (has_open_trackable_browsers_ ||
1110 browser_defaults::kBrowserAliveWithNoWindows ||
1111 g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
1112 return;
1115 // Check for any open windows for the current profile that we aren't tracking.
1116 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1117 if ((*it)->profile() == profile())
1118 return;
1120 DeleteSessionOnlyData(profile());
1123 sessions::BaseSessionService* SessionService::GetBaseSessionServiceForTest() {
1124 return base_session_service_.get();