blob: 071884fe257bbdf6237dc84e227830403bfe328d [file] [log] [blame]
// Copyright 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/app_session_service.h"
#include <algorithm>
#include <set>
#include <utility>
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/background/background_mode_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_common_utils.h"
#include "chrome/browser/sessions/session_data_deleter.h"
#include "chrome/browser/sessions/session_service_utils.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.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/session_storage_namespace.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#endif
#if defined(OS_MAC)
#include "chrome/browser/app_controller_mac.h"
#endif
using content::NavigationEntry;
using content::WebContents;
using sessions::ContentSerializedNavigationBuilder;
using sessions::SerializedNavigationEntry;
AppSessionService::AppSessionService(Profile* profile)
: SessionServiceBase(profile,
SessionServiceBase::SessionServiceType::kAppRestore) {}
AppSessionService::~AppSessionService() {
// The BrowserList should outlive the SessionService since it's static and
// the SessionService is a KeyedService.
// BrowserList is subscribed to by SessionServiceBase's constructor.
BrowserList::RemoveObserver(this);
command_storage_manager()->Save();
DestroyCommandStorageManager();
}
bool AppSessionService::ShouldRestoreWindowOfType(
sessions::SessionWindow::WindowType window_type) const {
if (window_type == sessions::SessionWindow::TYPE_APP ||
window_type == sessions::SessionWindow::TYPE_APP_POPUP) {
return true;
}
return false;
}
bool AppSessionService::ShouldTrackChangesToWindow(
const SessionID& window_id) const {
// AppSessionService does not validate windows passed to it.
// Callers should use SessionServiceFactory::RelevantToAppSessionService
// to validate windows before calling AppSessionService.
return true;
}
void AppSessionService::RebuildCommandsIfRequired() {
if (rebuild_on_next_save())
ScheduleResetCommands();
}
void AppSessionService::MaybeDeleteSessionOnlyData() {
// Don't try anything if we're testing. The browser_process is not fully
// created and DeleteSession will crash if we actually attempt it.
if (profile()->AsTestingProfile())
return;
// Clear session data if the last window for a profile has been closed and
// closing the last window would normally close Chrome, unless background mode
// is active. Tests don't have a background_mode_manager.
if (browser_defaults::kBrowserAliveWithNoWindows ||
g_browser_process->background_mode_manager()->IsBackgroundModeActive()) {
return;
}
// Check for any open windows for the current profile that we aren't tracking.
for (auto* browser : *BrowserList::GetInstance()) {
if (browser->profile() == profile() &&
browser->type() == Browser::Type::TYPE_APP)
return;
}
DeleteSessionOnlyData(profile());
}
void AppSessionService::TabClosed(const SessionID& window_id,
const 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 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));
}
void AppSessionService::WindowOpened(Browser* browser) {
if (!ShouldTrackBrowser(browser))
return;
SetWindowType(browser->session_id(), browser->type());
SetWindowAppName(browser->session_id(), browser->app_name());
// 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());
}
void AppSessionService::WindowClosing(const 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();
}
void AppSessionService::WindowClosed(const SessionID& window_id) {
if (!ShouldTrackChangesToWindow(window_id)) {
// The last window may be one that is not tracked.
MaybeDeleteSessionOnlyData();
return;
}
tab_to_available_range()->erase(window_id);
ScheduleCommand(sessions::CreateWindowClosedCommand(window_id));
MaybeDeleteSessionOnlyData();
}
void AppSessionService::BuildCommandsForBrowser(
Browser* browser,
IdToRange* tab_to_available_range,
std::set<SessionID>* windows_to_track) {
SessionServiceBase::BuildCommandsForBrowser(browser, tab_to_available_range,
windows_to_track);
TabStripModel* tab_strip = browser->tab_strip_model();
// PWAs only have 1 tab.
DCHECK_EQ(tab_strip->count(), 1);
WebContents* tab = tab_strip->GetWebContentsAt(0);
DCHECK(tab);
const base::Optional<tab_groups::TabGroupId> empty_group_id;
SessionServiceBase::BuildCommandsForTab(
browser->session_id(), tab, 0, empty_group_id,
/*is_pinned*/ false, tab_to_available_range);
}
void AppSessionService::SetWindowType(const SessionID& window_id,
Browser::Type type) {
sessions::SessionWindow::WindowType window_type =
WindowTypeForBrowserType(type);
if (!ShouldRestoreWindowOfType(window_type))
return;
ScheduleCommand(CreateSetWindowTypeCommand(window_id, window_type));
}
void AppSessionService::ScheduleResetCommands() {
command_storage_manager()->set_pending_reset(true);
command_storage_manager()->ClearPendingCommands();
tab_to_available_range()->clear();
set_rebuild_on_next_save(false);
BuildCommandsFromBrowsers(tab_to_available_range(), {});
command_storage_manager()->StartSaveTimer();
}