blob: 4cc73cd8cb354da6e51b4ab3b293aea787a4e2ee [file] [log] [blame]
// Copyright (c) 2021 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_base.h"
#include <stddef.h>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.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/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/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/web_applications/components/web_app_helpers.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 "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/session_storage_namespace.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crostini/crostini_util.h"
#endif
#if defined(OS_MAC)
#include "chrome/browser/app_controller_mac.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,
/* use_marker */ true,
/* 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(
const 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() {
ScheduleResetCommands();
}
void SessionServiceBase::SetTabWindow(const SessionID& window_id,
const SessionID& tab_id) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(sessions::CreateSetTabWindowCommand(window_id, tab_id));
}
void SessionServiceBase::SetWindowBounds(const SessionID& window_id,
const gfx::Rect& bounds,
ui::WindowShowState show_state) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateSetWindowBoundsCommand(window_id, bounds, show_state));
}
void SessionServiceBase::SetWindowWorkspace(const SessionID& window_id,
const std::string& workspace) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(
sessions::CreateSetWindowWorkspaceCommand(window_id, workspace));
}
void SessionServiceBase::SetTabIndexInWindow(const SessionID& window_id,
const 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) {
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, base::nullopt,
pinned, nullptr);
command_storage_manager()->StartSaveTimer();
}
void SessionServiceBase::SetSelectedTabInWindow(const 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(
const SessionID& window_id,
const 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(const SessionID& window_id,
const SessionID& tab_id,
base::TimeTicks 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(const SessionID& window_id,
const std::string& app_name) {
if (!ShouldTrackChangesToWindow(window_id))
return;
ScheduleCommand(sessions::CreateSetWindowAppNameCommand(window_id, app_name));
}
bool SessionServiceBase::ShouldUseDelayedSave() {
return should_use_delayed_save_;
}
void SessionServiceBase::OnWillSaveCommands() {
RebuildCommandsIfRequired();
}
void SessionServiceBase::OnErrorWritingSessionCommands() {
LogSessionServiceWriteErrorEvent(profile_, false);
rebuild_on_next_save_ = true;
RebuildCommandsIfRequired();
}
void SessionServiceBase::SetTabUserAgentOverride(
const SessionID& window_id,
const 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();
return;
}
void SessionServiceBase::SetSelectedNavigationIndex(const SessionID& window_id,
const 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(
const SessionID& window_id,
const 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(const SessionID& window_id,
const 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(
const SessionID& window_id,
const 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();
}
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();
sessions::RestoreSessionFromCommands(commands, &valid_windows,
&active_window_id);
RemoveUnusedRestoreWindows(&valid_windows);
std::move(callback).Run(std::move(valid_windows), active_window_id,
read_error);
}
void SessionServiceBase::BuildCommandsForTab(
const SessionID& window_id,
WebContents* tab,
int index_in_window,
base::Optional<tab_groups::TabGroupId> group,
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())) {
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));
}
// 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(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();
TabGroupModel* group_model = tab_strip->group_model();
for (const tab_groups::TabGroupId& group_id : group_model->ListTabGroups()) {
const tab_groups::TabGroupVisualData* visual_data =
group_model->GetTabGroup(group_id)->visual_data();
command_storage_manager()->AppendRebuildCommand(
sessions::CreateTabGroupMetadataUpdateCommand(group_id, visual_data));
}
for (int i = 0; i < tab_strip->count(); ++i) {
WebContents* tab = tab_strip->GetWebContentsAt(i);
DCHECK(tab);
const base::Optional<tab_groups::TabGroupId> group_id =
tab_strip->GetTabGroupForTab(i);
BuildCommandsForTab(browser->session_id(), tab, i, group_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) {
for (auto* 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) {
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();
}
}
bool SessionServiceBase::ShouldTrackChangesToWindow(
const 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 BUILDFLAG(IS_CHROMEOS_ASH)
// Do not track Crostini apps or terminal. Apps will fail since VMs are not
// restarted on restore, and we don't want terminal to force the VM to start.
if (web_app::GetAppIdFromApplicationName(browser->app_name()) ==
crostini::kCrostiniTerminalSystemAppId) {
return false;
}
// System Web App windows can't be properly restored without storing the app
// type. Until that is implemented we skip them for session restore.
// TODO(crbug.com/1003170): Enable session restore for System Web Apps.
if (browser->app_controller() &&
browser->app_controller()->is_for_system_web_app()) {
return false;
}
// Don't track custom_tab browser. It doesn't need to be restored.
if (browser->is_type_custom_tab())
return false;
#endif
// 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->deprecated_is_app() && !browser->is_trusted_source())
return false;
return ShouldRestoreWindowOfType(WindowTypeForBrowserType(browser->type()));
}
sessions::CommandStorageManager*
SessionServiceBase::GetCommandStorageManagerForTest() {
return command_storage_manager_.get();
}
void SessionServiceBase::SetAvailableRangeForTest(
const SessionID& tab_id,
const std::pair<int, int>& range) {
tab_to_available_range_[tab_id] = range;
}
bool SessionServiceBase::GetAvailableRangeForTest(const 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;
}