windows - move state handling into own class

This commit is contained in:
Benjamin Pasero 2020-12-14 09:59:07 +01:00
parent 75eeac0104
commit f413b81fcd
6 changed files with 325 additions and 290 deletions

View file

@ -297,10 +297,10 @@ export class LaunchMainService implements ILaunchMainService {
return this.browserWindowToInfo(window.win, folderURIs, window.remoteAuthority);
}
private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = [], remoteAuthority?: string): IWindowInfo {
private browserWindowToInfo(window: BrowserWindow, folderURIs: URI[] = [], remoteAuthority?: string): IWindowInfo {
return {
pid: win.webContents.getOSProcessId(),
title: win.getTitle(),
pid: window.webContents.getOSProcessId(),
title: window.getTitle(),
folderURIs,
remoteAuthority
};

View file

@ -124,9 +124,11 @@ export interface IWindowsMainService {
readonly _serviceBrand: undefined;
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
readonly onWindowOpened: Event<ICodeWindow>;
readonly onWindowReady: Event<ICodeWindow>;
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
readonly onWindowDestroyed: Event<ICodeWindow>;
open(openConfig: IOpenConfiguration): ICodeWindow[];
openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[];

View file

@ -13,7 +13,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { screen, BrowserWindow, MessageBoxOptions, Display, app, WebContents } from 'electron';
import { screen, BrowserWindow, MessageBoxOptions, Display, WebContents } from 'electron';
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
@ -30,7 +30,7 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { normalizePath, originalFSPath, removeTrailingPathSeparator, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData, IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsStateStorage';
import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler';
import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { once } from 'vs/base/common/functional';
import { Disposable } from 'vs/base/common/lifecycle';
@ -145,24 +145,22 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
declare readonly _serviceBrand: undefined;
private static readonly windowsStateStorageKey = 'windowsState';
private static readonly WINDOWS: ICodeWindow[] = [];
private readonly windowsState: IWindowsState;
private lastClosedWindowState?: IWindowState;
private shuttingDown = false;
private readonly _onWindowOpened = this._register(new Emitter<ICodeWindow>());
readonly onWindowOpened = this._onWindowOpened.event;
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
readonly onWindowReady = this._onWindowReady.event;
private readonly _onWindowDestroyed = this._register(new Emitter<ICodeWindow>());
readonly onWindowDestroyed = this._onWindowDestroyed.event;
private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
readonly onWindowsCountChanged = this._onWindowsCountChanged.event;
private readonly windowsStateHandler = this._register(new WindowsStateHandler(this.stateService, this.lifecycleMainService, this, this.logService));
constructor(
private readonly machineId: string,
private readonly initialUserEnv: IProcessEnvironment,
@ -179,166 +177,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
) {
super();
this.windowsState = restoreWindowsState(this.stateService.getItem<WindowsStateStorageData>(WindowsMainService.windowsStateStorageKey));
this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
}
private registerListeners(): void {
// When a window looses focus, save all windows state. This allows to
// prevent loss of window-state data when OS is restarted without properly
// shutting down the application (https://github.com/microsoft/vscode/issues/87171)
app.on('browser-window-blur', () => {
if (!this.shuttingDown) {
this.saveWindowsState();
}
});
// Handle various lifecycle events around windows
this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown());
this.onWindowsCountChanged(e => {
if (e.newCount - e.oldCount > 0) {
// clear last closed window state when a new window opens. this helps on macOS where
// otherwise closing the last window, opening a new window and then quitting would
// use the state of the previously closed window when restarting.
this.lastClosedWindowState = undefined;
}
});
// Signal a window is ready after having entered a workspace
this._register(this.workspacesMainService.onWorkspaceEntered(event => {
this._onWindowReady.fire(event.window);
}));
}
// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
// - macOS: since the app will not quit when closing the last window, you will always first get
// the onBeforeShutdown() event followed by N onBeforeWindowClose() events for each window
// - other: on other OS, closing the last window will quit the app so the order depends on the
// user interaction: closing the last window will first trigger onBeforeWindowClose()
// and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
// and then onBeforeWindowClose().
//
// Here is the behavior on different OS depending on action taken (Electron 1.7.x):
//
// Legend
// - quit(N): quit application with N windows opened
// - close(1): close one window via the window close button
// - closeAll: close all windows via the taskbar command
// - onBeforeShutdown(N): number of windows reported in this event handler
// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
//
// macOS
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - quit(0): onBeforeShutdown(0)
// - close(1): onBeforeWindowClose(1, false)
//
// Windows
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - close(1): onBeforeWindowClose(2, false)[not last window]
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
// Linux
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - close(1): onBeforeWindowClose(2, false)[not last window]
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
private onBeforeShutdown(): void {
this.shuttingDown = true;
this.saveWindowsState();
}
private saveWindowsState(): void {
const currentWindowsState: IWindowsState = {
openedWindows: [],
lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
lastActiveWindow: this.lastClosedWindowState
};
// 1.) Find a last active window (pick any other first window otherwise)
if (!currentWindowsState.lastActiveWindow) {
let activeWindow = this.getLastActiveWindow();
if (!activeWindow || activeWindow.isExtensionDevelopmentHost) {
activeWindow = this.getWindows().find(window => !window.isExtensionDevelopmentHost);
}
if (activeWindow) {
currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
}
}
// 2.) Find extension host window
const extensionHostWindow = this.getWindows().find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost);
if (extensionHostWindow) {
currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
}
// 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update
//
// Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0)
// so if we ever want to persist the UI state of the last closed window (window count === 1), it has
// to come from the stored lastClosedWindowState on Win/Linux at least
if (this.getWindowCount() > 1) {
currentWindowsState.openedWindows = this.getWindows().filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window));
}
// Persist
const state = getWindowsStateStoreData(currentWindowsState);
this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state);
if (this.shuttingDown) {
this.logService.trace('onBeforeShutdown', state);
}
}
// See note on #onBeforeShutdown() for details how these events are flowing
private onBeforeWindowClose(win: ICodeWindow): void {
if (this.lifecycleMainService.quitRequested) {
return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
}
// On Window close, update our stored UI state of this window
const state: IWindowState = this.toWindowState(win);
if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
}
// Any non extension host window with same workspace or folder
else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) {
this.windowsState.openedWindows.forEach(o => {
const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
const sameFolder = win.openedFolderUri && o.folderUri && extUriBiasedIgnorePathCase.isEqual(o.folderUri, win.openedFolderUri);
if (sameWorkspace || sameFolder) {
o.uiState = state.uiState;
}
});
}
// On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state
// before quitting, we need to remember the UI state of this window to be able to persist it.
// On macOS we keep the last closed window state ready in case the user wants to quit right after or
// wants to open another window, in which case we use this state over the persisted one.
if (this.getWindowCount() === 1) {
this.lastClosedWindowState = state;
}
}
private toWindowState(win: ICodeWindow): IWindowState {
return {
workspace: win.openedWorkspace,
folderUri: win.openedFolderUri,
backupPath: win.backupPath,
remoteAuthority: win.remoteAuthority,
uiState: win.serializeWindowState()
};
this._register(this.workspacesMainService.onWorkspaceEntered(event => this._onWindowReady.fire(event.window)));
}
openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] {
@ -434,13 +279,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Otherwise, find a good window based on open params
else {
const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
const focusLastActive = this.windowsStateHandler.state.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
let focusLastOpened = true;
let focusLastWindow = true;
// 2.) focus last active window if we are not instructed to open any paths
if (focusLastActive) {
const lastActiveWindow = usedWindows.filter(window => this.windowsState.lastActiveWindow && window.backupPath === this.windowsState.lastActiveWindow.backupPath);
const lastActiveWindow = usedWindows.filter(window => this.windowsStateHandler.state.lastActiveWindow && window.backupPath === this.windowsStateHandler.state.lastActiveWindow.backupPath);
if (lastActiveWindow.length) {
lastActiveWindow[0].focus();
focusLastOpened = false;
@ -987,10 +832,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Collect previously opened windows
const openedWindows: IWindowState[] = [];
if (restoreWindowsSetting !== 'one') {
openedWindows.push(...this.windowsState.openedWindows);
openedWindows.push(...this.windowsStateHandler.state.openedWindows);
}
if (this.windowsState.lastActiveWindow) {
openedWindows.push(this.windowsState.lastActiveWindow);
if (this.windowsStateHandler.state.lastActiveWindow) {
openedWindows.push(this.windowsStateHandler.state.lastActiveWindow);
}
const windowsToOpen: IPathToOpen[] = [];
@ -1287,7 +1132,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) {
const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
const extensionDevelopmentWindowState = this.windowsStateHandler.state.lastPluginDevelopmentHostWindow;
const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
if (workspaceToOpen) {
if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
@ -1471,7 +1316,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Window Events
once(createdWindow.onReady)(() => this._onWindowReady.fire(createdWindow));
once(createdWindow.onClose)(() => this.onWindowClosed(createdWindow));
once(createdWindow.onDestroy)(() => this.onBeforeWindowClose(createdWindow)); // try to save state before destroy because close will not fire
once(createdWindow.onDestroy)(() => this._onWindowDestroyed.fire(createdWindow));
createdWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
createdWindow.win.webContents.on('devtools-reload-page', () => this.lifecycleMainService.reload(createdWindow));
@ -1536,14 +1381,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (!configuration.extensionTestsPath) {
// extension development host Window - load from stored settings if any
if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
if (!!configuration.extensionDevelopmentPath && this.windowsStateHandler.state.lastPluginDevelopmentHostWindow) {
return this.windowsStateHandler.state.lastPluginDevelopmentHostWindow.uiState;
}
// Known Workspace - load from stored settings
const workspace = configuration.workspace;
if (workspace) {
const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
const stateForWorkspace = this.windowsStateHandler.state.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
if (stateForWorkspace.length) {
return stateForWorkspace[0];
}
@ -1551,7 +1396,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Known Folder - load from stored settings
if (configuration.folderUri) {
const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && extUriBiasedIgnorePathCase.isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
const stateForFolder = this.windowsStateHandler.state.openedWindows.filter(o => o.folderUri && extUriBiasedIgnorePathCase.isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
if (stateForFolder.length) {
return stateForFolder[0];
}
@ -1559,14 +1404,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Empty windows with backups
else if (configuration.backupPath) {
const stateForEmptyWindow = this.windowsState.openedWindows.filter(o => o.backupPath === configuration.backupPath).map(o => o.uiState);
const stateForEmptyWindow = this.windowsStateHandler.state.openedWindows.filter(o => o.backupPath === configuration.backupPath).map(o => o.uiState);
if (stateForEmptyWindow.length) {
return stateForEmptyWindow[0];
}
}
// First Window
const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
const lastActiveState = this.windowsStateHandler.lastClosedState || this.windowsStateHandler.state.lastActiveWindow;
if (!lastActive && lastActiveState) {
return lastActiveState.uiState;
}
@ -1660,10 +1505,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return state;
}
private onWindowClosed(win: ICodeWindow): void {
private onWindowClosed(window: ICodeWindow): void {
// Remove from our list so that Electron can clean it up
const index = WindowsMainService.WINDOWS.indexOf(win);
const index = WindowsMainService.WINDOWS.indexOf(window);
WindowsMainService.WINDOWS.splice(index, 1);
// Emit

View file

@ -0,0 +1,295 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { app } from 'electron';
import { Disposable } from 'vs/base/common/lifecycle';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export type WindowsStateStorageData = object;
export interface IWindowState {
workspace?: IWorkspaceIdentifier;
folderUri?: URI;
backupPath?: string;
remoteAuthority?: string;
uiState: IWindowUIState;
}
export interface IWindowsState {
lastActiveWindow?: IWindowState;
lastPluginDevelopmentHostWindow?: IWindowState;
openedWindows: IWindowState[];
}
interface ISerializedWindowsState {
readonly lastActiveWindow?: ISerializedWindowState;
readonly lastPluginDevelopmentHostWindow?: ISerializedWindowState;
readonly openedWindows: ISerializedWindowState[];
}
interface ISerializedWindowState {
readonly workspaceIdentifier?: { id: string; configURIPath: string };
readonly folder?: string;
readonly backupPath?: string;
readonly remoteAuthority?: string;
readonly uiState: IWindowUIState;
// deprecated
readonly folderUri?: UriComponents;
readonly folderPath?: string;
readonly workspace?: { id: string; configPath: string };
}
export class WindowsStateHandler extends Disposable {
private static readonly windowsStateStorageKey = 'windowsState';
get state() { return this._windowsState; }
private readonly _windowsState: IWindowsState;
get lastClosedState() { return this._lastClosedState; }
private _lastClosedState?: IWindowState;
private shuttingDown = false;
constructor(
@IStateService private readonly stateService: IStateService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@ILogService private readonly logService: ILogService
) {
super();
this._windowsState = restoreWindowsState(this.stateService.getItem<WindowsStateStorageData>(WindowsStateHandler.windowsStateStorageKey));
this.registerListeners();
}
private registerListeners(): void {
// When a window looses focus, save all windows state. This allows to
// prevent loss of window-state data when OS is restarted without properly
// shutting down the application (https://github.com/microsoft/vscode/issues/87171)
app.on('browser-window-blur', () => {
if (!this.shuttingDown) {
this.saveWindowsState();
}
});
// Handle various lifecycle events around windows
this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown());
this.windowsMainService.onWindowsCountChanged(e => {
if (e.newCount - e.oldCount > 0) {
// clear last closed window state when a new window opens. this helps on macOS where
// otherwise closing the last window, opening a new window and then quitting would
// use the state of the previously closed window when restarting.
this._lastClosedState = undefined;
}
});
// try to save state before destroy because close will not fire
this.windowsMainService.onWindowDestroyed(window => this.onBeforeWindowClose(window));
}
// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
// - macOS: since the app will not quit when closing the last window, you will always first get
// the onBeforeShutdown() event followed by N onBeforeWindowClose() events for each window
// - other: on other OS, closing the last window will quit the app so the order depends on the
// user interaction: closing the last window will first trigger onBeforeWindowClose()
// and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
// and then onBeforeWindowClose().
//
// Here is the behavior on different OS depending on action taken (Electron 1.7.x):
//
// Legend
// - quit(N): quit application with N windows opened
// - close(1): close one window via the window close button
// - closeAll: close all windows via the taskbar command
// - onBeforeShutdown(N): number of windows reported in this event handler
// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
//
// macOS
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - quit(0): onBeforeShutdown(0)
// - close(1): onBeforeWindowClose(1, false)
//
// Windows
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - close(1): onBeforeWindowClose(2, false)[not last window]
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
// Linux
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - close(1): onBeforeWindowClose(2, false)[not last window]
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
private onBeforeShutdown(): void {
this.shuttingDown = true;
this.saveWindowsState();
}
private saveWindowsState(): void {
const currentWindowsState: IWindowsState = {
openedWindows: [],
lastPluginDevelopmentHostWindow: this._windowsState.lastPluginDevelopmentHostWindow,
lastActiveWindow: this._lastClosedState
};
// 1.) Find a last active window (pick any other first window otherwise)
if (!currentWindowsState.lastActiveWindow) {
let activeWindow = this.windowsMainService.getLastActiveWindow();
if (!activeWindow || activeWindow.isExtensionDevelopmentHost) {
activeWindow = this.windowsMainService.getWindows().find(window => !window.isExtensionDevelopmentHost);
}
if (activeWindow) {
currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
}
}
// 2.) Find extension host window
const extensionHostWindow = this.windowsMainService.getWindows().find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost);
if (extensionHostWindow) {
currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
}
// 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update
//
// Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0)
// so if we ever want to persist the UI state of the last closed window (window count === 1), it has
// to come from the stored lastClosedWindowState on Win/Linux at least
if (this.windowsMainService.getWindowCount() > 1) {
currentWindowsState.openedWindows = this.windowsMainService.getWindows().filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window));
}
// Persist
const state = getWindowsStateStoreData(currentWindowsState);
this.stateService.setItem(WindowsStateHandler.windowsStateStorageKey, state);
if (this.shuttingDown) {
this.logService.trace('[WindowsStateHandler] onBeforeShutdown', state);
}
}
// See note on #onBeforeShutdown() for details how these events are flowing
private onBeforeWindowClose(window: ICodeWindow): void {
if (this.lifecycleMainService.quitRequested) {
return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
}
// On Window close, update our stored UI state of this window
const state: IWindowState = this.toWindowState(window);
if (window.isExtensionDevelopmentHost && !window.isExtensionTestHost) {
this._windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
}
// Any non extension host window with same workspace or folder
else if (!window.isExtensionDevelopmentHost && (!!window.openedWorkspace || !!window.openedFolderUri)) {
this._windowsState.openedWindows.forEach(openedWindow => {
const sameWorkspace = window.openedWorkspace && openedWindow.workspace && openedWindow.workspace.id === window.openedWorkspace.id;
const sameFolder = window.openedFolderUri && openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, window.openedFolderUri);
if (sameWorkspace || sameFolder) {
openedWindow.uiState = state.uiState;
}
});
}
// On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state
// before quitting, we need to remember the UI state of this window to be able to persist it.
// On macOS we keep the last closed window state ready in case the user wants to quit right after or
// wants to open another window, in which case we use this state over the persisted one.
if (this.windowsMainService.getWindowCount() === 1) {
this._lastClosedState = state;
}
}
private toWindowState(window: ICodeWindow): IWindowState {
return {
workspace: window.openedWorkspace,
folderUri: window.openedFolderUri,
backupPath: window.backupPath,
remoteAuthority: window.remoteAuthority,
uiState: window.serializeWindowState()
};
}
}
export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState {
const result: IWindowsState = { openedWindows: [] };
const windowsState = data as ISerializedWindowsState || { openedWindows: [] };
if (windowsState.lastActiveWindow) {
result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow);
}
if (windowsState.lastPluginDevelopmentHostWindow) {
result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow);
}
if (Array.isArray(windowsState.openedWindows)) {
result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState));
}
return result;
}
function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
const result: IWindowState = { uiState: windowState.uiState };
if (windowState.backupPath) {
result.backupPath = windowState.backupPath;
}
if (windowState.remoteAuthority) {
result.remoteAuthority = windowState.remoteAuthority;
}
if (windowState.folder) {
result.folderUri = URI.parse(windowState.folder);
} else if (windowState.folderUri) {
result.folderUri = URI.revive(windowState.folderUri);
} else if (windowState.folderPath) {
result.folderUri = URI.file(windowState.folderPath);
}
if (windowState.workspaceIdentifier) {
result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) };
} else if (windowState.workspace) {
result.workspace = { id: windowState.workspace.id, configPath: URI.file(windowState.workspace.configPath) };
}
return result;
}
export function getWindowsStateStoreData(windowsState: IWindowsState): WindowsStateStorageData {
return {
lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow),
lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow),
openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws))
};
}
function serializeWindowState(windowState: IWindowState): ISerializedWindowState {
return {
workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() },
folder: windowState.folderUri && windowState.folderUri.toString(),
backupPath: windowState.backupPath,
remoteAuthority: windowState.remoteAuthority,
uiState: windowState.uiState
};
}

