From 9a1c16bf7ca5e7eef6817fbbafde05a3a0c1f3ea Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Sat, 17 May 2025 05:31:46 +0530 Subject: [PATCH 01/26] rfc: Save/Restore Window-State API --- text/0013-save-restore-window-state.md | 649 +++++++++++++++++++++++++ 1 file changed, 649 insertions(+) create mode 100644 text/0013-save-restore-window-state.md diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md new file mode 100644 index 0000000..b43a61e --- /dev/null +++ b/text/0013-save-restore-window-state.md @@ -0,0 +1,649 @@ +# RFC +- Start Date: 2025-03-20 +- RFC PR: +- Electron Issues: Related to [electron/electron/issues/526](https://github.com/electron/electron/issues/526) +- Reference Implementation: +- Status: **Proposed** + +> [!NOTE] +> This RFC is part of GSOC 2025 [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6). + +# Save/Restore Window State API + +Currently, Electron does not have any built-in mechanism for saving and restoring the state of BrowserWindows, but this is a very common need for apps that want to feel more native. + +## Summary + +This proposal aims to implement a save/restore window state API for Electron by providing a simple but powerful configuration object `windowStateSavePolicy` that handles complex edge cases automatically. This approach offers a declarative way to configure window persistence behavior while maintaining flexibility for different application needs. The object would be optional in the `BrowserWindowConstructorOptions`. + +## Motivation + +**Why should we do this?** + +Window state persistence represents a core functionality that many production Electron apps implement. By elevating it to a first-class feature, Electron acknowledges its essential nature and provides a standardized approach directly in the framework. + +**What use cases does it support?** + +It's useful for developers who want to implement save/restore window state for their apps reliably. Different apps have different needs. An example use case for this API would be for when apps want to preserve window position (with display overflow) and size across multi-monitor setups with different display scales and resolutions. + +`windowStateSavePolicy` aims to cover most save/restore use cases while providing room for future extension. + +**What is the expected outcome?** + +Electron provides an option to save and restore window state out of the box with multiple configurations which will cover almost all application needs. + +While established applications with custom implementations may continue using their existing solutions, this feature will most likely see adoption by many smaller projects. + +## Implementation Details + +Before diving into the API specifications which is the next section, I’d like to outline the approach for saving a window’s state in Electron. + +We can use Chromium's [PrefService](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h) to store window state as a JSON object into the already existing Preferences folder inside *app.getPath('userData')*. + +We schedule an async write to disk every time a window is moved or resized with a 2.5 second window. If there is change in the window state within the time window, it is reset to 2.5 seconds and we would schedule a write again with the new values. Otherwise, it is written to disk. It is also written to disk on window close synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** + +**Questions that might arise:** + +**Why 2.5 seconds?** +It seems like a reasonable time window to ensure related window adjustments are batched together. I think we can assume that users are done moving/resizing the window if there is no movement for 2.5 seconds. The default value used by Chromium's PrefService is 10 seconds [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)] which is too long. +I don't think we should make this configurable as well because it's not intuitive to the developer. + +**What if the app crashes in the 2.5 second window?** Revert back to where the window was 2.5 seconds before the crash. This wouldn't be the worst user experience in the world. + +#### *Why PrefService?* +- Atomic writes using ImportantFileWriter [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.h;l=28-29)] +- Async writes which means no blocking of the UI thread, preventing jank during window state updates [[reference](https://docs.google.com/document/d/1rlwl_GvvokatMiRUUkR0vGNcraqe4weFGNilTsZgiXA/edit?tab=t.0#bookmark=id.49710km71rd7)] +- Synchronous writes on demand [[reference](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h;l=199-209;drc=51cc784590d00e6a95b48e1e1bf5c3fe099edf64)] +- Offers batched scheduled writes (using ImportantFileWriter) every 10 second window [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)]. This is too long for our use-case, but we can conveniently set it to 2.5 seconds +- Already implemented in Electron to preserve the devTools bounds [[reference](https://github.com/electron/electron/blob/73a017577e6d8cf67c76acb8f6a199c2b64ccb5d/shell/browser/ui/inspectable_web_contents.cc#L509)] +- Provides identical behavior across Windows, macOS, and Linux, simplifying implementation and maintenance +- Falls back to default values automatically when corrupted data is encountered [[reference](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h;l=233-235;drc=51cc784590d00e6a95b48e1e1bf5c3fe099edf64)] +- Safe failing to ensure apps don't crash on data corruption [[reference](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/json_pref_store.cc;l=91-95;drc=ccea09835fd67b49ef0d4aed8dda1a5f22a409c8)] + +
+ +Here's the schema I propose for the `windowStateSavePolicy` object. This is simply for reference as I have not explained what each field means yet. I will explain each field in the API specification section which is next. + +
+ +`windowStateSavePolicy` schema - This would be passed by the developer in the BrowserWindowConstructorOptions + +```json +"windowStateSavePolicy": { + "stateId": string, + "dimensions": boolean, + "position": { + "strictDisplay": boolean, + "adaptToDisplay": boolean, + "allowOverflow": boolean, + "openBehaviour": string, + "fallbackBehaviour": string, + }, + "displayMode": { + "strictDisplay": boolean, + "maximized": boolean, + "minimized": boolean, + "fullscreen": boolean, + "simpleFullscreen": boolean, __macOS__ + "kiosk": boolean, + }, + } +``` +> [!NOTE] +> The entire window state (bounds, maximized, minimized, fullscreen, etc.) would be saved internally along with the windowStateSavePolicy object. +> We would use the saved state and enforce the policy during restoration. + +#### **State Persistence Mechanism** + +Firstly, all the window states with their `windowStateSavePolicy` would be to loaded from disk synchronously during the startup process just like other preferences in Electron. Doing so would allow us the have the states in memory during window creation with minimal performance impact. + +Secondly, once the states (containing our save policy) are loaded into memory, we can use them to restore the window state with the `windowStateSavePolicy` rules in place during window creation. An algorithm that handles all the edges would be required for this. I have explained the flow of the algorithm after the API specification. Also, the default options provided in the BrowserWindow constructor options would be overridden (if we are restoring state). + +Each time a window is **moved** or **resized**, we schedule a write using Chromium's ScopedDictPrefUpdate [[example](https://github.com/electron/electron/blob/4ad20ccb396a3354d99b8843426bece2b28228bf/shell/browser/ui/inspectable_web_contents.cc#L837-L841)]. + +#### Developer-Facing Events + +I’m also considering emitting events such as: `will-save-window-state`, `saved-window-state`, `will-restore-window-state`, `restored-window-state`. However, I’m unsure about their practical usefulness for developers and hope to finalize them through this RFC. + + +## API Specification + +Here's how it would land in the electron documentation. + +### BrowserWindowConstructorOptions + +`windowStateSavePolicy` object (optional) + + * `stateId` string - A unique identifier used for saving and restoring window state. + + * `dimensions` boolean (optional) - Whether to restore the window's height and width. Fallback is default values. When restoring, window will automatically resize to fit within available screen real estate if height or width is too large. Dimensions will be resaved. + + * `position` object (optional) + + * `strictDisplay` boolean (optional) - Whether to restore window position (x, y) only if reopened on the same display (displays are considered the same if they span over the same x, y coordinates and have identical scale, and resolution). Default is `false`. + + * `adaptToDisplay` boolean (optional) - Whether to restore window position (x, y) when opened on different displays by coordinate extrapolation based on display resolution. Overrides `strictDisplay` and will position the window at a relatively similar spot on the new display. Default is `false`. + + * `allowOverflow` boolean (optional) - Whether to allow a window to be restored in a partially off-screen position. **Overflow is never preserved in the event of the window opening on a different display.** When `true`, overflow is only persisted if window is relauched on the same display and at least 10% of the window area is visible. If overflow is greater than 90% it will reopen at the closest edge of the display, fully visible. Default is `false`. + + * `openBehaviour` string (optional) - Special behavior when the window is opened. + * `"openOnLaunchedDisplay"` - Opens the window on the display that it was launched from always. + + * `"openOnPrimaryDisplay"` - Opens the window on the primary display always. + + * `fallbackBehaviour` string (optional) - Fallback behaviour when position is out of bounds [[read more](https://gist.github.com/nilayarya/6a5ae863f99f269f0759f6f4f86e91b5)]. + * `"snapToNearestDisplayEdge"` - Snaps the window to the nearest display edge. It is also the default behaviour when a window is out of bounds and `fallbackBehaviour` is not provided. + + * `"centerOnNearestDisplay"` - Centers the window on the nearest display. + + * `"centerOnPrimaryDisplay"` - Centers the window on the primary display. + + * `displayMode` object (optional) - Controls which display modes will be restored. + + * `strictDisplay` boolean (optional) - Whether to restore `displayMode` only if reopened on the same display. Otherwise, it is always preserved by default even across displays. Default is `false`. + + * `maximized` boolean (optional) - Whether to restore maximized state. Default is `false`. + + * `minimized` boolean (optional) - Whether to restore minimized state. Default is `false`. + + * `fullscreen` boolean (optional) - Whether to restore fullscreen state. Default is `false`. + + * `simpleFullscreen` boolean (optional) - Whether to restore simple fullscreen state. Default is `false`. + + * `kiosk` boolean (optional) - Whether to restore kiosk state. Default is `false`. + + + +Example usage that would cover most apps: +```js +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + ...existingOptions, + windowStateSavePolicy: { + stateId: '#1230', + dimensions: true, + position: { + strictDisplay: true, + allowOverflow: true, + fallbackBehaviour: "snapToNearestDisplayEdge", + }, + displayMode: { + maximized: true, + fullscreen: true, + }, + }, +}) +``` + +### API Design Rationale: +- Everything is set to false by default so that the developer knows what they are toggling. +- Displays are considered the same if they span over the same x, y coordinates and have identical scale, and resolution. So for example, replacing your primary display with same resolution and scale but different manufacturer will still be considered the same display. I think this makes sense. +- A window can never be restored out of reach. I am presuming no apps would want this behavior. `fallbackBehaviour` will take effect if the window is entirely off screen. We are still providing the option to relaunch in a minimized state. +- If window (width, height) reopens on a different display and does not fit on screen auto adjust to fit and resave the value (even if allowOverflow=true). This would reduce the number of edge cases significantly and I highly doubt that any app would want to preserve an overflow when opened on a different display. +- Not handling scaling as Windows, macOS, and Linux support multimonitor window scaling by default. +- Not handling other `displayModes` such as split screen because it's innapropriate I believe. We can restore back to the same bounds in 'normal' mode. I've excluded 'normal' from the options for `displayMode` as it is the default. + +### Additional APIs +I also plan on adding synchronous methods to `BrowserWindow` to save, restore, clear, and get the window state or rather window preferences. Down below is the API spec for that. + +Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. +I think this feature is more of a nice to have and not a must have. + +>[!NOTE] +> Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). + +#### `win.saveWindowPreferences([options])` + +* `options` Object + + * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux + + * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS + + * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS + + * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` + + * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux + + * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS + + * `title` boolean (optional) - Save window's current title. Default: `false` + + * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS + + * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS + + * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` + + * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` + + * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS + + * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS + + * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS + + * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` + + * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` + + * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` + + * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` + + * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` + + * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS + + * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` + + * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS + + * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS + + * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS + + * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` + + * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS + + * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS + + * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS + + * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows + + * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS + + * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux + + * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` + + * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` + + * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` + + * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` + + * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` + + * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` + + * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` + + +Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. + +```js +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + ...existingOptions, + windowStateSavePolicy: { + stateId: '#1230', + dimensions: true, + position: { + strictDisplay: true, + }, + displayMode: { + maximized: true, + fullscreen: true, + }, + }, +}); + +// Save additional properties +win.saveWindowPreferences({ + autoHideMenuBar: true, + focusable: true, + visibleOnAllWorkspaces: true, + shadow: true, + menuBarVisible: true, + representedFilename: true, +}); +``` + +#### `win.restoreWindowPreferences()` + +Returns `boolean` - Whether the state was successfully restored and applied to the window. Relevant events would be emitted. +Preferences will be restored in the order of the options object passed during savePreferences. +```js +// Restore the previously saved preferences +const success = win.restoreWindowPreferences() +console.log(success) // true if state was successfully restored +``` + +#### `win.clearSavedState()` + +Clears the saved state for the window **including bounds and save policy itself**. Relevant events would be emitted. + +```js +// Clear the entire saved state for the window +const success = win.clearSavedState() +console.log(success) // true if state was successfully cleared +``` + +#### `win.getSavedState()` + +Returns `JSON Object` - The saved state for the window including bounds, save policy, and preferences. + +```js +// Get the saved state for the window +const savedState = win.getSavedState() +console.log(savedState) +``` +Example output: +```json +{ + "display": { + "id": "#1230", + "scale": 2.0, + "resolution": { + "width": 1920, + "height": 1080 + } + }, + "bounds": { + "width": 800, + "height": 600, + "x": 350, + "y": 400 + }, + "displayMode": { + "normal": true, + "maximized": false, + "minimized": false, + "fullscreen": false, + "simpleFullscreen": false, + "kiosk": false, + }, + "windowStateSavePolicy": {}, // Refer to schema below + "autoHideMenuBar": false, + "backgroundColor": "#FFFFFF", + "title": "gsoc2025", + "minimizable": true, + "maximizable": true, + "fullscreenable": true, + "resizable": true, + "closable": true, + "movable": true, + "excludedFromShownWindowsMenu": true, + "accessibleTitle": "gsoc2025", + "aspectRatio": 1.0, + "minimumSize": { + "width": 800, + "height": 600 + }, + "maximumSize": { + "width": 1000, + "height": 800 + }, + "hiddenInMissionControl": true, +} +``` +`WindowStateSavePolicy` Schema for reference: +```json +"windowStateSavePolicy": { + "stateId": string, + "dimensions": boolean, + "position": { + "strictDisplay": boolean, + "adaptToDisplay": boolean, + "allowOverflow": boolean, + "openBehaviour": string, + "fallbackBehaviour": string, + }, + "displayMode": { + "strictDisplay": boolean, + "maximized": boolean, + "minimized": boolean, + "fullscreen": boolean, + "simpleFullscreen": boolean, __macOS__ + "kiosk": boolean, + }, + } +``` + +### Algorithm for saving/restoring the window state +As mentioned before, we would take a continuous approach to saving window state if the `windowStateSavePolicy` is passed through the constructor. + +Here's an overview of the algorithm that would run during window restoration: + +``` +Calculate new dimensions (width, height) -> Calculate new position (x, y) -> Adjust displayModes based on new (x,y) -> Set window state +``` +Each step is further broken down into more steps to accommodate for the many combinations of the `windowStateSavePolicy`. + +**Calculate dimensions (width, height):** +If `dimensions` is true then we would use the saved width and height. If `dimensions` is false then we would use the default width and height. We would also check if the restored window has some minimum height, width. I think (100, 100) is reasonable for minimum height, width. Any value less than that would be rounded up to 100. + +**Calculate position (x, y):** +Once we have the width and height of the window, we can calculate the position (x, y). +This part can be tricky as we need to take into account the `strictDisplay`, `adaptToDisplay`, `allowOverflow` parameters and make sure the window is never out of bounds. +I've broken it down into 3 parts for simplicity: + +First we would respect the `openBehaviour` and `fallbackBehaviour` parameters. + +Let's take `"openOnLaunchDisplay"` as an example `openBehaviour`. + +>[!NOTE] +> Multiple displays are represented in a unified coordinate space in Electron's window positioning system. Displays are considered the same if they span over the same x, y coordinates and have identical scale, and resolution. + +>[!NOTE] +> Nothing is set on the window until the final step. When a block says something like 'Snap window to the nearest edge' it means recalculate the x,y that would snap the window to the nearest edge. + +```mermaid +flowchart TD + Start[Start Position Calculation] --> OpenBehCheck{openBehaviour specified? openOnLaunchDisplay} + + %% If openBehaviour is specified + OpenBehCheck -->|Yes| AppCheck{Is app already open?} + AppCheck -->|Yes| CursorPos[Use cursor position to determine launched display] + AppCheck -->|No| API[Use platform-specific APIs to detect launched display] + CursorPos --> B{Same display as saved?} + API --> B + B -->|Yes| C[Proceed to next substep] + B -->|No| D{Position preservation enabled?} + D -->|No| E[Use default x,y if the defaults belongs to the same screen else center on expected display] --> H + D -->|Yes| F{adaptToDisplay enabled?} + F -->|Yes| G[Extrapolate x,y based on resolution] + G --> H[Adjust dimensions appropriately if window is too big for screen] --> I[Proceed to next step] + F -->|No| E + + %% If openBehaviour is not specified + OpenBehCheck -->|No| OutOfBounds{x,y out of bounds?} + OutOfBounds -->|Yes| FallbackApply[Apply fallbackBehaviour] + FallbackApply --> J[Proceed directly to adjust displayMode] + OutOfBounds -->|No| I[Proceed to next substep] +``` +
+ +Second step is the crux of the algorithm and a continuation on the first step. When I refer to the new (x,y) I mean the one +calculated in the previous step (because x,y could be different due to `openOnLaunchDisplay` in the first step). +I want to include openOnLaunchDisplay feature because I think it would be a great addition to Electron. + +```mermaid +flowchart TD + NextStep[Next Substep] --> SameDisplay{Is new x,y on the same display as saved?} + + %% Same Display - YES branch + SameDisplay -->|Yes| SavePos1{Should I save position?} + SavePos1 -->|Yes| SaveOverflow{Should I save overflow?} + SaveOverflow -->|Yes| AreaCheck{Is more than 10% of window area on screen?} + AreaCheck -->|Yes| Proceed[Proceed to next step] + AreaCheck -->|No| Snap1[Snap window to nearest edge] + SaveOverflow -->|No| CondSnap[Snap window to nearest edge if there is an overflow, else retain x,y] + SavePos1 -->|No| UseDefault1[Use default x,y if the defaults belongs to the same screen else center on expected display] + UseDefault1 --> AdjustSize0[Adjust height and width if dimensions are too large] + + %% Different Display - NO branch + SameDisplay -->|No| SavePos2{Should I save position?} + SavePos2 -->|Yes| AdaptDisplay{Should I adaptToDisplay? - Redundant but mentioning for clarity} + AdaptDisplay -->|Yes| Adjust1[Extrapolate x,y based on resolution of new screen] + Adjust1 --> AdjustSize1[Adjust height and width if dimensions are too large] + AdaptDisplay -->|No| Center1[Use default x,y if the defaults belongs to the same screen else center on expected display] + Center1 --> AdjustSize2[Adjust height and width if dimensions are too large] + SavePos2 -->|No| UseDefault2[Use default x,y if the defaults belongs to the same screen else center on expected display] + UseDefault2 --> AdjustSize3[Adjust height and width if dimensions are too large] +``` + +
+ +>[!NOTE] +> Nothing is set on the window until the final step. + +**Adjust displayModes:** +The Algorithm would adjust the `displayModes` based on its `strictDisplay` and recalculated position (x,y). Only if the recalculated position is on the same display as the saved (x,y) we would consider it as being on the same display. This is because displays could change flexibly with `openBehaviour` parameter. If any rules are broken the window will be restored in `normal` mode. + +**Set window state:** +Once we have the width, height, x, y, and displayModes we can set the window state. `displayMode` would take precedence over the saved bounds. + +## Guide-level explanation + +*Explain the feature as if it were already implemented in Electron and you were teaching it to +an Electron app developer.* + +Electron is introducing `windowStateSavePolicy`, an optional object in the `BrowserWindowConstructorOptions`. +It can be used to save and restore window state with multiple configurations. + +`windowStateSavePolicy` schema - Everything is optional except `stateId`. Developers can choose what they want to preserve and how they want to restore it. + +```json +"windowStateSavePolicy": { + "stateId": string, + "dimensions": boolean, + "position": { + "strictDisplay": boolean, + "adaptToDisplay": boolean, + "allowOverflow": boolean, + "openBehaviour": string, + "fallbackBehaviour": string, + }, + "displayMode": { + "strictDisplay": boolean, + "maximized": boolean, + "minimized": boolean, + "fullscreen": boolean, + "simpleFullscreen": boolean, __macOS__ + "kiosk": boolean, + }, + } +``` + +Here's an example that would let you save the dimensions (width, height), and whether the window is maximized, fullscreen. This configuration would **not** restore the window position when reopened. +```js +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + ...existingOptions, + windowStateSavePolicy: { + stateId: '#1230', + dimensions: true, + displayMode: { + maximized: true, + fullscreen: true, + }, + + }, +}) +``` +To save the window position, you can set the `position` property to a configurable object that will determine how the window position is restored. + +```js +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + ...existingOptions, + windowStateSavePolicy: { + stateId: '#1230', + dimensions: true, + position: { + strictDisplay: true, + }, + displayMode: { + maximized: true, + fullscreen: true, + }, + }, +}) +``` + +The above example would save the window position only if it reopens on the same display because `strictDisplay` is true. If `adaptToDisplay` was true it would restore "relative" position even if reopened on a different display. + +A window could reopen on a different display due to the `openBehaviour` property which is an option inside the `position` object. + +*Discuss how this impacts the ability to read, understand, and maintain Electron code. Will the +proposed feature make Electron code more maintainable? How difficult is the upgrade path for +existing apps?* + +It would be the same code for Windows, macOS, and Linux using Chromium's PrefService. I think it would be easy to read, understand, and maintain the new Electron code. + +While I'm not sure about this 100%, I don't think it would introduce any breaking changes. I am unclear about the interaction with other features. + +The path to upgrade for apps would be developers removing their existing implementation and using this new API if they want to. + + +## Reference-level explanation + +Covered in the [Implementation Details](#implementation-details) section. + + +## Drawbacks +- Writing to disk everytime window is moved or resized is not efficient, even though it's batched to a 2.5 second window. It might not be necessary and better to write to disk only on window close synchronously. +- Similar outcomes could be achieved via JavaScript APIs with miniscule performance difference. The only issue being the window state is not set at the right time in the window lifecycle. + +Why should we *not* do this? +- It's not a critical issue. +- Adds maintenance burden for the Electron team to support this feature long-term. + +## Rationale and alternatives + +*Why is this design the best in the space of possible designs?* + + Overall, providing a constructor option is the best design in my opinion. It provides maximum flexibility and future-proofing for different requests in the future. It also sets the window properties at the right time in the window lifecycle. Although not perfect right now, it can be improved by the community based on different use cases quite easily. We're also saving the window state in a continuous manner so it could be restored even after crashes. + +*What other designs have been considered and what is the rationale for not choosing them?* + + I've considered a JavaScript API that could be used. But it's not the best option since it would be setting the window state again after creation. + +*What is the impact of not doing this?* + +It's not a critical issue. Apps might vanish on rare occasions. + +*If this is an API proposal, could this be done as a JavaScript module or a native Node.js add-on?* + +The real value proposition isn't that this functionality can't be implemented in JavaScript - it absolutely can and has been attempted through various community libraries. Rather, the value lies in providing a standardized, well-maintained solution integrated directly into Electron's core. There's also miniscule performance benefits as it would avoid extra ipc calls while restoring window state. + +## Prior art + +Electron devTools persists bounds using PrefService. Implementation can be seen here [inspectable_web_contents.cc](https://github.com/electron/electron/blob/73a017577e6d8cf67c76acb8f6a199c2b64ccb5d/shell/browser/ui/inspectable_web_contents.cc#L509). +It also seems likely Chrome uses PrefService to store their window bounds [reference](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/chrome/browser/prefs/README.md). + +From my research, I found out native applications built directly with the operating system's own tools and frameworks often get window state persistence for free (macOS, Windows). +I thought it would be innapropriate to enforce such rules on Electron apps. Thus, `windowStateSavePolicy` to provide flexibility and the choice to opt-in to this behavior. + +## Unresolved questions +*What parts of the design do you expect to resolve through the RFC process before this gets merged?* +- Variable names and the entire API Spec. +- Would there be any breaking changes? I am unclear about the interaction with other features. I would like to know if I'm missing anything. +- 2.5 seconds window appropriate? Should it be configurable? Should we not do this at all and just save it in the end synchronously? +- Should we permit more than 90% overflow of the window area? (I would like to restrict it and snap to the nearest display edge if more than 90% overflow) +- What about other display modes such as split screen? Should we save and restore in the 'normal' display mode with similar dimensions? +- Are there any other edge cases that this proposal missed? + +*What parts of the design do you expect to resolve through the implementation of this feature +before stabilization?* + +Not much. I would need to figure out if there are platform specific APIs that could be used for properties such as `openOnLaunchDisplay`, `openOnPrimaryDisplay`. + +*What related issues do you consider out of scope for this RFC that could be addressed in the +future independently of the solution that comes out of this RFC?* + +Other display modes like split screen. I'm not sure if we should handle that. We can save and restore in the 'normal' display mode with similar dimensions. + +## Future possibilities + +Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. +One particular cool feature would be to provide an option to restore the window on closest available display/space dynamically. + +APIs to allow changes in `windowStateSavePolicy` during runtime for apps that want to let users decide save/restore behaviour. + +Overall, the `windowStateSavePolicy` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 06fb244e06c561559cc6621188b9e9dd82a4fa2f Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 5 Jun 2025 22:30:01 +0530 Subject: [PATCH 02/26] update: smaller api surface --- text/0013-save-restore-window-state.md | 365 +++++-------------------- 1 file changed, 75 insertions(+), 290 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index b43a61e..1984bca 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -14,7 +14,7 @@ Currently, Electron does not have any built-in mechanism for saving and restorin ## Summary -This proposal aims to implement a save/restore window state API for Electron by providing a simple but powerful configuration object `windowStateSavePolicy` that handles complex edge cases automatically. This approach offers a declarative way to configure window persistence behavior while maintaining flexibility for different application needs. The object would be optional in the `BrowserWindowConstructorOptions`. +This proposal aims to implement a save/restore window state API for Electron by providing a simple but powerful configuration object `windowStateRestoreOptions` that handles complex edge cases automatically. This approach offers a declarative way to configure window persistence behavior while maintaining flexibility for different application needs. The object would be optional in the `BaseWindowConstructorOptions`. ## Motivation @@ -26,7 +26,7 @@ Window state persistence represents a core functionality that many production El It's useful for developers who want to implement save/restore window state for their apps reliably. Different apps have different needs. An example use case for this API would be for when apps want to preserve window position (with display overflow) and size across multi-monitor setups with different display scales and resolutions. -`windowStateSavePolicy` aims to cover most save/restore use cases while providing room for future extension. +`windowStateRestoreOptions` aims to cover most save/restore use cases while providing room for future extension. **What is the expected outcome?** @@ -40,21 +40,21 @@ Before diving into the API specifications which is the next section, I’d like We can use Chromium's [PrefService](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h) to store window state as a JSON object into the already existing Preferences folder inside *app.getPath('userData')*. -We schedule an async write to disk every time a window is moved or resized with a 2.5 second window. If there is change in the window state within the time window, it is reset to 2.5 seconds and we would schedule a write again with the new values. Otherwise, it is written to disk. It is also written to disk on window close synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** +We schedule an async write to disk every time a window is moved or resized and batch write every 10 seconds (I will explain why later). We also write to disk on window close synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** **Questions that might arise:** -**Why 2.5 seconds?** -It seems like a reasonable time window to ensure related window adjustments are batched together. I think we can assume that users are done moving/resizing the window if there is no movement for 2.5 seconds. The default value used by Chromium's PrefService is 10 seconds [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)] which is too long. -I don't think we should make this configurable as well because it's not intuitive to the developer. +**Why 10 seconds?** +The default time window used by Chromium's PrefService is 10 seconds [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)]. +I think reusing the existing implementation and retaining the 10-second commit interval would be a simple and effective approach. -**What if the app crashes in the 2.5 second window?** Revert back to where the window was 2.5 seconds before the crash. This wouldn't be the worst user experience in the world. +**What if the app crashes in the 10 second window?** The window could possibly restore where it was a few seconds ago (worst case 10 seconds ago). This wouldn't be the worst user experience in the world. #### *Why PrefService?* - Atomic writes using ImportantFileWriter [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.h;l=28-29)] - Async writes which means no blocking of the UI thread, preventing jank during window state updates [[reference](https://docs.google.com/document/d/1rlwl_GvvokatMiRUUkR0vGNcraqe4weFGNilTsZgiXA/edit?tab=t.0#bookmark=id.49710km71rd7)] - Synchronous writes on demand [[reference](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h;l=199-209;drc=51cc784590d00e6a95b48e1e1bf5c3fe099edf64)] -- Offers batched scheduled writes (using ImportantFileWriter) every 10 second window [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)]. This is too long for our use-case, but we can conveniently set it to 2.5 seconds +- Offers batched scheduled writes (using ImportantFileWriter) every 10 second window [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)]. - Already implemented in Electron to preserve the devTools bounds [[reference](https://github.com/electron/electron/blob/73a017577e6d8cf67c76acb8f6a199c2b64ccb5d/shell/browser/ui/inspectable_web_contents.cc#L509)] - Provides identical behavior across Windows, macOS, and Linux, simplifying implementation and maintenance - Falls back to default values automatically when corrupted data is encountered [[reference](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h;l=233-235;drc=51cc784590d00e6a95b48e1e1bf5c3fe099edf64)] @@ -62,45 +62,33 @@ I don't think we should make this configurable as well because it's not intuitiv
-Here's the schema I propose for the `windowStateSavePolicy` object. This is simply for reference as I have not explained what each field means yet. I will explain each field in the API specification section which is next. +Here's the schema I propose for the `windowStateRestoreOptions` object. This is simply for reference as I have not explained what each field means yet. I will explain each field in the API specification section which is next.
-`windowStateSavePolicy` schema - This would be passed by the developer in the BrowserWindowConstructorOptions +`windowStateRestoreOptions` schema - This would be passed by the developer in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions ```json -"windowStateSavePolicy": { +"windowStateRestoreOptions": { "stateId": string, - "dimensions": boolean, - "position": { - "strictDisplay": boolean, - "adaptToDisplay": boolean, - "allowOverflow": boolean, - "openBehaviour": string, - "fallbackBehaviour": string, - }, - "displayMode": { - "strictDisplay": boolean, - "maximized": boolean, - "minimized": boolean, - "fullscreen": boolean, - "simpleFullscreen": boolean, __macOS__ - "kiosk": boolean, - }, + "bounds": true, + "displayMode": true } ``` > [!NOTE] -> The entire window state (bounds, maximized, minimized, fullscreen, etc.) would be saved internally along with the windowStateSavePolicy object. +> The entire window state (bounds, maximized, minimized, fullscreen, etc.) would be saved internally and restored using the provided `windowStateRestoreOptions` config > We would use the saved state and enforce the policy during restoration. #### **State Persistence Mechanism** -Firstly, all the window states with their `windowStateSavePolicy` would be to loaded from disk synchronously during the startup process just like other preferences in Electron. Doing so would allow us the have the states in memory during window creation with minimal performance impact. +Firstly, all the window states with their `windowStateRestoreOptions` would be to loaded from disk synchronously during the startup process just like other preferences in Electron. Doing so would allow us the have the states in memory during window creation with minimal performance impact. -Secondly, once the states (containing our save policy) are loaded into memory, we can use them to restore the window state with the `windowStateSavePolicy` rules in place during window creation. An algorithm that handles all the edges would be required for this. I have explained the flow of the algorithm after the API specification. Also, the default options provided in the BrowserWindow constructor options would be overridden (if we are restoring state). +Secondly, once the states are loaded into memory, we can use them to restore the window state with the `windowStateRestoreOptions` rules in place during window creation. An algorithm that handles all the edges would be required for this. Chromium's [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) seems like a good reference point that handles some of the edge cases. Also, the default options provided in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions constructor options would be overridden (if we are restoring state). Each time a window is **moved** or **resized**, we schedule a write using Chromium's ScopedDictPrefUpdate [[example](https://github.com/electron/electron/blob/4ad20ccb396a3354d99b8843426bece2b28228bf/shell/browser/ui/inspectable_web_contents.cc#L837-L841)]. +Here's a reference to all the variables we would saving internally using PrefService https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/chrome_views_delegate.cc;drc=1f9f3d7d4227fc2021915926b0e9f934cd610201;l=85 + #### Developer-Facing Events I’m also considering emitting events such as: `will-save-window-state`, `saved-window-state`, `will-restore-window-state`, `restored-window-state`. However, I’m unsure about their practical usefulness for developers and hope to finalize them through this RFC. @@ -110,78 +98,36 @@ I’m also considering emitting events such as: `will-save-window-state`, `saved Here's how it would land in the electron documentation. -### BrowserWindowConstructorOptions +### BaseWindowConstructorOptions -`windowStateSavePolicy` object (optional) +`windowStateRestoreOptions` object (optional) * `stateId` string - A unique identifier used for saving and restoring window state. - * `dimensions` boolean (optional) - Whether to restore the window's height and width. Fallback is default values. When restoring, window will automatically resize to fit within available screen real estate if height or width is too large. Dimensions will be resaved. - - * `position` object (optional) - - * `strictDisplay` boolean (optional) - Whether to restore window position (x, y) only if reopened on the same display (displays are considered the same if they span over the same x, y coordinates and have identical scale, and resolution). Default is `false`. - - * `adaptToDisplay` boolean (optional) - Whether to restore window position (x, y) when opened on different displays by coordinate extrapolation based on display resolution. Overrides `strictDisplay` and will position the window at a relatively similar spot on the new display. Default is `false`. - - * `allowOverflow` boolean (optional) - Whether to allow a window to be restored in a partially off-screen position. **Overflow is never preserved in the event of the window opening on a different display.** When `true`, overflow is only persisted if window is relauched on the same display and at least 10% of the window area is visible. If overflow is greater than 90% it will reopen at the closest edge of the display, fully visible. Default is `false`. - - * `openBehaviour` string (optional) - Special behavior when the window is opened. - * `"openOnLaunchedDisplay"` - Opens the window on the display that it was launched from always. - - * `"openOnPrimaryDisplay"` - Opens the window on the primary display always. - - * `fallbackBehaviour` string (optional) - Fallback behaviour when position is out of bounds [[read more](https://gist.github.com/nilayarya/6a5ae863f99f269f0759f6f4f86e91b5)]. - * `"snapToNearestDisplayEdge"` - Snaps the window to the nearest display edge. It is also the default behaviour when a window is out of bounds and `fallbackBehaviour` is not provided. - - * `"centerOnNearestDisplay"` - Centers the window on the nearest display. + * `bounds` boolean (optional) - Whether to restore the window's x, y, height, width. Default is true. - * `"centerOnPrimaryDisplay"` - Centers the window on the primary display. - - * `displayMode` object (optional) - Controls which display modes will be restored. - - * `strictDisplay` boolean (optional) - Whether to restore `displayMode` only if reopened on the same display. Otherwise, it is always preserved by default even across displays. Default is `false`. - - * `maximized` boolean (optional) - Whether to restore maximized state. Default is `false`. - - * `minimized` boolean (optional) - Whether to restore minimized state. Default is `false`. - - * `fullscreen` boolean (optional) - Whether to restore fullscreen state. Default is `false`. - - * `simpleFullscreen` boolean (optional) - Whether to restore simple fullscreen state. Default is `false`. - - * `kiosk` boolean (optional) - Whether to restore kiosk state. Default is `false`. - + * `displayMode` boolean (optional) - Whether to restore the window's display mode (fullscreen, kiosk, maximized, etc). Default is true. -Example usage that would cover most apps: +Example usage: ```js const { BrowserWindow } = require('electron') const win = new BrowserWindow({ ...existingOptions, - windowStateSavePolicy: { + windowStateRestoreOptions: { stateId: '#1230', - dimensions: true, - position: { - strictDisplay: true, - allowOverflow: true, - fallbackBehaviour: "snapToNearestDisplayEdge", - }, - displayMode: { - maximized: true, - fullscreen: true, - }, + displayMode: false }, }) ``` ### API Design Rationale: -- Everything is set to false by default so that the developer knows what they are toggling. -- Displays are considered the same if they span over the same x, y coordinates and have identical scale, and resolution. So for example, replacing your primary display with same resolution and scale but different manufacturer will still be considered the same display. I think this makes sense. -- A window can never be restored out of reach. I am presuming no apps would want this behavior. `fallbackBehaviour` will take effect if the window is entirely off screen. We are still providing the option to relaunch in a minimized state. -- If window (width, height) reopens on a different display and does not fit on screen auto adjust to fit and resave the value (even if allowOverflow=true). This would reduce the number of edge cases significantly and I highly doubt that any app would want to preserve an overflow when opened on a different display. +- Everything is set to true by default so that the developer can set whatever they want to exclude to false. This means less lines of code as more options are added to the config. +- Displays are considered the same if they span over the same work_area dimensions and have identical scale. So for example, replacing your primary display with same resolution and scale but different manufacturer will still be considered the same display. I think this makes sense. +- A window can never be restored out of reach. I am presuming no apps would want this behavior. +- If window (width, height) reopens on a different display and does not fit on screen auto adjust to fit and resave the value. This would reduce the number of edge cases significantly and I highly doubt that any app would want to preserve an overflow when opened on a different display. - Not handling scaling as Windows, macOS, and Linux support multimonitor window scaling by default. -- Not handling other `displayModes` such as split screen because it's innapropriate I believe. We can restore back to the same bounds in 'normal' mode. I've excluded 'normal' from the options for `displayMode` as it is the default. +- We treat split screen states as normal on Windows, Linux as this is default for the OS. For macOS, behavior to restore split screen needs to be finalized as the default is fullscreen for split view. ### Additional APIs I also plan on adding synchronous methods to `BrowserWindow` to save, restore, clear, and get the window state or rather window preferences. Down below is the API spec for that. @@ -192,7 +138,7 @@ I think this feature is more of a nice to have and not a must have. >[!NOTE] > Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). -#### `win.saveWindowPreferences([options])` +#### `win.savePreferences([options])` * `options` Object @@ -279,21 +225,13 @@ Returns `boolean` - Whether the state was successfully saved. Relevant events wo const { BrowserWindow } = require('electron') const win = new BrowserWindow({ ...existingOptions, - windowStateSavePolicy: { + windowStateRestoreOptions: { stateId: '#1230', - dimensions: true, - position: { - strictDisplay: true, - }, - displayMode: { - maximized: true, - fullscreen: true, - }, }, }); // Save additional properties -win.saveWindowPreferences({ +win.savePreferences({ autoHideMenuBar: true, focusable: true, visibleOnAllWorkspaces: true, @@ -303,9 +241,9 @@ win.saveWindowPreferences({ }); ``` -#### `win.restoreWindowPreferences()` +#### `win.restorePreferences()` -Returns `boolean` - Whether the state was successfully restored and applied to the window. Relevant events would be emitted. +Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. Preferences will be restored in the order of the options object passed during savePreferences. ```js // Restore the previously saved preferences @@ -313,7 +251,7 @@ const success = win.restoreWindowPreferences() console.log(success) // true if state was successfully restored ``` -#### `win.clearSavedState()` +#### `win.clearPreferences()` Clears the saved state for the window **including bounds and save policy itself**. Relevant events would be emitted. @@ -325,7 +263,7 @@ console.log(success) // true if state was successfully cleared #### `win.getSavedState()` -Returns `JSON Object` - The saved state for the window including bounds, save policy, and preferences. +Returns `JSON Object` - The saved state for the window including bounds, display info and preferences. ```js // Get the saved state for the window @@ -335,29 +273,20 @@ console.log(savedState) Example output: ```json { - "display": { - "id": "#1230", - "scale": 2.0, - "resolution": { - "width": 1920, - "height": 1080 - } - }, - "bounds": { - "width": 800, - "height": 600, - "x": 350, - "y": 400 - }, - "displayMode": { - "normal": true, - "maximized": false, - "minimized": false, - "fullscreen": false, - "simpleFullscreen": false, - "kiosk": false, - }, - "windowStateSavePolicy": {}, // Refer to schema below + "stateId": "#1230", + "top": 25, + "bottom": 847, + "left": 0, + "right": 718, + "fullscreen": false, + "maximized": false, + "minimized": false, + "kiosk": false, + "work_area_bottom": 847, + "work_area_left": 0, + "work_area_right": 1440, + "work_area_top": 25, + // Only Saved BrowserWindow Preferences "autoHideMenuBar": false, "backgroundColor": "#FFFFFF", "title": "gsoc2025", @@ -381,119 +310,23 @@ Example output: "hiddenInMissionControl": true, } ``` -`WindowStateSavePolicy` Schema for reference: +`windowStateRestoreOptions` Schema for reference: ```json -"windowStateSavePolicy": { +"windowStateRestoreOptions": { "stateId": string, - "dimensions": boolean, - "position": { - "strictDisplay": boolean, - "adaptToDisplay": boolean, - "allowOverflow": boolean, - "openBehaviour": string, - "fallbackBehaviour": string, - }, - "displayMode": { - "strictDisplay": boolean, - "maximized": boolean, - "minimized": boolean, - "fullscreen": boolean, - "simpleFullscreen": boolean, __macOS__ - "kiosk": boolean, - }, + "bounds": boolean, + "displayMode": boolean, } ``` ### Algorithm for saving/restoring the window state -As mentioned before, we would take a continuous approach to saving window state if the `windowStateSavePolicy` is passed through the constructor. - -Here's an overview of the algorithm that would run during window restoration: - -``` -Calculate new dimensions (width, height) -> Calculate new position (x, y) -> Adjust displayModes based on new (x,y) -> Set window state -``` -Each step is further broken down into more steps to accommodate for the many combinations of the `windowStateSavePolicy`. - -**Calculate dimensions (width, height):** -If `dimensions` is true then we would use the saved width and height. If `dimensions` is false then we would use the default width and height. We would also check if the restored window has some minimum height, width. I think (100, 100) is reasonable for minimum height, width. Any value less than that would be rounded up to 100. +As mentioned before, we would take a continuous approach to saving window state if the `windowStateRestoreOptions` is passed through the constructor. -**Calculate position (x, y):** -Once we have the width and height of the window, we can calculate the position (x, y). -This part can be tricky as we need to take into account the `strictDisplay`, `adaptToDisplay`, `allowOverflow` parameters and make sure the window is never out of bounds. -I've broken it down into 3 parts for simplicity: +The algorithm would be simple two step approach: -First we would respect the `openBehaviour` and `fallbackBehaviour` parameters. - -Let's take `"openOnLaunchDisplay"` as an example `openBehaviour`. - ->[!NOTE] -> Multiple displays are represented in a unified coordinate space in Electron's window positioning system. Displays are considered the same if they span over the same x, y coordinates and have identical scale, and resolution. - ->[!NOTE] -> Nothing is set on the window until the final step. When a block says something like 'Snap window to the nearest edge' it means recalculate the x,y that would snap the window to the nearest edge. - -```mermaid -flowchart TD - Start[Start Position Calculation] --> OpenBehCheck{openBehaviour specified? openOnLaunchDisplay} - - %% If openBehaviour is specified - OpenBehCheck -->|Yes| AppCheck{Is app already open?} - AppCheck -->|Yes| CursorPos[Use cursor position to determine launched display] - AppCheck -->|No| API[Use platform-specific APIs to detect launched display] - CursorPos --> B{Same display as saved?} - API --> B - B -->|Yes| C[Proceed to next substep] - B -->|No| D{Position preservation enabled?} - D -->|No| E[Use default x,y if the defaults belongs to the same screen else center on expected display] --> H - D -->|Yes| F{adaptToDisplay enabled?} - F -->|Yes| G[Extrapolate x,y based on resolution] - G --> H[Adjust dimensions appropriately if window is too big for screen] --> I[Proceed to next step] - F -->|No| E - - %% If openBehaviour is not specified - OpenBehCheck -->|No| OutOfBounds{x,y out of bounds?} - OutOfBounds -->|Yes| FallbackApply[Apply fallbackBehaviour] - FallbackApply --> J[Proceed directly to adjust displayMode] - OutOfBounds -->|No| I[Proceed to next substep] -``` -
- -Second step is the crux of the algorithm and a continuation on the first step. When I refer to the new (x,y) I mean the one -calculated in the previous step (because x,y could be different due to `openOnLaunchDisplay` in the first step). -I want to include openOnLaunchDisplay feature because I think it would be a great addition to Electron. - -```mermaid -flowchart TD - NextStep[Next Substep] --> SameDisplay{Is new x,y on the same display as saved?} - - %% Same Display - YES branch - SameDisplay -->|Yes| SavePos1{Should I save position?} - SavePos1 -->|Yes| SaveOverflow{Should I save overflow?} - SaveOverflow -->|Yes| AreaCheck{Is more than 10% of window area on screen?} - AreaCheck -->|Yes| Proceed[Proceed to next step] - AreaCheck -->|No| Snap1[Snap window to nearest edge] - SaveOverflow -->|No| CondSnap[Snap window to nearest edge if there is an overflow, else retain x,y] - SavePos1 -->|No| UseDefault1[Use default x,y if the defaults belongs to the same screen else center on expected display] - UseDefault1 --> AdjustSize0[Adjust height and width if dimensions are too large] - - %% Different Display - NO branch - SameDisplay -->|No| SavePos2{Should I save position?} - SavePos2 -->|Yes| AdaptDisplay{Should I adaptToDisplay? - Redundant but mentioning for clarity} - AdaptDisplay -->|Yes| Adjust1[Extrapolate x,y based on resolution of new screen] - Adjust1 --> AdjustSize1[Adjust height and width if dimensions are too large] - AdaptDisplay -->|No| Center1[Use default x,y if the defaults belongs to the same screen else center on expected display] - Center1 --> AdjustSize2[Adjust height and width if dimensions are too large] - SavePos2 -->|No| UseDefault2[Use default x,y if the defaults belongs to the same screen else center on expected display] - UseDefault2 --> AdjustSize3[Adjust height and width if dimensions are too large] -``` - -
- ->[!NOTE] -> Nothing is set on the window until the final step. - -**Adjust displayModes:** -The Algorithm would adjust the `displayModes` based on its `strictDisplay` and recalculated position (x,y). Only if the recalculated position is on the same display as the saved (x,y) we would consider it as being on the same display. This is because displays could change flexibly with `openBehaviour` parameter. If any rules are broken the window will be restored in `normal` mode. +**Calculate new bounds:** +This step invovles checking if the window is reopened on the same display by comparing the current work_area to the saved work_area, setting minimum height and width for the window (100, 100), ensuring proper fallback behaviour for when window is restored out of bounds or with overflow +We can use this as reference implementation: [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) **Set window state:** Once we have the width, height, x, y, and displayModes we can set the window state. `displayMode` would take precedence over the saved bounds. @@ -503,82 +336,39 @@ Once we have the width, height, x, y, and displayModes we can set the window sta *Explain the feature as if it were already implemented in Electron and you were teaching it to an Electron app developer.* -Electron is introducing `windowStateSavePolicy`, an optional object in the `BrowserWindowConstructorOptions`. +Electron is introducing `windowStateRestoreOptions`, an optional object in the `BaseWindowConstructorOptions` and would also be available in `BrowserWindowConstructorOptions`. It can be used to save and restore window state with multiple configurations. -`windowStateSavePolicy` schema - Everything is optional except `stateId`. Developers can choose what they want to preserve and how they want to restore it. +`windowStateRestoreOptions` schema - Everything is optional except `stateId`. Developers can choose what they want to preserve and how they want to restore it. ```json -"windowStateSavePolicy": { +"windowStateRestoreOptions": { "stateId": string, - "dimensions": boolean, - "position": { - "strictDisplay": boolean, - "adaptToDisplay": boolean, - "allowOverflow": boolean, - "openBehaviour": string, - "fallbackBehaviour": string, - }, - "displayMode": { - "strictDisplay": boolean, - "maximized": boolean, - "minimized": boolean, - "fullscreen": boolean, - "simpleFullscreen": boolean, __macOS__ - "kiosk": boolean, - }, + "bounds": boolean, + "displayMode": boolean } ``` -Here's an example that would let you save the dimensions (width, height), and whether the window is maximized, fullscreen. This configuration would **not** restore the window position when reopened. -```js -const { BrowserWindow } = require('electron') -const win = new BrowserWindow({ - ...existingOptions, - windowStateSavePolicy: { - stateId: '#1230', - dimensions: true, - displayMode: { - maximized: true, - fullscreen: true, - }, - - }, -}) -``` -To save the window position, you can set the `position` property to a configurable object that will determine how the window position is restored. - +Here's an example that would let you restore bounds (x, y, width, height) but not the display mode (maximized, minimized, fullscreen etc) ```js const { BrowserWindow } = require('electron') const win = new BrowserWindow({ ...existingOptions, - windowStateSavePolicy: { + windowStateRestoreOptions: { stateId: '#1230', - dimensions: true, - position: { - strictDisplay: true, - }, - displayMode: { - maximized: true, - fullscreen: true, - }, + bounds: true, + displayMode: false }, }) ``` -The above example would save the window position only if it reopens on the same display because `strictDisplay` is true. If `adaptToDisplay` was true it would restore "relative" position even if reopened on a different display. - -A window could reopen on a different display due to the `openBehaviour` property which is an option inside the `position` object. - *Discuss how this impacts the ability to read, understand, and maintain Electron code. Will the proposed feature make Electron code more maintainable? How difficult is the upgrade path for existing apps?* It would be the same code for Windows, macOS, and Linux using Chromium's PrefService. I think it would be easy to read, understand, and maintain the new Electron code. -While I'm not sure about this 100%, I don't think it would introduce any breaking changes. I am unclear about the interaction with other features. - -The path to upgrade for apps would be developers removing their existing implementation and using this new API if they want to. +The path to upgrade for apps would be developers removing their existing implementation and using this new API if they want to. If their implementation is on the JavaScript side it will always execute their restore logic after window construction which means our implementation would be overridden. ## Reference-level explanation @@ -587,7 +377,7 @@ Covered in the [Implementation Details](#implementation-details) section. ## Drawbacks -- Writing to disk everytime window is moved or resized is not efficient, even though it's batched to a 2.5 second window. It might not be necessary and better to write to disk only on window close synchronously. +- Writing to disk everytime window is moved or resized in a batched 10 second window might not be necessary and better to write to disk only on window close synchronously. - Similar outcomes could be achieved via JavaScript APIs with miniscule performance difference. The only issue being the window state is not set at the right time in the window lifecycle. Why should we *not* do this? @@ -618,16 +408,13 @@ Electron devTools persists bounds using PrefService. Implementation can be seen It also seems likely Chrome uses PrefService to store their window bounds [reference](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/chrome/browser/prefs/README.md). From my research, I found out native applications built directly with the operating system's own tools and frameworks often get window state persistence for free (macOS, Windows). -I thought it would be innapropriate to enforce such rules on Electron apps. Thus, `windowStateSavePolicy` to provide flexibility and the choice to opt-in to this behavior. +I thought it would be innapropriate to enforce such rules on Electron apps. Thus, `windowStateRestoreOptions` to provide flexibility and the choice to opt-in to this behavior. ## Unresolved questions *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Variable names and the entire API Spec. -- Would there be any breaking changes? I am unclear about the interaction with other features. I would like to know if I'm missing anything. -- 2.5 seconds window appropriate? Should it be configurable? Should we not do this at all and just save it in the end synchronously? -- Should we permit more than 90% overflow of the window area? (I would like to restrict it and snap to the nearest display edge if more than 90% overflow) -- What about other display modes such as split screen? Should we save and restore in the 'normal' display mode with similar dimensions? - Are there any other edge cases that this proposal missed? +- Restoring split screen for macOS *What parts of the design do you expect to resolve through the implementation of this feature before stabilization?* @@ -637,13 +424,11 @@ Not much. I would need to figure out if there are platform specific APIs that co *What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?* -Other display modes like split screen. I'm not sure if we should handle that. We can save and restore in the 'normal' display mode with similar dimensions. - ## Future possibilities Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. One particular cool feature would be to provide an option to restore the window on closest available display/space dynamically. -APIs to allow changes in `windowStateSavePolicy` during runtime for apps that want to let users decide save/restore behaviour. +APIs to allow changes in `windowStateRestoreOptions` during runtime for apps that want to let users decide save/restore behaviour. -Overall, the `windowStateSavePolicy` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file +Overall, the `windowStateRestoreOptions` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From f882e8bf3b082aeb7733a5df727bd0050d8d83f1 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 6 Jun 2025 00:41:17 +0530 Subject: [PATCH 03/26] future possibilities --- text/0013-save-restore-window-state.md | 53 +++++++++++++++++++++----- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index 1984bca..b7630d7 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -76,8 +76,8 @@ Here's the schema I propose for the `windowStateRestoreOptions` object. This is } ``` > [!NOTE] -> The entire window state (bounds, maximized, minimized, fullscreen, etc.) would be saved internally and restored using the provided `windowStateRestoreOptions` config -> We would use the saved state and enforce the policy during restoration. +> The entire window state (bounds, maximized, minimized, fullscreen, etc.) would be saved internally and restored using the provided `windowStateRestoreOptions` config. +> We would use the saved state and enforce the chosen config during restoration. #### **State Persistence Mechanism** @@ -133,7 +133,6 @@ const win = new BrowserWindow({ I also plan on adding synchronous methods to `BrowserWindow` to save, restore, clear, and get the window state or rather window preferences. Down below is the API spec for that. Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. -I think this feature is more of a nice to have and not a must have. >[!NOTE] > Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). @@ -227,7 +226,7 @@ const win = new BrowserWindow({ ...existingOptions, windowStateRestoreOptions: { stateId: '#1230', - }, + } }); // Save additional properties @@ -320,13 +319,15 @@ Example output: ``` ### Algorithm for saving/restoring the window state -As mentioned before, we would take a continuous approach to saving window state if the `windowStateRestoreOptions` is passed through the constructor. +As mentioned before, we would take a continuous approach to saving window state if the `windowStateRestoreOptions` is passed through the constructor. + +For the preferences it will be important to store the order of the properties as well when win.savePreferences is called due to possible conflicting properties. We can restore them in the same order and it will be as though the properties were set via JavaScript APIs in the exact same order. The algorithm would be simple two step approach: **Calculate new bounds:** -This step invovles checking if the window is reopened on the same display by comparing the current work_area to the saved work_area, setting minimum height and width for the window (100, 100), ensuring proper fallback behaviour for when window is restored out of bounds or with overflow -We can use this as reference implementation: [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) +This step invovles checking if the window is reopened on the same display by comparing the current work_area to the saved work_area, setting minimum height and width for the window (100, 100) if it's too less, ensuring proper fallback behaviour for when window is restored out of bounds or with overflow +There are some tricks we can use from [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) **Set window state:** Once we have the width, height, x, y, and displayModes we can set the window state. `displayMode` would take precedence over the saved bounds. @@ -414,12 +415,12 @@ I thought it would be innapropriate to enforce such rules on Electron apps. Thus *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Variable names and the entire API Spec. - Are there any other edge cases that this proposal missed? -- Restoring split screen for macOS +- Restoring split screen for macOS. Should it be treated as normal or fullscreen restore? I think we can go ahead restoring as though it was fullscreened as apps need to go fullscreen to have a split view on macOS. *What parts of the design do you expect to resolve through the implementation of this feature before stabilization?* -Not much. I would need to figure out if there are platform specific APIs that could be used for properties such as `openOnLaunchDisplay`, `openOnPrimaryDisplay`. +Implementation should be straightforward. I was wondering if there would be any delay setting displayMode during window construction since functions such as SetFullScreen are platform dependent and async under the hood in Electron. *What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?* @@ -427,8 +428,42 @@ future independently of the solution that comes out of this RFC?* ## Future possibilities Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. + +* `openBehaviour` string (optional) - Special behavior when the window is opened. + + * `openOnLaunchedDisplay` - Opens the window on the display that it was launched from always. + + * `openOnPrimaryDisplay` - Opens the window on the primary display always. + + +* `fallbackBehaviour` string (optional) - Fallback behaviour when position is out of bounds. + + * `"snapToNearestDisplayEdge"` - Snaps the window to the nearest display edge. + + * `"centerOnNearestDisplay"` - Centers the window on the nearest display. + + * `"centerOnPrimaryDisplay"` - Centers the window on the primary display. + + +We would need an algorithm that calculates bounds based on these parameters. +During restore we would have to respect `openBehaviour` before restoring our state. + +Default `fallbackBehaviour` would be `snapToNearestDisplayEdge` if out of bounds. +This parameter would provide a declarative way to define how the window should behave if it restored out of bounds. + +Both of these were part of the initial GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6) + +The algorithm to restore window state is detailed in this [section](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state) + One particular cool feature would be to provide an option to restore the window on closest available display/space dynamically. +The `bounds` property could possible switched from boolean to an object that allows more configurability and control over reopening windows +on different monitors with different dpi scaling and resolution. + +We could add `allowOverflow` property inside the object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402). + +Again, everything is covered in this GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6) + APIs to allow changes in `windowStateRestoreOptions` during runtime for apps that want to let users decide save/restore behaviour. Overall, the `windowStateRestoreOptions` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 344e75c5e48b156c9f7fce0c2f282843398de5b7 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 6 Jun 2025 01:24:45 +0530 Subject: [PATCH 04/26] update: future possibilities --- text/0013-save-restore-window-state.md | 40 ++++++++++++-------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index b7630d7..dd8f54c 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -26,7 +26,7 @@ Window state persistence represents a core functionality that many production El It's useful for developers who want to implement save/restore window state for their apps reliably. Different apps have different needs. An example use case for this API would be for when apps want to preserve window position (with display overflow) and size across multi-monitor setups with different display scales and resolutions. -`windowStateRestoreOptions` aims to cover most save/restore use cases while providing room for future extension. +`windowStateRestoreOptions` aims to cover most save/restore use cases while providing room for future extensions. **What is the expected outcome?** @@ -71,8 +71,8 @@ Here's the schema I propose for the `windowStateRestoreOptions` object. This is ```json "windowStateRestoreOptions": { "stateId": string, - "bounds": true, - "displayMode": true + "bounds": boolean, + "displayMode": boolean } ``` > [!NOTE] @@ -246,7 +246,7 @@ Returns `boolean` - Whether the preferences were successfully restored and appli Preferences will be restored in the order of the options object passed during savePreferences. ```js // Restore the previously saved preferences -const success = win.restoreWindowPreferences() +const success = win.restorePreferences() console.log(success) // true if state was successfully restored ``` @@ -256,7 +256,7 @@ Clears the saved state for the window **including bounds and save policy itself* ```js // Clear the entire saved state for the window -const success = win.clearSavedState() +const success = win.clearPreferences() console.log(success) // true if state was successfully cleared ``` @@ -326,7 +326,7 @@ For the preferences it will be important to store the order of the properties as The algorithm would be simple two step approach: **Calculate new bounds:** -This step invovles checking if the window is reopened on the same display by comparing the current work_area to the saved work_area, setting minimum height and width for the window (100, 100) if it's too less, ensuring proper fallback behaviour for when window is restored out of bounds or with overflow +This step involves checking if the window is reopened on the same display by comparing the current work_area to the saved work_area, setting minimum height and width for the window (100, 100), ensuring proper fallback behaviour for when window is restored out of bounds or with overflow There are some tricks we can use from [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) **Set window state:** @@ -369,7 +369,7 @@ existing apps?* It would be the same code for Windows, macOS, and Linux using Chromium's PrefService. I think it would be easy to read, understand, and maintain the new Electron code. -The path to upgrade for apps would be developers removing their existing implementation and using this new API if they want to. If their implementation is on the JavaScript side it will always execute their restore logic after window construction which means our implementation would be overridden. +The path to upgrade for apps would be for developers to remove their existing implementation and use this new API if they want to. If their implementation is on the JavaScript side it will always execute their restore logic after window construction which means our implementation would be overridden. ## Reference-level explanation @@ -378,7 +378,7 @@ Covered in the [Implementation Details](#implementation-details) section. ## Drawbacks -- Writing to disk everytime window is moved or resized in a batched 10 second window might not be necessary and better to write to disk only on window close synchronously. +- Writing to disk everytime the window is moved or resized in a batched 10 second window might not be necessary and better to write to disk only on window close synchronously. - Similar outcomes could be achieved via JavaScript APIs with miniscule performance difference. The only issue being the window state is not set at the right time in the window lifecycle. Why should we *not* do this? @@ -389,7 +389,7 @@ Why should we *not* do this? *Why is this design the best in the space of possible designs?* - Overall, providing a constructor option is the best design in my opinion. It provides maximum flexibility and future-proofing for different requests in the future. It also sets the window properties at the right time in the window lifecycle. Although not perfect right now, it can be improved by the community based on different use cases quite easily. We're also saving the window state in a continuous manner so it could be restored even after crashes. + Overall, providing a constructor option is the best design in my opinion. It provides maximum flexibility and future-proofing for different requests in the future. It also sets the window properties at the right time in the window lifecycle. Although not perfect right now, it can be improved by the community based on different use cases quite easily. We're also saving the window state in a continuous manner so it can be restored even after crashes. *What other designs have been considered and what is the rationale for not choosing them?* @@ -401,7 +401,7 @@ It's not a critical issue. Apps might vanish on rare occasions. *If this is an API proposal, could this be done as a JavaScript module or a native Node.js add-on?* -The real value proposition isn't that this functionality can't be implemented in JavaScript - it absolutely can and has been attempted through various community libraries. Rather, the value lies in providing a standardized, well-maintained solution integrated directly into Electron's core. There's also miniscule performance benefits as it would avoid extra ipc calls while restoring window state. +The real value proposition isn't that this functionality can't be implemented in JavaScript - it absolutely can and has been attempted through various community libraries. Rather, the value lies in providing a standardized, well-maintained solution integrated directly into Electron's core. There's also a miniscule performance benefits as it would avoid extra ipc calls while restoring window state. ## Prior art @@ -409,13 +409,12 @@ Electron devTools persists bounds using PrefService. Implementation can be seen It also seems likely Chrome uses PrefService to store their window bounds [reference](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/chrome/browser/prefs/README.md). From my research, I found out native applications built directly with the operating system's own tools and frameworks often get window state persistence for free (macOS, Windows). -I thought it would be innapropriate to enforce such rules on Electron apps. Thus, `windowStateRestoreOptions` to provide flexibility and the choice to opt-in to this behavior. +I thought it would be inappropriate to enforce such rules on Electron apps. Thus, `windowStateRestoreOptions` to provide flexibility and the choice to opt-in to this behavior. ## Unresolved questions *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Variable names and the entire API Spec. -- Are there any other edge cases that this proposal missed? -- Restoring split screen for macOS. Should it be treated as normal or fullscreen restore? I think we can go ahead restoring as though it was fullscreened as apps need to go fullscreen to have a split view on macOS. +- Restoring split screen for macOS. Should it be treated as normal or fullscreen restore? I think we can go ahead restoring as though it was fullscreened because apps need to go fullscreen to have a split view on macOS. *What parts of the design do you expect to resolve through the implementation of this feature before stabilization?* @@ -446,23 +445,22 @@ Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` paramete We would need an algorithm that calculates bounds based on these parameters. -During restore we would have to respect `openBehaviour` before restoring our state. +During window restore we would have to respect `openBehaviour` before restoring our state. Default `fallbackBehaviour` would be `snapToNearestDisplayEdge` if out of bounds. -This parameter would provide a declarative way to define how the window should behave if it restored out of bounds. +This parameter would provide a declarative way to define how the window should behave if it was restored out of bounds. -Both of these were part of the initial GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6) +Both of these were part of the initial GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6). -The algorithm to restore window state is detailed in this [section](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state) +The algorithm to restore window state is detailed in this [section](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state). -One particular cool feature would be to provide an option to restore the window on closest available display/space dynamically. +One particularly cool feature would be to provide an option to restore the window on closest available display/space dynamically. -The `bounds` property could possible switched from boolean to an object that allows more configurability and control over reopening windows -on different monitors with different dpi scaling and resolution. +The `bounds` property could possibly be switched from boolean to an object that allows more configurability and control over reopening windows on different monitors with different dpi scaling and resolution. We could add `allowOverflow` property inside the object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402). -Again, everything is covered in this GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6) +Again, everything is covered in this GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6). APIs to allow changes in `windowStateRestoreOptions` during runtime for apps that want to let users decide save/restore behaviour. From 9ef5771ede96d590eb93eb8af9407c4f2e19d4ff Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 13 Jun 2025 17:40:17 +0530 Subject: [PATCH 05/26] add api for app.clearWindowState --- text/0013-save-restore-window-state.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index dd8f54c..44676ea 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -250,13 +250,25 @@ const success = win.restorePreferences() console.log(success) // true if state was successfully restored ``` -#### `win.clearPreferences()` +#### `win.clearState()` -Clears the saved state for the window **including bounds and save policy itself**. Relevant events would be emitted. +Clears the saved preferences for the window **including bounds**. +Returns `boolean` - Whether the state was successfully cleared. Relevant events would be emitted. ```js // Clear the entire saved state for the window -const success = win.clearPreferences() +const success = win.clearState() +console.log(success) // true if state was successfully cleared +``` + +#### `app.clearWindowState([stateId])` + +Additional API that does the same thing as `win.clearState()`. +Returns `boolean` - Whether the state was successfully cleared. Relevant events would be emitted. + +```js +// Clear the entire saved state for the window +const success = app.clearWindowState('#1230') console.log(success) // true if state was successfully cleared ``` From 9555e952ff991a08e5aec8553e6acda2c38cc33b Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 13 Jun 2025 17:49:35 +0530 Subject: [PATCH 06/26] don't save kiosk and minimized displayModes --- text/0013-save-restore-window-state.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index 44676ea..b9b7865 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -76,7 +76,7 @@ Here's the schema I propose for the `windowStateRestoreOptions` object. This is } ``` > [!NOTE] -> The entire window state (bounds, maximized, minimized, fullscreen, etc.) would be saved internally and restored using the provided `windowStateRestoreOptions` config. +> The entire window state (bounds, maximized, fullscreen, etc.) would be saved internally and restored using the provided `windowStateRestoreOptions` config. > We would use the saved state and enforce the chosen config during restoration. #### **State Persistence Mechanism** @@ -106,7 +106,7 @@ Here's how it would land in the electron documentation. * `bounds` boolean (optional) - Whether to restore the window's x, y, height, width. Default is true. - * `displayMode` boolean (optional) - Whether to restore the window's display mode (fullscreen, kiosk, maximized, etc). Default is true. + * `displayMode` boolean (optional) - Whether to restore the window's display mode (fullscreen, maximized, etc). Default is true. Example usage: @@ -291,8 +291,6 @@ Example output: "right": 718, "fullscreen": false, "maximized": false, - "minimized": false, - "kiosk": false, "work_area_bottom": 847, "work_area_left": 0, "work_area_right": 1440, @@ -362,7 +360,7 @@ It can be used to save and restore window state with multiple configurations. } ``` -Here's an example that would let you restore bounds (x, y, width, height) but not the display mode (maximized, minimized, fullscreen etc) +Here's an example that would let you restore bounds (x, y, width, height) but not the display mode (maximized, fullscreen etc) ```js const { BrowserWindow } = require('electron') const win = new BrowserWindow({ From 2bd40021c5f193ee2ce5b1f83b4e9dc1107fcf19 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 13 Jun 2025 18:19:53 +0530 Subject: [PATCH 07/26] don't save display scale and resolution --- text/0013-save-restore-window-state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index b9b7865..1094b2b 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -123,7 +123,7 @@ const win = new BrowserWindow({ ### API Design Rationale: - Everything is set to true by default so that the developer can set whatever they want to exclude to false. This means less lines of code as more options are added to the config. -- Displays are considered the same if they span over the same work_area dimensions and have identical scale. So for example, replacing your primary display with same resolution and scale but different manufacturer will still be considered the same display. I think this makes sense. +- Displays are considered the same if they span over the same work_area dimensions. - A window can never be restored out of reach. I am presuming no apps would want this behavior. - If window (width, height) reopens on a different display and does not fit on screen auto adjust to fit and resave the value. This would reduce the number of edge cases significantly and I highly doubt that any app would want to preserve an overflow when opened on a different display. - Not handling scaling as Windows, macOS, and Linux support multimonitor window scaling by default. From 7ec84c84784726823189dba480c08faf06730b48 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Sat, 14 Jun 2025 05:01:07 +0530 Subject: [PATCH 08/26] add api for app.setWindowState() --- text/0013-save-restore-window-state.md | 47 +++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index 1094b2b..836e0be 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -272,7 +272,7 @@ const success = app.clearWindowState('#1230') console.log(success) // true if state was successfully cleared ``` -#### `win.getSavedState()` +#### `app.getWindowState([stateId])` Returns `JSON Object` - The saved state for the window including bounds, display info and preferences. @@ -285,6 +285,10 @@ Example output: ```json { "stateId": "#1230", + "x": 0, + "y": 25, + "height": 822, + "width": 718, "top": 25, "bottom": 847, "left": 0, @@ -319,15 +323,42 @@ Example output: "hiddenInMissionControl": true, } ``` -`windowStateRestoreOptions` Schema for reference: -```json -"windowStateRestoreOptions": { - "stateId": string, - "bounds": boolean, - "displayMode": boolean, - } +#### `app.setWindowState([stateObj])` + +* `stateObj` Object - Complete state object to save (replaces existing state). Must include `stateId`. + +Returns `boolean` - Whether the state was successfully set. Returns `false` if `stateId` is not provided in the state object or if the state was not set successfully. + +**Examples:** +```js +// Replace entire state with new object +app.setWindowState({ + stateId: '#1230', + x: 100, + y: 200, + width: 800, + height: 600, + maximized: false +}); + +// If you want to merge with existing state, use spread operator +const existingState = app.getWindowState('#1230'); +app.setWindowState({ + ...existingState, + x: 100, + y: 200 +}); + +// This will return false and not save anything (missing stateId) +app.setWindowState({ + x: 100, + y: 200 +}); // Returns false ``` +> [!NOTE] +> The `stateId` property must be included in the state object. If `stateId` is missing, the method returns `false` and no state is saved. + ### Algorithm for saving/restoring the window state As mentioned before, we would take a continuous approach to saving window state if the `windowStateRestoreOptions` is passed through the constructor. From 45e370582cc955ddedcb87a78d0329aff6cc7a2c Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Wed, 18 Jun 2025 12:10:09 +0530 Subject: [PATCH 09/26] update event emitters --- text/0013-save-restore-window-state.md | 49 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index 836e0be..accd068 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -87,11 +87,56 @@ Secondly, once the states are loaded into memory, we can use them to restore the Each time a window is **moved** or **resized**, we schedule a write using Chromium's ScopedDictPrefUpdate [[example](https://github.com/electron/electron/blob/4ad20ccb396a3354d99b8843426bece2b28228bf/shell/browser/ui/inspectable_web_contents.cc#L837-L841)]. -Here's a reference to all the variables we would saving internally using PrefService https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/chrome_views_delegate.cc;drc=1f9f3d7d4227fc2021915926b0e9f934cd610201;l=85 +Here's a reference to all the variables we would saving be internally using PrefService https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/chrome_views_delegate.cc;drc=1f9f3d7d4227fc2021915926b0e9f934cd610201;l=85 #### Developer-Facing Events -I’m also considering emitting events such as: `will-save-window-state`, `saved-window-state`, `will-restore-window-state`, `restored-window-state`. However, I’m unsure about their practical usefulness for developers and hope to finalize them through this RFC. +I’m also considering emitting these events: `will-save-window-state`, `saved-window-state`, `restored-window-state`. **However, I’m unsure about their practical usefulness for developers and hope to finalize them through this RFC.** + +I'm thinking we could make these events simple telemetry events. Simple telemetry events would provide observability without the performance cost of running the custom logic for hundreds of events per second during window manipulation. Adding these events with the ability to intercept and modify values that are going to be saved, ability to intercept with custom restoration logic, would increase the implementation complexity and maintainability. + +With the help of APIs `app.setWindowState([stateObj])` and `app.getWindowState([stateId])` (these could be made static as well) we can modify and manipulate saved states synchronously to accomplish our goals. + +The following events will be emitted for window state persistence process: + +#### Event: 'will-save-window-state' + +Emitted before window state is saved. This event is triggered when the window is closed, resized, moved or when `win.savePreferences()` is called. + +**Parameters:** +- `event` Event - The event object with `preventDefault()` method + +```js +win.on('will-save-window-state', (event) => { + console.log('About to save window state'); + + // Prevent the save operation if needed + if (someCondition) { + event.preventDefault(); + } +}); +``` + +#### Event: 'saved-window-state' + +Emitted after window state has been successfully written to disk. + +```js +win.on('saved-window-state', () => { + console.log('Window state successfully saved'); +}); +``` + +#### Event: 'restored-window-state' + +Emitted immediately after the window constructor completes with the restored state applied. + +```js +win.on('restored-window-state', () => { + console.log('Window state restored'); +}); +``` +``` ## API Specification From 4802bda341d3f95273ee8af86fc927222849ad4c Mon Sep 17 00:00:00 2001 From: Nilay Arya <84241885+nilayarya@users.noreply.github.com> Date: Thu, 19 Jun 2025 22:32:46 +0530 Subject: [PATCH 10/26] Update text/0013-save-restore-window-state.md Co-authored-by: Erick Zhao --- text/0013-save-restore-window-state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index accd068..7ef2952 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -10,7 +10,7 @@ # Save/Restore Window State API -Currently, Electron does not have any built-in mechanism for saving and restoring the state of BrowserWindows, but this is a very common need for apps that want to feel more native. +Currently, Electron does not have any built-in mechanism for saving and restoring the state of BaseWindows, but this is a very common need for apps that want to feel more native. ## Summary From 64b6bd83b94a2cbfa28bee3cbfb231a1fb95eabc Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 20 Jun 2025 00:54:20 +0530 Subject: [PATCH 11/26] update clearWindowState and remove get/setWindowState --- text/0013-save-restore-window-state.md | 127 +++---------------------- 1 file changed, 11 insertions(+), 116 deletions(-) diff --git a/text/0013-save-restore-window-state.md b/text/0013-save-restore-window-state.md index 7ef2952..9d30484 100644 --- a/text/0013-save-restore-window-state.md +++ b/text/0013-save-restore-window-state.md @@ -95,8 +95,6 @@ I’m also considering emitting these events: `will-save-window-state`, `saved-w I'm thinking we could make these events simple telemetry events. Simple telemetry events would provide observability without the performance cost of running the custom logic for hundreds of events per second during window manipulation. Adding these events with the ability to intercept and modify values that are going to be saved, ability to intercept with custom restoration logic, would increase the implementation complexity and maintainability. -With the help of APIs `app.setWindowState([stateObj])` and `app.getWindowState([stateId])` (these could be made static as well) we can modify and manipulate saved states synchronously to accomplish our goals. - The following events will be emitted for window state persistence process: #### Event: 'will-save-window-state' @@ -136,7 +134,6 @@ win.on('restored-window-state', () => { console.log('Window state restored'); }); ``` -``` ## API Specification @@ -295,114 +292,19 @@ const success = win.restorePreferences() console.log(success) // true if state was successfully restored ``` -#### `win.clearState()` - -Clears the saved preferences for the window **including bounds**. -Returns `boolean` - Whether the state was successfully cleared. Relevant events would be emitted. - -```js -// Clear the entire saved state for the window -const success = win.clearState() -console.log(success) // true if state was successfully cleared -``` +#### `BaseWindow.clearState([stateId])` -#### `app.clearWindowState([stateId])` +Static function over BaseWindow. -Additional API that does the same thing as `win.clearState()`. -Returns `boolean` - Whether the state was successfully cleared. Relevant events would be emitted. +Clears the saved preferences for the window **including bounds**. +Returns `boolean` - Whether the state was successfully cleared. Returns true if stateId was not found. ```js // Clear the entire saved state for the window -const success = app.clearWindowState('#1230') +const success = BaseWindow.clearState('#1230'); console.log(success) // true if state was successfully cleared ``` -#### `app.getWindowState([stateId])` - -Returns `JSON Object` - The saved state for the window including bounds, display info and preferences. - -```js -// Get the saved state for the window -const savedState = win.getSavedState() -console.log(savedState) -``` -Example output: -```json -{ - "stateId": "#1230", - "x": 0, - "y": 25, - "height": 822, - "width": 718, - "top": 25, - "bottom": 847, - "left": 0, - "right": 718, - "fullscreen": false, - "maximized": false, - "work_area_bottom": 847, - "work_area_left": 0, - "work_area_right": 1440, - "work_area_top": 25, - // Only Saved BrowserWindow Preferences - "autoHideMenuBar": false, - "backgroundColor": "#FFFFFF", - "title": "gsoc2025", - "minimizable": true, - "maximizable": true, - "fullscreenable": true, - "resizable": true, - "closable": true, - "movable": true, - "excludedFromShownWindowsMenu": true, - "accessibleTitle": "gsoc2025", - "aspectRatio": 1.0, - "minimumSize": { - "width": 800, - "height": 600 - }, - "maximumSize": { - "width": 1000, - "height": 800 - }, - "hiddenInMissionControl": true, -} -``` -#### `app.setWindowState([stateObj])` - -* `stateObj` Object - Complete state object to save (replaces existing state). Must include `stateId`. - -Returns `boolean` - Whether the state was successfully set. Returns `false` if `stateId` is not provided in the state object or if the state was not set successfully. - -**Examples:** -```js -// Replace entire state with new object -app.setWindowState({ - stateId: '#1230', - x: 100, - y: 200, - width: 800, - height: 600, - maximized: false -}); - -// If you want to merge with existing state, use spread operator -const existingState = app.getWindowState('#1230'); -app.setWindowState({ - ...existingState, - x: 100, - y: 200 -}); - -// This will return false and not save anything (missing stateId) -app.setWindowState({ - x: 100, - y: 200 -}); // Returns false -``` - -> [!NOTE] -> The `stateId` property must be included in the state object. If `stateId` is missing, the method returns `false` and no state is saved. ### Algorithm for saving/restoring the window state As mentioned before, we would take a continuous approach to saving window state if the `windowStateRestoreOptions` is passed through the constructor. @@ -530,24 +432,17 @@ Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` paramete * `"centerOnPrimaryDisplay"` - Centers the window on the primary display. -We would need an algorithm that calculates bounds based on these parameters. -During window restore we would have to respect `openBehaviour` before restoring our state. - -Default `fallbackBehaviour` would be `snapToNearestDisplayEdge` if out of bounds. -This parameter would provide a declarative way to define how the window should behave if it was restored out of bounds. - -Both of these were part of the initial GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6). +We would need an algorithm that calculates bounds based on these parameters. Many things would be needed to be taken into consideration to accomplish this. -The algorithm to restore window state is detailed in this [section](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state). +The algorithm to restore window state with the newly introduced options `fallbackBehaviour` and `openBehaviour` is detailed [here](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state). One particularly cool feature would be to provide an option to restore the window on closest available display/space dynamically. -One particularly cool feature would be to provide an option to restore the window on closest available display/space dynamically. +The `bounds` property that is suggested above could possibly accept an object or boolean (as suggest as of now). The object would allow more configurability and control over reopening windows on different monitors with different dpi scaling and resolution. -The `bounds` property could possibly be switched from boolean to an object that allows more configurability and control over reopening windows on different monitors with different dpi scaling and resolution. +We could add `allowOverflow` property inside the `bounds` object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we won't be considering this and can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402) for the time being. -We could add `allowOverflow` property inside the object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402). +APIs to allow changes in `windowStateRestoreOptions` during runtime for apps that want to let users decide save/restore behaviour. -Again, everything is covered in this GSoC [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6). +APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([stateId])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 -APIs to allow changes in `windowStateRestoreOptions` during runtime for apps that want to let users decide save/restore behaviour. Overall, the `windowStateRestoreOptions` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 960a99c0f15668918426ef7cf0afeacd98ef2384 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Fri, 20 Jun 2025 00:56:24 +0530 Subject: [PATCH 12/26] update rfc file name --- ...-restore-window-state.md => 0016-save-restore-window-state.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0013-save-restore-window-state.md => 0016-save-restore-window-state.md} (100%) diff --git a/text/0013-save-restore-window-state.md b/text/0016-save-restore-window-state.md similarity index 100% rename from text/0013-save-restore-window-state.md rename to text/0016-save-restore-window-state.md From 27d4b48428152615f3dd1e206a8231596b664b9b Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Wed, 9 Jul 2025 15:14:08 +0530 Subject: [PATCH 13/26] update api surface to have name and windowStatePersistence --- text/0016-save-restore-window-state.md | 102 ++++++++++++------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 9d30484..a659472 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -5,16 +5,13 @@ - Reference Implementation: - Status: **Proposed** -> [!NOTE] -> This RFC is part of GSOC 2025 [proposal](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6). - # Save/Restore Window State API Currently, Electron does not have any built-in mechanism for saving and restoring the state of BaseWindows, but this is a very common need for apps that want to feel more native. ## Summary -This proposal aims to implement a save/restore window state API for Electron by providing a simple but powerful configuration object `windowStateRestoreOptions` that handles complex edge cases automatically. This approach offers a declarative way to configure window persistence behavior while maintaining flexibility for different application needs. The object would be optional in the `BaseWindowConstructorOptions`. +This proposal aims to implement a save/restore window state API for Electron by providing a simple but powerful configuration object `windowStatePersistence` that handles complex edge cases automatically. This approach offers a declarative way to configure window persistence behavior while maintaining flexibility for different application needs. The object would be optional in the `BaseWindowConstructorOptions`. ## Motivation @@ -26,7 +23,7 @@ Window state persistence represents a core functionality that many production El It's useful for developers who want to implement save/restore window state for their apps reliably. Different apps have different needs. An example use case for this API would be for when apps want to preserve window position (with display overflow) and size across multi-monitor setups with different display scales and resolutions. -`windowStateRestoreOptions` aims to cover most save/restore use cases while providing room for future extensions. +`windowStatePersistence` aims to cover most save/restore use cases while providing room for future extensions. **What is the expected outcome?** @@ -38,9 +35,9 @@ While established applications with custom implementations may continue using th Before diving into the API specifications which is the next section, I’d like to outline the approach for saving a window’s state in Electron. -We can use Chromium's [PrefService](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h) to store window state as a JSON object into the already existing Preferences folder inside *app.getPath('userData')*. +We can use Chromium's [PrefService](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h) to store window state as a JSON object into the already existing Local State folder inside *app.getPath('userData')*. -We schedule an async write to disk every time a window is moved or resized and batch write every 10 seconds (I will explain why later). We also write to disk on window close synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** +We schedule an async write to disk every time a window is moved or resized and batch write every 10 seconds (I will explain why later). We also write to disk on app quit synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** **Questions that might arise:** @@ -62,28 +59,33 @@ I think reusing the existing implementation and retaining the 10-second commit i
-Here's the schema I propose for the `windowStateRestoreOptions` object. This is simply for reference as I have not explained what each field means yet. I will explain each field in the API specification section which is next. +Here's the schema I propose for the `windowStatePersistence` object. This is simply for reference as I have not explained what each field means yet. I will explain each field in the API specification section which is next.
-`windowStateRestoreOptions` schema - This would be passed by the developer in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions +BaseWindowConstructorOptions schema - This would be passed by the developer in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions ```json -"windowStateRestoreOptions": { - "stateId": string, - "bounds": boolean, - "displayMode": boolean - } +{ + "name": string, + "windowStatePersistence": { + "bounds": boolean, + "displayMode": boolean + } +} ``` + > [!NOTE] -> The entire window state (bounds, maximized, fullscreen, etc.) would be saved internally and restored using the provided `windowStateRestoreOptions` config. -> We would use the saved state and enforce the chosen config during restoration. +> The entire window state (bounds, maximized, fullscreen, etc.) would be saved internally if name and windowStatePersistence are passed. +> We would use the saved state and enforce the chosen config during restoration the next time the window is constructed. #### **State Persistence Mechanism** -Firstly, all the window states with their `windowStateRestoreOptions` would be to loaded from disk synchronously during the startup process just like other preferences in Electron. Doing so would allow us the have the states in memory during window creation with minimal performance impact. +Firstly, all the window states with their `windowStatePersistence` would be to loaded from disk synchronously during the startup process just like other preferences in Electron. Doing so would allow us the have the states in memory during window creation with minimal performance impact. + +Secondly, once the states are loaded into memory, we can use them to restore the window state with the `windowStatePersistence` rules in place during window creation. An algorithm that handles all the edges would be required for this. Chromium's [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) seems like a good reference point that handles some of the edge cases. Also, the default options provided in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions constructor options would be overridden (if we are restoring state). -Secondly, once the states are loaded into memory, we can use them to restore the window state with the `windowStateRestoreOptions` rules in place during window creation. An algorithm that handles all the edges would be required for this. Chromium's [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) seems like a good reference point that handles some of the edge cases. Also, the default options provided in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions constructor options would be overridden (if we are restoring state). +We can respect the min/max height/width, fullscreenable, maximizable, minimizable properties set inside `BaseWindowConstructorOptions` if applicable. Meaning these properties would take a higher priority during the restoration of a window. Each time a window is **moved** or **resized**, we schedule a write using Chromium's ScopedDictPrefUpdate [[example](https://github.com/electron/electron/blob/4ad20ccb396a3354d99b8843426bece2b28228bf/shell/browser/ui/inspectable_web_contents.cc#L837-L841)]. @@ -142,22 +144,23 @@ Here's how it would land in the electron documentation. ### BaseWindowConstructorOptions -`windowStateRestoreOptions` object (optional) - - * `stateId` string - A unique identifier used for saving and restoring window state. +* `name` string (optional) - An identifier for the window that enables features such as state persistence. - * `bounds` boolean (optional) - Whether to restore the window's x, y, height, width. Default is true. +* `windowStatePersistence` ([WindowStatePersistence]() | Boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. _Experimental_ - * `displayMode` boolean (optional) - Whether to restore the window's display mode (fullscreen, maximized, etc). Default is true. - +### WindowStatePersistence Object + +* `bounds` boolean (optional) - Whether to persist window position and size across application restarts. Defaults to `true` if not specified. + +* `displayMode` boolean (optional) - Whether to persist display modes (fullscreen, kiosk, maximized, etc.) across application restarts. Defaults to `true` if not specified. + +We could add a tutorial on how the new constructor option can be used effectively. -Example usage: ```js const { BrowserWindow } = require('electron') const win = new BrowserWindow({ ...existingOptions, - windowStateRestoreOptions: { - stateId: '#1230', + windowStatePersistence: { displayMode: false }, }) @@ -169,10 +172,9 @@ const win = new BrowserWindow({ - A window can never be restored out of reach. I am presuming no apps would want this behavior. - If window (width, height) reopens on a different display and does not fit on screen auto adjust to fit and resave the value. This would reduce the number of edge cases significantly and I highly doubt that any app would want to preserve an overflow when opened on a different display. - Not handling scaling as Windows, macOS, and Linux support multimonitor window scaling by default. -- We treat split screen states as normal on Windows, Linux as this is default for the OS. For macOS, behavior to restore split screen needs to be finalized as the default is fullscreen for split view. ### Additional APIs -I also plan on adding synchronous methods to `BrowserWindow` to save, restore, clear, and get the window state or rather window preferences. Down below is the API spec for that. +I also plan on adding synchronous methods to `BaseWindow` to save, restore, clear, and get the window state or rather window preferences. Down below is the API spec for that. Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. @@ -265,10 +267,9 @@ Returns `boolean` - Whether the state was successfully saved. Relevant events wo ```js const { BrowserWindow } = require('electron') const win = new BrowserWindow({ - ...existingOptions, - windowStateRestoreOptions: { - stateId: '#1230', - } + ...existingOptions, + name: '#1230', + windowStatePersistence: true }); // Save additional properties @@ -292,12 +293,12 @@ const success = win.restorePreferences() console.log(success) // true if state was successfully restored ``` -#### `BaseWindow.clearState([stateId])` +#### `BaseWindow.clearState([name])` Static function over BaseWindow. Clears the saved preferences for the window **including bounds**. -Returns `boolean` - Whether the state was successfully cleared. Returns true if stateId was not found. +Returns `boolean` - Whether the state was successfully cleared. Returns true if name was not found. ```js // Clear the entire saved state for the window @@ -307,7 +308,7 @@ console.log(success) // true if state was successfully cleared ### Algorithm for saving/restoring the window state -As mentioned before, we would take a continuous approach to saving window state if the `windowStateRestoreOptions` is passed through the constructor. +As mentioned before, we would take a continuous approach to saving window state if `name` and `windowStatePersistence` are passed through the constructor. For the preferences it will be important to store the order of the properties as well when win.savePreferences is called due to possible conflicting properties. We can restore them in the same order and it will be as though the properties were set via JavaScript APIs in the exact same order. @@ -325,14 +326,14 @@ Once we have the width, height, x, y, and displayModes we can set the window sta *Explain the feature as if it were already implemented in Electron and you were teaching it to an Electron app developer.* -Electron is introducing `windowStateRestoreOptions`, an optional object in the `BaseWindowConstructorOptions` and would also be available in `BrowserWindowConstructorOptions`. +Electron is introducing `windowStatePersistence`, an optional object in the `BaseWindowConstructorOptions` and would also be available in `BrowserWindowConstructorOptions`. It can be used to save and restore window state with multiple configurations. -`windowStateRestoreOptions` schema - Everything is optional except `stateId`. Developers can choose what they want to preserve and how they want to restore it. +`windowStatePersistence` schema - Everything is optional. `name` property would be required. Developers can choose what they want to preserve and how they want to restore it. ```json -"windowStateRestoreOptions": { - "stateId": string, +"name": string, +"windowStatePersistence": { "bounds": boolean, "displayMode": boolean } @@ -343,11 +344,10 @@ Here's an example that would let you restore bounds (x, y, width, height) but no const { BrowserWindow } = require('electron') const win = new BrowserWindow({ ...existingOptions, - windowStateRestoreOptions: { - stateId: '#1230', - bounds: true, + name: '#1230', + windowStatePersistence: { displayMode: false - }, + } }) ``` @@ -397,21 +397,20 @@ Electron devTools persists bounds using PrefService. Implementation can be seen It also seems likely Chrome uses PrefService to store their window bounds [reference](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/chrome/browser/prefs/README.md). From my research, I found out native applications built directly with the operating system's own tools and frameworks often get window state persistence for free (macOS, Windows). -I thought it would be inappropriate to enforce such rules on Electron apps. Thus, `windowStateRestoreOptions` to provide flexibility and the choice to opt-in to this behavior. +I thought it would be inappropriate to enforce such rules on Electron apps. Thus, `windowStatePersistence` to provide flexibility and the choice to opt-in to this behavior. ## Unresolved questions *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Variable names and the entire API Spec. -- Restoring split screen for macOS. Should it be treated as normal or fullscreen restore? I think we can go ahead restoring as though it was fullscreened because apps need to go fullscreen to have a split view on macOS. *What parts of the design do you expect to resolve through the implementation of this feature before stabilization?* -Implementation should be straightforward. I was wondering if there would be any delay setting displayMode during window construction since functions such as SetFullScreen are platform dependent and async under the hood in Electron. - *What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?* +Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. + ## Future possibilities Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. @@ -440,9 +439,6 @@ The `bounds` property that is suggested above could possibly accept an object or We could add `allowOverflow` property inside the `bounds` object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we won't be considering this and can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402) for the time being. -APIs to allow changes in `windowStateRestoreOptions` during runtime for apps that want to let users decide save/restore behaviour. - -APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([stateId])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 - +APIs to allow changes in `windowStatePersistence` during runtime for apps that want to let users decide save/restore behaviour. -Overall, the `windowStateRestoreOptions` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file +APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([name])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 From 411d7ff75d605f2bf2253f89501044b5ccde8855 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Wed, 9 Jul 2025 15:31:46 +0530 Subject: [PATCH 14/26] improve guide level explanation --- text/0016-save-restore-window-state.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index a659472..7b8037f 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -2,7 +2,7 @@ - Start Date: 2025-03-20 - RFC PR: - Electron Issues: Related to [electron/electron/issues/526](https://github.com/electron/electron/issues/526) -- Reference Implementation: +- Reference Implementation: https://github.com/electron/electron/pull/47425/files - Status: **Proposed** # Save/Restore Window State API @@ -326,11 +326,16 @@ Once we have the width, height, x, y, and displayModes we can set the window sta *Explain the feature as if it were already implemented in Electron and you were teaching it to an Electron app developer.* -Electron is introducing `windowStatePersistence`, an optional object in the `BaseWindowConstructorOptions` and would also be available in `BrowserWindowConstructorOptions`. +Electron is introducing `windowStatePersistence`, an optional object/boolean in the `BaseWindowConstructorOptions` and would also be available in `BrowserWindowConstructorOptions`. It can be used to save and restore window state with multiple configurations. -`windowStatePersistence` schema - Everything is optional. `name` property would be required. Developers can choose what they want to preserve and how they want to restore it. +`windowStatePersistence` schema - Everything is optional. Developers can choose what they want to preserve and how they want to restore it or simply set it to true. The default behaviour in this case would be `bounds` and `displayMode` both are restored. +```json +"name": string, +"windowStatePersistence": true +``` +OR ```json "name": string, "windowStatePersistence": { @@ -403,14 +408,6 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Variable names and the entire API Spec. -*What parts of the design do you expect to resolve through the implementation of this feature -before stabilization?* - -*What related issues do you consider out of scope for this RFC that could be addressed in the -future independently of the solution that comes out of this RFC?* - -Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. - ## Future possibilities Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. @@ -442,3 +439,5 @@ We could add `allowOverflow` property inside the `bounds` object to control the APIs to allow changes in `windowStatePersistence` during runtime for apps that want to let users decide save/restore behaviour. APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([name])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 + +Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 469350dd58e559baaa2cc2e2819f9b6c1531c931 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 14 Aug 2025 18:06:23 -0400 Subject: [PATCH 15/26] updates to reflect the new api surface --- text/0016-save-restore-window-state.md | 325 ++++++++++++------------- 1 file changed, 150 insertions(+), 175 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 7b8037f..c470bb1 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -2,7 +2,7 @@ - Start Date: 2025-03-20 - RFC PR: - Electron Issues: Related to [electron/electron/issues/526](https://github.com/electron/electron/issues/526) -- Reference Implementation: https://github.com/electron/electron/pull/47425/files +- Reference Implementation: https://github.com/electron/electron/tree/gsoc-2025 - Status: **Proposed** # Save/Restore Window State API @@ -19,17 +19,17 @@ This proposal aims to implement a save/restore window state API for Electron by Window state persistence represents a core functionality that many production Electron apps implement. By elevating it to a first-class feature, Electron acknowledges its essential nature and provides a standardized approach directly in the framework. -**What use cases does it support?** - +**What use cases does it support?** + It's useful for developers who want to implement save/restore window state for their apps reliably. Different apps have different needs. An example use case for this API would be for when apps want to preserve window position (with display overflow) and size across multi-monitor setups with different display scales and resolutions. `windowStatePersistence` aims to cover most save/restore use cases while providing room for future extensions. **What is the expected outcome?** -Electron provides an option to save and restore window state out of the box with multiple configurations which will cover almost all application needs. +Electron provides an option to save and restore window state out of the box with multiple configurations which will cover almost all application needs. -While established applications with custom implementations may continue using their existing solutions, this feature will most likely see adoption by many smaller projects. +While established applications with custom implementations may continue using their existing solutions, this feature will most likely see adoption by many smaller projects. ## Implementation Details @@ -37,17 +37,18 @@ Before diving into the API specifications which is the next section, I’d like We can use Chromium's [PrefService](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h) to store window state as a JSON object into the already existing Local State folder inside *app.getPath('userData')*. -We schedule an async write to disk every time a window is moved or resized and batch write every 10 seconds (I will explain why later). We also write to disk on app quit synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** +We schedule an async write to disk every time a window is moved or resized and batch write every 10 seconds (explained later why). We also write to disk on app quit synchronously. **This continuous writing approach would enable us to restore the window state even when there are crashes or improper app exits.** **Questions that might arise:** **Why 10 seconds?** -The default time window used by Chromium's PrefService is 10 seconds [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)]. +The default time window used by Chromium's PrefService to flush to disk is 10 seconds [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.cc;l=48;drc=ff37fb5e8b02e0c123063b89ef2ac3423829b010)]. I think reusing the existing implementation and retaining the 10-second commit interval would be a simple and effective approach. **What if the app crashes in the 10 second window?** The window could possibly restore where it was a few seconds ago (worst case 10 seconds ago). This wouldn't be the worst user experience in the world. -#### *Why PrefService?* +#### *Why PrefService?* + - Atomic writes using ImportantFileWriter [[reference](https://source.chromium.org/chromium/chromium/src/+/main:base/files/important_file_writer.h;l=28-29)] - Async writes which means no blocking of the UI thread, preventing jank during window state updates [[reference](https://docs.google.com/document/d/1rlwl_GvvokatMiRUUkR0vGNcraqe4weFGNilTsZgiXA/edit?tab=t.0#bookmark=id.49710km71rd7)] - Synchronous writes on demand [[reference](https://source.chromium.org/chromium/chromium/src/+/main:components/prefs/pref_service.h;l=199-209;drc=51cc784590d00e6a95b48e1e1bf5c3fe099edf64)] @@ -59,7 +60,7 @@ I think reusing the existing implementation and retaining the 10-second commit i
-Here's the schema I propose for the `windowStatePersistence` object. This is simply for reference as I have not explained what each field means yet. I will explain each field in the API specification section which is next. +Here's the schema I propose for the `windowStatePersistence` object. This is simply for reference. The explanation for each field in the object will be in the API specification section which is next.
@@ -74,10 +75,18 @@ BaseWindowConstructorOptions schema - This would be passed by the developer in t } } ``` +OR + +```json +{ + "name": string, + "windowStatePersistence": boolean +} +``` > [!NOTE] > The entire window state (bounds, maximized, fullscreen, etc.) would be saved internally if name and windowStatePersistence are passed. -> We would use the saved state and enforce the chosen config during restoration the next time the window is constructed. +> We would use the saved state and enforce the chosen config during restoration the next time the window is constructed. Setting `windowStatePersistence": true` is equivalent to setting `bounds: true` and `displayMode: true` #### **State Persistence Mechanism** @@ -89,47 +98,18 @@ We can respect the min/max height/width, fullscreenable, maximizable, minimizabl Each time a window is **moved** or **resized**, we schedule a write using Chromium's ScopedDictPrefUpdate [[example](https://github.com/electron/electron/blob/4ad20ccb396a3354d99b8843426bece2b28228bf/shell/browser/ui/inspectable_web_contents.cc#L837-L841)]. -Here's a reference to all the variables we would saving be internally using PrefService https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/chrome_views_delegate.cc;drc=1f9f3d7d4227fc2021915926b0e9f934cd610201;l=85 +Here's a reference to the variables we would saving be internally using PrefService https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/chrome_views_delegate.cc;drc=1f9f3d7d4227fc2021915926b0e9f934cd610201;l=85. Additional to these we will also save the current displayMode `fullscreen`, `maximized`, or `kiosk`. #### Developer-Facing Events -I’m also considering emitting these events: `will-save-window-state`, `saved-window-state`, `restored-window-state`. **However, I’m unsure about their practical usefulness for developers and hope to finalize them through this RFC.** - -I'm thinking we could make these events simple telemetry events. Simple telemetry events would provide observability without the performance cost of running the custom logic for hundreds of events per second during window manipulation. Adding these events with the ability to intercept and modify values that are going to be saved, ability to intercept with custom restoration logic, would increase the implementation complexity and maintainability. - -The following events will be emitted for window state persistence process: - -#### Event: 'will-save-window-state' - -Emitted before window state is saved. This event is triggered when the window is closed, resized, moved or when `win.savePreferences()` is called. - -**Parameters:** -- `event` Event - The event object with `preventDefault()` method - -```js -win.on('will-save-window-state', (event) => { - console.log('About to save window state'); - - // Prevent the save operation if needed - if (someCondition) { - event.preventDefault(); - } -}); -``` - -#### Event: 'saved-window-state' +I’m also considering emitting only a single event: `restored-window-state` which would be emitted after window construction. I'm unsure about the rest `will-restore-window-state`, `will-save-window-state` and `saved-window-state`. My doubts regarding these are -Emitted after window state has been successfully written to disk. - -```js -win.on('saved-window-state', () => { - console.log('Window state successfully saved'); -}); -``` +1) Static event for `will-restore-window-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-window-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. +2) Save events: Is it worth adding `will-save-window-state` and `saved-window-state`? Chromium's PrefService doesn't emit events, so this might be tricky to implement cleanly. #### Event: 'restored-window-state' -Emitted immediately after the window constructor completes with the restored state applied. +Emitted immediately after the window constructor completes. ```js win.on('restored-window-state', () => { @@ -137,16 +117,15 @@ win.on('restored-window-state', () => { }); ``` - ## API Specification Here's how it would land in the electron documentation. ### BaseWindowConstructorOptions -* `name` string (optional) - An identifier for the window that enables features such as state persistence. +* `name` string (optional) - A unique identifier for the window that enables features such as state persistence. -* `windowStatePersistence` ([WindowStatePersistence]() | Boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. _Experimental_ +* `windowStatePersistence` ([WindowStatePersistence] | Boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. _Experimental_ ### WindowStatePersistence Object @@ -168,137 +147,19 @@ const win = new BrowserWindow({ ### API Design Rationale: - Everything is set to true by default so that the developer can set whatever they want to exclude to false. This means less lines of code as more options are added to the config. -- Displays are considered the same if they span over the same work_area dimensions. +- Displays are considered the same if they span over the same work_area (x, y) and have the same dimensions. - A window can never be restored out of reach. I am presuming no apps would want this behavior. - If window (width, height) reopens on a different display and does not fit on screen auto adjust to fit and resave the value. This would reduce the number of edge cases significantly and I highly doubt that any app would want to preserve an overflow when opened on a different display. - Not handling scaling as Windows, macOS, and Linux support multimonitor window scaling by default. ### Additional APIs -I also plan on adding synchronous methods to `BaseWindow` to save, restore, clear, and get the window state or rather window preferences. Down below is the API spec for that. - -Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. - ->[!NOTE] -> Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). - -#### `win.savePreferences([options])` - -* `options` Object - - * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux - - * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS - - * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS - - * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` - - * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux - - * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS - - * `title` boolean (optional) - Save window's current title. Default: `false` - - * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS - - * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS - - * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` - - * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` - - * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS - - * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS - - * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS - - * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` - - * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` - - * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` - - * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` - - * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` - - * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS - - * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` - - * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS - - * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS - - * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS - - * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` - - * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS - - * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS - - * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS - - * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows - - * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS - - * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux - - * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` - - * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` - - * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` - - * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` - - * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` - - * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` - - * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` - - -Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. - -```js -const { BrowserWindow } = require('electron') -const win = new BrowserWindow({ - ...existingOptions, - name: '#1230', - windowStatePersistence: true -}); - -// Save additional properties -win.savePreferences({ - autoHideMenuBar: true, - focusable: true, - visibleOnAllWorkspaces: true, - shadow: true, - menuBarVisible: true, - representedFilename: true, -}); -``` - -#### `win.restorePreferences()` - -Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. -Preferences will be restored in the order of the options object passed during savePreferences. -```js -// Restore the previously saved preferences -const success = win.restorePreferences() -console.log(success) // true if state was successfully restored -``` #### `BaseWindow.clearState([name])` Static function over BaseWindow. Clears the saved preferences for the window **including bounds**. -Returns `boolean` - Whether the state was successfully cleared. Returns true if name was not found. +Returns `boolean` - Whether the state was successfully cleared. Returns true even if the name was not found. ```js // Clear the entire saved state for the window @@ -306,12 +167,9 @@ const success = BaseWindow.clearState('#1230'); console.log(success) // true if state was successfully cleared ``` - ### Algorithm for saving/restoring the window state As mentioned before, we would take a continuous approach to saving window state if `name` and `windowStatePersistence` are passed through the constructor. -For the preferences it will be important to store the order of the properties as well when win.savePreferences is called due to possible conflicting properties. We can restore them in the same order and it will be as though the properties were set via JavaScript APIs in the exact same order. - The algorithm would be simple two step approach: **Calculate new bounds:** @@ -327,7 +185,7 @@ Once we have the width, height, x, y, and displayModes we can set the window sta an Electron app developer.* Electron is introducing `windowStatePersistence`, an optional object/boolean in the `BaseWindowConstructorOptions` and would also be available in `BrowserWindowConstructorOptions`. -It can be used to save and restore window state with multiple configurations. +It can be used to save and restore your window state (x, y, height, width, fullscreen, etc.) `windowStatePersistence` schema - Everything is optional. Developers can choose what they want to preserve and how they want to restore it or simply set it to true. The default behaviour in this case would be `bounds` and `displayMode` both are restored. @@ -364,12 +222,10 @@ It would be the same code for Windows, macOS, and Linux using Chromium's PrefSer The path to upgrade for apps would be for developers to remove their existing implementation and use this new API if they want to. If their implementation is on the JavaScript side it will always execute their restore logic after window construction which means our implementation would be overridden. - ## Reference-level explanation Covered in the [Implementation Details](#implementation-details) section. - ## Drawbacks - Writing to disk everytime the window is moved or resized in a batched 10 second window might not be necessary and better to write to disk only on window close synchronously. - Similar outcomes could be achieved via JavaScript APIs with miniscule performance difference. The only issue being the window state is not set at the right time in the window lifecycle. @@ -390,7 +246,7 @@ Why should we *not* do this? *What is the impact of not doing this?* -It's not a critical issue. Apps might vanish on rare occasions. +It's not a critical issue. App windows might vanish on rare occasions. *If this is an API proposal, could this be done as a JavaScript module or a native Node.js add-on?* @@ -436,8 +292,127 @@ The `bounds` property that is suggested above could possibly accept an object or We could add `allowOverflow` property inside the `bounds` object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we won't be considering this and can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402) for the time being. -APIs to allow changes in `windowStatePersistence` during runtime for apps that want to let users decide save/restore behaviour. +APIs to allow changes in `windowStatePersistence` during runtime for apps that want to let users of the application decide save/restore behaviour. APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([name])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 +Additional synchronous API methods to `BaseWindow` to save, restore, and get the window state or rather window preferences. Down below is the API spec for that. + +Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. + +>[!NOTE] +> Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). + +#### `win.savePreferences([options])` + +* `options` Object + + * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux + + * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS + + * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS + + * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` + + * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux + + * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS + + * `title` boolean (optional) - Save window's current title. Default: `false` + + * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS + + * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS + + * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` + + * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` + + * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS + + * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS + + * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS + + * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` + + * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` + + * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` + + * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` + + * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` + + * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS + + * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` + + * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS + + * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS + + * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS + + * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` + + * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS + + * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS + + * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS + + * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows + + * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS + + * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux + + * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` + + * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` + + * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` + + * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` + + * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` + + * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` + + * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` + + +Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. + +```js +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + ...existingOptions, + name: '#1230', + windowStatePersistence: true +}); + +// Save additional properties +win.savePreferences({ + autoHideMenuBar: true, + focusable: true, + visibleOnAllWorkspaces: true, + shadow: true, + menuBarVisible: true, + representedFilename: true, +}); +``` + +#### `win.restorePreferences()` + +Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. +Preferences will be restored in the order of the options object passed during savePreferences. +```js +// Restore the previously saved preferences +const success = win.restorePreferences() +console.log(success) // true if state was successfully restored +``` + Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 2628ed9f3ce55bdf128442123d96f48dac978754 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 14 Aug 2025 18:43:38 -0400 Subject: [PATCH 16/26] unresolved questions and future possibilities --- text/0016-save-restore-window-state.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index c470bb1..20504c6 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -227,10 +227,12 @@ The path to upgrade for apps would be for developers to remove their existing im Covered in the [Implementation Details](#implementation-details) section. ## Drawbacks + - Writing to disk everytime the window is moved or resized in a batched 10 second window might not be necessary and better to write to disk only on window close synchronously. - Similar outcomes could be achieved via JavaScript APIs with miniscule performance difference. The only issue being the window state is not set at the right time in the window lifecycle. Why should we *not* do this? + - It's not a critical issue. - Adds maintenance burden for the Electron team to support this feature long-term. @@ -262,11 +264,13 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus ## Unresolved questions *What parts of the design do you expect to resolve through the RFC process before this gets merged?* -- Variable names and the entire API Spec. +- Should we switch from `name` to something more descriptive? Should we use the existing unique identifier `id` and allow developers to pass a unique `id`? +- Static event for `will-restore-window-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-window-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. +- Save events: Is it worth adding `will-save-window-state` and `saved-window-state`? Chromium's PrefService doesn't emit events, so this might be tricky to implement cleanly. ## Future possibilities -Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. +1) Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. * `openBehaviour` string (optional) - Special behavior when the window is opened. @@ -288,15 +292,15 @@ We would need an algorithm that calculates bounds based on these parameters. Man The algorithm to restore window state with the newly introduced options `fallbackBehaviour` and `openBehaviour` is detailed [here](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state). One particularly cool feature would be to provide an option to restore the window on closest available display/space dynamically. -The `bounds` property that is suggested above could possibly accept an object or boolean (as suggest as of now). The object would allow more configurability and control over reopening windows on different monitors with different dpi scaling and resolution. +2) The `bounds` property that is suggested above could possibly accept an object or boolean (boolean in current proposal). The object would allow more configurability and control over reopening windows on different monitors with different dpi scaling and resolution. -We could add `allowOverflow` property inside the `bounds` object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we won't be considering this and can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402) for the time being. +3) We could add `allowOverflow` property inside the `bounds` object to control the restore overflow behaviour (some apps would specifically like to not restore in an overflown state). In our current implementation we won't be considering this and can have something like [this](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=402) for the time being. -APIs to allow changes in `windowStatePersistence` during runtime for apps that want to let users of the application decide save/restore behaviour. +4) APIs to allow changes in `windowStatePersistence` during runtime for apps that want to let users of the application decide save/restore behaviour. -APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([name])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 +5) APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([name])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 -Additional synchronous API methods to `BaseWindow` to save, restore, and get the window state or rather window preferences. Down below is the API spec for that. +6) Additional synchronous API methods to `BaseWindow` to save, restore, and get the window state or rather window preferences. Down below is the API spec for that. Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. From 453ae587a22824e3eb55d6ea95b670446e31a27f Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 14 Aug 2025 18:50:06 -0400 Subject: [PATCH 17/26] better formatting --- text/0016-save-restore-window-state.md | 162 ++++++++++++------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 20504c6..0241f69 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -272,25 +272,25 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus 1) Introduce custom behavior under `fallbackBehaviour` and `openBehaviour` parameters based on community requests. -* `openBehaviour` string (optional) - Special behavior when the window is opened. + * `openBehaviour` string (optional) - Special behavior when the window is opened. - * `openOnLaunchedDisplay` - Opens the window on the display that it was launched from always. + * `openOnLaunchedDisplay` - Opens the window on the display that it was launched from always. - * `openOnPrimaryDisplay` - Opens the window on the primary display always. + * `openOnPrimaryDisplay` - Opens the window on the primary display always. -* `fallbackBehaviour` string (optional) - Fallback behaviour when position is out of bounds. + * `fallbackBehaviour` string (optional) - Fallback behaviour when position is out of bounds. - * `"snapToNearestDisplayEdge"` - Snaps the window to the nearest display edge. + * `"snapToNearestDisplayEdge"` - Snaps the window to the nearest display edge. - * `"centerOnNearestDisplay"` - Centers the window on the nearest display. + * `"centerOnNearestDisplay"` - Centers the window on the nearest display. - * `"centerOnPrimaryDisplay"` - Centers the window on the primary display. + * `"centerOnPrimaryDisplay"` - Centers the window on the primary display. -We would need an algorithm that calculates bounds based on these parameters. Many things would be needed to be taken into consideration to accomplish this. + We would need an algorithm that calculates bounds based on these parameters. Many things would be needed to be taken into consideration to accomplish this. -The algorithm to restore window state with the newly introduced options `fallbackBehaviour` and `openBehaviour` is detailed [here](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state). One particularly cool feature would be to provide an option to restore the window on closest available display/space dynamically. + The algorithm to restore window state with the newly introduced options `fallbackBehaviour` and `openBehaviour` is detailed [here](https://gist.github.com/nilayarya/48d24e38d8dbf67dd05eef9310f147c6#algorithm-for-savingrestoring-the-window-state). One particularly cool feature would be to provide an option to restore the window on closest available display/space dynamically. 2) The `bounds` property that is suggested above could possibly accept an object or boolean (boolean in current proposal). The object would allow more configurability and control over reopening windows on different monitors with different dpi scaling and resolution. @@ -302,121 +302,121 @@ The algorithm to restore window state with the newly introduced options `fallbac 6) Additional synchronous API methods to `BaseWindow` to save, restore, and get the window state or rather window preferences. Down below is the API spec for that. -Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. + Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. ->[!NOTE] -> Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). + >[!NOTE] + > Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). -#### `win.savePreferences([options])` + #### `win.savePreferences([options])` -* `options` Object - - * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux - - * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS + * `options` Object + + * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux + + * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS - * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS + * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS - * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` + * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` - * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux + * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux - * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS + * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS - * `title` boolean (optional) - Save window's current title. Default: `false` + * `title` boolean (optional) - Save window's current title. Default: `false` - * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS + * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS - * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS + * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS - * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` + * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` - * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` + * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` - * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS + * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS - * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS + * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS - * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS + * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS - * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` + * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` - * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` + * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` - * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` + * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` - * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` + * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` - * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` + * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` - * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS + * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS - * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` + * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` - * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS + * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS - * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS + * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS - * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS + * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS - * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` + * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` - * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS + * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS - * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS + * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS - * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS + * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS - * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows + * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows - * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS + * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS - * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux + * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux - * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` + * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` - * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` + * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` - * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` + * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` - * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` + * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` - * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` + * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` - * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` + * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` - * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` - + * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` + -Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. + Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. -```js -const { BrowserWindow } = require('electron') -const win = new BrowserWindow({ - ...existingOptions, - name: '#1230', - windowStatePersistence: true -}); + ```js + const { BrowserWindow } = require('electron') + const win = new BrowserWindow({ + ...existingOptions, + name: '#1230', + windowStatePersistence: true + }); -// Save additional properties -win.savePreferences({ - autoHideMenuBar: true, - focusable: true, - visibleOnAllWorkspaces: true, - shadow: true, - menuBarVisible: true, - representedFilename: true, -}); -``` + // Save additional properties + win.savePreferences({ + autoHideMenuBar: true, + focusable: true, + visibleOnAllWorkspaces: true, + shadow: true, + menuBarVisible: true, + representedFilename: true, + }); + ``` -#### `win.restorePreferences()` + #### `win.restorePreferences()` -Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. -Preferences will be restored in the order of the options object passed during savePreferences. -```js -// Restore the previously saved preferences -const success = win.restorePreferences() -console.log(success) // true if state was successfully restored -``` + Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. + Preferences will be restored in the order of the options object passed during savePreferences. + ```js + // Restore the previously saved preferences + const success = win.restorePreferences() + console.log(success) // true if state was successfully restored + ``` Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 1ad2b0fe28c97e6619c8e97dd36faa9935dc5b4c Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 14 Aug 2025 18:51:36 -0400 Subject: [PATCH 18/26] better formatting --- text/0016-save-restore-window-state.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 0241f69..88ad26f 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -304,8 +304,8 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. - >[!NOTE] - > Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). +> [!NOTE] +> Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). #### `win.savePreferences([options])` From a5cb50508a03716c54d78bf7f73ea2c9397692de Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 14 Aug 2025 18:53:22 -0400 Subject: [PATCH 19/26] better formatting --- text/0016-save-restore-window-state.md | 140 ++++++++++++------------- 1 file changed, 69 insertions(+), 71 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 88ad26f..dc8983e 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -302,121 +302,119 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus 6) Additional synchronous API methods to `BaseWindow` to save, restore, and get the window state or rather window preferences. Down below is the API spec for that. - Here's an exhaustive list of the options that can be saved. It would provide a way to save additional properties on the window apart from the bounds itself. - > [!NOTE] > Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). - #### `win.savePreferences([options])` +#### `win.savePreferences([options])` - * `options` Object - - * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux - - * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS +* `options` Object + + * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux + + * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS - * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS + * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS - * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` + * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` - * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux + * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux - * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS + * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS - * `title` boolean (optional) - Save window's current title. Default: `false` + * `title` boolean (optional) - Save window's current title. Default: `false` - * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS + * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS - * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS + * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS - * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` + * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` - * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` + * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` - * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS + * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS - * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS + * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS - * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS + * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS - * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` + * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` - * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` + * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` - * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` + * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` - * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` + * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` - * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` + * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` - * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS + * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS - * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` + * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` - * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS + * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS - * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS + * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS - * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS + * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS - * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` + * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` - * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS + * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS - * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS + * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS - * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS + * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS - * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows + * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows - * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS + * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS - * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux + * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux - * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` + * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` - * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` + * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` - * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` + * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` - * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` + * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` - * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` + * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` - * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` + * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` - * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` - + * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` + - Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. +Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. - ```js - const { BrowserWindow } = require('electron') - const win = new BrowserWindow({ - ...existingOptions, - name: '#1230', - windowStatePersistence: true - }); +```js +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + ...existingOptions, + name: '#1230', + windowStatePersistence: true +}); - // Save additional properties - win.savePreferences({ - autoHideMenuBar: true, - focusable: true, - visibleOnAllWorkspaces: true, - shadow: true, - menuBarVisible: true, - representedFilename: true, - }); - ``` +// Save additional properties +win.savePreferences({ + autoHideMenuBar: true, + focusable: true, + visibleOnAllWorkspaces: true, + shadow: true, + menuBarVisible: true, + representedFilename: true, +}); +``` - #### `win.restorePreferences()` +#### `win.restorePreferences()` - Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. - Preferences will be restored in the order of the options object passed during savePreferences. - ```js - // Restore the previously saved preferences - const success = win.restorePreferences() - console.log(success) // true if state was successfully restored - ``` +Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. +Preferences will be restored in the order of the options object passed during savePreferences. +```js +// Restore the previously saved preferences +const success = win.restorePreferences() +console.log(success) // true if state was successfully restored +``` Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file From 4d6caf81ee1b126b1209c0e27e7a2a041dfcfcf0 Mon Sep 17 00:00:00 2001 From: Nilay Arya <84241885+nilayarya@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:54:29 -0400 Subject: [PATCH 20/26] Apply suggestion from @erickzhao Co-authored-by: Erick Zhao --- text/0016-save-restore-window-state.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index dc8983e..70a4ad2 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -1,7 +1,7 @@ # RFC - Start Date: 2025-03-20 -- RFC PR: -- Electron Issues: Related to [electron/electron/issues/526](https://github.com/electron/electron/issues/526) +- RFC PR: [electron/rfcs#16](https://github.com/electron/rfcs/pull/16) +- Electron Issues: [electron/electron#526](https://github.com/electron/electron/issues/526) - Reference Implementation: https://github.com/electron/electron/tree/gsoc-2025 - Status: **Proposed** From 47d4996351146566075d563befe2ad744af6d938 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 28 Aug 2025 15:25:11 -0400 Subject: [PATCH 21/26] update docs for name and clearWindowState --- text/0016-save-restore-window-state.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 70a4ad2..4a2ef10 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -123,7 +123,7 @@ Here's how it would land in the electron documentation. ### BaseWindowConstructorOptions -* `name` string (optional) - A unique identifier for the window that enables features such as state persistence. +* `name` string (optional) - A unique identifier for the window, used internally by Electron to enable features such as state persistence. Each window must have a distinct name. It can only be reused after the corresponding window has been destroyed. An error is thrown if the name is already in use. This is not the visible title shown to users on the title bar. * `windowStatePersistence` ([WindowStatePersistence] | Boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. _Experimental_ @@ -154,18 +154,15 @@ const win = new BrowserWindow({ ### Additional APIs -#### `BaseWindow.clearState([name])` +#### `BaseWindow.clearWindowState(name)` Static function over BaseWindow. -Clears the saved preferences for the window **including bounds**. -Returns `boolean` - Whether the state was successfully cleared. Returns true even if the name was not found. +* `name` string - The window `name` to clear state for (see [BaseWindowConstructorOptions](structures/base-window-options.md)). -```js -// Clear the entire saved state for the window -const success = BaseWindow.clearState('#1230'); -console.log(success) // true if state was successfully cleared -``` +Clears the saved state for a window with the given name. This removes all persisted window bounds, display mode, and work area information that was previously saved when `windowStatePersistence` was enabled. + +If the window `name` is empty or the window state doesn't exist, the method will log a warning. ### Algorithm for saving/restoring the window state As mentioned before, we would take a continuous approach to saving window state if `name` and `windowStatePersistence` are passed through the constructor. From a0141f31008b0a489a9706b4e0d57ddf78307543 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 28 Aug 2025 15:43:45 -0400 Subject: [PATCH 22/26] update API surface for win.restorePreferences([options]) --- text/0016-save-restore-window-state.md | 103 +++++++++++-------------- 1 file changed, 47 insertions(+), 56 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 4a2ef10..5044a0a 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -260,6 +260,7 @@ From my research, I found out native applications built directly with the operat I thought it would be inappropriate to enforce such rules on Electron apps. Thus, `windowStatePersistence` to provide flexibility and the choice to opt-in to this behavior. ## Unresolved questions + *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Should we switch from `name` to something more descriptive? Should we use the existing unique identifier `id` and allow developers to pass a unique `id`? - Static event for `will-restore-window-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-window-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. @@ -297,93 +298,91 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus 5) APIs to allow changes to the saved window state on disk such as `BrowserWindow.getWindowState([name])` and `BrowserWindow.setWindowState([stateObj])` might be useful for cloud synchronization of window states as suggested by this comment https://github.com/electron/rfcs/pull/16#issuecomment-2983249038 -6) Additional synchronous API methods to `BaseWindow` to save, restore, and get the window state or rather window preferences. Down below is the API spec for that. +6) Additional API called `win.restorePreferences([options])` to restore other properties on the `BaseWindow` > [!NOTE] -> Restoring these properties would be set in order passed by in the options object. It would be the equivalent of calling the instance methods on BrowserWindow in the same order as provided by the developer. For example, win.setAutoHideMenuBar(true). +> We always save these properties internally. Calling this API and restoring these properties would be set on the window in order passed by in the options object. It would be the equivalent of calling the instance methods on BaseWindow/BrowserWindow in the same order. For example, win.setAutoHideMenuBar(true). -#### `win.savePreferences([options])` +#### `win.restorePreferences([options])` * `options` Object + + * `autoHideMenuBar` boolean (optional) - Restore window's previous autoHideMenuBar state. Default: `false`   Windows Linux - * `autoHideMenuBar` boolean (optional) - Save window's current autoHideMenuBar state. Default: `false`   Windows Linux - - * `focusable` boolean (optional) - Save window's current focusable state. Default: `false`   Windows macOS + * `focusable` boolean (optional) - Restore window's previous focusable state. Default: `false`   Windows macOS - * `visibleOnAllWorkspaces` boolean (optional) - Save window's current visibleOnAllWorkspaces state. Default: `false`   macOS + * `visibleOnAllWorkspaces` boolean (optional) - Restore window's previous visibleOnAllWorkspaces state. Default: `false`   macOS - * `shadow` boolean (optional) - Save window's current shadow state. Default: `false` + * `shadow` boolean (optional) - Restore window's previous shadow state. Default: `false` - * `menuBarVisible` boolean (optional) - Save window's current menuBarVisible state. Default: `false`   Windows Linux + * `menuBarVisible` boolean (optional) - Restore window's previous menuBarVisible state. Default: `false`   Windows Linux - * `representedFilename` boolean (optional) - Save window's current representedFilename. Default: `false`   macOS + * `representedFilename` boolean (optional) - Restore window's previous representedFilename. Default: `false`   macOS - * `title` boolean (optional) - Save window's current title. Default: `false` + * `title` boolean (optional) - Restore window's previous title. Default: `false` - * `minimizable` boolean (optional) - Save window's current minimizable state. Default: `false`   Windows macOS + * `minimizable` boolean (optional) - Restore window's previous minimizable state. Default: `false`   Windows macOS - * `maximizable` boolean (optional) - Save window's current maximizable state. Default: `false`   Windows macOS + * `maximizable` boolean (optional) - Restore window's previous maximizable state. Default: `false`   Windows macOS - * `fullscreenable` boolean (optional) - Save window's current fullscreenable state. Default: `false` + * `fullscreenable` boolean (optional) - Restore window's previous fullscreenable state. Default: `false` - * `resizable` boolean (optional) - Save window's current resizable state. Default: `false` + * `resizable` boolean (optional) - Restore window's previous resizable state. Default: `false` - * `closable` boolean (optional) - Save window's current closable state. Default: `false`   Windows macOS + * `closable` boolean (optional) - Restore window's previous closable state. Default: `false`   Windows macOS - * `movable` boolean (optional) - Save window's current movable state. Default: `false`   Windows macOS + * `movable` boolean (optional) - Restore window's previous movable state. Default: `false`   Windows macOS - * `excludedFromShownWindowsMenu` boolean (optional) - Save window's current excludedFromShownWindowsMenu state. Default: `false`   macOS + * `excludedFromShownWindowsMenu` boolean (optional) - Restore window's previous excludedFromShownWindowsMenu state. Default: `false`   macOS - * `accessibleTitle` boolean (optional) - Save window's current accessibleTitle. Default: `false` + * `accessibleTitle` boolean (optional) - Restore window's previous accessibleTitle. Default: `false` - * `backgroundColor` boolean (optional) - Save window's current backgroundColor. Default: `false` + * `backgroundColor` boolean (optional) - Restore window's previous backgroundColor. Default: `false` - * `aspectRatio` boolean (optional) - Save window's current aspectRatio. Default: `false` + * `aspectRatio` boolean (optional) - Restore window's previous aspectRatio. Default: `false` - * `minimumSize` boolean (optional) - Save window's current minimumSize (width, height). Default: `false` + * `minimumSize` boolean (optional) - Restore window's previous minimumSize (width, height). Default: `false` - * `maximumSize` boolean (optional) - Save window's current maximumSize (width, height). Default: `false` + * `maximumSize` boolean (optional) - Restore window's previous maximumSize (width, height). Default: `false` - * `hiddenInMissionControl` boolean (optional) - Save window's current hiddenInMissionControl state. Default: `false`   macOS + * `hiddenInMissionControl` boolean (optional) - Restore window's previous hiddenInMissionControl state. Default: `false`   macOS - * `alwaysOnTop` boolean (optional) - Save window's current alwaysOnTop state. Default: `false` + * `alwaysOnTop` boolean (optional) - Restore window's previous alwaysOnTop state. Default: `false` - * `skipTaskbar` boolean (optional) - Save window's current skipTaskbar state. Default: `false`   Windows macOS + * `skipTaskbar` boolean (optional) - Restore window's previous skipTaskbar state. Default: `false`   Windows macOS - * `opacity` boolean (optional) - Save window's current opacity. Default: `false`   Windows macOS + * `opacity` boolean (optional) - Restore window's previous opacity. Default: `false`   Windows macOS - * `windowButtonVisibility` boolean (optional) - Save window's current windowButtonVisibility state. Default: `false`   macOS + * `windowButtonVisibility` boolean (optional) - Restore window's previous windowButtonVisibility state. Default: `false`   macOS - * `ignoreMouseEvents` boolean (optional) - Save window's current ignoreMouseEvents state. Default: `false` + * `ignoreMouseEvents` boolean (optional) - Restore window's previous ignoreMouseEvents state. Default: `false` - * `contentProtection` boolean (optional) - Save window's current contentProtection state. Default: `false`   Windows macOS + * `contentProtection` boolean (optional) - Restore window's previous contentProtection state. Default: `false`   Windows macOS - * `autoHideCursor` boolean (optional) - Save window's current autoHideCursor state. Default: `false`   macOS + * `autoHideCursor` boolean (optional) - Restore window's previous autoHideCursor state. Default: `false`   macOS - * `vibrancy` boolean (optional) - Save window's current vibrancy state. Default: `false`   macOS + * `vibrancy` boolean (optional) - Restore window's previous vibrancy state. Default: `false`   macOS - * `backgroundMaterial` boolean (optional) - Save window's current backgroundMaterial state. Default: `false`   Windows + * `backgroundMaterial` boolean (optional) - Restore window's previous backgroundMaterial state. Default: `false`   Windows - * `windowButtonPosition` boolean (optional) - Save window's current windowButtonPosition state. Default: `false`   macOS + * `windowButtonPosition` boolean (optional) - Restore window's previous windowButtonPosition state. Default: `false`   macOS - * `titleBarOverlay` boolean (optional) - Save window's current titleBarOverlay state. Default: `false`   Windows Linux + * `titleBarOverlay` boolean (optional) - Restore window's previous titleBarOverlay state. Default: `false`   Windows Linux - * `zoomLevel` boolean (optional) - Save window webcontent's current zoomLevel. Default: `false` + * `zoomLevel` boolean (optional) - Restore window webcontent's previous zoomLevel. Default: `false` - * `audioMuted` boolean (optional) - Save window webcontent's current audioMuted state. Default: `false` + * `audioMuted` boolean (optional) - Restore window webcontent's previous audioMuted state. Default: `false` - * `isDevToolsOpened` boolean (optional) - Save window webcontent's current isDevToolsOpened state. Default: `false` + * `isDevToolsOpened` boolean (optional) - Restore window webcontent's previous isDevToolsOpened state. Default: `false` - * `devToolsTitle` boolean (optional) - Save window webcontent's current devToolsTitle. Default: `false` + * `devToolsTitle` boolean (optional) - Restore window webcontent's previous devToolsTitle. Default: `false` - * `ignoreMenuShortcuts` boolean (optional) - Save window webcontent's current ignoreMenuShortcuts state. Default: `false` + * `ignoreMenuShortcuts` boolean (optional) - Restore window webcontent's previous ignoreMenuShortcuts state. Default: `false` - * `frameRate` boolean (optional) - Save window webcontent's current frameRate. Default: `false` + * `frameRate` boolean (optional) - Restore window webcontent's previous frameRate. Default: `false` - * `backgroundThrottling` boolean (optional) - Save window webcontent's current backgroundThrottling state. Default: `false` - + * `backgroundThrottling` boolean (optional) - Restore window webcontent's previous backgroundThrottling state. Default: `false` -Returns `boolean` - Whether the state was successfully saved. Relevant events would be emitted. ```js const { BrowserWindow } = require('electron') @@ -393,8 +392,8 @@ const win = new BrowserWindow({ windowStatePersistence: true }); -// Save additional properties -win.savePreferences({ + +win.restorePreferences({ autoHideMenuBar: true, focusable: true, visibleOnAllWorkspaces: true, @@ -404,14 +403,6 @@ win.savePreferences({ }); ``` -#### `win.restorePreferences()` - -Returns `boolean` - Whether the preferences were successfully restored and applied to the window. Relevant events would be emitted. -Preferences will be restored in the order of the options object passed during savePreferences. -```js -// Restore the previously saved preferences -const success = win.restorePreferences() -console.log(success) // true if state was successfully restored -``` +Preferences will be restored in the order of the options object passed during savePreferences. Relevant events would be emitted with the state of the restored window in an object. -Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. \ No newline at end of file +Overall, the `windowStatePersistence` is very configurable so I think it's future-proof. More configurations can be added based on community requests. From 901a7e97fde743d517579b701af1903098b3c6df Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Thu, 4 Sep 2025 15:21:40 -0400 Subject: [PATCH 23/26] update clearWindowState to clearPersistedState --- text/0016-save-restore-window-state.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 5044a0a..5a08be8 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -123,7 +123,7 @@ Here's how it would land in the electron documentation. ### BaseWindowConstructorOptions -* `name` string (optional) - A unique identifier for the window, used internally by Electron to enable features such as state persistence. Each window must have a distinct name. It can only be reused after the corresponding window has been destroyed. An error is thrown if the name is already in use. This is not the visible title shown to users on the title bar. +* `name` string (optional) - A unique identifier for the window to enable features such as state persistence. An error will be thrown in the constructor if a window exists using the same identifier. It can only be reused after the corresponding window has been destroyed. An error is thrown if the name is already in use. This is not the visible title shown to users on the title bar. * `windowStatePersistence` ([WindowStatePersistence] | Boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. _Experimental_ @@ -154,7 +154,7 @@ const win = new BrowserWindow({ ### Additional APIs -#### `BaseWindow.clearWindowState(name)` +#### `BaseWindow.clearPersistedState(name)` Static function over BaseWindow. From ef3d3e268b1493b3724602a3b1f50b01d8a25aa9 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Wed, 10 Sep 2025 13:08:53 -0400 Subject: [PATCH 24/26] better formatting --- text/0016-save-restore-window-state.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 5a08be8..fe3e7db 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -64,7 +64,7 @@ Here's the schema I propose for the `windowStatePersistence` object. This is sim
-BaseWindowConstructorOptions schema - This would be passed by the developer in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions +`BaseWindowConstructorOptions` schema - This would be passed by the developer in the `BaseWindowConstructorOptions`/`BrowserWindowConstructorOptions` ```json { @@ -92,7 +92,7 @@ OR Firstly, all the window states with their `windowStatePersistence` would be to loaded from disk synchronously during the startup process just like other preferences in Electron. Doing so would allow us the have the states in memory during window creation with minimal performance impact. -Secondly, once the states are loaded into memory, we can use them to restore the window state with the `windowStatePersistence` rules in place during window creation. An algorithm that handles all the edges would be required for this. Chromium's [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) seems like a good reference point that handles some of the edge cases. Also, the default options provided in the BaseWindowConstructorOptions/BrowserWindowConstructorOptions constructor options would be overridden (if we are restoring state). +Secondly, once the states are loaded into memory, we can use them to restore the window state with the `windowStatePersistence` rules in place during window creation. An algorithm that handles all the edges would be required for this. Chromium's [WindowSizer::AdjustBoundsToBeVisibleOnDisplay](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/window_sizer/window_sizer.cc;drc=0ec56065ba588552f21633aa47280ba02c3cd160;l=350) seems like a good reference point that handles some of the edge cases. Also, the default options provided in the `BaseWindowConstructorOptions`/`BrowserWindowConstructorOptions` constructor options would be overridden (if we are restoring state). We can respect the min/max height/width, fullscreenable, maximizable, minimizable properties set inside `BaseWindowConstructorOptions` if applicable. Meaning these properties would take a higher priority during the restoration of a window. From 90bf523f14e98a2bf2836129f57a6dad89fc2ed3 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Wed, 10 Sep 2025 13:10:17 -0400 Subject: [PATCH 25/26] add docs for no display scenario --- text/0016-save-restore-window-state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index fe3e7db..7f0ed33 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -125,7 +125,7 @@ Here's how it would land in the electron documentation. * `name` string (optional) - A unique identifier for the window to enable features such as state persistence. An error will be thrown in the constructor if a window exists using the same identifier. It can only be reused after the corresponding window has been destroyed. An error is thrown if the name is already in use. This is not the visible title shown to users on the title bar. -* `windowStatePersistence` ([WindowStatePersistence] | Boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. _Experimental_ +`windowStatePersistence` ([WindowStatePersistence] | boolean) (optional) - Configures or enables the persistence of window state (position, size, maximized state, etc.) across application restarts. Has no effect if window `name` is not provided. Automatically disabled when there is no available display. _Experimental_ ### WindowStatePersistence Object From af0ad6297cafb1d70dd43bfc8b7ada04323ec4d1 Mon Sep 17 00:00:00 2001 From: Nilay Arya Date: Wed, 10 Sep 2025 13:11:24 -0400 Subject: [PATCH 26/26] update event names to reduce ambiguity --- text/0016-save-restore-window-state.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/text/0016-save-restore-window-state.md b/text/0016-save-restore-window-state.md index 7f0ed33..ccdda18 100644 --- a/text/0016-save-restore-window-state.md +++ b/text/0016-save-restore-window-state.md @@ -102,17 +102,19 @@ Here's a reference to the variables we would saving be internally using PrefServ #### Developer-Facing Events -I’m also considering emitting only a single event: `restored-window-state` which would be emitted after window construction. I'm unsure about the rest `will-restore-window-state`, `will-save-window-state` and `saved-window-state`. My doubts regarding these are +I’m also considering emitting only a single event: `restored-persisted-state` which would be emitted after window construction. -1) Static event for `will-restore-window-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-window-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. +I'm unsure about the rest `will-restore-persisted-state`, `will-save-window-state` and `saved-window-state`. My doubts regarding these are + +1) Static event for `will-restore-persisted-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-persisted-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. 2) Save events: Is it worth adding `will-save-window-state` and `saved-window-state`? Chromium's PrefService doesn't emit events, so this might be tricky to implement cleanly. -#### Event: 'restored-window-state' +#### Event: 'restored-persisted-state' Emitted immediately after the window constructor completes. ```js -win.on('restored-window-state', () => { +win.on('restored-persisted-state', () => { console.log('Window state restored'); }); ``` @@ -263,7 +265,7 @@ I thought it would be inappropriate to enforce such rules on Electron apps. Thus *What parts of the design do you expect to resolve through the RFC process before this gets merged?* - Should we switch from `name` to something more descriptive? Should we use the existing unique identifier `id` and allow developers to pass a unique `id`? -- Static event for `will-restore-window-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-window-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. +- Static event for `will-restore-persisted-state`? Since restoration happens during window construction, should we add `BaseWindow.on('will-restore-persisted-state', ...)` as a static event to allow `e.preventDefault()` call before the JS window object exists? Without a static event it won't be possible to listen to this event meaningfully. - Save events: Is it worth adding `will-save-window-state` and `saved-window-state`? Chromium's PrefService doesn't emit events, so this might be tricky to implement cleanly. ## Future possibilities