From 147d632359979c3ea9fe86e4f8e44d22bdb2b69a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 7 Feb 2019 12:50:14 +0100 Subject: [PATCH] Test windows state serialization --- src/vs/code/electron-main/windows.ts | 94 +----- src/vs/code/electron-main/windowsState.ts | 100 ++++++ .../test/electron-main/windowsState.test.ts | 290 ++++++++++++++++++ 3 files changed, 396 insertions(+), 88 deletions(-) create mode 100644 src/vs/code/electron-main/windowsState.ts create mode 100644 src/vs/code/test/electron-main/windowsState.test.ts diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index a12e590c8e7..14a78336fb8 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -31,12 +31,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { normalizeNFC } from 'vs/base/common/normalization'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { Queue, timeout } from 'vs/base/common/async'; import { exists } from 'vs/base/node/pfs'; import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, fsPath } from 'vs/base/common/resources'; import { endsWith } from 'vs/base/common/strings'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; +import { IWindowsState, restoreWindowsState, WindowsStateStoreData, IWindowState, getWindowsStateStoreData } from 'vs/code/electron-main/windowsState'; const enum WindowError { UNRESPONSIVE = 1, @@ -47,40 +48,6 @@ interface INewWindowState extends ISingleWindowState { hasDefaultState?: boolean; } -interface IWindowState { - workspace?: IWorkspaceIdentifier; - folderUri?: URI; - backupPath: string; - remoteAuthority?: string; - uiState: ISingleWindowState; -} - -interface IWindowsState { - lastActiveWindow?: IWindowState; - lastPluginDevelopmentHostWindow?: IWindowState; - openedWindows: IWindowState[]; -} - -interface ISerializedWindowsState { - lastActiveWindow?: ISerializedWindowState; - lastPluginDevelopmentHostWindow?: ISerializedWindowState; - openedWindows: ISerializedWindowState[]; -} - -interface ISerializedWindowState { - workspaceIdentifier?: { id: string; configURIPath: string }; - folder?: string; - backupPath: string; - remoteAuthority?: string; - uiState: ISingleWindowState; - - // deprecated - folderUri?: UriComponents; - folderPath?: string; - workspace?: { id: string; configPath: string }; - -} - type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none'; interface IOpenBrowserWindowOptions { @@ -177,7 +144,9 @@ export class WindowsManager implements IWindowsMainService { @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - this.windowsState = this.getWindowsState(); + const windowsStateStoreData = this.stateService.getItem(WindowsManager.windowsStateStorageKey); + + this.windowsState = restoreWindowsState(windowsStateStoreData); if (!Array.isArray(this.windowsState.openedWindows)) { this.windowsState.openedWindows = []; } @@ -186,39 +155,6 @@ export class WindowsManager implements IWindowsMainService { this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, historyMainService, this); } - private getWindowsState(): IWindowsState { - const result: IWindowsState = { openedWindows: [] }; - const windowsState = this.stateService.getItem(WindowsManager.windowsStateStorageKey) || { openedWindows: [] }; - - if (windowsState.lastActiveWindow) { - result.lastActiveWindow = this.deserialize(windowsState.lastActiveWindow); - } - if (windowsState.lastPluginDevelopmentHostWindow) { - result.lastPluginDevelopmentHostWindow = this.deserialize(windowsState.lastPluginDevelopmentHostWindow); - } - if (Array.isArray(windowsState.openedWindows)) { - result.openedWindows = windowsState.openedWindows.map(windowState => this.deserialize(windowState)); - } - return result; - } - - private deserialize(windowState: ISerializedWindowState): IWindowState { - const result: IWindowState = { backupPath: windowState.backupPath, remoteAuthority: windowState.remoteAuthority, uiState: windowState.uiState }; - 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; - } - ready(initialUserEnv: IProcessEnvironment): void { this.initialUserEnv = initialUserEnv; @@ -337,25 +273,7 @@ export class WindowsManager implements IWindowsMainService { } // Persist - this.stateService.setItem(WindowsManager.windowsStateStorageKey, this.serializeWindowsState(currentWindowsState)); - } - - private serializeWindowsState(windowsState: IWindowsState): ISerializedWindowsState { - return { - lastActiveWindow: windowsState.lastActiveWindow && this.serialize(windowsState.lastActiveWindow), - lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && this.serialize(windowsState.lastPluginDevelopmentHostWindow), - openedWindows: windowsState.openedWindows.map(ws => this.serialize(ws)) - }; - } - - private serialize(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 - }; + this.stateService.setItem(WindowsManager.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState)); } // See note on #onBeforeShutdown() for details how these events are flowing diff --git a/src/vs/code/electron-main/windowsState.ts b/src/vs/code/electron-main/windowsState.ts new file mode 100644 index 00000000000..22b4ff725b2 --- /dev/null +++ b/src/vs/code/electron-main/windowsState.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWindowState as IWindowUIState, } from 'vs/platform/windows/electron-main/windows'; + +export interface IWindowState { + workspace?: IWorkspaceIdentifier; + folderUri?: URI; + backupPath?: string; + remoteAuthority?: string; + uiState: IWindowUIState; +} + +export interface IWindowsState { + lastActiveWindow?: IWindowState; + lastPluginDevelopmentHostWindow?: IWindowState; + openedWindows: IWindowState[]; +} + +export type WindowsStateStoreData = object; + +interface ISerializedWindowsState { + lastActiveWindow?: ISerializedWindowState; + lastPluginDevelopmentHostWindow?: ISerializedWindowState; + openedWindows: ISerializedWindowState[]; +} + +interface ISerializedWindowState { + workspaceIdentifier?: { id: string; configURIPath: string }; + folder?: string; + backupPath?: string; + remoteAuthority?: string; + uiState: IWindowUIState; + + // deprecated + folderUri?: UriComponents; + folderPath?: string; + workspace?: { id: string; configPath: string }; +} + +export function restoreWindowsState(data: WindowsStateStoreData | 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): WindowsStateStoreData { + 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 + }; +} \ No newline at end of file diff --git a/src/vs/code/test/electron-main/windowsState.test.ts b/src/vs/code/test/electron-main/windowsState.test.ts new file mode 100644 index 00000000000..4ab638a5b34 --- /dev/null +++ b/src/vs/code/test/electron-main/windowsState.test.ts @@ -0,0 +1,290 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import * as os from 'os'; + +import { IWindowsState, restoreWindowsState, getWindowsStateStoreData, IWindowState } from 'vs/code/electron-main/windowsState'; +import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; +import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; + +function getUIState(): IWindowUIState { + return { + x: 0, + y: 10, + width: 100, + height: 200, + mode: 0 + }; +} + +function toWorkspace(uri: URI): IWorkspaceIdentifier { + return { + id: '1234', + configPath: uri + }; +} +function assertEqualURI(u1: URI, u2: URI, message?: string): void { + assert.equal(u1 && u1.toString(), u2 && u2.toString(), message); +} + +function assertEqualWorkspace(w1: IWorkspaceIdentifier, w2: IWorkspaceIdentifier, message?: string): void { + if (!w1 || !w2) { + assert.equal(w1, w2, message); + return; + } + assert.equal(w1.id, w2.id, message); + assertEqualURI(w1.configPath, w2.configPath, message); +} + +function assertEqualWindowState(expected: IWindowState, actual: IWindowState, message?: string) { + if (!expected || !actual) { + assert.deepEqual(expected, actual, message); + return; + } + assert.equal(expected.backupPath, actual.backupPath, message); + assertEqualURI(expected.folderUri, actual.folderUri, message); + assert.equal(expected.remoteAuthority, actual.remoteAuthority, message); + assertEqualWorkspace(expected.workspace, actual.workspace, message); + assert.deepEqual(expected.uiState, actual.uiState, message); +} + +function assertEqualWindowsState(expected: IWindowsState, actual: IWindowsState, message?: string) { + assertEqualWindowState(expected.lastPluginDevelopmentHostWindow, actual.lastPluginDevelopmentHostWindow, message); + assertEqualWindowState(expected.lastActiveWindow, actual.lastActiveWindow, message); + assert.equal(expected.openedWindows.length, actual.openedWindows.length, message); + for (let i = 0; i < expected.openedWindows.length; i++) { + assertEqualWindowState(expected.openedWindows[i], actual.openedWindows[i], message); + } +} + +function assertRestoring(state: IWindowsState, message?: string) { + const stored = getWindowsStateStoreData(state); + const restored = restoreWindowsState(stored); + assertEqualWindowsState(state, restored, message); +} + +const testBackupPath1 = getRandomTestPath(os.tmpdir(), 'windowStateTest', 'backupFolder1'); +const testBackupPath2 = getRandomTestPath(os.tmpdir(), 'windowStateTest', 'backupFolder2'); + +const testWSPath = URI.file(getRandomTestPath(os.tmpdir(), 'windowStateTest', 'test.code-workspace')); +const testFolderURI = URI.file(getRandomTestPath(os.tmpdir(), 'windowStateTest', 'testFolder')); + +const testRemoteFolderURI = URI.parse('foo://bar/c/d'); + +suite('Windows State Storing', () => { + test('storing and restoring', () => { + let windowState: IWindowsState; + windowState = { + openedWindows: [] + }; + assertRestoring(windowState, 'no windows'); + windowState = { + openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState() }] + }; + assertRestoring(windowState, 'empty workspace'); + + windowState = { + openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), workspace: toWorkspace(testWSPath) }] + }; + assertRestoring(windowState, 'workspace'); + + windowState = { + openedWindows: [{ backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }] + }; + assertRestoring(windowState, 'folder'); + + windowState = { + openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), folderUri: testFolderURI }, { backupPath: testBackupPath1, uiState: getUIState(), folderUri: testRemoteFolderURI, remoteAuthority: 'bar' }] + }; + assertRestoring(windowState, 'multiple windows'); + + windowState = { + lastActiveWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }, + openedWindows: [] + }; + assertRestoring(windowState, 'lastActiveWindow'); + + windowState = { + lastPluginDevelopmentHostWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }, + openedWindows: [] + }; + assertRestoring(windowState, 'lastPluginDevelopmentHostWindow'); + }); + + test('open 1_31', () => { + const v1_31_workspace = `{ + "openedWindows": [], + "lastActiveWindow": { + "workspace": { + "id": "a41787288b5e9cc1a61ba2dd84cd0d80", + "configPath": "/home/aeschli/workspaces/code-and-docs.code-workspace" + }, + "backupPath": "/home/aeschli/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80", + "uiState": { + "mode": 0, + "x": 0, + "y": 27, + "width": 2560, + "height": 1364 + } + } + }`; + + let windowsState = restoreWindowsState(JSON.parse(v1_31_workspace)); + let expected: IWindowsState = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/aeschli/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80', + uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 }, + workspace: { id: 'a41787288b5e9cc1a61ba2dd84cd0d80', configPath: URI.file('/home/aeschli/workspaces/code-and-docs.code-workspace') } + } + }; + + assertEqualWindowsState(expected, windowsState, 'v1_31_workspace'); + + const v1_31_folder = `{ + "openedWindows": [], + "lastPluginDevelopmentHostWindow": { + "folderUri": { + "$mid": 1, + "fsPath": "/home/aeschli/workspaces/testing/customdata", + "external": "file:///home/aeschli/workspaces/testing/customdata", + "path": "/home/aeschli/workspaces/testing/customdata", + "scheme": "file" + }, + "uiState": { + "mode": 1, + "x": 593, + "y": 617, + "width": 1625, + "height": 595 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_31_folder)); + expected = { + openedWindows: [], + lastPluginDevelopmentHostWindow: { + uiState: { mode: WindowMode.Normal, x: 593, y: 617, width: 1625, height: 595 }, + folderUri: URI.parse('file:///home/aeschli/workspaces/testing/customdata') + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_31_folder'); + + const v1_31_empty_window = ` { + "openedWindows": [ + ], + "lastActiveWindow": { + "backupPath": "C:\\\\Users\\\\martinae\\\\AppData\\\\Roaming\\\\Code\\\\Backups\\\\1549538599815", + "uiState": { + "mode": 0, + "x": -8, + "y": -8, + "width": 2576, + "height": 1344 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_31_empty_window)); + expected = { + openedWindows: [], + lastActiveWindow: { + backupPath: 'C:\\Users\\martinae\\AppData\\Roaming\\Code\\Backups\\1549538599815', + uiState: { mode: WindowMode.Maximized, x: -8, y: -8, width: 2576, height: 1344 } + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_31_empty_window'); + + }); + + test('open 1_32', () => { + const v1_32_workspace = `{ + "openedWindows": [], + "lastActiveWindow": { + "workspaceIdentifier": { + "id": "53b714b46ef1a2d4346568b4f591028c", + "configURIPath": "file:///home/aeschli/workspaces/testing/custom.code-workspace" + }, + "backupPath": "/home/aeschli/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c", + "uiState": { + "mode": 0, + "x": 0, + "y": 27, + "width": 2560, + "height": 1364 + } + } + }`; + + let windowsState = restoreWindowsState(JSON.parse(v1_32_workspace)); + let expected: IWindowsState = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/aeschli/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c', + uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 }, + workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/aeschli/workspaces/testing/custom.code-workspace') } + } + }; + + assertEqualWindowsState(expected, windowsState, 'v1_32_workspace'); + + const v1_32_folder = `{ + "openedWindows": [], + "lastActiveWindow": { + "folder": "file:///home/aeschli/workspaces/testing/folding", + "backupPath": "/home/aeschli/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5", + "uiState": { + "mode": 1, + "x": 625, + "y": 263, + "width": 1718, + "height": 953 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_32_folder)); + expected = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/aeschli/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5', + uiState: { mode: WindowMode.Normal, x: 625, y: 263, width: 1718, height: 953 }, + folderUri: URI.parse('file:///home/aeschli/workspaces/testing/folding') + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_32_folder'); + + const v1_32_empty_window = ` { + "openedWindows": [ + ], + "lastActiveWindow": { + "backupPath": "/home/aeschli/.config/code-oss-dev/Backups/1549539668998", + "uiState": { + "mode": 1, + "x": 768, + "y": 336, + "width": 1024, + "height": 768 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_32_empty_window)); + expected = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/aeschli/.config/code-oss-dev/Backups/1549539668998', + uiState: { mode: WindowMode.Normal, x: 768, y: 336, width: 1024, height: 768 } + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_32_empty_window'); + + }); + +}); \ No newline at end of file