View file

@ -1,107 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export type WindowsStateStorageData = object;
export interface IWindowState {
workspace?: IWorkspaceIdentifier;
folderUri?: URI;
backupPath?: string;
remoteAuthority?: string;
uiState: IWindowUIState;
}
export interface IWindowsState {
lastActiveWindow?: IWindowState;
lastPluginDevelopmentHostWindow?: IWindowState;
openedWindows: IWindowState[];
}
interface ISerializedWindowsState {
readonly lastActiveWindow?: ISerializedWindowState;
readonly lastPluginDevelopmentHostWindow?: ISerializedWindowState;
readonly openedWindows: ISerializedWindowState[];
}
interface ISerializedWindowState {
readonly workspaceIdentifier?: { id: string; configURIPath: string };
readonly folder?: string;
readonly backupPath?: string;
readonly remoteAuthority?: string;
readonly uiState: IWindowUIState;
// deprecated
readonly folderUri?: UriComponents;
readonly folderPath?: string;
readonly workspace?: { id: string; configPath: string };
}
export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState {
const result: IWindowsState = { openedWindows: [] };
const windowsState = data as ISerializedWindowsState || { openedWindows: [] };
if (windowsState.lastActiveWindow) {
result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow);
}
if (windowsState.lastPluginDevelopmentHostWindow) {
result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow);
}
if (Array.isArray(windowsState.openedWindows)) {
result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState));
}
return result;
}
function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
const result: IWindowState = { uiState: windowState.uiState };
if (windowState.backupPath) {
result.backupPath = windowState.backupPath;
}
if (windowState.remoteAuthority) {
result.remoteAuthority = windowState.remoteAuthority;
}
if (windowState.folder) {
result.folderUri = URI.parse(windowState.folder);
} else if (windowState.folderUri) {
result.folderUri = URI.revive(windowState.folderUri);
} else if (windowState.folderPath) {
result.folderUri = URI.file(windowState.folderPath);
}
if (windowState.workspaceIdentifier) {
result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) };
} else if (windowState.workspace) {
result.workspace = { id: windowState.workspace.id, configPath: URI.file(windowState.workspace.configPath) };
}
return result;
}
export function getWindowsStateStoreData(windowsState: IWindowsState): WindowsStateStorageData {
return {
lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow),
lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow),
openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws))
};
}
function serializeWindowState(windowState: IWindowState): ISerializedWindowState {
return {
workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() },
folder: windowState.folderUri && windowState.folderUri.toString(),
backupPath: windowState.backupPath,
remoteAuthority: windowState.remoteAuthority,
uiState: windowState.uiState
};
}

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { restoreWindowsState, getWindowsStateStoreData, IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsStateStorage';
import { restoreWindowsState, getWindowsStateStoreData, IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsStateHandler';
import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';