| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/sessions/session_service.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <set> |
| #include <vector> |
| |
| #include "base/file_util.h" |
| #include "base/memory/scoped_vector.h" |
| #include "base/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/pickle.h" |
| #include "base/threading/thread.h" |
| #include "chrome/browser/extensions/extension_tab_helper.h" |
| #include "chrome/browser/prefs/session_startup_pref.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sessions/session_backend.h" |
| #include "chrome/browser/sessions/session_command.h" |
| #include "chrome/browser/sessions/session_restore.h" |
| #include "chrome/browser/sessions/session_types.h" |
| #include "chrome/browser/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/browser_init.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "content/browser/tab_contents/navigation_controller.h" |
| #include "content/browser/tab_contents/navigation_entry.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| #include "content/common/notification_details.h" |
| #include "content/common/notification_service.h" |
| |
| #if defined(OS_MACOSX) |
| #include "chrome/browser/app_controller_cppsafe_mac.h" |
| #endif |
| |
| using base::Time; |
| |
| // Identifier for commands written to file. |
| static const SessionCommand::id_type kCommandSetTabWindow = 0; |
| // kCommandSetWindowBounds is no longer used (it's superseded by |
| // kCommandSetWindowBounds2). I leave it here to document what it was. |
| // static const SessionCommand::id_type kCommandSetWindowBounds = 1; |
| static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2; |
| static const SessionCommand::id_type kCommandTabClosed = 3; |
| static const SessionCommand::id_type kCommandWindowClosed = 4; |
| static const SessionCommand::id_type |
| kCommandTabNavigationPathPrunedFromBack = 5; |
| static const SessionCommand::id_type kCommandUpdateTabNavigation = 6; |
| static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7; |
| static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8; |
| static const SessionCommand::id_type kCommandSetWindowType = 9; |
| static const SessionCommand::id_type kCommandSetWindowBounds2 = 10; |
| static const SessionCommand::id_type |
| kCommandTabNavigationPathPrunedFromFront = 11; |
| static const SessionCommand::id_type kCommandSetPinnedState = 12; |
| static const SessionCommand::id_type kCommandSetExtensionAppID = 13; |
| |
| // Every kWritesPerReset commands triggers recreating the file. |
| static const int kWritesPerReset = 250; |
| |
| namespace { |
| |
| // The callback from GetLastSession is internally routed to SessionService |
| // first and then the caller. This is done so that the SessionWindows can be |
| // recreated from the SessionCommands and the SessionWindows passed to the |
| // caller. The following class is used for this. |
| class InternalSessionRequest |
| : public BaseSessionService::InternalGetCommandsRequest { |
| public: |
| InternalSessionRequest( |
| CallbackType* callback, |
| SessionService::SessionCallback* real_callback) |
| : BaseSessionService::InternalGetCommandsRequest(callback), |
| real_callback(real_callback) { |
| } |
| |
| // The callback supplied to GetLastSession and GetCurrentSession. |
| scoped_ptr<SessionService::SessionCallback> real_callback; |
| |
| private: |
| ~InternalSessionRequest() {} |
| |
| DISALLOW_COPY_AND_ASSIGN(InternalSessionRequest); |
| }; |
| |
| // Various payload structures. |
| struct ClosedPayload { |
| SessionID::id_type id; |
| int64 close_time; |
| }; |
| |
| struct WindowBoundsPayload2 { |
| SessionID::id_type window_id; |
| int32 x; |
| int32 y; |
| int32 w; |
| int32 h; |
| bool is_maximized; |
| }; |
| |
| struct IDAndIndexPayload { |
| SessionID::id_type id; |
| int32 index; |
| }; |
| |
| typedef IDAndIndexPayload TabIndexInWindowPayload; |
| |
| typedef IDAndIndexPayload TabNavigationPathPrunedFromBackPayload; |
| |
| typedef IDAndIndexPayload SelectedNavigationIndexPayload; |
| |
| typedef IDAndIndexPayload SelectedTabInIndexPayload; |
| |
| typedef IDAndIndexPayload WindowTypePayload; |
| |
| typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; |
| |
| struct PinnedStatePayload { |
| SessionID::id_type tab_id; |
| bool pinned_state; |
| }; |
| |
| } // namespace |
| |
| // SessionService ------------------------------------------------------------- |
| |
| SessionService::SessionService(Profile* profile) |
| : BaseSessionService(SESSION_RESTORE, profile, FilePath()), |
| has_open_trackable_browsers_(false), |
| move_on_new_browser_(false), |
| save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), |
| save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), |
| save_delay_in_hrs_(base::TimeDelta::FromHours(8)) { |
| Init(); |
| } |
| |
| SessionService::SessionService(const FilePath& save_path) |
| : BaseSessionService(SESSION_RESTORE, NULL, save_path), |
| has_open_trackable_browsers_(false), |
| move_on_new_browser_(false), |
| save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), |
| save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), |
| save_delay_in_hrs_(base::TimeDelta::FromHours(8)) { |
| Init(); |
| } |
| |
| SessionService::~SessionService() { |
| Save(); |
| } |
| |
| bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open) { |
| return RestoreIfNecessary(urls_to_open, NULL); |
| } |
| |
| void SessionService::ResetFromCurrentBrowsers() { |
| ScheduleReset(); |
| } |
| |
| void SessionService::MoveCurrentSessionToLastSession() { |
| pending_tab_close_ids_.clear(); |
| window_closing_ids_.clear(); |
| pending_window_close_ids_.clear(); |
| |
| Save(); |
| |
| if (!backend_thread()) { |
| backend()->MoveCurrentSessionToLastSession(); |
| } else { |
| backend_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( |
| backend(), &SessionBackend::MoveCurrentSessionToLastSession)); |
| } |
| } |
| |
| void SessionService::SetTabWindow(const SessionID& window_id, |
| const SessionID& tab_id) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(CreateSetTabWindowCommand(window_id, tab_id)); |
| } |
| |
| void SessionService::SetWindowBounds(const SessionID& window_id, |
| const gfx::Rect& bounds, |
| bool is_maximized) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(CreateSetWindowBoundsCommand(window_id, bounds, |
| is_maximized)); |
| } |
| |
| void SessionService::SetTabIndexInWindow(const SessionID& window_id, |
| const SessionID& tab_id, |
| int new_index) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(CreateSetTabIndexInWindowCommand(tab_id, new_index)); |
| } |
| |
| void SessionService::SetPinnedState(const SessionID& window_id, |
| const SessionID& tab_id, |
| bool is_pinned) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(CreatePinnedStateCommand(tab_id, is_pinned)); |
| } |
| |
| void SessionService::TabClosed(const SessionID& window_id, |
| const SessionID& tab_id, |
| bool closed_by_user_gesture) { |
| if (!tab_id.id()) |
| return; // Hapens when the tab is replaced. |
| |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| IdToRange::iterator i = tab_to_available_range_.find(tab_id.id()); |
| if (i != tab_to_available_range_.end()) |
| tab_to_available_range_.erase(i); |
| |
| if (find(pending_window_close_ids_.begin(), pending_window_close_ids_.end(), |
| window_id.id()) != pending_window_close_ids_.end()) { |
| // Tab is in last window. Don't commit it immediately, instead add it to the |
| // list of tabs to close. If the user creates another window, the close is |
| // committed. |
| pending_tab_close_ids_.insert(tab_id.id()); |
| } else if (find(window_closing_ids_.begin(), window_closing_ids_.end(), |
| window_id.id()) != window_closing_ids_.end() || |
| !IsOnlyOneTabLeft() || |
| closed_by_user_gesture) { |
| // Close is the result of one of the following: |
| // . window close (and it isn't the last window). |
| // . closing a tab and there are other windows/tabs open. |
| // . closed by a user gesture. |
| // In all cases we need to mark the tab as explicitly closed. |
| ScheduleCommand(CreateTabClosedCommand(tab_id.id())); |
| } else { |
| // User closed the last tab in the last tabbed browser. Don't mark the |
| // tab closed. |
| pending_tab_close_ids_.insert(tab_id.id()); |
| has_open_trackable_browsers_ = false; |
| } |
| } |
| |
| void SessionService::WindowClosing(const SessionID& window_id) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| // The window is about to close. If there are other tabbed browsers with the |
| // same original profile commit the close immediately. |
| // |
| // NOTE: if the user chooses the exit menu item session service is destroyed |
| // and this code isn't hit. |
| if (has_open_trackable_browsers_) { |
| // Closing a window can never make has_open_trackable_browsers_ go from |
| // false to true, so only update it if already true. |
| has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id); |
| } |
| if (should_record_close_as_pending()) |
| pending_window_close_ids_.insert(window_id.id()); |
| else |
| window_closing_ids_.insert(window_id.id()); |
| } |
| |
| void SessionService::WindowClosed(const SessionID& window_id) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| windows_tracking_.erase(window_id.id()); |
| |
| if (window_closing_ids_.find(window_id.id()) != window_closing_ids_.end()) { |
| window_closing_ids_.erase(window_id.id()); |
| ScheduleCommand(CreateWindowClosedCommand(window_id.id())); |
| } else if (pending_window_close_ids_.find(window_id.id()) == |
| pending_window_close_ids_.end()) { |
| // We'll hit this if user closed the last tab in a window. |
| has_open_trackable_browsers_ = HasOpenTrackableBrowsers(window_id); |
| if (should_record_close_as_pending()) |
| pending_window_close_ids_.insert(window_id.id()); |
| else |
| ScheduleCommand(CreateWindowClosedCommand(window_id.id())); |
| } |
| } |
| |
| void SessionService::SetWindowType(const SessionID& window_id, |
| Browser::Type type) { |
| if (!should_track_changes_for_browser_type(type)) |
| return; |
| |
| windows_tracking_.insert(window_id.id()); |
| |
| // The user created a new tabbed browser with our profile. Commit any |
| // pending closes. |
| CommitPendingCloses(); |
| |
| has_open_trackable_browsers_ = true; |
| move_on_new_browser_ = true; |
| |
| ScheduleCommand( |
| CreateSetWindowTypeCommand(window_id, WindowTypeForBrowserType(type))); |
| } |
| |
| void SessionService::TabNavigationPathPrunedFromBack(const SessionID& window_id, |
| const SessionID& tab_id, |
| int count) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| TabNavigationPathPrunedFromBackPayload payload = { 0 }; |
| payload.id = tab_id.id(); |
| payload.index = count; |
| SessionCommand* command = |
| new SessionCommand(kCommandTabNavigationPathPrunedFromBack, |
| sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| ScheduleCommand(command); |
| } |
| |
| void SessionService::TabNavigationPathPrunedFromFront( |
| const SessionID& window_id, |
| const SessionID& tab_id, |
| int count) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| // Update the range of indices. |
| if (tab_to_available_range_.find(tab_id.id()) != |
| tab_to_available_range_.end()) { |
| std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; |
| range.first = std::max(0, range.first - count); |
| range.second = std::max(0, range.second - count); |
| } |
| |
| TabNavigationPathPrunedFromFrontPayload payload = { 0 }; |
| payload.id = tab_id.id(); |
| payload.index = count; |
| SessionCommand* command = |
| new SessionCommand(kCommandTabNavigationPathPrunedFromFront, |
| sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| ScheduleCommand(command); |
| } |
| |
| void SessionService::UpdateTabNavigation(const SessionID& window_id, |
| const SessionID& tab_id, |
| int index, |
| const NavigationEntry& entry) { |
| if (!ShouldTrackEntry(entry) || !ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| if (tab_to_available_range_.find(tab_id.id()) != |
| tab_to_available_range_.end()) { |
| std::pair<int, int>& range = tab_to_available_range_[tab_id.id()]; |
| range.first = std::min(index, range.first); |
| range.second = std::max(index, range.second); |
| } |
| ScheduleCommand(CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, |
| tab_id.id(), index, entry)); |
| } |
| |
| void SessionService::TabRestored(NavigationController* controller, |
| bool pinned) { |
| if (!ShouldTrackChangesToWindow(controller->window_id())) |
| return; |
| |
| BuildCommandsForTab(controller->window_id(), controller, -1, |
| pinned, &pending_commands(), NULL); |
| StartSaveTimer(); |
| } |
| |
| void SessionService::SetSelectedNavigationIndex(const SessionID& window_id, |
| const SessionID& tab_id, |
| int index) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| if (tab_to_available_range_.find(tab_id.id()) != |
| tab_to_available_range_.end()) { |
| if (index < tab_to_available_range_[tab_id.id()].first || |
| index > tab_to_available_range_[tab_id.id()].second) { |
| // The new index is outside the range of what we've archived, schedule |
| // a reset. |
| ResetFromCurrentBrowsers(); |
| return; |
| } |
| } |
| ScheduleCommand(CreateSetSelectedNavigationIndexCommand(tab_id, index)); |
| } |
| |
| void SessionService::SetSelectedTabInWindow(const SessionID& window_id, |
| int index) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(CreateSetSelectedTabInWindow(window_id, index)); |
| } |
| |
| SessionService::Handle SessionService::GetLastSession( |
| CancelableRequestConsumerBase* consumer, |
| SessionCallback* callback) { |
| return ScheduleGetLastSessionCommands( |
| new InternalSessionRequest( |
| NewCallback(this, &SessionService::OnGotSessionCommands), |
| callback), consumer); |
| } |
| |
| SessionService::Handle SessionService::GetCurrentSession( |
| CancelableRequestConsumerBase* consumer, |
| SessionCallback* callback) { |
| if (pending_window_close_ids_.empty()) { |
| // If there are no pending window closes, we can get the current session |
| // from memory. |
| scoped_refptr<InternalSessionRequest> request(new InternalSessionRequest( |
| NewCallback(this, &SessionService::OnGotSessionCommands), |
| callback)); |
| AddRequest(request, consumer); |
| IdToRange tab_to_available_range; |
| std::set<SessionID::id_type> windows_to_track; |
| BuildCommandsFromBrowsers(&(request->commands), |
| &tab_to_available_range, |
| &windows_to_track); |
| request->ForwardResult( |
| BaseSessionService::InternalGetCommandsRequest::TupleType( |
| request->handle(), request)); |
| return request->handle(); |
| } else { |
| // If there are pending window closes, read the current session from disk. |
| return ScheduleGetCurrentSessionCommands( |
| new InternalSessionRequest( |
| NewCallback(this, &SessionService::OnGotSessionCommands), |
| callback), consumer); |
| } |
| } |
| |
| void SessionService::Save() { |
| bool had_commands = !pending_commands().empty(); |
| BaseSessionService::Save(); |
| if (had_commands) { |
| RecordSessionUpdateHistogramData(NotificationType::SESSION_SERVICE_SAVED, |
| &last_updated_save_time_); |
| NotificationService::current()->Notify( |
| NotificationType::SESSION_SERVICE_SAVED, |
| Source<Profile>(profile()), |
| NotificationService::NoDetails()); |
| } |
| } |
| |
| void SessionService::Init() { |
| // Register for the notifications we're interested in. |
| registrar_.Add(this, NotificationType::TAB_PARENTED, |
| NotificationService::AllSources()); |
| registrar_.Add(this, NotificationType::TAB_CLOSED, |
| NotificationService::AllSources()); |
| registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, |
| NotificationService::AllSources()); |
| registrar_.Add(this, NotificationType::NAV_ENTRY_CHANGED, |
| NotificationService::AllSources()); |
| registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, |
| NotificationService::AllSources()); |
| registrar_.Add(this, NotificationType::BROWSER_OPENED, |
| NotificationService::AllSources()); |
| registrar_.Add(this, |
| NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, |
| NotificationService::AllSources()); |
| } |
| |
| bool SessionService::RestoreIfNecessary(const std::vector<GURL>& urls_to_open, |
| Browser* browser) { |
| if (!has_open_trackable_browsers_ && !BrowserInit::InProcessStartup() && |
| !SessionRestore::IsRestoring() |
| #if defined(OS_MACOSX) |
| // OSX has a fairly different idea of application lifetime than the |
| // other platforms. We need to check that we aren't opening a window |
| // from the dock or the menubar. |
| && !app_controller_mac::IsOpeningNewWindow() |
| #endif |
| ) { |
| // We're going from no tabbed browsers to a tabbed browser (and not in |
| // process startup), restore the last session. |
| if (move_on_new_browser_) { |
| // Make the current session the last. |
| MoveCurrentSessionToLastSession(); |
| move_on_new_browser_ = false; |
| } |
| SessionStartupPref pref = SessionStartupPref::GetStartupPref(profile()); |
| if (pref.type == SessionStartupPref::LAST) { |
| SessionRestore::RestoreSession( |
| profile(), browser, false, browser ? false : true, urls_to_open); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void SessionService::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| // All of our messages have the NavigationController as the source. |
| switch (type.value) { |
| case NotificationType::BROWSER_OPENED: { |
| Browser* browser = Source<Browser>(source).ptr(); |
| if (browser->profile() != profile() || |
| !should_track_changes_for_browser_type(browser->type())) { |
| return; |
| } |
| |
| RestoreIfNecessary(std::vector<GURL>(), browser); |
| SetWindowType(browser->session_id(), browser->type()); |
| break; |
| } |
| |
| case NotificationType::TAB_PARENTED: { |
| NavigationController* controller = |
| Source<NavigationController>(source).ptr(); |
| SetTabWindow(controller->window_id(), controller->session_id()); |
| TabContentsWrapper* wrapper = |
| TabContentsWrapper::GetCurrentWrapperForContents( |
| controller->tab_contents()); |
| if (wrapper->extension_tab_helper()->extension_app()) { |
| SetTabExtensionAppID( |
| controller->window_id(), |
| controller->session_id(), |
| wrapper->extension_tab_helper()->extension_app()->id()); |
| } |
| break; |
| } |
| |
| case NotificationType::TAB_CLOSED: { |
| NavigationController* controller = |
| Source<NavigationController>(source).ptr(); |
| TabClosed(controller->window_id(), controller->session_id(), |
| controller->tab_contents()->closed_by_user_gesture()); |
| RecordSessionUpdateHistogramData(NotificationType::TAB_CLOSED, |
| &last_updated_tab_closed_time_); |
| break; |
| } |
| |
| case NotificationType::NAV_LIST_PRUNED: { |
| NavigationController* controller = |
| Source<NavigationController>(source).ptr(); |
| Details<NavigationController::PrunedDetails> pruned_details(details); |
| if (pruned_details->from_front) { |
| TabNavigationPathPrunedFromFront(controller->window_id(), |
| controller->session_id(), |
| pruned_details->count); |
| } else { |
| TabNavigationPathPrunedFromBack(controller->window_id(), |
| controller->session_id(), |
| controller->entry_count()); |
| } |
| RecordSessionUpdateHistogramData(NotificationType::NAV_LIST_PRUNED, |
| &last_updated_nav_list_pruned_time_); |
| break; |
| } |
| |
| case NotificationType::NAV_ENTRY_CHANGED: { |
| NavigationController* controller = |
| Source<NavigationController>(source).ptr(); |
| Details<NavigationController::EntryChangedDetails> changed(details); |
| UpdateTabNavigation(controller->window_id(), controller->session_id(), |
| changed->index, *changed->changed_entry); |
| break; |
| } |
| |
| case NotificationType::NAV_ENTRY_COMMITTED: { |
| NavigationController* controller = |
| Source<NavigationController>(source).ptr(); |
| int current_entry_index = controller->GetCurrentEntryIndex(); |
| SetSelectedNavigationIndex(controller->window_id(), |
| controller->session_id(), |
| current_entry_index); |
| UpdateTabNavigation(controller->window_id(), controller->session_id(), |
| current_entry_index, |
| *controller->GetEntryAtIndex(current_entry_index)); |
| Details<NavigationController::LoadCommittedDetails> changed(details); |
| if (changed->type == NavigationType::NEW_PAGE || |
| changed->type == NavigationType::EXISTING_PAGE) { |
| RecordSessionUpdateHistogramData(NotificationType::NAV_ENTRY_COMMITTED, |
| &last_updated_nav_entry_commit_time_); |
| } |
| break; |
| } |
| |
| case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { |
| ExtensionTabHelper* extension_tab_helper = |
| Source<ExtensionTabHelper>(source).ptr(); |
| if (extension_tab_helper->extension_app()) { |
| SetTabExtensionAppID( |
| extension_tab_helper->tab_contents()->controller().window_id(), |
| extension_tab_helper->tab_contents()->controller().session_id(), |
| extension_tab_helper->extension_app()->id()); |
| } |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void SessionService::SetTabExtensionAppID( |
| const SessionID& window_id, |
| const SessionID& tab_id, |
| const std::string& extension_app_id) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(CreateSetTabExtensionAppIDCommand( |
| kCommandSetExtensionAppID, |
| tab_id.id(), |
| extension_app_id)); |
| } |
| |
| SessionCommand* SessionService::CreateSetSelectedTabInWindow( |
| const SessionID& window_id, |
| int index) { |
| SelectedTabInIndexPayload payload = { 0 }; |
| payload.id = window_id.id(); |
| payload.index = index; |
| SessionCommand* command = new SessionCommand(kCommandSetSelectedTabInIndex, |
| sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateSetTabWindowCommand( |
| const SessionID& window_id, |
| const SessionID& tab_id) { |
| SessionID::id_type payload[] = { window_id.id(), tab_id.id() }; |
| SessionCommand* command = |
| new SessionCommand(kCommandSetTabWindow, sizeof(payload)); |
| memcpy(command->contents(), payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateSetWindowBoundsCommand( |
| const SessionID& window_id, |
| const gfx::Rect& bounds, |
| bool is_maximized) { |
| WindowBoundsPayload2 payload = { 0 }; |
| payload.window_id = window_id.id(); |
| payload.x = bounds.x(); |
| payload.y = bounds.y(); |
| payload.w = bounds.width(); |
| payload.h = bounds.height(); |
| payload.is_maximized = is_maximized; |
| SessionCommand* command = new SessionCommand(kCommandSetWindowBounds2, |
| sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateSetTabIndexInWindowCommand( |
| const SessionID& tab_id, |
| int new_index) { |
| TabIndexInWindowPayload payload = { 0 }; |
| payload.id = tab_id.id(); |
| payload.index = new_index; |
| SessionCommand* command = |
| new SessionCommand(kCommandSetTabIndexInWindow, sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateTabClosedCommand( |
| const SessionID::id_type tab_id) { |
| ClosedPayload payload; |
| // Because of what appears to be a compiler bug setting payload to {0} doesn't |
| // set the padding to 0, resulting in Purify reporting an UMR when we write |
| // the structure to disk. To avoid this we explicitly memset the struct. |
| memset(&payload, 0, sizeof(payload)); |
| payload.id = tab_id; |
| payload.close_time = Time::Now().ToInternalValue(); |
| SessionCommand* command = |
| new SessionCommand(kCommandTabClosed, sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateWindowClosedCommand( |
| const SessionID::id_type window_id) { |
| ClosedPayload payload; |
| // See comment in CreateTabClosedCommand as to why we do this. |
| memset(&payload, 0, sizeof(payload)); |
| payload.id = window_id; |
| payload.close_time = Time::Now().ToInternalValue(); |
| SessionCommand* command = |
| new SessionCommand(kCommandWindowClosed, sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateSetSelectedNavigationIndexCommand( |
| const SessionID& tab_id, |
| int index) { |
| SelectedNavigationIndexPayload payload = { 0 }; |
| payload.id = tab_id.id(); |
| payload.index = index; |
| SessionCommand* command = new SessionCommand( |
| kCommandSetSelectedNavigationIndex, sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreateSetWindowTypeCommand( |
| const SessionID& window_id, |
| WindowType type) { |
| WindowTypePayload payload = { 0 }; |
| payload.id = window_id.id(); |
| payload.index = static_cast<int32>(type); |
| SessionCommand* command = new SessionCommand( |
| kCommandSetWindowType, sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| SessionCommand* SessionService::CreatePinnedStateCommand( |
| const SessionID& tab_id, |
| bool is_pinned) { |
| PinnedStatePayload payload = { 0 }; |
| payload.tab_id = tab_id.id(); |
| payload.pinned_state = is_pinned; |
| SessionCommand* command = |
| new SessionCommand(kCommandSetPinnedState, sizeof(payload)); |
| memcpy(command->contents(), &payload, sizeof(payload)); |
| return command; |
| } |
| |
| void SessionService::OnGotSessionCommands( |
| Handle handle, |
| scoped_refptr<InternalGetCommandsRequest> request) { |
| if (request->canceled()) |
| return; |
| ScopedVector<SessionWindow> valid_windows; |
| RestoreSessionFromCommands( |
| request->commands, &(valid_windows.get())); |
| static_cast<InternalSessionRequest*>(request.get())-> |
| real_callback->RunWithParams( |
| SessionCallback::TupleType(request->handle(), |
| &(valid_windows.get()))); |
| } |
| |
| void SessionService::RestoreSessionFromCommands( |
| const std::vector<SessionCommand*>& commands, |
| std::vector<SessionWindow*>* valid_windows) { |
| std::map<int, SessionTab*> tabs; |
| std::map<int, SessionWindow*> windows; |
| |
| if (CreateTabsAndWindows(commands, &tabs, &windows)) { |
| AddTabsToWindows(&tabs, &windows); |
| SortTabsBasedOnVisualOrderAndPrune(&windows, valid_windows); |
| UpdateSelectedTabIndex(valid_windows); |
| } |
| STLDeleteValues(&tabs); |
| // Don't delete conents of windows, that is done by the caller as all |
| // valid windows are added to valid_windows. |
| } |
| |
| void SessionService::UpdateSelectedTabIndex( |
| std::vector<SessionWindow*>* windows) { |
| for (std::vector<SessionWindow*>::const_iterator i = windows->begin(); |
| i != windows->end(); ++i) { |
| // See note in SessionWindow as to why we do this. |
| int new_index = 0; |
| for (std::vector<SessionTab*>::const_iterator j = (*i)->tabs.begin(); |
| j != (*i)->tabs.end(); ++j) { |
| if ((*j)->tab_visual_index == (*i)->selected_tab_index) { |
| new_index = static_cast<int>(j - (*i)->tabs.begin()); |
| break; |
| } |
| } |
| (*i)->selected_tab_index = new_index; |
| } |
| } |
| |
| SessionWindow* SessionService::GetWindow( |
| SessionID::id_type window_id, |
| IdToSessionWindow* windows) { |
| std::map<int, SessionWindow*>::iterator i = windows->find(window_id); |
| if (i == windows->end()) { |
| SessionWindow* window = new SessionWindow(); |
| window->window_id.set_id(window_id); |
| (*windows)[window_id] = window; |
| return window; |
| } |
| return i->second; |
| } |
| |
| SessionTab* SessionService::GetTab( |
| SessionID::id_type tab_id, |
| IdToSessionTab* tabs) { |
| DCHECK(tabs); |
| std::map<int, SessionTab*>::iterator i = tabs->find(tab_id); |
| if (i == tabs->end()) { |
| SessionTab* tab = new SessionTab(); |
| tab->tab_id.set_id(tab_id); |
| (*tabs)[tab_id] = tab; |
| return tab; |
| } |
| return i->second; |
| } |
| |
| std::vector<TabNavigation>::iterator |
| SessionService::FindClosestNavigationWithIndex( |
| std::vector<TabNavigation>* navigations, |
| int index) { |
| DCHECK(navigations); |
| for (std::vector<TabNavigation>::iterator i = navigations->begin(); |
| i != navigations->end(); ++i) { |
| if (i->index() >= index) |
| return i; |
| } |
| return navigations->end(); |
| } |
| |
| // Function used in sorting windows. Sorting is done based on window id. As |
| // window ids increment for each new window, this effectively sorts by creation |
| // time. |
| static bool WindowOrderSortFunction(const SessionWindow* w1, |
| const SessionWindow* w2) { |
| return w1->window_id.id() < w2->window_id.id(); |
| } |
| |
| // Compares the two tabs based on visual index. |
| static bool TabVisualIndexSortFunction(const SessionTab* t1, |
| const SessionTab* t2) { |
| const int delta = t1->tab_visual_index - t2->tab_visual_index; |
| return delta == 0 ? (t1->tab_id.id() < t2->tab_id.id()) : (delta < 0); |
| } |
| |
| void SessionService::SortTabsBasedOnVisualOrderAndPrune( |
| std::map<int, SessionWindow*>* windows, |
| std::vector<SessionWindow*>* valid_windows) { |
| std::map<int, SessionWindow*>::iterator i = windows->begin(); |
| while (i != windows->end()) { |
| if (i->second->tabs.empty() || i->second->is_constrained || |
| !should_track_changes_for_browser_type( |
| static_cast<Browser::Type>(i->second->type))) { |
| delete i->second; |
| windows->erase(i++); |
| } else { |
| // Valid window; sort the tabs and add it to the list of valid windows. |
| std::sort(i->second->tabs.begin(), i->second->tabs.end(), |
| &TabVisualIndexSortFunction); |
| // Add the window such that older windows appear first. |
| if (valid_windows->empty()) { |
| valid_windows->push_back(i->second); |
| } else { |
| valid_windows->insert( |
| std::upper_bound(valid_windows->begin(), valid_windows->end(), |
| i->second, &WindowOrderSortFunction), |
| i->second); |
| } |
| ++i; |
| } |
| } |
| } |
| |
| void SessionService::AddTabsToWindows(std::map<int, SessionTab*>* tabs, |
| std::map<int, SessionWindow*>* windows) { |
| std::map<int, SessionTab*>::iterator i = tabs->begin(); |
| while (i != tabs->end()) { |
| SessionTab* tab = i->second; |
| if (tab->window_id.id() && !tab->navigations.empty()) { |
| SessionWindow* window = GetWindow(tab->window_id.id(), windows); |
| window->tabs.push_back(tab); |
| tabs->erase(i++); |
| |
| // See note in SessionTab as to why we do this. |
| std::vector<TabNavigation>::iterator j = |
| FindClosestNavigationWithIndex(&(tab->navigations), |
| tab->current_navigation_index); |
| if (j == tab->navigations.end()) { |
| tab->current_navigation_index = |
| static_cast<int>(tab->navigations.size() - 1); |
| } else { |
| tab->current_navigation_index = |
| static_cast<int>(j - tab->navigations.begin()); |
| } |
| } else { |
| // Never got a set tab index in window, or tabs are empty, nothing |
| // to do. |
| ++i; |
| } |
| } |
| } |
| |
| bool SessionService::CreateTabsAndWindows( |
| const std::vector<SessionCommand*>& data, |
| std::map<int, SessionTab*>* tabs, |
| std::map<int, SessionWindow*>* windows) { |
| // If the file is corrupt (command with wrong size, or unknown command), we |
| // still return true and attempt to restore what we we can. |
| |
| for (std::vector<SessionCommand*>::const_iterator i = data.begin(); |
| i != data.end(); ++i) { |
| const SessionCommand* command = *i; |
| |
| switch (command->id()) { |
| case kCommandSetTabWindow: { |
| SessionID::id_type payload[2]; |
| if (!command->GetPayload(payload, sizeof(payload))) |
| return true; |
| GetTab(payload[1], tabs)->window_id.set_id(payload[0]); |
| break; |
| } |
| |
| case kCommandSetWindowBounds2: { |
| WindowBoundsPayload2 payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| GetWindow(payload.window_id, windows)->bounds.SetRect(payload.x, |
| payload.y, |
| payload.w, |
| payload.h); |
| GetWindow(payload.window_id, windows)->is_maximized = |
| payload.is_maximized; |
| break; |
| } |
| |
| case kCommandSetTabIndexInWindow: { |
| TabIndexInWindowPayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| GetTab(payload.id, tabs)->tab_visual_index = payload.index; |
| break; |
| } |
| |
| case kCommandTabClosed: |
| case kCommandWindowClosed: { |
| ClosedPayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| if (command->id() == kCommandTabClosed) { |
| delete GetTab(payload.id, tabs); |
| tabs->erase(payload.id); |
| } else { |
| delete GetWindow(payload.id, windows); |
| windows->erase(payload.id); |
| } |
| break; |
| } |
| |
| case kCommandTabNavigationPathPrunedFromBack: { |
| TabNavigationPathPrunedFromBackPayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| SessionTab* tab = GetTab(payload.id, tabs); |
| tab->navigations.erase( |
| FindClosestNavigationWithIndex(&(tab->navigations), payload.index), |
| tab->navigations.end()); |
| break; |
| } |
| |
| case kCommandTabNavigationPathPrunedFromFront: { |
| TabNavigationPathPrunedFromFrontPayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload)) || |
| payload.index <= 0) { |
| return true; |
| } |
| SessionTab* tab = GetTab(payload.id, tabs); |
| |
| // Update the selected navigation index. |
| tab->current_navigation_index = |
| std::max(-1, tab->current_navigation_index - payload.index); |
| |
| // And update the index of existing navigations. |
| for (std::vector<TabNavigation>::iterator i = tab->navigations.begin(); |
| i != tab->navigations.end();) { |
| i->set_index(i->index() - payload.index); |
| if (i->index() < 0) |
| i = tab->navigations.erase(i); |
| else |
| ++i; |
| } |
| break; |
| } |
| |
| case kCommandUpdateTabNavigation: { |
| TabNavigation navigation; |
| SessionID::id_type tab_id; |
| if (!RestoreUpdateTabNavigationCommand(*command, &navigation, &tab_id)) |
| return true; |
| |
| SessionTab* tab = GetTab(tab_id, tabs); |
| std::vector<TabNavigation>::iterator i = |
| FindClosestNavigationWithIndex(&(tab->navigations), |
| navigation.index()); |
| if (i != tab->navigations.end() && i->index() == navigation.index()) |
| *i = navigation; |
| else |
| tab->navigations.insert(i, navigation); |
| break; |
| } |
| |
| case kCommandSetSelectedNavigationIndex: { |
| SelectedNavigationIndexPayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| GetTab(payload.id, tabs)->current_navigation_index = payload.index; |
| break; |
| } |
| |
| case kCommandSetSelectedTabInIndex: { |
| SelectedTabInIndexPayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| GetWindow(payload.id, windows)->selected_tab_index = payload.index; |
| break; |
| } |
| |
| case kCommandSetWindowType: { |
| WindowTypePayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| GetWindow(payload.id, windows)->is_constrained = false; |
| GetWindow(payload.id, windows)->type = |
| BrowserTypeForWindowType( |
| static_cast<WindowType>(payload.index)); |
| break; |
| } |
| |
| case kCommandSetPinnedState: { |
| PinnedStatePayload payload; |
| if (!command->GetPayload(&payload, sizeof(payload))) |
| return true; |
| GetTab(payload.tab_id, tabs)->pinned = payload.pinned_state; |
| break; |
| } |
| |
| case kCommandSetExtensionAppID: { |
| SessionID::id_type tab_id; |
| std::string extension_app_id; |
| if (!RestoreSetTabExtensionAppIDCommand( |
| *command, &tab_id, &extension_app_id)) { |
| return true; |
| } |
| |
| GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id); |
| break; |
| } |
| |
| default: |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| void SessionService::BuildCommandsForTab( |
| const SessionID& window_id, |
| NavigationController* controller, |
| int index_in_window, |
| bool is_pinned, |
| std::vector<SessionCommand*>* commands, |
| IdToRange* tab_to_available_range) { |
| DCHECK(controller && commands && window_id.id()); |
| commands->push_back( |
| CreateSetTabWindowCommand(window_id, controller->session_id())); |
| const int current_index = controller->GetCurrentEntryIndex(); |
| const int min_index = std::max(0, |
| current_index - max_persist_navigation_count); |
| const int max_index = std::min(current_index + max_persist_navigation_count, |
| controller->entry_count()); |
| const int pending_index = controller->pending_entry_index(); |
| if (tab_to_available_range) { |
| (*tab_to_available_range)[controller->session_id().id()] = |
| std::pair<int, int>(min_index, max_index); |
| } |
| if (is_pinned) { |
| commands->push_back( |
| CreatePinnedStateCommand(controller->session_id(), true)); |
| } |
| TabContentsWrapper* wrapper = |
| TabContentsWrapper::GetCurrentWrapperForContents( |
| controller->tab_contents()); |
| if (wrapper->extension_tab_helper()->extension_app()) { |
| commands->push_back( |
| CreateSetTabExtensionAppIDCommand( |
| kCommandSetExtensionAppID, |
| controller->session_id().id(), |
| wrapper->extension_tab_helper()->extension_app()->id())); |
| } |
| for (int i = min_index; i < max_index; ++i) { |
| const NavigationEntry* entry = (i == pending_index) ? |
| controller->pending_entry() : controller->GetEntryAtIndex(i); |
| DCHECK(entry); |
| if (ShouldTrackEntry(*entry)) { |
| commands->push_back( |
| CreateUpdateTabNavigationCommand(kCommandUpdateTabNavigation, |
| controller->session_id().id(), |
| i, |
| *entry)); |
| } |
| } |
| commands->push_back( |
| CreateSetSelectedNavigationIndexCommand(controller->session_id(), |
| current_index)); |
| |
| if (index_in_window != -1) { |
| commands->push_back( |
| CreateSetTabIndexInWindowCommand(controller->session_id(), |
| index_in_window)); |
| } |
| } |
| |
| void SessionService::BuildCommandsForBrowser( |
| Browser* browser, |
| std::vector<SessionCommand*>* commands, |
| IdToRange* tab_to_available_range, |
| std::set<SessionID::id_type>* windows_to_track) { |
| DCHECK(browser && commands); |
| DCHECK(browser->session_id().id()); |
| |
| commands->push_back( |
| CreateSetWindowBoundsCommand(browser->session_id(), |
| browser->window()->GetRestoredBounds(), |
| browser->window()->IsMaximized())); |
| |
| commands->push_back(CreateSetWindowTypeCommand( |
| browser->session_id(), WindowTypeForBrowserType(browser->type()))); |
| |
| bool added_to_windows_to_track = false; |
| for (int i = 0; i < browser->tab_count(); ++i) { |
| TabContents* tab = browser->GetTabContentsAt(i); |
| DCHECK(tab); |
| if (tab->profile() == profile() || profile() == NULL) { |
| BuildCommandsForTab(browser->session_id(), &tab->controller(), i, |
| browser->tabstrip_model()->IsTabPinned(i), |
| commands, tab_to_available_range); |
| if (windows_to_track && !added_to_windows_to_track) { |
| windows_to_track->insert(browser->session_id().id()); |
| added_to_windows_to_track = true; |
| } |
| } |
| } |
| commands->push_back( |
| CreateSetSelectedTabInWindow(browser->session_id(), |
| browser->active_index())); |
| } |
| |
| void SessionService::BuildCommandsFromBrowsers( |
| std::vector<SessionCommand*>* commands, |
| IdToRange* tab_to_available_range, |
| std::set<SessionID::id_type>* windows_to_track) { |
| DCHECK(commands); |
| for (BrowserList::const_iterator i = BrowserList::begin(); |
| i != BrowserList::end(); ++i) { |
| // Make sure the browser has tabs and a window. Browsers destructor |
| // removes itself from the BrowserList. When a browser is closed the |
| // destructor is not necessarily run immediately. This means its possible |
| // for us to get a handle to a browser that is about to be removed. If |
| // the tab count is 0 or the window is NULL, the browser is about to be |
| // deleted, so we ignore it. |
| if (should_track_changes_for_browser_type((*i)->type()) && |
| (*i)->tab_count() && (*i)->window()) { |
| BuildCommandsForBrowser(*i, commands, tab_to_available_range, |
| windows_to_track); |
| } |
| } |
| } |
| |
| void SessionService::ScheduleReset() { |
| set_pending_reset(true); |
| STLDeleteElements(&pending_commands()); |
| tab_to_available_range_.clear(); |
| windows_tracking_.clear(); |
| BuildCommandsFromBrowsers(&pending_commands(), &tab_to_available_range_, |
| &windows_tracking_); |
| if (!windows_tracking_.empty()) { |
| // We're lazily created on startup and won't get an initial batch of |
| // SetWindowType messages. Set these here to make sure our state is correct. |
| has_open_trackable_browsers_ = true; |
| move_on_new_browser_ = true; |
| } |
| StartSaveTimer(); |
| } |
| |
| bool SessionService::ReplacePendingCommand(SessionCommand* command) { |
| // We only optimize page navigations, which can happen quite frequently and |
| // are expensive. If necessary, other commands could be searched for as |
| // well. |
| if (command->id() != kCommandUpdateTabNavigation) |
| return false; |
| void* iterator = NULL; |
| scoped_ptr<Pickle> command_pickle(command->PayloadAsPickle()); |
| SessionID::id_type command_tab_id; |
| int command_nav_index; |
| if (!command_pickle->ReadInt(&iterator, &command_tab_id) || |
| !command_pickle->ReadInt(&iterator, &command_nav_index)) { |
| return false; |
| } |
| for (std::vector<SessionCommand*>::reverse_iterator i = |
| pending_commands().rbegin(); i != pending_commands().rend(); ++i) { |
| SessionCommand* existing_command = *i; |
| if (existing_command->id() == kCommandUpdateTabNavigation) { |
| SessionID::id_type existing_tab_id; |
| int existing_nav_index; |
| { |
| // Creating a pickle like this means the Pickle references the data from |
| // the command. Make sure we delete the pickle before the command, else |
| // the pickle references deleted memory. |
| scoped_ptr<Pickle> existing_pickle(existing_command->PayloadAsPickle()); |
| iterator = NULL; |
| if (!existing_pickle->ReadInt(&iterator, &existing_tab_id) || |
| !existing_pickle->ReadInt(&iterator, &existing_nav_index)) { |
| return false; |
| } |
| } |
| if (existing_tab_id == command_tab_id && |
| existing_nav_index == command_nav_index) { |
| // existing_command is an update for the same tab/index pair. Replace |
| // it with the new one. We need to add to the end of the list just in |
| // case there is a prune command after the update command. |
| delete existing_command; |
| pending_commands().erase(i.base() - 1); |
| pending_commands().push_back(command); |
| return true; |
| } |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| void SessionService::ScheduleCommand(SessionCommand* command) { |
| DCHECK(command); |
| if (ReplacePendingCommand(command)) |
| return; |
| BaseSessionService::ScheduleCommand(command); |
| // Don't schedule a reset on tab closed/window closed. Otherwise we may |
| // lose tabs/windows we want to restore from if we exit right after this. |
| if (!pending_reset() && pending_window_close_ids_.empty() && |
| commands_since_reset() >= kWritesPerReset && |
| (command->id() != kCommandTabClosed && |
| command->id() != kCommandWindowClosed)) { |
| ScheduleReset(); |
| } |
| } |
| |
| void SessionService::CommitPendingCloses() { |
| for (PendingTabCloseIDs::iterator i = pending_tab_close_ids_.begin(); |
| i != pending_tab_close_ids_.end(); ++i) { |
| ScheduleCommand(CreateTabClosedCommand(*i)); |
| } |
| pending_tab_close_ids_.clear(); |
| |
| for (PendingWindowCloseIDs::iterator i = pending_window_close_ids_.begin(); |
| i != pending_window_close_ids_.end(); ++i) { |
| ScheduleCommand(CreateWindowClosedCommand(*i)); |
| } |
| pending_window_close_ids_.clear(); |
| } |
| |
| bool SessionService::IsOnlyOneTabLeft() { |
| if (!profile()) { |
| // We're testing, always return false. |
| return false; |
| } |
| |
| int window_count = 0; |
| for (BrowserList::const_iterator i = BrowserList::begin(); |
| i != BrowserList::end(); ++i) { |
| const SessionID::id_type window_id = (*i)->session_id().id(); |
| if (should_track_changes_for_browser_type((*i)->type()) && |
| (*i)->profile() == profile() && |
| window_closing_ids_.find(window_id) == window_closing_ids_.end()) { |
| if (++window_count > 1) |
| return false; |
| // By the time this is invoked the tab has been removed. As such, we use |
| // > 0 here rather than > 1. |
| if ((*i)->tab_count() > 0) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SessionService::HasOpenTrackableBrowsers(const SessionID& window_id) { |
| if (!profile()) { |
| // We're testing, always return false. |
| return true; |
| } |
| |
| for (BrowserList::const_iterator i = BrowserList::begin(); |
| i != BrowserList::end(); ++i) { |
| Browser* browser = *i; |
| const SessionID::id_type browser_id = browser->session_id().id(); |
| if (browser_id != window_id.id() && |
| window_closing_ids_.find(browser_id) == window_closing_ids_.end() && |
| should_track_changes_for_browser_type(browser->type()) && |
| browser->profile() == profile()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool SessionService::ShouldTrackChangesToWindow(const SessionID& window_id) { |
| return windows_tracking_.find(window_id.id()) != windows_tracking_.end(); |
| } |
| |
| |
| SessionService::WindowType SessionService::WindowTypeForBrowserType( |
| Browser::Type type) { |
| // We don't support masks here, only discrete types. |
| switch (type) { |
| case Browser::TYPE_POPUP: |
| return TYPE_POPUP; |
| case Browser::TYPE_APP: |
| return TYPE_APP; |
| case Browser::TYPE_APP_POPUP: |
| return TYPE_APP_POPUP; |
| case Browser::TYPE_DEVTOOLS: |
| return TYPE_DEVTOOLS; |
| case Browser::TYPE_APP_PANEL: |
| return TYPE_APP_PANEL; |
| case Browser::TYPE_NORMAL: |
| default: |
| return TYPE_NORMAL; |
| } |
| } |
| |
| Browser::Type SessionService::BrowserTypeForWindowType( |
| SessionService::WindowType type) { |
| switch (type) { |
| case TYPE_POPUP: |
| return Browser::TYPE_POPUP; |
| case TYPE_APP: |
| return Browser::TYPE_APP; |
| case TYPE_APP_POPUP: |
| return Browser::TYPE_APP_POPUP; |
| case TYPE_DEVTOOLS: |
| return Browser::TYPE_DEVTOOLS; |
| case TYPE_APP_PANEL: |
| return Browser::TYPE_APP_PANEL; |
| case TYPE_NORMAL: |
| default: |
| return Browser::TYPE_NORMAL; |
| } |
| } |
| |
| void SessionService::RecordSessionUpdateHistogramData(NotificationType type, |
| base::TimeTicks* last_updated_time) { |
| if (!last_updated_time->is_null()) { |
| base::TimeDelta delta = base::TimeTicks::Now() - *last_updated_time; |
| // We're interested in frequent updates periods longer than |
| // 10 minutes. |
| bool use_long_period = false; |
| if (delta >= save_delay_in_mins_) { |
| use_long_period = true; |
| } |
| switch (type.value) { |
| case NotificationType::SESSION_SERVICE_SAVED : |
| RecordUpdatedSaveTime(delta, use_long_period); |
| RecordUpdatedSessionNavigationOrTab(delta, use_long_period); |
| break; |
| case NotificationType::TAB_CLOSED: |
| RecordUpdatedTabClosed(delta, use_long_period); |
| RecordUpdatedSessionNavigationOrTab(delta, use_long_period); |
| break; |
| case NotificationType::NAV_LIST_PRUNED: |
| RecordUpdatedNavListPruned(delta, use_long_period); |
| RecordUpdatedSessionNavigationOrTab(delta, use_long_period); |
| break; |
| case NotificationType::NAV_ENTRY_COMMITTED: |
| RecordUpdatedNavEntryCommit(delta, use_long_period); |
| RecordUpdatedSessionNavigationOrTab(delta, use_long_period); |
| break; |
| default: |
| NOTREACHED() << "Bad type sent to RecordSessionUpdateHistogramData"; |
| break; |
| } |
| } |
| (*last_updated_time) = base::TimeTicks::Now(); |
| } |
| |
| void SessionService::RecordUpdatedTabClosed(base::TimeDelta delta, |
| bool use_long_period) { |
| std::string name("SessionRestore.TabClosedPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(name, |
| delta, |
| // 2500ms is the default save delay. |
| save_delay_in_millis_, |
| save_delay_in_mins_, |
| 50); |
| if (use_long_period) { |
| std::string long_name_("SessionRestore.TabClosedLongPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, |
| delta, |
| save_delay_in_mins_, |
| save_delay_in_hrs_, |
| 50); |
| } |
| } |
| |
| void SessionService::RecordUpdatedNavListPruned(base::TimeDelta delta, |
| bool use_long_period) { |
| std::string name("SessionRestore.NavigationListPrunedPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(name, |
| delta, |
| // 2500ms is the default save delay. |
| save_delay_in_millis_, |
| save_delay_in_mins_, |
| 50); |
| if (use_long_period) { |
| std::string long_name_("SessionRestore.NavigationListPrunedLongPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, |
| delta, |
| save_delay_in_mins_, |
| save_delay_in_hrs_, |
| 50); |
| } |
| } |
| |
| void SessionService::RecordUpdatedNavEntryCommit(base::TimeDelta delta, |
| bool use_long_period) { |
| std::string name("SessionRestore.NavEntryCommittedPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(name, |
| delta, |
| // 2500ms is the default save delay. |
| save_delay_in_millis_, |
| save_delay_in_mins_, |
| 50); |
| if (use_long_period) { |
| std::string long_name_("SessionRestore.NavEntryCommittedLongPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, |
| delta, |
| save_delay_in_mins_, |
| save_delay_in_hrs_, |
| 50); |
| } |
| } |
| |
| void SessionService::RecordUpdatedSessionNavigationOrTab(base::TimeDelta delta, |
| bool use_long_period) { |
| std::string name("SessionRestore.NavOrTabUpdatePeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(name, |
| delta, |
| // 2500ms is the default save delay. |
| save_delay_in_millis_, |
| save_delay_in_mins_, |
| 50); |
| if (use_long_period) { |
| std::string long_name_("SessionRestore.NavOrTabUpdateLongPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, |
| delta, |
| save_delay_in_mins_, |
| save_delay_in_hrs_, |
| 50); |
| } |
| } |
| |
| void SessionService::RecordUpdatedSaveTime(base::TimeDelta delta, |
| bool use_long_period) { |
| std::string name("SessionRestore.SavePeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(name, |
| delta, |
| // 2500ms is the default save delay. |
| save_delay_in_millis_, |
| save_delay_in_mins_, |
| 50); |
| if (use_long_period) { |
| std::string long_name_("SessionRestore.SaveLongPeriod"); |
| UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, |
| delta, |
| save_delay_in_mins_, |
| save_delay_in_hrs_, |
| 50); |
| } |
| } |