blob: 5411c546b1f0ae063d72f53d0347bcad700b77f7 [file] [log] [blame]
// Copyright 2021 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_base.h"
#include <stddef.h>
#include <set>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/to_string.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/web_contents_app_id_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.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_window.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/ui_features.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_id.h"
#include "components/sessions/core/session_types.h"
#include "components/tabs/public/split_tab_data.h"
#include "components/tabs/public/split_tab_id.h"
#include "components/tabs/public/split_tab_visual_data.h"
#include "components/tabs/public/tab_group.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/session_storage_namespace.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/app_controller_mac.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/platform_session_manager.h"
#endif
using base::Time;
using content::NavigationEntry;
using content::WebContents;
using sessions::ContentSerializedNavigationBuilder;
using sessions::SerializedNavigationEntry;
namespace {
// Every kWritesPerReset commands triggers recreating the file.
const int kWritesPerReset = 250;
// User data key for WebContents to derive their types.
const void* const kSessionServiceBaseUserDataKey =
&kSessionServiceBaseUserDataKey;
// User data key for BrowserContextData.
const void* const kProfileTaskRunnerKey = &kProfileTaskRunnerKey;
const void* const kProfileTaskRunnerKeyForApps = &kProfileTaskRunnerKeyForApps;
// Tracks the SequencedTaskRunner that SessionService uses for a particular
// profile. At certain points SessionService may be destroyed, and then
// recreated. This class ensures that when this happens, the same
// SequencedTaskRunner is used. Without this, each instance would have its
// own SequencedTaskRunner, which is problematic as it might then be possible
// for the newly created SessionService to attempt to read from a file the
// previous SessionService was still writing to. No two instances of
// SessionService for a particular profile and type combination can exist at the
// same time, but the backend (CommandStorageBackend) is destroyed on the
// SequencedTaskRunner, meaning without this, it might be possible for two
// CommandStorageBackends to exist at the same time and attempt to use the same
// file.
class TaskRunnerData : public base::SupportsUserData::Data {
public:
TaskRunnerData()
: task_runner_(
sessions::CommandStorageManager::CreateDefaultBackendTaskRunner()) {
}
TaskRunnerData(const TaskRunnerData&) = delete;
TaskRunnerData& operator=(const TaskRunnerData&) = delete;
~TaskRunnerData() override = default;
static scoped_refptr<base::SequencedTaskRunner>
GetBackendTaskRunnerForProfile(Profile* profile,
SessionServiceBase::SessionServiceType type) {
const void* key =
type == SessionServiceBase::SessionServiceType::kAppRestore
? kProfileTaskRunnerKeyForApps
: kProfileTaskRunnerKey;
TaskRunnerData* data =
static_cast<TaskRunnerData*>(profile->GetUserData(key));
if (!data) {
profile->SetUserData(key, std::make_unique<TaskRunnerData>());
data = static_cast<TaskRunnerData*>(profile->GetUserData(key));
}
return data->task_runner_;
}
private:
scoped_refptr<base::SequencedTaskRunner> task_runner_;
};
// SessionServiceBaseUserData
// -------------------------------------------------------------
class SessionServiceBaseUserData : public base::SupportsUserData::Data {
public:
explicit SessionServiceBaseUserData(Browser::Type type) : type_(type) {}
~SessionServiceBaseUserData() override = default;
SessionServiceBaseUserData(const SessionServiceBaseUserData&) = delete;
SessionServiceBaseUserData& operator=(const SessionServiceBaseUserData&) =
delete;
Browser::Type type() const { return type_; }
private:
const Browser::Type type_;
};
} // namespace
// SessionServiceBase
// -------------------------------------------------------------
// Note: This is protected as it is only intended to be called by child classes
SessionServiceBase::SessionServiceBase(Profile* profile,
SessionServiceType type)
: profile_(profile) {
// Covert SessionServiceType to backend CSM::SessionType enum.
sessions::CommandStorageManager::SessionType backend_type =
type == SessionServiceType::kSessionRestore
? sessions::CommandStorageManager::kSessionRestore
: sessions::CommandStorageManager::kAppRestore;
command_storage_manager_ = std::make_unique<sessions::CommandStorageManager>(
backend_type, profile->GetPath(), this,
/* enable_crypto */ false, std::vector<uint8_t>(),
TaskRunnerData::GetBackendTaskRunnerForProfile(profile, type));
// We should never be created when incognito.
DCHECK(!profile->IsOffTheRecord());
BrowserList::AddObserver(this);
}
SessionServiceBase::~SessionServiceBase() {
// The BrowserList should outlive the SessionService since it's static and
// the SessionService is a KeyedService.
BrowserList::RemoveObserver(this);
// command_storage_manager_->Save() should be called by child classes which
// should have destructed the command_storage_manager.
DCHECK(command_storage_manager_ == nullptr);
}
// static
Browser::Type SessionServiceBase::GetBrowserTypeFromWebContents(
content::WebContents* web_contents) {
SessionServiceBaseUserData* data = static_cast<SessionServiceBaseUserData*>(
web_contents->GetUserData(&kSessionServiceBaseUserDataKey));
// Browser tab WebContents will have the UserData set on them. However, it is
// possible that WebContents that are not tabs call into this code.
// In that case, data will be null and we just return TYPE_NORMAL.
if (!data)
return Browser::Type::TYPE_NORMAL;
return data->type();
}
void SessionServiceBase::SetWindowVisibleOnAllWorkspaces(
SessionID window_id,
bool visible_on_all_workspaces) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(sessions::CreateSetWindowVisibleOnAllWorkspacesCommand(
window_id, visible_on_all_workspaces));
}
void SessionServiceBase::ResetFromCurrentBrowsers() {
if (is_saving_enabled_)
ScheduleResetCommands();
}
void SessionServiceBase::SetTabWindow(SessionID window_id, SessionID tab_id) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(sessions::CreateSetTabWindowCommand(window_id, tab_id));
}
void SessionServiceBase::SetWindowBounds(
SessionID window_id,
const gfx::Rect& bounds,
ui::mojom::WindowShowState show_state) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateSetWindowBoundsCommand(window_id, bounds, show_state));
}
void SessionServiceBase::SetWindowWorkspace(SessionID window_id,
const std::string& workspace) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateSetWindowWorkspaceCommand(window_id, workspace));
}
void SessionServiceBase::SetTabIndexInWindow(SessionID window_id,
SessionID tab_id,
int new_index) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateSetTabIndexInWindowCommand(tab_id, new_index));
}
void SessionServiceBase::TabInserted(WebContents* contents) {
sessions::SessionTabHelper* session_tab_helper =
sessions::SessionTabHelper::FromWebContents(contents);
if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
return;
SetTabWindow(session_tab_helper->window_id(),
session_tab_helper->session_id());
std::string app_id = apps::GetAppIdForWebContents(contents);
if (!app_id.empty()) {
SetTabExtensionAppID(session_tab_helper->window_id(),
session_tab_helper->session_id(), app_id);
}
// Record the association between the SessionStorageNamespace and the
// tab.
//
// TODO(ajwong): This should be processing the whole map rather than
// just the default. This in particular will not work for tabs with only
// isolated apps which won't have a default partition.
content::SessionStorageNamespace* session_storage_namespace =
contents->GetController().GetDefaultSessionStorageNamespace();
ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
session_tab_helper->session_id(), session_storage_namespace->id()));
session_storage_namespace->SetShouldPersist(true);
// The tab being inserted is most likely already registered by
// Browser::WebContentsCreated, but just register the UserData again.
contents->SetUserData(&kSessionServiceBaseUserDataKey,
std::make_unique<SessionServiceBaseUserData>(
GetDesiredBrowserTypeForWebContents()));
}
void SessionServiceBase::TabClosing(WebContents* contents) {
// Allow the associated sessionStorage to get deleted; it won't be needed
// in the session restore.
content::SessionStorageNamespace* session_storage_namespace =
contents->GetController().GetDefaultSessionStorageNamespace();
session_storage_namespace->SetShouldPersist(false);
sessions::SessionTabHelper* session_tab_helper =
sessions::SessionTabHelper::FromWebContents(contents);
TabClosed(session_tab_helper->window_id(), session_tab_helper->session_id());
}
void SessionServiceBase::TabRestored(WebContents* tab, bool pinned) {
if (!is_saving_enabled_)
return;
sessions::SessionTabHelper* session_tab_helper =
sessions::SessionTabHelper::FromWebContents(tab);
if (!ShouldTrackChangesToWindow(session_tab_helper->window_id()))
return;
BuildCommandsForTab(session_tab_helper->window_id(), tab, -1, std::nullopt,
std::nullopt, pinned, nullptr);
command_storage_manager()->StartSaveTimer();
}
void SessionServiceBase::SetSelectedTabInWindow(SessionID window_id,
int index) {
if (!ShouldTrackChangesToWindow(window_id))
return;
auto it = last_selected_tab_in_window_.find(window_id);
if (it != last_selected_tab_in_window_.end() && it->second == index)
return;
last_selected_tab_in_window_[window_id] = index;
ScheduleCommand(
sessions::CreateSetSelectedTabInWindowCommand(window_id, index));
}
void SessionServiceBase::SetTabExtensionAppID(
SessionID window_id,
SessionID tab_id,
const std::string& extension_app_id) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateSetTabExtensionAppIDCommand(tab_id, extension_app_id));
}
void SessionServiceBase::SetLastActiveTime(SessionID window_id,
SessionID tab_id,
base::Time last_active_time) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateLastActiveTimeCommand(tab_id, last_active_time));
}
void SessionServiceBase::GetLastSession(
sessions::GetLastSessionCallback callback) {
// OnGotSessionCommands maps the SessionCommands to browser state, then run
// the callback.
return command_storage_manager_->GetLastSessionCommands(
base::BindOnce(&SessionServiceBase::OnGotSessionCommands,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void SessionServiceBase::SetWindowAppName(SessionID window_id,
const std::string& app_name) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(sessions::CreateSetWindowAppNameCommand(window_id, app_name));
}
void SessionServiceBase::SetPinnedState(SessionID window_id,
SessionID tab_id,
bool is_pinned) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(sessions::CreatePinnedStateCommand(tab_id, is_pinned));
}
bool SessionServiceBase::ShouldUseDelayedSave() {
return should_use_delayed_save_;
}
void SessionServiceBase::OnWillSaveCommands() {
if (!is_saving_enabled_) {
// There should be no commands scheduled, otherwise data will be written,
// potentially clobbering the last file.
DCHECK(command_storage_manager_->pending_commands().empty());
return;
}
RebuildCommandsIfRequired();
did_save_commands_at_least_once_ |=
!command_storage_manager()->pending_commands().empty();
}
void SessionServiceBase::OnErrorWritingSessionCommands() {
LogSessionServiceWriteErrorEvent(profile_, false);
rebuild_on_next_save_ = true;
RebuildCommandsIfRequired();
}
void SessionServiceBase::SetTabUserAgentOverride(
SessionID window_id,
SessionID tab_id,
const sessions::SerializedUserAgentOverride& user_agent_override) {
// This is overridden by session_service implementation.
// We still need it here because we derive from
// sessions::SessionTabHelperDelegate.
NOTREACHED();
}
void SessionServiceBase::SetSelectedNavigationIndex(SessionID window_id,
SessionID tab_id,
int index) {
if (!ShouldTrackChangesToWindow(window_id))
return;
if (tab_to_available_range_.find(tab_id) != tab_to_available_range_.end()) {
if (index < tab_to_available_range_[tab_id].first ||
index > tab_to_available_range_[tab_id].second) {
// The new index is outside the range of what we've archived, schedule
// a reset.
ResetFromCurrentBrowsers();
return;
}
}
ScheduleCommand(
sessions::CreateSetSelectedNavigationIndexCommand(tab_id, index));
}
void SessionServiceBase::UpdateTabNavigation(
SessionID window_id,
SessionID tab_id,
const SerializedNavigationEntry& navigation) {
if (!ShouldTrackURLForRestore(navigation.virtual_url()) ||
!ShouldTrackChangesToWindow(window_id)) {
return;
}
if (tab_to_available_range_.find(tab_id) != tab_to_available_range_.end()) {
std::pair<int, int>& range = tab_to_available_range_[tab_id];
range.first = std::min(navigation.index(), range.first);
range.second = std::max(navigation.index(), range.second);
}
ScheduleCommand(CreateUpdateTabNavigationCommand(tab_id, navigation));
}
void SessionServiceBase::TabNavigationPathPruned(SessionID window_id,
SessionID tab_id,
int index,
int count) {
if (!ShouldTrackChangesToWindow(window_id))
return;
DCHECK_GE(index, 0);
DCHECK_GT(count, 0);
// Update the range of available indices.
if (tab_to_available_range_.find(tab_id) != tab_to_available_range_.end()) {
std::pair<int, int>& range = tab_to_available_range_[tab_id];
// if both range.first and range.second are also deleted.
if (range.second >= index && range.second < index + count &&
range.first >= index && range.first < index + count) {
range.first = range.second = 0;
} else {
// Update range.first
if (range.first >= index + count)
range.first = range.first - count;
else if (range.first >= index && range.first < index + count)
range.first = index;
// Update range.second
if (range.second >= index + count)
range.second = std::max(range.first, range.second - count);
else if (range.second >= index && range.second < index + count)
range.second = std::max(range.first, index - 1);
}
}
return ScheduleCommand(
sessions::CreateTabNavigationPathPrunedCommand(tab_id, index, count));
}
void SessionServiceBase::TabNavigationPathEntriesDeleted(SessionID window_id,
SessionID tab_id) {
if (!ShouldTrackChangesToWindow(window_id))
return;
// Multiple tabs might be affected by this deletion, so the rebuild is
// delayed until next save.
rebuild_on_next_save_ = true;
command_storage_manager_->StartSaveTimer();
}
#if DCHECK_IS_ON()
base::Value SessionServiceBase::ToDebugValue() const {
base::Value::Dict result;
result.Set("profile", base::ToString(profile_.get()));
if (command_storage_manager_) {
result.Set("command_storage_manager",
command_storage_manager_->ToDebugValue());
}
for (const auto& [id, range] : tab_to_available_range_) {
base::Value::Dict* range_dict =
result.EnsureDict("tab_to_available_range")
->EnsureDict(base::NumberToString(id.id()));
range_dict->Set("first", range.first);
range_dict->Set("second", range.first);
}
result.Set("rebuild_on_next_save", rebuild_on_next_save_);
for (const auto& [id, tab_index] : last_selected_tab_in_window_) {
result.EnsureDict("last_selected_tab_in_window")
->Set(base::NumberToString(id.id()), tab_index);
}
for (const SessionID& window_tracking : windows_tracking_) {
result.EnsureList("windows_tracking")
->Append(base::NumberToString(window_tracking.id()));
}
result.Set("is_saving_enabled", is_saving_enabled_);
result.Set("did_save_commands_at_least_once",
did_save_commands_at_least_once_);
return base::Value(std::move(result));
}
#endif // DCHECK_IS_ON()
void SessionServiceBase::DestroyCommandStorageManager() {
command_storage_manager_.reset(nullptr);
}
void SessionServiceBase::RemoveUnusedRestoreWindows(
std::vector<std::unique_ptr<sessions::SessionWindow>>* window_list) {
auto i = window_list->begin();
while (i != window_list->end()) {
sessions::SessionWindow* window = i->get();
if (!ShouldRestoreWindowOfType(window->type)) {
i = window_list->erase(i);
} else {
++i;
}
}
}
void SessionServiceBase::OnBrowserSetLastActive(Browser* browser) {
if (ShouldTrackBrowser(browser))
ScheduleCommand(
sessions::CreateSetActiveWindowCommand(browser->session_id()));
}
void SessionServiceBase::OnGotSessionCommands(
sessions::GetLastSessionCallback callback,
std::vector<std::unique_ptr<sessions::SessionCommand>> commands,
bool read_error) {
std::vector<std::unique_ptr<sessions::SessionWindow>> valid_windows;
SessionID active_window_id = SessionID::InvalidValue();
std::string platform_session_id;
std::set<SessionID> discarded_window_ids;
sessions::RestoreSessionFromCommands(commands, &valid_windows,
&active_window_id, &platform_session_id,
&discarded_window_ids);
RemoveUnusedRestoreWindows(&valid_windows);
InitializePlatformSessionIfNeeded(platform_session_id, discarded_window_ids);
std::move(callback).Run(std::move(valid_windows), active_window_id,
read_error);
}
void SessionServiceBase::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(tab);
DCHECK(window_id.is_valid());
sessions::SessionTabHelper* session_tab_helper =
sessions::SessionTabHelper::FromWebContents(tab);
const SessionID session_id(session_tab_helper->session_id());
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetTabWindowCommand(window_id, session_id));
const int current_index = tab->GetController().GetCurrentEntryIndex();
const int min_index =
std::max(current_index - sessions::gMaxPersistNavigationCount, 0);
const int max_index =
std::min(current_index + sessions::gMaxPersistNavigationCount,
tab->GetController().GetEntryCount());
const int pending_index = tab->GetController().GetPendingEntryIndex();
if (tab_to_available_range) {
(*tab_to_available_range)[session_id] =
std::pair<int, int>(min_index, max_index);
}
command_storage_manager()->AppendRebuildCommand(
sessions::CreateLastActiveTimeCommand(session_id,
tab->GetLastActiveTime()));
// TODO(stahon@microsoft.com) This might be movable to SessionService
// when Chrome OS uses AppSessionService for app restores.
// For now it needs to stay to support SessionService restoring apps.
std::string app_id = apps::GetAppIdForWebContents(tab);
if (!app_id.empty()) {
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetTabExtensionAppIDCommand(session_id, app_id));
}
for (int i = min_index; i < max_index; ++i) {
NavigationEntry* entry = (i == pending_index)
? tab->GetController().GetPendingEntry()
: tab->GetController().GetEntryAtIndex(i);
DCHECK(entry);
if (ShouldTrackURLForRestore(entry->GetVirtualURL()) &&
!entry->IsInitialEntry()) {
// Don't try to persist initial NavigationEntry, as it is not actually
// associated with any navigation and will just result in about:blank on
// session restore.
const SerializedNavigationEntry navigation =
ContentSerializedNavigationBuilder::FromNavigationEntry(i, entry);
command_storage_manager()->AppendRebuildCommand(
CreateUpdateTabNavigationCommand(session_id, navigation));
}
}
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetSelectedNavigationIndexCommand(session_id,
current_index));
if (index_in_window != -1) {
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetTabIndexInWindowCommand(session_id,
index_in_window));
}
if (is_pinned) {
command_storage_manager()->AppendRebuildCommand(
sessions::CreatePinnedStateCommand(session_id, true));
}
// Record the association between the sessionStorage namespace and the tab.
content::SessionStorageNamespace* session_storage_namespace =
tab->GetController().GetDefaultSessionStorageNamespace();
ScheduleCommand(sessions::CreateSessionStorageAssociatedCommand(
session_tab_helper->session_id(), session_storage_namespace->id()));
}
void SessionServiceBase::BuildCommandsForBrowser(
Browser* browser,
IdToRange* tab_to_available_range,
std::set<SessionID>* windows_to_track) {
DCHECK(is_saving_enabled_);
DCHECK(browser);
DCHECK(browser->session_id().is_valid());
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetWindowBoundsCommand(
browser->session_id(), browser->window()->GetRestoredBounds(),
browser->window()->GetRestoredState()));
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetWindowTypeCommand(
browser->session_id(), WindowTypeForBrowserType(browser->type())));
if (!browser->app_name().empty()) {
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetWindowAppNameCommand(browser->session_id(),
browser->app_name()));
}
if (!browser->user_title().empty()) {
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetWindowUserTitleCommand(browser->session_id(),
browser->user_title()));
}
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetWindowWorkspaceCommand(
browser->session_id(), browser->window()->GetWorkspace()));
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetWindowVisibleOnAllWorkspacesCommand(
browser->session_id(),
browser->window()->IsVisibleOnAllWorkspaces()));
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSetSelectedTabInWindowCommand(
browser->session_id(), browser->tab_strip_model()->active_index()));
// Set the visual data for each tab group.
TabStripModel* tab_strip = browser->tab_strip_model();
if (tab_strip->SupportsTabGroups()) {
TabGroupModel* group_model = tab_strip->group_model();
tab_groups::TabGroupSyncService* tab_group_service =
tab_groups::TabGroupSyncServiceFactory::GetForProfile(
browser->profile());
for (const tab_groups::TabGroupId& group_id :
group_model->ListTabGroups()) {
const tab_groups::TabGroupVisualData* visual_data =
group_model->GetTabGroup(group_id)->visual_data();
std::optional<std::string> saved_guid;
if (tab_group_service) {
const std::optional<tab_groups::SavedTabGroup> saved_group =
tab_group_service->GetGroup(group_id);
if (saved_group.has_value()) {
saved_guid = saved_group->saved_guid().AsLowercaseString();
}
}
command_storage_manager()->AppendRebuildCommand(
sessions::CreateTabGroupMetadataUpdateCommand(group_id, visual_data,
std::move(saved_guid)));
}
}
if (features::IsRestoringSplitViewEnabled()) {
for (split_tabs::SplitTabId split_id : tab_strip->ListSplits()) {
command_storage_manager()->AppendRebuildCommand(
sessions::CreateSplitTabDataUpdateCommand(
split_id, tab_strip->GetSplitData(split_id)->visual_data()));
}
}
for (int i = 0; i < tab_strip->count(); ++i) {
WebContents* tab = tab_strip->GetWebContentsAt(i);
DCHECK(tab);
const std::optional<tab_groups::TabGroupId> group_id =
tab_strip->GetTabGroupForTab(i);
const std::optional<split_tabs::SplitTabId> split_id =
tab_strip->GetSplitForTab(i);
BuildCommandsForTab(browser->session_id(), tab, i, group_id, split_id,
tab_strip->IsTabPinned(i), tab_to_available_range);
}
windows_to_track->insert(browser->session_id());
}
void SessionServiceBase::BuildCommandsFromBrowsers(
IdToRange* tab_to_available_range,
std::set<SessionID>* windows_to_track) {
DCHECK(is_saving_enabled_);
for (Browser* browser : *BrowserList::GetInstance()) {
// Make sure the browser has tabs and a window. Browser's destructor
// removes itself from the BrowserList. When a browser is closed the
// destructor is not necessarily run immediately. This means it's 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 (ShouldTrackBrowser(browser) && browser->tab_strip_model()->count() &&
browser->window()) {
BuildCommandsForBrowser(browser, tab_to_available_range,
windows_to_track);
}
}
}
void SessionServiceBase::ScheduleCommand(
std::unique_ptr<sessions::SessionCommand> command) {
if (!is_saving_enabled_)
return;
DCHECK(command);
if (ReplacePendingCommand(command_storage_manager_.get(), &command))
return;
bool is_closing_command = IsClosingCommand(command.get());
command_storage_manager_->ScheduleCommand(std::move(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 (!command_storage_manager_->pending_reset() &&
command_storage_manager_->commands_since_reset() >= kWritesPerReset &&
!is_closing_command) {
ScheduleResetCommands();
}
DidScheduleCommand();
}
bool SessionServiceBase::ShouldTrackChangesToWindow(SessionID window_id) const {
return windows_tracking_.find(window_id) != windows_tracking_.end();
}
bool SessionServiceBase::ShouldTrackBrowser(Browser* browser) const {
if (browser->profile() != profile())
return false;
if (browser->omit_from_session_restore())
return false;
// Never track app popup windows that do not have a trusted source (i.e.
// popup windows spawned by an app). If this logic changes, be sure to also
// change SessionRestoreImpl::CreateRestoredBrowser().
if ((browser->is_type_app() || browser->is_type_app_popup()) &&
!browser->is_trusted_source()) {
return false;
}
#if BUILDFLAG(IS_CHROMEOS)
// Windows that are auto-started and prevented from closing are exempted from
// tracking for session restore to prevent multiple unclosable open instances
// of the same app.
web_app::AppBrowserController* app_controller = browser->app_controller();
web_app::WebAppProvider* provider =
web_app::WebAppProvider::GetForWebApps(profile());
// Checking for close prevention does not require an `AppLock` and
// therefore `registrar_unsafe()` is safe to use.
if (app_controller && provider &&
provider->registrar_unsafe().IsPreventCloseEnabled(
app_controller->app_id())) {
return false;
}
#endif // #if BUILDFLAG(IS_CHROMEOS)
return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()));
}
sessions::CommandStorageManager*
SessionServiceBase::GetCommandStorageManagerForTest() {
return command_storage_manager_.get();
}
void SessionServiceBase::SetAvailableRangeForTest(
SessionID tab_id,
const std::pair<int, int>& range) {
tab_to_available_range_[tab_id] = range;
}
bool SessionServiceBase::GetAvailableRangeForTest(SessionID tab_id,
std::pair<int, int>* range) {
auto i = tab_to_available_range_.find(tab_id);
if (i == tab_to_available_range_.end())
return false;
*range = i->second;
return true;
}
void SessionServiceBase::SetSavingEnabled(bool enabled) {
if (is_saving_enabled_ == enabled)
return;
is_saving_enabled_ = enabled;
if (!is_saving_enabled_) {
// Transitioning from enabled to disabled should happen very early on,
// before any commands are actually written. If commands are written, then
// the purpose of disabling will have failed (because by writing some
// commands the previous session is going to be lost on exit).
DCHECK(!did_save_commands_at_least_once_);
command_storage_manager()->ClearPendingCommands();
} else {
ScheduleResetCommands();
}
}
std::optional<std::string> SessionServiceBase::GetPlatformSessionId() {
InitializePlatformSessionIfNeeded(/*restored_platform_session_id=*/{},
/*discarded_window_ids=*/{});
return platform_session_id_;
}
void SessionServiceBase::SetPlatformSessionIdForTesting(const std::string& id) {
platform_session_id_ = id;
ScheduleCommand(sessions::CreateSetPlatformSessionIdCommand(
platform_session_id_.value()));
}
void SessionServiceBase::InitializePlatformSessionIfNeeded(
const std::string& restored_platform_session_id,
const std::set<SessionID>& discarded_window_ids) {
#if BUILDFLAG(IS_OZONE)
ui::OzonePlatform* platform = ui::OzonePlatform::GetInstance();
if (!platform->GetPlatformRuntimeProperties().supports_session_management ||
platform_session_id_.has_value()) {
return;
}
DCHECK(platform->GetSessionManager());
ui::PlatformSessionManager* session_manager = platform->GetSessionManager();
const bool should_restore = !restored_platform_session_id.empty();
// TODO(crbug.com/352081012): Support post-crash restore reason.
std::optional<std::string> actual_platform_session_id =
should_restore ? session_manager->RestoreSession(
restored_platform_session_id,
ui::PlatformSessionManager::RestoreReason::kLaunch)
: platform->GetSessionManager()->CreateSession();
if (actual_platform_session_id.has_value()) {
DVLOG(1) << "Successfully initialized platform session."
<< " session_service=" << this
<< " session_id=" << actual_platform_session_id.value()
<< " discarded_windows=" << discarded_window_ids.size();
platform_session_id_ = std::move(actual_platform_session_id);
for (const SessionID& session_id : discarded_window_ids) {
session_manager->RemoveWindow(platform_session_id_.value(),
session_id.id());
}
ScheduleCommand(sessions::CreateSetPlatformSessionIdCommand(
platform_session_id_.value()));
}
#endif
}