| // Copyright 2012 The Chromium Authors |
| // 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 <stddef.h> |
| |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/debug/alias.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/apps/app_service/launch_utils.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/buildflags.h" |
| #include "chrome/browser/lifetime/application_lifetime_desktop.h" |
| #include "chrome/browser/lifetime/browser_shutdown.h" |
| #include "chrome/browser/prefs/session_startup_pref.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_entry.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profiles_state.h" |
| #include "chrome/browser/sessions/exit_type_service.h" |
| #include "chrome/browser/sessions/session_common_utils.h" |
| #include "chrome/browser/sessions/session_data_deleter.h" |
| #include "chrome/browser/sessions/session_restore.h" |
| #include "chrome/browser/sessions/session_service_log.h" |
| #include "chrome/browser/sessions/session_service_utils.h" |
| #include "chrome/browser/sessions/tab_restore_service_factory.h" |
| #include "chrome/browser/tab_group_sync/tab_group_sync_service_factory.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/session_crashed_bubble.h" |
| #include "chrome/browser/ui/startup/startup_browser_creator.h" |
| #include "chrome/browser/ui/startup/startup_tab.h" |
| #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "components/saved_tab_groups/public/tab_group_sync_service.h" |
| #include "components/sessions/content/content_serialized_navigation_builder.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/sessions/core/command_storage_manager.h" |
| #include "components/sessions/core/session_command.h" |
| #include "components/sessions/core/session_constants.h" |
| #include "components/sessions/core/session_types.h" |
| #include "components/sessions/core/tab_restore_service.h" |
| #include "content/public/browser/navigation_details.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/session_storage_namespace.h" |
| #include "content/public/browser/web_contents.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/ash/crostini/crostini_util.h" |
| #include "chromeos/components/kiosk/kiosk_utils.h" |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "chrome/browser/app_controller_mac.h" |
| #endif |
| |
| using content::NavigationEntry; |
| using content::WebContents; |
| using sessions::ContentSerializedNavigationBuilder; |
| using sessions::SerializedNavigationEntry; |
| |
| namespace { |
| |
| const void* const kInstanceTrackerKey = &kInstanceTrackerKey; |
| |
| // An instance of this class is created pre-profile. It is used to track how |
| // many SessionServices have been created. |
| class InstanceTracker : public base::SupportsUserData::Data { |
| public: |
| InstanceTracker() = default; |
| InstanceTracker(const InstanceTracker&) = delete; |
| InstanceTracker& operator=(const InstanceTracker&) = delete; |
| ~InstanceTracker() override = default; |
| |
| // Registers a new instance. Returns true is this is the first time called |
| // with the specified profile. |
| static bool RegisterNewSessionService(Profile* profile) { |
| InstanceTracker* tracker = static_cast<InstanceTracker*>( |
| profile->GetUserData(kInstanceTrackerKey)); |
| if (!tracker) { |
| profile->SetUserData(kInstanceTrackerKey, |
| std::make_unique<InstanceTracker>()); |
| tracker = static_cast<InstanceTracker*>( |
| profile->GetUserData(kInstanceTrackerKey)); |
| } |
| return tracker->IncrementSessionServiceCount(); |
| } |
| |
| private: |
| bool IncrementSessionServiceCount() { |
| const bool is_first = session_service_count_ == 0; |
| ++session_service_count_; |
| return is_first; |
| } |
| |
| int session_service_count_ = 0; |
| }; |
| |
| } // namespace |
| |
| SessionService::SessionService(Profile* profile) |
| : SessionServiceBase( |
| profile, |
| SessionServiceBase::SessionServiceType::kSessionRestore), |
| is_first_session_service_( |
| InstanceTracker::RegisterNewSessionService(profile)) { |
| if (is_first_session_service_) |
| LogSessionServiceStartEvent(profile, HasPendingUncleanExit(profile)); |
| closing_all_browsers_subscription_ = chrome::AddClosingAllBrowsersCallback( |
| base::BindRepeating(&SessionService::OnClosingAllBrowsersChanged, |
| base::Unretained(this))); |
| ExitTypeService* exit_type_service = |
| ExitTypeService::GetInstanceForProfile(profile); |
| if (exit_type_service && exit_type_service->waiting_for_user_to_ack_crash()) { |
| SetSavingEnabled(false); |
| exit_type_service->AddCrashAckCallback(base::BindOnce( |
| &SessionService::SetSavingEnabled, weak_factory_.GetWeakPtr(), true)); |
| } |
| } |
| |
| SessionService::~SessionService() { |
| base::UmaHistogramCounts100("SessionRestore.UnrecoverableWriteErrorCount", |
| unrecoverable_write_error_count_); |
| |
| // This must be called from SessionService because Save() calls back into |
| // SessionService, which will have been destructed already if we try to |
| // do this in SessionServiceBase. |
| command_storage_manager()->Save(); |
| |
| DestroyCommandStorageManager(); |
| |
| // Certain code paths explicitly destroy the SessionService as part of |
| // shutdown. |
| if (!did_log_exit_) |
| LogExitEvent(); |
| } |
| |
| // static |
| bool SessionService::IsRelevantWindowType( |
| sessions::SessionWindow::WindowType window_type) { |
| return (window_type == sessions::SessionWindow::TYPE_NORMAL) || |
| (window_type == sessions::SessionWindow::TYPE_POPUP); |
| } |
| |
| bool SessionService::ShouldRestore(Browser* browser) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Do not restore browser window in the kiosk session. |
| if (chromeos::IsKioskSession()) { |
| return false; |
| } |
| #endif |
| |
| // ChromeOS and OSX have different ideas of application lifetime than |
| // the other platforms. |
| // On ChromeOS opening a new window should never start a new session. |
| #if BUILDFLAG(IS_CHROMEOS) |
| // On Chrome OS, sessions are restored (or not) based on the startup setting. |
| |
| // If there are other browser windows, or during the restoring process, or |
| // restore from crash, or should not restore for `browser`, sessions should |
| // not be restored. |
| if (SessionRestore::IsRestoring(profile()) || has_open_trackable_browsers_ || |
| HasPendingUncleanExit(profile()) || |
| (browser && !browser->should_trigger_session_restore())) { |
| return false; |
| } |
| |
| // If the on startup setting is not restore, sessions should not be |
| // restored. |
| SessionStartupPref pref = |
| SessionStartupPref::GetStartupPref(profile()->GetPrefs()); |
| if (!pref.ShouldRestoreLastSession()) { |
| return false; |
| } |
| |
| if (!browser) |
| return true; |
| |
| // App windows should not be restored. |
| auto window_type = WindowTypeForBrowserType(browser->type()); |
| if (window_type == sessions::SessionWindow::TYPE_APP || |
| window_type == sessions::SessionWindow::TYPE_APP_POPUP) { |
| return false; |
| } |
| |
| // If the browser does not have a `restore_id`, then we restore the session. |
| return browser->create_params().restore_id == Browser::kDefaultRestoreId; |
| #else |
| if (!has_open_trackable_browsers_ && |
| !StartupBrowserCreator::InSynchronousProfileLaunch() && |
| !SessionRestore::IsRestoring(profile()) |
| #if BUILDFLAG(IS_MAC) |
| // On OSX, a new window should not start a new session if it was opened |
| // from the dock or the menubar. |
| && !app_controller_mac::IsOpeningNewWindow() |
| #endif // BUILDFLAG(IS_MAC) |
| ) { |
| return true; |
| } |
| return false; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| bool SessionService::RestoreIfNecessary(const StartupTabs& startup_tabs, |
| bool restore_apps) { |
| return RestoreIfNecessary(startup_tabs, nullptr, restore_apps); |
| } |
| |
| void SessionService::MoveCurrentSessionToLastSession() { |
| DCHECK(is_saving_enabled()); |
| pending_tab_close_ids_.clear(); |
| window_closing_ids_.clear(); |
| pending_window_close_ids_.clear(); |
| |
| command_storage_manager()->MoveCurrentSessionToLastSession(); |
| ScheduleResetCommands(); |
| } |
| |
| void SessionService::DeleteLastSession() { |
| command_storage_manager()->DeleteLastSession(); |
| ++count_delete_last_session_for_testing_; |
| } |
| |
| void SessionService::SetSplitTab(SessionID window_id, |
| SessionID tab_id, |
| std::optional<split_tabs::SplitTabId> split) { |
| if (!ShouldTrackChangesToWindow(window_id) || |
| !features::IsRestoringSplitViewEnabled()) { |
| return; |
| } |
| |
| // Tabs get unsplit as they close. However, if the whole window is closing |
| // tabs should stay in their split. So, ignore this call in that case. |
| if (base::Contains(pending_window_close_ids_, window_id) || |
| base::Contains(window_closing_ids_, window_id)) { |
| return; |
| } |
| |
| ScheduleCommand(sessions::CreateSplitTabCommand(tab_id, std::move(split))); |
| } |
| |
| void SessionService::SetSplitTabData( |
| SessionID window_id, |
| const split_tabs::SplitTabId split, |
| const split_tabs::SplitTabVisualData* visual_data) { |
| if (!ShouldTrackChangesToWindow(window_id) || |
| !features::IsRestoringSplitViewEnabled()) { |
| return; |
| } |
| |
| // Any split metadata changes happening in a closing window can be ignored. |
| if (base::Contains(pending_window_close_ids_, window_id) || |
| base::Contains(window_closing_ids_, window_id)) { |
| return; |
| } |
| |
| ScheduleCommand( |
| sessions::CreateSplitTabDataUpdateCommand(split, visual_data)); |
| } |
| |
| void SessionService::SetTabGroup(SessionID window_id, |
| SessionID tab_id, |
| std::optional<tab_groups::TabGroupId> group) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| // Tabs get ungrouped as they close. However, if the whole window is closing |
| // tabs should stay in their groups. So, ignore this call in that case. |
| if (base::Contains(pending_window_close_ids_, window_id) || |
| base::Contains(window_closing_ids_, window_id)) |
| return; |
| |
| ScheduleCommand(sessions::CreateTabGroupCommand(tab_id, std::move(group))); |
| } |
| |
| void SessionService::SetTabGroupMetadata( |
| SessionID window_id, |
| const tab_groups::TabGroupId& group_id, |
| const tab_groups::TabGroupVisualData* visual_data) { |
| tab_groups::TabGroupSyncService* tab_group_service = |
| tab_groups::TabGroupSyncServiceFactory::GetForProfile(profile()); |
| std::optional<std::string> saved_guid; |
| if (tab_group_service) { |
| if (const std::optional<tab_groups::SavedTabGroup> saved_group = |
| tab_group_service->GetGroup(group_id)) { |
| saved_guid = saved_group->saved_guid().AsLowercaseString(); |
| } else if (local_to_sync_id_mapping_.contains(group_id)) { |
| saved_guid = local_to_sync_id_mapping_.at(group_id); |
| } |
| } |
| |
| SetTabGroupMetadata(window_id, group_id, visual_data, std::move(saved_guid)); |
| } |
| |
| void SessionService::SetTabGroupMetadata( |
| SessionID window_id, |
| const tab_groups::TabGroupId& group_id, |
| const tab_groups::TabGroupVisualData* visual_data, |
| std::optional<std::string> saved_guid) { |
| if (!ShouldTrackChangesToWindow(window_id)) { |
| return; |
| } |
| |
| // Any group metadata changes happening in a closing window can be ignored. |
| if (base::Contains(pending_window_close_ids_, window_id) || |
| base::Contains(window_closing_ids_, window_id)) { |
| return; |
| } |
| |
| ScheduleCommand(sessions::CreateTabGroupMetadataUpdateCommand( |
| group_id, visual_data, std::move(saved_guid))); |
| } |
| |
| void SessionService::AddSavedTabGroupsMapping( |
| const tab_groups::TabGroupId& group_id, |
| const std::string& saved_guid) { |
| // TODO(crbug.com/376733439): Clear mapping when TabGroupSyncService has |
| // finished initializing. |
| CHECK(!local_to_sync_id_mapping_.contains(group_id)); |
| local_to_sync_id_mapping_.emplace(group_id, saved_guid); |
| } |
| |
| void SessionService::AddTabExtraData(SessionID window_id, |
| SessionID tab_id, |
| const char* key, |
| const std::string& data) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(sessions::CreateAddTabExtraDataCommand(tab_id, key, data)); |
| } |
| |
| void SessionService::AddWindowExtraData(SessionID window_id, |
| const char* key, |
| const std::string& data) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand( |
| sessions::CreateAddWindowExtraDataCommand(window_id, key, data)); |
| } |
| |
| void SessionService::TabClosed(SessionID window_id, SessionID tab_id) { |
| if (!tab_id.id()) |
| return; // Happens when the tab is replaced. |
| |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| auto i = tab_to_available_range()->find(tab_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) != pending_window_close_ids_.end()) { |
| // Tab is in last window and the window is being closed. 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. |
| // This is necessary to ensure the session is properly stored when closing |
| // the final window. |
| pending_tab_close_ids_.insert(tab_id); |
| } else { |
| // If an individual tab is being closed or a secondary window is being |
| // closed, just mark the tab as closed now. |
| ScheduleCommand(sessions::CreateTabClosedCommand(tab_id)); |
| if ((find(window_closing_ids_.begin(), window_closing_ids_.end(), |
| window_id) == window_closing_ids_.end()) && |
| IsOnlyOneTabLeft()) { |
| // This is the last tab in the last tabbed browser. |
| has_open_trackable_browsers_ = false; |
| } |
| } |
| } |
| |
| void SessionService::WindowOpened(Browser* browser) { |
| if (!ShouldTrackBrowser(browser)) |
| return; |
| |
| RestoreIfNecessary(StartupTabs(), browser, /* restore_apps */ false); |
| SetWindowType(browser->session_id(), browser->type()); |
| SetWindowAppName(browser->session_id(), browser->app_name()); |
| SetWindowUserTitle(browser->session_id(), browser->user_title()); |
| |
| // Save a browser workspace after window is created in `Browser()`. |
| // Bento desks restore feature in ash requires this line to restore correctly |
| // after creating a new browser window in a particular desk. |
| SetWindowWorkspace(browser->session_id(), browser->window()->GetWorkspace()); |
| SetWindowVisibleOnAllWorkspaces( |
| browser->session_id(), browser->window()->IsVisibleOnAllWorkspaces()); |
| } |
| |
| void SessionService::WindowClosing(SessionID window_id) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| // If Chrome is closed immediately after a history deletion, we have to |
| // rebuild commands before this window is closed, otherwise these tabs would |
| // be lost. |
| RebuildCommandsIfRequired(); |
| |
| // 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); |
| } |
| bool use_pending_close = !has_open_trackable_browsers_; |
| if (!use_pending_close) { |
| // Somewhat outside of "normal behavior" is profile locking. In this case |
| // (when IsSiginRequired has already been set True), we're closing all |
| // browser windows in turn but want them all to be restored when the user |
| // unlocks. To accomplish this, we do a "pending close" on all windows |
| // instead of just the last one (which has no open_trackable_browsers). |
| // http://crbug.com/356818 |
| // |
| // Some editions (like iOS) don't have a profile_manager and some tests |
| // don't supply one so be lenient. |
| if (g_browser_process) { |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| if (profile_manager) { |
| ProfileAttributesEntry* entry = |
| profile_manager->GetProfileAttributesStorage() |
| .GetProfileAttributesWithPath(profile()->GetPath()); |
| use_pending_close = entry && entry->IsSigninRequired(); |
| } |
| } |
| } |
| if (use_pending_close) { |
| LogExitEvent(); |
| pending_window_close_ids_.insert(window_id); |
| } else { |
| window_closing_ids_.insert(window_id); |
| } |
| } |
| |
| void SessionService::WindowClosed(SessionID window_id) { |
| windows_tracking()->erase(window_id); |
| last_selected_tab_in_window()->erase(window_id); |
| |
| if (window_closing_ids_.find(window_id) != window_closing_ids_.end()) { |
| window_closing_ids_.erase(window_id); |
| ScheduleCommand(sessions::CreateWindowClosedCommand(window_id)); |
| } else if (pending_window_close_ids_.find(window_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 (!has_open_trackable_browsers_) { |
| LogExitEvent(); |
| pending_window_close_ids_.insert(window_id); |
| } else { |
| ScheduleCommand(sessions::CreateWindowClosedCommand(window_id)); |
| } |
| } |
| } |
| |
| void SessionService::SetWindowType(SessionID window_id, Browser::Type type) { |
| sessions::SessionWindow::WindowType window_type = |
| WindowTypeForBrowserType(type); |
| if (!ShouldRestoreWindowOfType(window_type)) |
| return; |
| |
| windows_tracking()->insert(window_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, window_type)); |
| } |
| |
| void SessionService::SetWindowUserTitle(SessionID window_id, |
| const std::string& user_title) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand( |
| sessions::CreateSetWindowUserTitleCommand(window_id, user_title)); |
| } |
| |
| void SessionService::OnErrorWritingSessionCommands() { |
| // TODO(sky): if `pending_window_close_ids_` is non-empty, then |
| // RebuildCommandsIfRequired() will not call ScheduleResetCommands(). This is |
| // because a rebuild can't happen (because the browsers in |
| // `pending_window_close_ids_` have been deleted). My hope is that this |
| // happens seldom enough that we don't need to deal with this case, as it will |
| // necessitate some amount of snapshotting in memory when a window is closing. |
| // The histogram should give us an idea of how often this happens in practice. |
| const bool unrecoverable_write_error = !pending_window_close_ids_.empty(); |
| if (unrecoverable_write_error) |
| ++unrecoverable_write_error_count_; |
| LogSessionServiceWriteErrorEvent(profile(), unrecoverable_write_error); |
| set_rebuild_on_next_save(true); |
| RebuildCommandsIfRequired(); |
| } |
| |
| void SessionService::SetTabUserAgentOverride( |
| SessionID window_id, |
| SessionID tab_id, |
| const sessions::SerializedUserAgentOverride& user_agent_override) { |
| if (!ShouldTrackChangesToWindow(window_id)) |
| return; |
| |
| ScheduleCommand(sessions::CreateSetTabUserAgentOverrideCommand( |
| tab_id, user_agent_override)); |
| } |
| |
| Browser::Type SessionService::GetDesiredBrowserTypeForWebContents() { |
| return Browser::Type::TYPE_NORMAL; |
| } |
| |
| void SessionService::DidScheduleCommand() { |
| if (did_schedule_command_) |
| return; |
| did_schedule_command_ = true; |
| if (is_first_session_service_) |
| return; |
| |
| // TODO(crbug.com/40196304): for debugging, remove once tracked down |
| // source of problem. |
| // A command has been scheduled for a SessionService other than the first. |
| // Recreating the SessionService happens if shutdown is canceled, which is |
| // valid, but bugs seem to indicate we are getting here in scenarios we don't |
| // expect. This debug code is attempting to identify how that is happening. |
| const bool shutdown_started = browser_shutdown::HasShutdownStarted(); |
| base::debug::Alias(&shutdown_started); |
| base::debug::DumpWithoutCrashing(); |
| } |
| |
| bool SessionService::ShouldRestoreWindowOfType( |
| sessions::SessionWindow::WindowType window_type) const { |
| // TYPE_APP and TYPE_APP_POPUP are handled by app_session_service. |
| return IsRelevantWindowType(window_type); |
| } |
| |
| bool SessionService::RestoreIfNecessary(const StartupTabs& startup_tabs, |
| Browser* browser, |
| bool restore_apps) { |
| if (ShouldRestore(browser)) { |
| // 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_ && is_saving_enabled()) { |
| // Make the current session the last. |
| MoveCurrentSessionToLastSession(); |
| move_on_new_browser_ = false; |
| } |
| SessionStartupPref pref = StartupBrowserCreator::GetSessionStartupPref( |
| *base::CommandLine::ForCurrentProcess(), profile()); |
| sessions::TabRestoreService* tab_restore_service = |
| TabRestoreServiceFactory::GetForProfileIfExisting(profile()); |
| if (pref.ShouldRestoreLastSession() && |
| (!tab_restore_service || !tab_restore_service->IsRestoring())) { |
| SessionRestore::RestoreSession( |
| profile(), browser, |
| SessionRestore::RESTORE_BROWSER | |
| (browser ? 0 : SessionRestore::ALWAYS_CREATE_TABBED_BROWSER) | |
| (restore_apps ? SessionRestore::RESTORE_APPS : 0), |
| startup_tabs); |
| return true; |
| } |
| #if BUILDFLAG(IS_CHROMEOS) |
| } else if (HasPendingUncleanExit(profile())) { |
| if (!browser) { |
| // If 'browser' is null, call StartupBrowserCreator to create a new |
| // browser instance. |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| StartupBrowserCreator browser_creator; |
| browser_creator.LaunchBrowser(*command_line, profile(), base::FilePath(), |
| chrome::startup::IsProcessStartup::kYes, |
| chrome::startup::IsFirstRun::kNo, |
| /*restore_tabbed_browser=*/true); |
| return true; |
| } else { |
| // If 'browser' is not null, show the crash bubble in the current browser |
| // instance. |
| SessionCrashedBubble::ShowIfNotOffTheRecordProfile( |
| browser, /*skip_tab_checking=*/true); |
| AddLaunchedProfile(profile()); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| return false; |
| } |
| |
| void SessionService::BuildCommandsForTab( |
| SessionID window_id, |
| WebContents* tab, |
| int index_in_window, |
| std::optional<tab_groups::TabGroupId> group, |
| std::optional<split_tabs::SplitTabId> split, |
| bool is_pinned, |
| IdToRange* tab_to_available_range) { |
| DCHECK(is_saving_enabled()); |
| SessionServiceBase::BuildCommandsForTab(window_id, tab, index_in_window, |
| group, split, is_pinned, |
| tab_to_available_range); |
| |
| sessions::SessionTabHelper* session_tab_helper = |
| sessions::SessionTabHelper::FromWebContents(tab); |
| SessionID session_id(session_tab_helper->session_id()); |
| |
| const blink::UserAgentOverride& ua_override = tab->GetUserAgentOverride(); |
| |
| if (!ua_override.ua_string_override.empty()) { |
| sessions::SerializedUserAgentOverride serialized_ua_override; |
| serialized_ua_override.ua_string_override = ua_override.ua_string_override; |
| serialized_ua_override.opaque_ua_metadata_override = |
| blink::UserAgentMetadata::Marshal(ua_override.ua_metadata_override); |
| |
| command_storage_manager()->AppendRebuildCommand( |
| sessions::CreateSetTabUserAgentOverrideCommand(session_id, |
| serialized_ua_override)); |
| } |
| |
| if (group.has_value()) { |
| command_storage_manager()->AppendRebuildCommand( |
| sessions::CreateTabGroupCommand(session_id, std::move(group))); |
| } |
| |
| if (features::IsRestoringSplitViewEnabled() && split.has_value()) { |
| command_storage_manager()->AppendRebuildCommand( |
| sessions::CreateSplitTabCommand(session_id, std::move(split))); |
| } |
| } |
| |
| void SessionService::ScheduleResetCommands() { |
| DCHECK(is_saving_enabled()); |
| command_storage_manager()->set_pending_reset(true); |
| command_storage_manager()->ClearPendingCommands(); |
| tab_to_available_range()->clear(); |
| windows_tracking()->clear(); |
| last_selected_tab_in_window()->clear(); |
| set_rebuild_on_next_save(false); |
| BuildCommandsFromBrowsers(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; |
| } |
| command_storage_manager()->StartSaveTimer(); |
| } |
| |
| void SessionService::CommitPendingCloses() { |
| for (auto i = pending_tab_close_ids_.begin(); |
| i != pending_tab_close_ids_.end(); ++i) { |
| ScheduleCommand(sessions::CreateTabClosedCommand(*i)); |
| } |
| pending_tab_close_ids_.clear(); |
| |
| for (auto i = pending_window_close_ids_.begin(); |
| i != pending_window_close_ids_.end(); ++i) { |
| ScheduleCommand(sessions::CreateWindowClosedCommand(*i)); |
| } |
| pending_window_close_ids_.clear(); |
| |
| RemoveExitEvent(); |
| } |
| |
| bool SessionService::IsOnlyOneTabLeft() const { |
| if (profile()->AsTestingProfile()) |
| return is_only_one_tab_left_for_test_; |
| |
| int window_count = 0; |
| for (Browser* browser : *BrowserList::GetInstance()) { |
| const SessionID window_id = browser->session_id(); |
| if (ShouldTrackBrowser(browser) && |
| 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 (browser->tab_strip_model()->count() > 0) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SessionService::HasOpenTrackableBrowsers(SessionID window_id) const { |
| if (profile()->AsTestingProfile()) |
| return has_open_trackable_browser_for_test_; |
| |
| for (Browser* browser : *BrowserList::GetInstance()) { |
| const SessionID browser_id = browser->session_id(); |
| if (browser_id != window_id && |
| window_closing_ids_.find(browser_id) == window_closing_ids_.end() && |
| ShouldTrackBrowser(browser)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void SessionService::RebuildCommandsIfRequired() { |
| if (rebuild_on_next_save() && pending_window_close_ids_.empty() && |
| is_saving_enabled()) { |
| ScheduleResetCommands(); |
| } |
| } |
| |
| void SessionService::OnClosingAllBrowsersChanged(bool closing) { |
| if (closing) |
| LogExitEvent(); |
| } |
| |
| void SessionService::LogExitEvent() { |
| // If there are pending closes, then we have already logged the exit. |
| if (!pending_window_close_ids_.empty()) |
| return; |
| |
| RemoveExitEvent(); |
| int browser_count = 0; |
| int tab_count = 0; |
| for (Browser* browser : *BrowserList::GetInstance()) { |
| if (browser->profile() == profile()) { |
| ++browser_count; |
| tab_count += browser->tab_strip_model()->count(); |
| } |
| } |
| did_log_exit_ = true; |
| LogSessionServiceExitEvent(profile(), browser_count, tab_count, |
| is_first_session_service_, did_schedule_command_); |
| } |
| |
| void SessionService::RemoveExitEvent() { |
| if (!did_log_exit_) |
| return; |
| |
| RemoveLastSessionServiceEventOfType(profile(), |
| SessionServiceEventLogType::kExit); |
| did_log_exit_ = false; |
| } |