>[!warning]- Info
> Author:
[email protected]
> Publish data: 2025/02/21
> Last update: 2025/03/05
> Status: WIP
> Revision: 1
> Visibility: Public
# Analysis
```mermaid
classDiagram
class SessionServiceBase {
+GetLastSession(callback)
+RestoreSessionFromCommands(commands, windows, window_id)
+SaveWorkspace(window_id, workspace)
+SaveWindowUserTitle(window_id, title)
}
class SessionRestoreImpl {
+Browser* Restore()
-OnGotSession(windows, session_id)
}
class KeyedService {
<<interface>>
}
class BrowserListObserver {
<<interface>>
}
class CommandStorageManager["sessions::CommandStorageManager"] {
+GetSessionLastCommands(callback)
}
class CommandStorageBackend["sessions::CommandStorageBackend"] {
+ReadSessionLastCommands(callback)
}
class SessionCommand["sessions::SessionCommand"]
class SessionWindow["sessions::SessionWindow"] {
-window_id: SessionID
-bounds: Rect
-workspace: string
-visible_in_all_workspaces: bool
}
class SessionID
class SessionIdGenerator["sessions::SessionIdGenerator"] {
+NewUnique()
}
KeyedService <|.. SessionServiceBase
BrowserListObserver <|.. SessionServiceBase
class SessionRestore {
+RestoreSession(profile, browser, behavior)
}
class StartupBrowserCreator {
+RestoreOrCreateBrowser(tabs, behavior, ..)
}
class Browser {
+session_id(): SessionID
+profile(): Profile
+window(): BrowserWindow
}
class SessionService {
+WindowOpened(Browser* browser)
}
class AppSessionService {
+WindowOpened(Browser* browser)
}
SessionServiceBase <|-- SessionService
SessionServiceBase <|-- AppSessionService
SessionServiceBase o-- Profile
SessionServiceBase o-- CommandStorageManager
SessionServiceBase .. SessionCommand
CommandStorageManager --o CommandStorageBackend
SessionCommand .. CommandStorageManager
SessionService <.. SessionRestoreImpl
AppSessionService <.. SessionRestoreImpl
SessionWindow o-- SessionRestoreImpl
Profile ..o SessionRestoreImpl
Browser ..o SessionRestoreImpl
BrowserListObserver ..o Browser
SessionRestoreImpl <.. SessionRestore
SessionRestore <.. StartupBrowserCreator
PrefService ..o SessionIdGenerator
SessionID .. SessionIdGenerator
SessionIdGenerator <.. Browser
```
## Session Restore
- All session data is [per-profile](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/sessions/session_service_factory.cc;l=14-15;drc=0152e92ec66fcd6a5558112607ef170151506788).
- `SessionServiceBase` extends `KeyedService` and [SessionServiceFactory](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/sessions/session_service_factory.h;l=19;drc=899bce7f823577fd7ff2366dc91b76179076d112) implements ProfileKeyedServiceFactory.
- It might sound confusing, but `Browser` 's `session_id` is equivalent to a toplevel session name in xdg-session-management.
- It means more like "id of something in the context of a browser session" instead of "the id of the session"
- It is also used to identify open tabs within a session, for example.
- [CreateTabAndWindows](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_service_commands.cc;l=902;drc=0b9cdb2d33b1bc902fdebe7ac23f871741ebe01d) function at `components/sessions/core/session_service_commands.cc` implements session data de-serialization.
- [User title](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_service_commands.cc;l=899-905;drc=0b9cdb2d33b1bc902fdebe7ac23f871741ebe01d) field there might be used as reference for "wayland session id". But in that case it's per-session, instead of per-window.
- `PrefService` is used to store the last [SessionID](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_id_generator.cc;l=18;drc=0b9cdb2d33b1bc902fdebe7ac23f871741ebe01d) value used. `session_id_generator_last_value` preference is used for that.
- [SessionService::WindowOpened](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/sessions/session_service.cc;l=365;drc=0b9cdb2d33b1bc902fdebe7ac23f871741ebe01d) function (and its counterpart in [AppRestoreService](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/sessions/app_session_service.cc;drc=0b9cdb2d33b1bc902fdebe7ac23f871741ebe01d;bpv=1;bpt=1;l=87)) calls into `SessionServiceBase::SetWindowWorkspace` (among others) to save data to the session command backing storage.
## Full Restore
- ChromeOS-specific feature.
- Saves/restores app windows and launch information for CrOS Apps
- [components/app_restore]() contains the core code to save/restore required information from the data storage
- [FullRestoreReadHandler::GetWIndowInfo](https://source.chromium.org/chromium/chromium/src/+/main:components/app_restore/full_restore_read_handler.cc;l=339;drc=899bce7f823577fd7ff2366dc91b76179076d112) function for example builds a `app_restore::WindowInfo` instance out of browser's [restore_id](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/browser.h;l=296;drc=899bce7f823577fd7ff2366dc91b76179076d112)
- Called, for example, when creating the `views::Widget::InitParams` in [BrowserFrameAsh](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_frame_ash.cc;l=215;drc=0b9cdb2d33b1bc902fdebe7ac23f871741ebe01d)
# Supporting xdg-session-management
> crbug: https://issues.chromium.org/352081012
## Draft design
- Session "name" is generated asynchronously by the Wayland compositor. Need a way of plumbing it all the way up from ozone/wayland to Chrome's Session Service, such that it is saved in the session backing store.
- A new "platform session id" must be persisted somewhere in profile storage.
- Session commands backing storage will be used (ie: files managed by [sessions::CommandStorageManager](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/command_storage_manager.h;l=37;drc=c0a5c389115450bb92a80d8f2c2ffad4449e11ea)
- A new "platform window session token" property needs to be added to browser windows, since `Browser::session_id` is not sufficient (see [[#Appendix#Session file path and session IDs]])
- [base::UnguessableToken](https://source.chromium.org/chromium/chromium/src/+/main:base/unguessable_token.h;l=50;drc=af7705f279241eebcae915ea9d0a07f2768ee477) might be used to avoid unnecessary complex/ad-hoc solutions, such as [SessionIdGenerator](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_id_generator.cc;l=15;drc=899bce7f823577fd7ff2366dc91b76179076d112)
- If `UnguessableToken` is too much, maybe [base::Uuid](https://source.chromium.org/chromium/chromium/src/+/main:base/uuid.h;l=27;drc=1705e3f027f4a31b610dcd13ce4c4bc9bbb0f0c9)?
- To be able to handle window removal from the session (now partially handled at the Wayland compositor side), some changes in [sessions::RestoreSessionFromCommands](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_service_commands.h;l=129;drc=304476fc888332d14c19ecc697414b5d29a55d97) API are needed, including:
- Return a data structure instead, which will encapsulate the following fields (as drafted bellow):
- `restored_windows`: metadata of the windows to be restored, same as current `valid_windows` param in the current API.
- `active_window_id`: same as current `active_window_id` in the current API.
- `platform_session_id`: Ozone-only platform session id to be passed in to the Ozone layer such that it can restore the session at the display server side.
- `discarded_platform_window_tokens`: Ozone-only list of tokens to be removed from the platform session, i.e: done through `xdg_session_v1.remove_toplevel` requests in xdg-session-management.
- that vector must be populated with the window tokens discarded in [sessions::SortTabsBasedOnVisualOrderAndClear](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_service_commands.cc;l=331-332;drc=304476fc888332d14c19ecc697414b5d29a55d97) function.
- Such new struct can be added to [components/sessions/core/session_types.h](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_types.h;l=31;drc=764f01d25d02a994e3d61a0edd384ca3b4caa272) where other similar data structs are defined.
```cpp
struct RestoredSession {
std::vector<std::unique_ptr<SessionWindow>> restored_windows;
SessionID active_window_id;
#if BUILDFLAG(IS_OZONE)
std::string platform_session_id;
std::vector<base::Token> discarded_platform_windows_tokens;
#endif
};
```
### Session initialization
```mermaid
sequenceDiagram
box chrome
participant SessionService
participant CommandStorage as sessions::CommandStorageManager
end
participant OzonePlatform
participant Wayland as Wayland Compositor
SessionService ->> CommandStorage: GetLastSessionCommands()
SessionService ->> CommandStorage: session = RestoreSessionFromCommands(commands)
SessionService ->> OzonePlatform: StartSession(session.platform_session_id)
OzonePlatform -->> Wayland: xdg_session_manager.get_session(session_id)
Wayland --) OzonePlatform: xdg_session.created(session_id)
OzonePlatform --) SessionService: returns session_id
SessionService -->> CommandStorage: CreateSetPlatformSessionIdCommand(session_id)
SessionService ->> OzonePlatform: RemoveWindows(session.discarded_platform_window_tokens)
```
## Prototyping
- Experimenting with Mutter 48 xdg-session-management impl
- WIP CL at https://chromium-review.googlesource.com/6298538
- Quick demo:

### Setup
To run mutter in nested mode with experimental session-management enabled, use the following command line args and vars:
```sh=bash
MUTTER_DEBUG='session-management' \
MUTTER_DEBUG_SESSION_MANAGEMENT_PROTOCOL=1 \
MUTTER_DEBUG_DUMMY_MODE_SPECS="
[email protected]" \
dbus-run-session mutter --wayland --nested
```
#### Native sessions
- As of 2025/02 (Gnome 48 about to be released), to turn on `xx-session-management-v1` protocol in Mutter, the very same env var mentioned above must be used.
- When using GDM, env vars can be set using `~/.config/environment.d/envvars.conf` [^1], for example:
```conf
MUTTER_DEBUG_SESSION_MANAGEMENT_PROTOCOL=1
MUTTER_DEBUG='session-management'
```
[^1]: https://wiki.archlinux.org/title/Environment_variables#Per_Wayland_session
### Compositor issues
A few Mutter issues were observed along the way:
- Maximized windows restored to wrong display
- Filed: https://gitlab.gnome.org/GNOME/mutter/-/issues/3939
- Fail to restore toplevel workspace when dynamic workspaces are in use
- Not filed yet (to be confirmed).
### Iteration 1: No plumbing in //chrome
Quick and simple proof-of-concept with focus on understanding and doing initial validation of the protocol.
- [x] V1 of Ozone's session management public API.
- [x] Initially using a command line arg for passing in session id
- [x] Toplevel session "names" are auto-generated as `toplevel-0`, `toplevel-1`, etc for now.
- [ ] Multi-sessions support.
- At least one per-profile.
- Maybe also browser vs app window sessions. To be confirmed.
Status: Functional.
- TODOs:
- [ ] Plumb them through from //chrome/browser/ui code (done in [[#Iteration 2 Chrome plumbing]])
- [ ] Request access to https://issues.chromium.org/40181917
To test it:
1. First run with no `wayland-xdg-session-name` arg (it will save state of the windows you open):
```sh=bash
export WAYLAND_DEBUG=client
chr_run --ozone-platform=wayland --vmodule='"*/wayland/*=2"' 2>&1 | grep -e 'ERROR\|VERBOSE\|^#[0-9]\+\|xx_'
```
Observe Chromium logs, session name gets printed there, something like
```
[1344657.225] xx_session_v1#33.created("ce8ccb83-c805-4a53-8bed-b2a14e4b1b85")
[262114:262114:0222/012353.912144:VERBOSE1:xdg_session_manager.cc(150)] OnCreated session_id=ce8ccb83-c805-4a53-8bed-b2a14e4b1b85
```
On the Mutter logs, a few log messages will show up also, when the browser is shut down with Ctrl+Q, for example:
```
libmutter-Message: 01:24:55.368: SESSION_MANAGEMENT: Saving window toplevel-0
libmutter-Message: 01:24:55.368: SESSION_MANAGEMENT: Saved window state toplevel-0: floating Rect [942,424 +632,502]
libmutter-Message: 01:24:58.546: SESSION_MANAGEMENT: Serializing state
libmutter-Message: 01:24:58.546: SESSION_MANAGEMENT: Serializing toplevel state toplevel-0
```
1. Now run passing the session ID collected above as `--wayland-xdg-session-name=<Session Name>` as well as `--restore-last-session`:
```sh=bash
export WAYLAND_DEBUG=client
chr_run --ozone-platform=wayland \
--vmodule='"*/wayland/*=2"'
--wayland-xdg-session-name='ce8ccb83-c805-4a53-8bed-b2a14e4b1b85'
--restore-last-session 2>&1 |
grep -e 'ERROR\|VERBOSE\|^#[0-9]\+\|xx_'
```
### Iteration 2: Chrome plumbing
Tentatively implementing the WIP design at [[#Draft design]]
- [x] Create and restore platform sessions in chrome's session restore
- [x] Support saving and restoring platform_session_id in
sessions::CommandStorage{Manager,Backend}.
- [x] Use it to create or restore an platform session in chrome's
SessionService{Base}.
- [x] Support platform window tokens for each chrome browser window
- [x] Support saving and restoring platform window tokens in
sessions::CommandStorage{Manager,Backend}.
- [x] Plumb them up in chrome's Browser and Widget code all the way
down to Ozone/Wayland.
- [ ] Plumb webapp windows restore
- [ ] Handle window removal
- How does it differentiate regular window close from session removal?
E.g: Browser shutdown (ie: close all windows) vs close a single
window.
For productization phase:
- [ ] Support post-crash restore.
- [ ] Add tests for newly introduced code.
# Appendix
## Debugging
### Session file path and session IDs
Findings:
- Session IDs are persisted, though are discarded and new ones are created when that session is restored later on.
- Thus, for protocols such as `xdg-session-management` they do not seem appropriate.
- Neither `Browser::session_id_` nor (Cros-specific) `Browser::create_params_::restore_id`.
- Session files seem to be created at each browser run, even if it's resulting of a session restore.
- All this can be observed with the debugging logs below.
#### How to reproduce
With the following patch applied:
- For ref, HEAD commit: `15af07f652bb`
```diff
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc
index 9ec57b0f332a..0cd68a4355f1 100644
--- a/chrome/browser/sessions/session_restore.cc
+++ b/chrome/browser/sessions/session_restore.cc
@@ -740,6 +740,11 @@ class SessionRestoreImpl : public BrowserListObserver {
window->app_name, window->user_title, window->extra_data,
window->window_id.id());
+ VLOG(1) << __func__
+ << " just created browser for restored window."
+ << " browser.session_id=" << browser->session_id()
+ << " restored.session_id=" << window->window_id;
+
#if BUILDFLAG(IS_CHROMEOS)
aura::Window* browser_window = browser->window()->GetNativeWindow();
if (occlusion_helper) {
```
Run cmd:
```sh=bash
out/linux/chrome --enable-logging=stderr --no-sandbox \
--ozone-platform=wayland --user-data-dir=/tmp/chr-devel \
--vmodule="*/wayland/*=2,*/sessions/*=2" \
--restore-last-session
```
Relevant log messages:
```
# 1st restore:
[465721:465746:0223/175054.696208:VERBOSE1:command_storage_backend.cc(608)] CommandStorageBackend::ReadLastSessionCommands, reading commands from: /tmp/chr-devel/Default/Sessions/Session_13384820982094590
[465721:465721:0223/175055.183020:VERBOSE1:session_restore.cc(743)] ProcessSessionWindows just created browser for restored window. browser.session_id=162707528 restored.session_id=162707464
[465721:465721:0223/175055.618703:VERBOSE1:session_restore.cc(743)] ProcessSessionWindows just created browser for restored window. browser.session_id=162707530 restored.session_id=162707466
# 2nd restore:
[466473:466501:0223/175216.283685:VERBOSE1:command_storage_backend.cc(608)] CommandStorageBackend::ReadLastSessionCommands, reading commands from: /tmp/chr-devel/Default/Sessions/Session_13384821057185892
[466473:466473:0223/175216.756836:VERBOSE1:session_restore.cc(743)] ProcessSessionWindows just created browser for restored window. browser.session_id=162707594 restored.session_id=162707528
[466473:466473:0223/175217.182845:VERBOSE1:session_restore.cc(743)] ProcessSessionWindows just created browser for restored window. browser.session_id=162707596 restored.session_id=162707530
# 3rd restore:
[467274:467303:0223/175535.188632:VERBOSE1:command_storage_backend.cc(608)] CommandStorageBackend::ReadLastSessionCommands, reading commands from: /tmp/chr-devel/Default/Sessions/Session_13384821138772267
[467274:467274:0223/175535.674851:VERBOSE1:session_restore.cc(743)] ProcessSessionWindows just created browser for restored window. browser.session_id=162707662 restored.session_id=162707594
[467274:467274:0223/175536.096416:VERBOSE1:session_restore.cc(743)] ProcessSessionWindows just created browser for restored window. browser.session_id=162707664 restored.session_id=162707596
```