>[!warning]- Info
> Author:
[email protected]
> Publish data: 2025/02/21
> Last update: 2025/03/28
> Status: WIP
> Revision: 2
> 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
> protocol MR: https://wayland.app/protocols/wayland-protocols/18
## Design
- XDG Session id 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)
- XDG toplevel IDs are generated by the client application, which must take care for example to avoid id collision, and so on. To maximize compatibility with the current chrome's impl of session restore, the toplevel ID assignment will be done two-fold: (1) browser windows are added to the xdg-session using their `session_id_`, basically an `int32_t` assigned at construction time which already has anti-collision handling and is already persisted in the session commands backing storage. (2) when restoring that window, the previous window id is retrieved from the commands backing storage and then passed in to ozone platform code so that it is used to restore the window state and then replace its id with the newly generated browser's `session_id_`.
- For more context on this approach, see [[#Conclusions (round 2)|Appendix: Session and Window IDs]]
### 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)
```
### Window removal
Originally, chrome handles window removals when restoring a session (ie: at browser startup). After retrieving the session commands from the backing storage, it [filters out "empty" browser windows](https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_types.h;l=31;drc=764f01d25d02a994e3d61a0edd384ca3b4caa272). The natural approach would be to keep it as is and just plumb it through all the way down to ozone/wayland so that it requests the compositor to remove that toplevel.
> [!bug] Potential issue
> There's no `xdg_session.remove_toplevel(toplevel_id)` request currently in the protocol. The protocol assumes toplevels are removed while the associated toplevel is still alive. **TODOs:**
> - Alternative 1: Initialize a "dummy" toplevel window just to get its associated `xdg_toplevel_session` and then remove it.
> - Pros: No additional protocol request.
> - Cons: Sounds a bit hacky.
> - Alternative 2: Check if a new protocol request can be added.
- **Approach 1 (WIP/To Be Validated):** To be able to handle window removal from the session (on Wayland, partially handled at 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
};
```
## Implementation
Code changes were then split into the following smaller patches:
- [x] [chrome: add wayland-session-management flag](https://crrev.com/c/6398027)
- [x] [wayland: xdg-session-management: Add //chrome required changes](https://crrev.com/c/6329216)
- [x] [wayland: xdg-session-management: Add ozone/wayland implementation](https://crrev.com/c/6329003)
- [x] [wayland: xdg-session-management: Add required UI/Views toolkit bits](https://crrev.com/c/6329156)
- [x] [wayland: xdg-session-management: Add command for platform_session_id](https://crrev.com/c/6329136)
- [x] [wayland: No longer attach null buffer in ToplevelWindow::Show](https://crrev.com/c/6311274)
- [x] [wayland: remove redundant wl_surface.commit when mapping toplevels](https://crrev.com/c/6311511)
With all of them merged, the feature might be enabled by:
- Switching the `wayland-session-management` chrome flag (ie: via [chrome://flags]) or using the `--enable-features=WaylandSessionManagement` command line argument, **and**;
- Check [chromiumdash](https://chromiumdash.appspot.com/commit/981f70ee657e69c6aab24f4f4d2d8494c6c08e59) to see on which versions the feature (and flag) started shipping.
- On Gnome 48 and 47, enabling the experimental session management support, as explained on the [[#Setup|setup section below]].
Pending items:
- Window removal handling. See [[#Design]], "removal" bullet.
- Add tests
### 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
### Iteration 2: Refactor toplevel IDs
- Implementing the requested changes by dljames@. See [[#Conclusions (round 2)|Appendix: Session and Toplevel IDs]] for further details.
- Iterating in the review and productization phase.
- Pushed the other CLs for review as well, except for the UI/Views one as I prefer to get final feedback/approval from dljames@ about the overall approach.
Outcomes:
- [Patchset 4](https://chromium-review.googlesource.com/c/chromium/src/+/6329003/4) onwards.
- Other chained CLs.
- [x] Re-use `Browser::InitParams::restore_id` (originally chromeos-only)
- [x] Plumb it through to be set with the restored window's `session_id`, so that it can be used in browser window's initialization to restore the toplevel.
- [x] Pass in `Browser::session_id_` (respun) as the id to be after restoring a toplevel, that's how the id will be rotating each time the browser launches.
- [x] Modify ozone/wayland impl, such that it now uses the new ID model described above.
- remark: the whole two-fold ID assignments is encapsulated at and handled by `WaylandToplevelWindow` and `XdgSession[Manager]`. All the browser (or other embedder) needs to do is to fill the `Widget::InitParams::session_data` (ie: session id, restore id (if any), and the new window id) at creation time.
- [x] Support for multiple simultaneous sessions.
- [x] Webpp windows support
Follow-ups:
- [ ] Toplevel removals
- [ ] Exercise in ozone unit tests.
- [ ] Support post-crash restore.
### Iteration 1: Chrome plumbing
- Chrome and UI/View plumbing reaching a fully functional prototype
- Proof-of-concept is complete.
- Started productization phase.
Outcomes:
- [Fully functional prototype](https://chromium-review.googlesource.com/c/chromium/src/+/6298538/2)
- CLs broken down preparing for review:
- https://chromium-review.googlesource.com/6329136
- https://chromium-review.googlesource.com/6329156
- https://chromium-review.googlesource.com/6329003
- https://chromium-review.googlesource.com/6329216
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.
### Iteration 0: No plumbing in //chrome
Outcome: [Prototype 0](https://chromium-review.googlesource.com/c/chromium/src/+/6298538/1)
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_'
```
### Known issues
#### Mutter
>[!note] Observed in both version 47.3 and 48.0
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).
#### Protocol
- Posted several comments in the upstream MR (under review)
- https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/18
- Identified one more potential issue with "toplevel removals"
- See [[#Design]], "removal" bullet.
- TODO: Not filed yet.
# Appendix
## Digging deeper
### Session and window IDs
#### Findings:
- Session IDs are persisted, though are discarded and new ones are created when that session is restored later on.
- Session files seem to be created at each browser run, even if it's resulting of a session restore.
#### Conclusions (round 2)
- For each window:
- The `window_id` retrieved from the command sessions backing storage (if any) can be passed in when instantiating the corresponding `Browser`, stored as `create_params_.restore_id` just like in ChromeOS. Then it can used to restore the window session state, i.e: passed in to ozone/wayland, which in turn uses it to issue `xdg_session.restore_toplevel`
- The new id created for that `Browser` instance in its contructor, ie: its `session_id_`, would need also to be passed in to ozone/wayland, so that it can be set as the new toplevel id with 2 subsequent requests to remove the old one and re-insert it with this new id, i.e: `xdg_toplevel_session.remove` + `xdg_session.add_toplevel(window, new_window_id)`
For more context backing this conclusion, see the discussion at https://chromium-review.googlesource.com/6329136
#### ~~Conclusions (round 1)~~
- For protocols such as `xdg-session-management` they do not seem appropriate.
- Neither `Browser::session_id_` nor (Cros-specific) `Browser::create_params_::restore_id`.
#### How to reproduce
The findings mentioned above can be reproduced/observed with the debugging logs below:
- 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
```