-
Notifications
You must be signed in to change notification settings - Fork 9
rfc: Save/Restore Window-State API #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I typed up some comments while reviewing last week, but forgot to hit submit 🤦♂️
- 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this also include things like "Window Snapping"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Snapping a window does not make the window enter any special displayMode on windows and linux. We can handle it as 'normal' state only. I've updated the RFC to reflect the same.
On macOS the 'tile window to the left/right of the screen' makes it enter fullscreen mode officially. We can treat it as a fullscreen state. If you think that's inappropriate we can restore it in a normal state to where it was before the split screen. This is something that needs to be finalized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@electron/api-wg
|
||
#### 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@electron/api-wg any thoughts on these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@electron/api-wg any thoughts on these?
If these events allow for intercepting and overriding the state save, I can see them being used to redirect the new state to other places. The same applies to reading the window state, as it could be read or loaded from a different source.
For example, in my comment on electron/electron#47425 (comment) (4th paragraph), I am thinking of saving the users' window states in the cloud and attempting to read them from there on window initialization. This way, their preferred window position would persist even across different machines and instances of the application executable. If these events allow for that use case, they would be very welcome.
Edit: If this is not possible, maybe it could be done by providing a loader and saver handler to windowStateRestoreOptions
but if this is not planned to be supported as of now, I suppose it would complicate the API and implementation a lot. Flexibility is always good, but it may not be worth pursuing this at this stage of this RFC.
Edit 2: The option in my first edit seems very cumbersome. Maybe the will-save-window-state
and saved-window-state
can pass the data to be saved as a parameter to their handler? And also allow for intercepting and cancelling the save in the default format. Then, maybe the will-restore-window-state
could restore the window saved with custom data if the event handler returns such custom data, while also allowing for the cancellation of the default behaviour? I imagine I could integrate cloud persistence of the window state on a per-user basis if these features were available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: If this is not possible, maybe it could be done by providing a loader and saver handler to windowStateRestoreOptions but if this is not planned to be supported as of now, I suppose it would complicate the API and implementation a lot. Flexibility is always good, but it may not be worth pursuing this at this stage of this RFC.
The issue with this is that const win = new BrowserWindow()
is synchronous. Custom loader functions could introduce async operations during window construction. Also, Custom loader/saver handlers would completely defeat the purpose of having a declarative API.
Edit 2: The option in my first edit seems very cumbersome. Maybe the will-save-window-state and saved-window-state can pass the data to be saved as a parameter to their handler? And also allow for intercepting and cancelling the save in the default format. Then, maybe the will-restore-window-state could restore the window saved with custom data if the event handler returns such custom data, while also allowing for the cancellation of the default behaviour? I imagine I could integrate cloud persistence of the window state on a per-user basis if these features were available.
On second thought there isn't much utility for these events beyond just telemetry. Adding these events with the ability to intercept and modify saved values, add custom restoration logic, use a custom format would unnecessarily increase the implementation complexity and maintainability. Performance implications of running custom logic for hundreds of events per second during window manipulation needs to be considered.
We can definitely reconsider this upon further discussion. Thanks for the suggestion.
Let me know if you can think of any use case that cannot be accomplished via app.setWindowState()
app.getWindowState()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For your cloud sync use case, a simpler approach might be for electron to provide an API like app.setWindowState([stateId], [stateObj])
that allows you to synchronously modify saved states before new BrowserWindow()
initialization.
This would enable you to leverage electron's restoration logic for all the edge cases (display changes, bounds validation, etc.) rather than requiring you to reimplement that complexity yourself.
For saving to the cloud you could use app.getWindowState([stateId])
at your leisure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the API should be kept as simple as possible, while allowing versatility.
For your cloud sync use case, a simpler approach might be for electron to provide an API like
app.setWindowState([stateId], [stateObj])
that allows you to synchronously modify saved states beforenew BrowserWindow()
initialization.
Would app.setWindowState([stateId], [stateObj])
overwrite the locally saved window states for the specific stateId
s provided? This would make it possible to support my use case, because I could pull the latest state from the cloud and update it locally upon each app launch.
For saving to the cloud you could use
app.getWindowState([stateId])
at your leisure.
I would suggest having a separate BrowserWindow.getState()
or BrowserWindow.getCurrentState()
that avoids reading the window state from the disk and returns the BrowserWindow
's current state computed directly from memory. This would avoid a file read if I understand correctly, right?
Furthermore, this API would make the events you suggest very useful for my cloud use case. It would enable getting the new window state via BrowserWindow.getState()
and pushing it to the cloud only when will-save-window-state
fires (provided that BrowserWindow.getState()
would return the same state that will be saved when will-save-window-state
fires).
Having will-save-window-state
would allow for 1) intercepting the new state even if, for any reason, the file write fails and 2) avoid adding handlers to the resize
and move
events, which fire very often when the window is moving and need manual debouncing to avoid performance issues (I've faced this in my JS soultion, handling it using a setTimeout
as in electron/electron#47425 (comment)).
While the events would mainly serve for telemetry purposes, I suppose other developers who want to do quirky things like my cloud use case will find them useful in other contexts. I'd suggest keeping them unless they add complexity that is a maintenance burden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest having a separate
BrowserWindow.getState()
orBrowserWindow.getCurrentState()
that avoids reading the window state from the disk and returns theBrowserWindow
's current state computed directly from memory. This would avoid a file read if I understand correctly, right?
Yes, it would avoid a file read. BrowserWindow.getState()
or BrowserWindow.getCurrentState()
are planned but not finalized yet.
While the events would mainly serve for telemetry purposes, I suppose other developers who want to do quirky things like my cloud use case will find them useful in other contexts. I'd suggest keeping them unless they add complexity that is a maintenance burden.
Implementation complexity and maintenance for these events should be fine. I've added them to the rfc.
- 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Snapping a window does not make the window enter any special displayMode on windows and linux. We can handle it as 'normal' state only. I've updated the RFC to reflect the same.
On macOS the 'tile window to the left/right of the screen' makes it enter fullscreen mode officially. We can treat it as a fullscreen state. If you think that's inappropriate we can restore it in a normal state to where it was before the split screen. This is something that needs to be finalized.
- 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@electron/api-wg
I'm not trying to be disruptive here, but after giving my proposed cloud sync use case some thought, I would like to put forward a few ideas, based on my opinions of how such a use case (and other potential non-standard use cases) could be supported most cleanly. Would it be possible for Alternatively, the window state loading could be fully decoupled into a dedicated API ( To complete this flow, two instance-level events could be added. EDIT: This new What I try to achieve with these proposals is the ability to pass the window state to a new window and to get it for external saving, when it is updated, by completely bypassing the local JSON file persistence functionality. The current way the API is proposed would not allow for that and would require additional code to clean up window states periodically from disk. I'm eager to hear your feedback and opinions. |
This can be considered as an alternative to
To skip local persistence developers can skip passing the
This can be moved to future possibilities. |
But I suppose
While this is true, setting the window position manually with state from an external source would bypass the proposed repositioning algorithm that avoids overflow and excessive clipping.
Yes, but is If state persistence is disabled (by not passing the If there were a way to add a callback to the moment the window would normally persist the state (even when it does not do it), it could be intercepted by a user function and processed further (i.e., sent to the cloud). |
I just noticed that All that is left is the ability to have the state passed to/used by the window be read from any source rather than the local disk, thus my initial suggestion to allow passing the state directly to the window, avoiding I feel like I'm disrupting your work with my specific, non-standard use case, and I don't want to impede the development of this feature. As such, I will sum up my final ideas as concisely as possible here:
With just these 2 features, non-standard use cases such as mine could be supported while avoiding file reads and writes completely. For example, my cloud sync use case could fetch the window state from the cloud, adjust it using I hope you don't mind my comments, I'm just trying to help make this feature as flexible as possible, the best I can. |
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: We don't have an App
class here to attach static functions on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I meant static over BaseWindow
. So BaseWindow.getWindowState([stateId])
BaseWindow.setWindowState([stateObj])
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving these APIs to future possibilities as it adds complications to the restoration process as discussed yesterday during the weekly gsoc meeting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving these APIs to future possibilities as it adds complications to the restoration process as discussed yesterday during the weekly gsoc meeting.
Are there public notes or details of these meetings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we don't post public transcripts/minutes from our Summer of Code meetings. 😅
Co-authored-by: Erick Zhao <erick@hotmail.ca>
Note
This RFC is part of GSoC 2025
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 theBaseWindowConstructorOptions
.Related: electron/electron#526