>[!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: ![](https://youtu.be/OG9ZLXzlwkQ) ### 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 ```