mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
* feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: restore window state * feat: flush display modes on show * refactor: move utility functions to common area * feat: clear window state * fix: wait for the prefs to update * test: clearWindowState extra test * test: refine clear window state tests * test: single monitor restore window tests chore: rebase on gsoc-2025 * refactor: refine clearWindowState test * fix: revert default_app back to original * docs: add comment linking AdjustBoundsToBeVisibleOnDisplay to Chromium code * fix: add correct permalink * refactor: ci friendly * fix: disable windowStatePersistence when no display * refactor: use reference instead pointer * fix: skip window state persistence for invalid/fake displays * refactor: better flag placement * test: add test to verify window state is not saved when no display * fix: restore display mode inside show() * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * test: clear sharedUserPath before and test * refactor: hasInvalidDisplay function * debug: add display info logging for CI * fix: do not save/restore when window is 0x0 * test: support for multimonitor tests (#47911) * test: support for multimonitor tests * fix: update yarn.lock file * test: support any resolution for new displays * test: support display positioning * docs: multi-monitor tests * test: remove dummy test * feat: enforce unique window names across BaseWindow and BrowserWindow (#47764) * feat: save window state (#47425) * feat: save/restore window state * cleanup * remove constructor option * refactor: apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: forward declare prefservice * refactor: remove constructor option * refactor: save window state on move/resize instead of moved/resized * feat: resave window state after construction * test: add basic window save tests * test: add work area tests * test: asynchronous batching behavior * docs: add windowStateRestoreOptions to BaseWindowConstructorOptions * chore: move includes to main block * Update spec/api-browser-window-spec.ts Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * docs: update docs/api/structures/base-window-options.md Co-authored-by: Erick Zhao <erick@hotmail.ca> * fix: preserve original bounds during window state save in special modes * feat: save kiosk state in window preferences * chore: remove ts-expect-error * test: check hasCapturableScreen before running tests * test: remove multimonitor tests * test: add missing hasCapturableScreen checks before tests * docs: add blurb on saving mechanism * feat: add debounce window of 200ms to saveWindowState * docs: remove blurb until finalized * style: convert constants from snake_case to camelCase * refactor: initialize prefs_ only if window state is configured to be saved/restored * refactor: rename window states key * refactor: store in application-level Local State instead of browser context * refactor: switch to more accurate function names * fix: add dcheck for browser_process * fix: flush window state to avoid race condition * refactor: change stateId to name * refactor: change windowStateRestoreOptions to windowStatePersistence * Update docs/api/structures/base-window-options.md Co-authored-by: David Sanders <dsanders11@ucsbalum.com> * fix: add warning when window state persistence enabled without window name * docs: lowercase capital B for consistency --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * feat: enforce unique window names across BaseWindow and BrowserWindow * docs: update docs for name property * fix: linter issue with symbol --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> * test: remove invalid display test --------- Co-authored-by: Charles Kerr <charles@charleskerr.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com> Co-authored-by: Erick Zhao <erick@hotmail.ca> Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
258 lines
7.6 KiB
TypeScript
258 lines
7.6 KiB
TypeScript
import { BaseWindow, WebContents, BrowserView } from 'electron/main';
|
|
import type { BrowserWindow as BWT } from 'electron/main';
|
|
|
|
const { BrowserWindow } = process._linkedBinding('electron_browser_window') as { BrowserWindow: typeof BWT };
|
|
|
|
Object.setPrototypeOf(BrowserWindow.prototype, BaseWindow.prototype);
|
|
|
|
BrowserWindow.prototype._init = function (this: BWT) {
|
|
// Call parent class's _init.
|
|
(BaseWindow.prototype as any)._init.call(this);
|
|
|
|
// Avoid recursive require.
|
|
const { app } = require('electron');
|
|
|
|
// Set ID at construction time so it's accessible after
|
|
// underlying window destruction.
|
|
const id = this.id;
|
|
Object.defineProperty(this, 'id', {
|
|
value: id,
|
|
writable: false
|
|
});
|
|
|
|
const nativeSetBounds = this.setBounds;
|
|
this.setBounds = (bounds, ...opts) => {
|
|
bounds = {
|
|
...this.getBounds(),
|
|
...bounds
|
|
};
|
|
nativeSetBounds.call(this, bounds, ...opts);
|
|
};
|
|
|
|
// Redirect focus/blur event to app instance too.
|
|
this.on('blur', (event: Electron.Event) => {
|
|
app.emit('browser-window-blur', event, this);
|
|
});
|
|
this.on('focus', (event: Electron.Event) => {
|
|
app.emit('browser-window-focus', event, this);
|
|
});
|
|
|
|
let unresponsiveEvent: NodeJS.Timeout | null = null;
|
|
const emitUnresponsiveEvent = () => {
|
|
unresponsiveEvent = null;
|
|
if (!this.isDestroyed() && this.isEnabled()) { this.emit('unresponsive'); }
|
|
};
|
|
this.webContents.on('unresponsive', () => {
|
|
if (!unresponsiveEvent) { unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50); }
|
|
});
|
|
this.webContents.on('responsive', () => {
|
|
if (unresponsiveEvent) {
|
|
clearTimeout(unresponsiveEvent);
|
|
unresponsiveEvent = null;
|
|
}
|
|
this.emit('responsive');
|
|
});
|
|
this.on('close', (event) => {
|
|
queueMicrotask(() => {
|
|
if (!unresponsiveEvent && !event?.defaultPrevented) {
|
|
unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 5000);
|
|
}
|
|
});
|
|
});
|
|
this.webContents.on('destroyed', () => {
|
|
if (unresponsiveEvent) clearTimeout(unresponsiveEvent);
|
|
unresponsiveEvent = null;
|
|
});
|
|
|
|
// Subscribe to visibilityState changes and pass to renderer process.
|
|
let isVisible = this.isVisible() && !this.isMinimized();
|
|
const visibilityChanged = () => {
|
|
const newState = this.isVisible() && !this.isMinimized();
|
|
if (isVisible !== newState) {
|
|
isVisible = newState;
|
|
const visibilityState = isVisible ? 'visible' : 'hidden';
|
|
this.webContents.emit('-window-visibility-change', visibilityState);
|
|
}
|
|
};
|
|
|
|
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
|
|
for (const event of visibilityEvents) {
|
|
this.on(event as any, visibilityChanged);
|
|
}
|
|
|
|
this._browserViews = [];
|
|
|
|
this.on('closed', () => {
|
|
this._browserViews.forEach(b => b.webContents?.close({ waitForBeforeUnload: true }));
|
|
});
|
|
|
|
// Notify the creation of the window.
|
|
app.emit('browser-window-created', { preventDefault () {} }, this);
|
|
|
|
Object.defineProperty(this, 'devToolsWebContents', {
|
|
enumerable: true,
|
|
configurable: false,
|
|
get () {
|
|
return this.webContents.devToolsWebContents;
|
|
}
|
|
});
|
|
};
|
|
|
|
const isBrowserWindow = (win: any) => {
|
|
return win && win.constructor.name === 'BrowserWindow';
|
|
};
|
|
|
|
BrowserWindow.fromId = (id: number) => {
|
|
const win = BaseWindow.fromId(id);
|
|
return isBrowserWindow(win) ? win as any as BWT : null;
|
|
};
|
|
|
|
BrowserWindow.getAllWindows = () => {
|
|
return BaseWindow.getAllWindows().filter(isBrowserWindow) as any[] as BWT[];
|
|
};
|
|
|
|
BrowserWindow.clearWindowState = BaseWindow.clearWindowState;
|
|
|
|
BrowserWindow.getFocusedWindow = () => {
|
|
for (const window of BrowserWindow.getAllWindows()) {
|
|
if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
|
|
if (window.isFocused() || window.webContents.isDevToolsFocused()) return window;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
BrowserWindow.fromWebContents = (webContents: WebContents) => {
|
|
return webContents.getOwnerBrowserWindow();
|
|
};
|
|
|
|
BrowserWindow.fromBrowserView = (browserView: BrowserView) => {
|
|
return BrowserWindow.fromWebContents(browserView.webContents);
|
|
};
|
|
|
|
// Forwarded to webContents:
|
|
|
|
BrowserWindow.prototype.loadURL = function (...args) {
|
|
return this.webContents.loadURL(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.getURL = function () {
|
|
return this.webContents.getURL();
|
|
};
|
|
|
|
BrowserWindow.prototype.loadFile = function (...args) {
|
|
return this.webContents.loadFile(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.reload = function (...args) {
|
|
return this.webContents.reload(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.send = function (...args) {
|
|
return this.webContents.send(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.openDevTools = function (...args) {
|
|
return this.webContents.openDevTools(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.closeDevTools = function () {
|
|
return this.webContents.closeDevTools();
|
|
};
|
|
|
|
BrowserWindow.prototype.isDevToolsOpened = function () {
|
|
return this.webContents.isDevToolsOpened();
|
|
};
|
|
|
|
BrowserWindow.prototype.isDevToolsFocused = function () {
|
|
return this.webContents.isDevToolsFocused();
|
|
};
|
|
|
|
BrowserWindow.prototype.toggleDevTools = function () {
|
|
return this.webContents.toggleDevTools();
|
|
};
|
|
|
|
BrowserWindow.prototype.inspectElement = function (...args) {
|
|
return this.webContents.inspectElement(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.inspectSharedWorker = function () {
|
|
return this.webContents.inspectSharedWorker();
|
|
};
|
|
|
|
BrowserWindow.prototype.inspectServiceWorker = function () {
|
|
return this.webContents.inspectServiceWorker();
|
|
};
|
|
|
|
BrowserWindow.prototype.showDefinitionForSelection = function () {
|
|
return this.webContents.showDefinitionForSelection();
|
|
};
|
|
|
|
BrowserWindow.prototype.capturePage = function (...args) {
|
|
return this.webContents.capturePage(...args);
|
|
};
|
|
|
|
BrowserWindow.prototype.getBackgroundThrottling = function () {
|
|
return this.webContents.getBackgroundThrottling();
|
|
};
|
|
|
|
BrowserWindow.prototype.setBackgroundThrottling = function (allowed: boolean) {
|
|
return this.webContents.setBackgroundThrottling(allowed);
|
|
};
|
|
|
|
BrowserWindow.prototype.addBrowserView = function (browserView: BrowserView) {
|
|
if (this._browserViews.includes(browserView)) {
|
|
return;
|
|
}
|
|
|
|
const ownerWindow = browserView.ownerWindow;
|
|
if (ownerWindow && ownerWindow !== this) {
|
|
ownerWindow.removeBrowserView(browserView);
|
|
}
|
|
this.contentView.addChildView(browserView.webContentsView);
|
|
browserView.ownerWindow = this;
|
|
browserView.webContents._setOwnerWindow(this);
|
|
this._browserViews.push(browserView);
|
|
};
|
|
|
|
BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
|
|
this._browserViews.forEach(bv => {
|
|
this.removeBrowserView(bv);
|
|
});
|
|
if (browserView) { this.addBrowserView(browserView); }
|
|
};
|
|
|
|
BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {
|
|
const idx = this._browserViews.indexOf(browserView);
|
|
if (idx >= 0) {
|
|
this.contentView.removeChildView(browserView.webContentsView);
|
|
browserView.ownerWindow = null;
|
|
this._browserViews.splice(idx, 1);
|
|
}
|
|
};
|
|
|
|
BrowserWindow.prototype.getBrowserView = function () {
|
|
if (this._browserViews.length > 1) {
|
|
throw new Error('This BrowserWindow has multiple BrowserViews - use getBrowserViews() instead');
|
|
}
|
|
return this._browserViews[0] ?? null;
|
|
};
|
|
|
|
BrowserWindow.prototype.getBrowserViews = function () {
|
|
return [...this._browserViews];
|
|
};
|
|
|
|
BrowserWindow.prototype.setTopBrowserView = function (browserView: BrowserView) {
|
|
if (browserView.ownerWindow !== this) {
|
|
throw new Error('Given BrowserView is not attached to the window');
|
|
}
|
|
const idx = this._browserViews.indexOf(browserView);
|
|
if (idx >= 0) {
|
|
this.contentView.addChildView(browserView.webContentsView);
|
|
this._browserViews.splice(idx, 1);
|
|
this._browserViews.push(browserView);
|
|
}
|
|
};
|
|
|
|
module.exports = BrowserWindow;
|