diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 04b54a97d91..a87f4fbf69a 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -25,19 +25,21 @@ const preloadGlobals = globals(); const webFrame = preloadGlobals.webFrame; const safeProcess = preloadGlobals.process; - const configuration = parseWindowConfiguration(); const useCustomProtocol = safeProcess.sandboxed || typeof safeProcess.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] === 'string'; // Start to resolve process.env before anything gets load // so that we can run loading and resolving in parallel - const whenEnvResolved = safeProcess.resolveEnv(configuration.userEnv); + const whenEnvResolved = safeProcess.resolveEnv(); /** * @param {string[]} modulePaths * @param {(result: unknown, configuration: object) => Promise | undefined} resultCallback * @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options */ - function load(modulePaths, resultCallback, options) { + async function load(modulePaths, resultCallback, options) { + performance.mark('code/willWaitForWindowConfig'); + const configuration = await preloadGlobals.context.configuration; + performance.mark('code/didWaitForWindowConfig'); // Apply zoom level early before even building the // window DOM elements to avoid UI flicker. We always @@ -180,30 +182,6 @@ }, onUnexpectedError); } - /** - * Parses the contents of the window condiguration that - * is passed into the URL from the `electron-main` side. - * - * @returns {{ - * isInitialStartup?: boolean, - * zoomLevel?: number, - * extensionDevelopmentPath?: string[], - * extensionTestsPath?: string, - * userEnv?: { [key: string]: string | undefined }, - * appRoot: string, - * nodeCachedDataDir?: string - * }} - */ - function parseWindowConfiguration() { - const rawConfiguration = (window.location.search || '').split(/[?&]/) - .filter(function (param) { return !!param; }) - .map(function (param) { return param.split('='); }) - .filter(function (param) { return param.length === 2; }) - .reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {}); - - return JSON.parse(rawConfiguration['config'] || '{}') || {}; - } - /** * @param {boolean | undefined} disallowReloadKeybinding * @returns {() => void} diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 477f416943c..56aed158203 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -363,7 +363,7 @@ export interface IQuickPickItemButtonContext extends I export type QuickPickInput = T | IQuickPickSeparator; -//region Fuzzy Scorer Support +//#region Fuzzy Scorer Support export type IQuickPickItemWithResource = IQuickPickItem & { resource?: URI }; diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index b68c5aac16b..30cf2774683 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -183,11 +183,10 @@ }, /** - * @param {{[key: string]: string}} userEnv * @returns {Promise} */ - resolveEnv(userEnv) { - return resolveEnv(userEnv); + resolveEnv() { + return resolveEnv(); }, /** @@ -210,7 +209,37 @@ return this; } } - } + }, + + /** + * Some information about the context we are running in. + * + * @type {import('../electron-sandbox/globals').ISandboxContext} + */ + context: { + + /** + * A configuration object made accessible from the main side + * to configure the sandbox browser window. + * + * The property is intentionally not using a getter to resolve + * it as soon as possible to prevent waterfalls. + * + * @type {Promise} + */ + configuration: (async () => { + const windowConfigIpcChannel = parseArgv('vscode-window-config'); + if (!windowConfigIpcChannel) { + throw new Error('Preload: did not find expected vscode-window-config in renderer process arguments list.'); + } + + try { + return await ipcRenderer.invoke(windowConfigIpcChannel); + } catch (error) { + throw new Error(`Preload: unable to fetch vscode-window-config: ${error}`); + } + })() + }, }; // Use `contextBridge` APIs to expose globals to VSCode @@ -258,6 +287,24 @@ return true; } + /** + * @param {string} key the name of the process argument to parse + * @returns {string | undefined} + */ + function parseArgv(key) { + for (const arg of process.argv) { + if (arg.indexOf(`--${key}=`) === 0) { + return arg.split('=')[1]; + } + } + + return undefined; + } + + //#endregion + + //#region Resolve Shell Environment + /** @type {Promise | undefined} */ let shellEnv = undefined; @@ -267,11 +314,12 @@ * all development related environment variables. We do this from the * main process because it may involve spawning a shell. * - * @param {{[key: string]: string}} userEnv * @returns {Promise} */ - async function resolveEnv(userEnv) { + async function resolveEnv() { if (!shellEnv) { + const configuration = await globals.context.configuration; + const userEnv = configuration.userEnv; // Apply `userEnv` directly Object.assign(process.env, userEnv); diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 7fc10080a4d..f457a47331e 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -90,7 +90,7 @@ export interface ISandboxNodeProcess extends INodeProcess { * It is critical that every process awaits this method early on startup to get the right * set of environment in `process.env`. */ - resolveEnv(userEnv: IProcessEnvironment): Promise; + resolveEnv(): Promise; /** * Returns a process environment that includes any shell environment even if the application @@ -114,8 +114,31 @@ export interface IpcMessagePort { connect(channelRequest: string, channelResponse: string, requestNonce: string): void; } +/** + * The common properties required for any sandboxed + * renderer to function. + */ +export interface ISandboxConfiguration { + appRoot: string; + userEnv: IProcessEnvironment; + zoomLevel: number; + nodeCachedDataDir?: string; + extensionDevelopmentPath?: string; + extensionTestsPath?: string; +} + +export interface ISandboxContext { + + /** + * A configuration object made accessible from the main side + * to configure the sandbox browser window. + */ + configuration: Promise; +} + export const ipcRenderer: IpcRenderer = globals.vscode.ipcRenderer; export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort; export const webFrame: WebFrame = globals.vscode.webFrame; export const crashReporter: CrashReporter = globals.vscode.crashReporter; export const process: ISandboxNodeProcess = globals.vscode.process; +export const context: ISandboxContext = globals.vscode.context; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js index 50d8d3f7f8f..ed856e21165 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -18,9 +18,6 @@ return sharedProcess.main(configuration); }); - - //#region Globals - /** * @returns {{ avoidMonkeyPatchFromAppInsights: () => void; }} */ @@ -30,12 +27,10 @@ } /** - * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }} + * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => unknown, options?: object) => unknown }} */ function bootstrapWindowLib() { // @ts-ignore (defined in bootstrap-window.js) return window.MonacoBootstrapWindow; } - - //#endregion }()); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 0c52f3cda25..222c691168d 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -62,11 +62,11 @@ } }); - //region Helpers + //#region Helpers /** * @returns {{ - * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown, + * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => unknown, options: object) => unknown, * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') * }} */ diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 1ee2c887531..6aa5ee6ede5 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -35,7 +35,6 @@ import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { IProductService } from 'vs/platform/product/common/productService'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; -import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows'; import { URI } from 'vs/base/common/uri'; @@ -439,9 +438,6 @@ export class CodeApplication extends Disposable { this.logService.error(error); } - // Setup Protocol Handler - const fileProtocolHandler = this._register(this.mainInstantiationService.createInstance(FileProtocolHandler)); - // Main process server (electron IPC based) const mainProcessElectronServer = new ElectronIPCServer(); @@ -471,7 +467,7 @@ export class CodeApplication extends Disposable { appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer, fileProtocolHandler)); + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer)); // Post Open Windows Tasks appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); @@ -682,7 +678,7 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); } - private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { + private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); const urlService = accessor.get(IURLService); const nativeHostMainService = accessor.get(INativeHostMainService); @@ -690,9 +686,6 @@ export class CodeApplication extends Disposable { // Signal phase: ready (services set) this.lifecycleMainService.phase = LifecycleMainPhase.Ready; - // Forward windows main service to protocol handler - fileProtocolHandler.injectWindowsMainService(this.windowsMainService); - // Check for initial URLs to handle from protocol link invocations const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; const pendingProtocolLinksToHandle = [ diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 187366c9ec5..9f66c45a146 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -54,6 +54,8 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { cwd } from 'vs/base/common/process'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { ProtocolMainService } from 'vs/platform/protocol/electron-main/protocolMainService'; /** * The main VS Code entry point. @@ -175,6 +177,9 @@ class CodeMain { // Tunnel services.set(ITunnelService, new SyncDescriptor(TunnelService)); + // Protocol + services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService)); + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogService, productService]; } diff --git a/src/vs/code/electron-sandbox/issue/issueReporter.js b/src/vs/code/electron-sandbox/issue/issueReporter.js index a51159e580e..4f669e91f7a 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporter.js +++ b/src/vs/code/electron-sandbox/issue/issueReporter.js @@ -14,16 +14,11 @@ issueReporter.startup(configuration); }, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true }); - - //#region Globals - /** - * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }} + * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => unknown, options?: object) => unknown }} */ function bootstrapWindowLib() { // @ts-ignore (defined in bootstrap-window.js) return window.MonacoBootstrapWindow; } - - //#endregion }()); diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.js b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js index 36fb6ee5530..2a3f356c43d 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.js +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.js @@ -14,16 +14,11 @@ processExplorer.startup(configuration.windowId, configuration.data); }, { forceEnableDeveloperKeybindings: true }); - - //#region Globals - /** - * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }} + * @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => unknown, options?: object) => unknown }} */ function bootstrapWindowLib() { // @ts-ignore (defined in bootstrap-window.js) return window.MonacoBootstrapWindow; } - - //#endregion }()); diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index 1946d7f5e3b..9b1380677dc 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -62,11 +62,11 @@ } }); - //region Helpers + //#region Helpers /** * @returns {{ - * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown, + * load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => unknown, options: object) => unknown, * globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals') * }} */ diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index d4fc1f30b37..9b26c79ff85 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -6,33 +6,38 @@ import { localize } from 'vs/nls'; import { arch, release, type } from 'os'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ICommonIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { ICommonIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { BrowserWindow, ipcMain, screen, IpcMainEvent, Display } from 'electron'; import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { IDiagnosticsService, PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowState, toWindowUrl } from 'vs/platform/windows/electron-main/windows'; +import { IWindowState } from 'vs/platform/windows/electron-main/windows'; import { listProcesses } from 'vs/base/node/ps'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; import { FileAccess } from 'vs/base/common/network'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; - -const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; +import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export const IIssueMainService = createDecorator('issueMainService'); export interface IIssueMainService extends ICommonIssueService { } export class IssueMainService implements ICommonIssueService { + declare readonly _serviceBrand: undefined; - _issueWindow: BrowserWindow | null = null; - _issueParentWindow: BrowserWindow | null = null; - _processExplorerWindow: BrowserWindow | null = null; - _processExplorerParentWindow: BrowserWindow | null = null; + + private static readonly DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; + + private issueReporterWindow: BrowserWindow | null = null; + private issueReporterParentWindow: BrowserWindow | null = null; + + private processExplorerWindow: BrowserWindow | null = null; + private processExplorerParentWindow: BrowserWindow | null = null; constructor( private machineId: string, @@ -43,44 +48,43 @@ export class IssueMainService implements ICommonIssueService { @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { this.registerListeners(); } private registerListeners(): void { - ipcMain.on('vscode:issueSystemInfoRequest', async (event: IpcMainEvent) => { - Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) - .then(result => { - const [info, remoteData] = result; - this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { - this.safeSend(event, 'vscode:issueSystemInfoResponse', msg); - }); - }); + ipcMain.on('vscode:issueSystemInfoRequest', async event => { + const [info, remoteData] = await Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); + const msg = await this.diagnosticsService.getSystemInfo(info, remoteData); + + this.safeSend(event, 'vscode:issueSystemInfoResponse', msg); }); - ipcMain.on('vscode:listProcesses', async (event: IpcMainEvent) => { + ipcMain.on('vscode:listProcesses', async event => { const processes = []; try { const mainPid = await this.launchMainService.getMainProcessId(); processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(mainPid) }); - (await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true })) - .forEach(data => { - if (isRemoteDiagnosticError(data)) { + + const remoteDiagnostics = await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true }); + remoteDiagnostics.forEach(data => { + if (isRemoteDiagnosticError(data)) { + processes.push({ + name: data.hostName, + rootProcess: data + }); + } else { + if (data.processes) { processes.push({ name: data.hostName, - rootProcess: data + rootProcess: data.processes }); - } else { - if (data.processes) { - processes.push({ - name: data.hostName, - rootProcess: data.processes - }); - } } - }); + } + }); } catch (e) { this.logService.error(`Listing processes failed: ${e}`); } @@ -88,7 +92,7 @@ export class IssueMainService implements ICommonIssueService { this.safeSend(event, 'vscode:listProcessesResponse', processes); }); - ipcMain.on('vscode:issueReporterClipboard', (event: IpcMainEvent) => { + ipcMain.on('vscode:issueReporterClipboard', async event => { const messageOptions = { message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub directly. The data will be copied to the clipboard, please paste it into the GitHub issue page that is opened."), type: 'warning', @@ -98,21 +102,18 @@ export class IssueMainService implements ICommonIssueService { ] }; - if (this._issueWindow) { - this.dialogMainService.showMessageBox(messageOptions, this._issueWindow) - .then(result => { - this.safeSend(event, 'vscode:issueReporterClipboardResponse', result.response === 0); - }); + if (this.issueReporterWindow) { + const result = await this.dialogMainService.showMessageBox(messageOptions, this.issueReporterWindow); + this.safeSend(event, 'vscode:issueReporterClipboardResponse', result.response === 0); } }); - ipcMain.on('vscode:issuePerformanceInfoRequest', (event: IpcMainEvent) => { - this.getPerformanceInfo().then(msg => { - this.safeSend(event, 'vscode:issuePerformanceInfoResponse', msg); - }); + ipcMain.on('vscode:issuePerformanceInfoRequest', async event => { + const performanceInfo = await this.getPerformanceInfo(); + this.safeSend(event, 'vscode:issuePerformanceInfoResponse', performanceInfo); }); - ipcMain.on('vscode:issueReporterConfirmClose', () => { + ipcMain.on('vscode:issueReporterConfirmClose', async () => { const messageOptions = { message: localize('confirmCloseIssueReporter', "Your input will not be saved. Are you sure you want to close this window?"), type: 'warning', @@ -122,16 +123,14 @@ export class IssueMainService implements ICommonIssueService { ] }; - if (this._issueWindow) { - this.dialogMainService.showMessageBox(messageOptions, this._issueWindow) - .then(result => { - if (result.response === 0) { - if (this._issueWindow) { - this._issueWindow.destroy(); - this._issueWindow = null; - } - } - }); + if (this.issueReporterWindow) { + const result = await this.dialogMainService.showMessageBox(messageOptions, this.issueReporterWindow); + if (result.response === 0) { + if (this.issueReporterWindow) { + this.issueReporterWindow.destroy(); + this.issueReporterWindow = null; + } + } } }); @@ -141,10 +140,10 @@ export class IssueMainService implements ICommonIssueService { let parentWindow: BrowserWindow | null; switch (from) { case 'issueReporter': - parentWindow = this._issueParentWindow; + parentWindow = this.issueReporterParentWindow; break; case 'processExplorer': - parentWindow = this._processExplorerParentWindow; + parentWindow = this.processExplorerParentWindow; break; default: throw new Error(`Unexpected command source: ${from}`); @@ -159,22 +158,21 @@ export class IssueMainService implements ICommonIssueService { this.nativeHostMainService.openExternal(undefined, arg); }); - ipcMain.on('vscode:closeIssueReporter', (event: IpcMainEvent) => { - if (this._issueWindow) { - this._issueWindow.close(); + ipcMain.on('vscode:closeIssueReporter', event => { + if (this.issueReporterWindow) { + this.issueReporterWindow.close(); } }); - ipcMain.on('vscode:closeProcessExplorer', (event: IpcMainEvent) => { - if (this._processExplorerWindow) { - this._processExplorerWindow.close(); + ipcMain.on('vscode:closeProcessExplorer', event => { + if (this.processExplorerWindow) { + this.processExplorerWindow.close(); } }); - ipcMain.on('vscode:windowsInfoRequest', (event: IpcMainEvent) => { - this.launchMainService.getMainProcessInfo().then(info => { - this.safeSend(event, 'vscode:windowsInfoResponse', info.windows); - }); + ipcMain.on('vscode:windowsInfoRequest', async event => { + const mainProcessInfo = await this.launchMainService.getMainProcessInfo(); + this.safeSend(event, 'vscode:windowsInfoResponse', mainProcessInfo.windows); }); } @@ -185,128 +183,143 @@ export class IssueMainService implements ICommonIssueService { } async openReporter(data: IssueReporterData): Promise { - if (!this._issueWindow) { - this._issueParentWindow = BrowserWindow.getFocusedWindow(); - if (this._issueParentWindow) { - const position = this.getWindowPosition(this._issueParentWindow, 700, 800); + if (!this.issueReporterWindow) { + this.issueReporterParentWindow = BrowserWindow.getFocusedWindow(); + if (this.issueReporterParentWindow) { + const issueReporterDisposables = new DisposableStore(); + const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl()); + const position = this.getWindowPosition(this.issueReporterParentWindow, 700, 800); - this._issueWindow = new BrowserWindow({ - fullscreen: false, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - title: localize('issueReporter', "Issue Reporter"), - backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - v8CacheOptions: 'bypassHeatCheck', - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - sandbox: true, - contextIsolation: true + this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, data.styles.backgroundColor, localize('issueReporter', "Issue Reporter"), data.zoomLevel); + + issueReporterWindowConfigUrl.update({ + appRoot: this.environmentMainService.appRoot, + windowId: this.issueReporterWindow.id, + machineId: this.machineId, + userEnv: this.userEnv, + data, + features: {}, + disableExtensions: this.environmentMainService.disableExtensions, + os: { + type: type(), + arch: arch(), + release: release(), + }, + product: { + nameShort: this.productService.nameShort, + version: !!this.productService.darwinUniversalAssetId ? `${this.productService.version} (Universal)` : this.productService.version, + commit: this.productService.commit, + date: this.productService.date, + reportIssueUrl: this.productService.reportIssueUrl, + reportMarketplaceIssueUrl: this.productService.reportMarketplaceIssueUrl } }); - this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented + this.issueReporterWindow.loadURL( + FileAccess.asBrowserUri('vs/code/electron-sandbox/issue/issueReporter.html', require, true).toString(true) + ); - // Modified when testing UI - const features: IssueReporterFeatures = {}; + this.issueReporterWindow.on('close', () => { + this.issueReporterWindow = null; - this.logService.trace('issueService#openReporter: opening issue reporter'); - this._issueWindow.loadURL(this.getIssueReporterPath(data, features)); + issueReporterDisposables.dispose(); + }); - this._issueWindow.on('close', () => this._issueWindow = null); + this.issueReporterParentWindow.on('closed', () => { + if (this.issueReporterWindow) { + this.issueReporterWindow.close(); + this.issueReporterWindow = null; - this._issueParentWindow.on('closed', () => { - if (this._issueWindow) { - this._issueWindow.close(); - this._issueWindow = null; + issueReporterDisposables.dispose(); } }); } } - if (this._issueWindow) { - this._issueWindow.focus(); - } + this.issueReporterWindow?.focus(); } async openProcessExplorer(data: ProcessExplorerData): Promise { - // Create as singleton - if (!this._processExplorerWindow) { - this._processExplorerParentWindow = BrowserWindow.getFocusedWindow(); - if (this._processExplorerParentWindow) { - const position = this.getWindowPosition(this._processExplorerParentWindow, 800, 500); - this._processExplorerWindow = new BrowserWindow({ - skipTaskbar: true, - resizable: true, - fullscreen: false, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - backgroundColor: data.styles.backgroundColor, - title: localize('processExplorer', "Process Explorer"), - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, - v8CacheOptions: 'bypassHeatCheck', - enableWebSQL: false, - enableRemoteModule: false, - spellcheck: false, - nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), - sandbox: true, - contextIsolation: true - } - }); + if (!this.processExplorerWindow) { + this.processExplorerParentWindow = BrowserWindow.getFocusedWindow(); + if (this.processExplorerParentWindow) { + const processExplorerDisposables = new DisposableStore(); + const processExplorerWindowConfigUrl = processExplorerDisposables.add(this.protocolMainService.createIPCObjectUrl()); + const position = this.getWindowPosition(this.processExplorerParentWindow, 800, 500); - this._processExplorerWindow.setMenuBarVisibility(false); + this.processExplorerWindow = this.createBrowserWindow(position, processExplorerWindowConfigUrl, data.styles.backgroundColor, localize('issueReporter', "Issue Reporter"), data.zoomLevel); - const windowConfiguration = { + processExplorerWindowConfigUrl.update({ appRoot: this.environmentMainService.appRoot, - windowId: this._processExplorerWindow.id, + windowId: this.processExplorerWindow.id, userEnv: this.userEnv, machineId: this.machineId, data - }; + }); - this._processExplorerWindow.loadURL( - toWindowUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration, true)); + this.processExplorerWindow.loadURL( + FileAccess.asBrowserUri('vs/code/electron-sandbox/processExplorer/processExplorer.html', require, true).toString(true) + ); - this._processExplorerWindow.on('close', () => this._processExplorerWindow = null); + this.processExplorerWindow.on('close', () => { + this.processExplorerWindow = null; + processExplorerDisposables.dispose(); + }); - this._processExplorerParentWindow.on('close', () => { - if (this._processExplorerWindow) { - this._processExplorerWindow.close(); - this._processExplorerWindow = null; + this.processExplorerParentWindow.on('close', () => { + if (this.processExplorerWindow) { + this.processExplorerWindow.close(); + this.processExplorerWindow = null; + + processExplorerDisposables.dispose(); } }); } } - // Focus - if (this._processExplorerWindow) { - this._processExplorerWindow.focus(); - } + this.processExplorerWindow?.focus(); } - public async getSystemStatus(): Promise { - return Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) - .then(result => { - const [info, remoteData] = result; - return this.diagnosticsService.getDiagnostics(info, remoteData); - }); + private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, backgroundColor: string | undefined, title: string, zoomLevel: number): BrowserWindow { + const window = new BrowserWindow({ + fullscreen: false, + skipTaskbar: true, + resizable: true, + width: position.width, + height: position.height, + minWidth: 300, + minHeight: 200, + x: position.x, + y: position.y, + title, + backgroundColor: backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR, + webPreferences: { + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`], + v8CacheOptions: 'bypassHeatCheck', + enableWebSQL: false, + enableRemoteModule: false, + spellcheck: false, + nativeWindowOpen: true, + zoomFactor: zoomLevelToZoomFactor(zoomLevel), + sandbox: true, + contextIsolation: true + } + }); + + window.setMenuBarVisibility(false); + + return window; + } + + async getSystemStatus(): Promise { + const [info, remoteData] = await Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]); + + return this.diagnosticsService.getDiagnostics(info, remoteData); } private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IWindowState { + // We want the new window to open on the same display that the parent is in let displayToUse: Display | undefined; const displays = screen.getAllDisplays(); @@ -374,51 +387,14 @@ export class IssueMainService implements ICommonIssueService { return state; } - private getPerformanceInfo(): Promise { - return new Promise(async (resolve, reject) => { - Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) - .then(result => { - const [info, remoteData] = result; - this.diagnosticsService.getPerformanceInfo(info, remoteData) - .then(diagnosticInfo => { - resolve(diagnosticInfo); - }) - .catch(err => { - this.logService.warn('issueService#getPerformanceInfo ', err.message); - reject(err); - }); - }); - }); - } + private async getPerformanceInfo(): Promise { + try { + const [info, remoteData] = await Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]); + return await this.diagnosticsService.getPerformanceInfo(info, remoteData); + } catch (error) { + this.logService.warn('issueService#getPerformanceInfo ', error.message); - private getIssueReporterPath(data: IssueReporterData, features: IssueReporterFeatures): string { - if (!this._issueWindow) { - throw new Error('Issue window has been disposed'); + throw error; } - - const windowConfiguration = { - appRoot: this.environmentMainService.appRoot, - windowId: this._issueWindow.id, - machineId: this.machineId, - userEnv: this.userEnv, - data, - features, - disableExtensions: this.environmentMainService.disableExtensions, - os: { - type: type(), - arch: arch(), - release: release(), - }, - product: { - nameShort: this.productService.nameShort, - version: !!this.productService.darwinUniversalAssetId ? `${this.productService.version} (Universal)` : this.productService.version, - commit: this.productService.commit, - date: this.productService.date, - reportIssueUrl: this.productService.reportIssueUrl, - reportMarketplaceIssueUrl: this.productService.reportMarketplaceIssueUrl - } - }; - - return toWindowUrl('vs/code/electron-sandbox/issue/issueReporter.html', windowConfiguration, true); } } diff --git a/src/vs/platform/protocol/electron-main/protocol.ts b/src/vs/platform/protocol/electron-main/protocol.ts new file mode 100644 index 00000000000..9c961967265 --- /dev/null +++ b/src/vs/platform/protocol/electron-main/protocol.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IProtocolMainService = createDecorator('protocolMainService'); + +export interface IIPCObjectUrl extends IDisposable { + + /** + * A `URI` that a renderer can use to retrieve the + * object via `ipcRenderer.invoke(resource.toString())` + */ + resource: URI; + + /** + * Allows to update the value of the object after it + * has been created. + * + * @param obj the object to make accessible to the + * renderer. + */ + update(obj: object): void; +} + +export interface IProtocolMainService { + + readonly _serviceBrand: undefined; + + /** + * Allows to make an object accessible to a renderer + * via `ipcRenderer.invoke(resource.toString())`. + * + * @param obj the (optional) object to make accessible to the + * renderer. Can be updated later via the `IObjectUrl#update` + * method too. + */ + createIPCObjectUrl(obj?: object): IIPCObjectUrl; + + /** + * Adds a `URI` as root to the list of allowed + * resources for file access. + * + * @param root the URI to allow for file access + */ + addValidFileRoot(root: URI): IDisposable; +} diff --git a/src/vs/code/electron-main/protocol.ts b/src/vs/platform/protocol/electron-main/protocolMainService.ts similarity index 70% rename from src/vs/code/electron-main/protocol.ts rename to src/vs/platform/protocol/electron-main/protocolMainService.ts index 05998534d0a..cff0881b74e 100644 --- a/src/vs/code/electron-main/protocol.ts +++ b/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -3,21 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { session } from 'electron'; +import { ipcMain, session } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { TernarySearchTree } from 'vs/base/common/map'; import { isLinux, isPreferringBrowserCodeLoad } from 'vs/base/common/platform'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { extname } from 'vs/base/common/resources'; +import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; +import { generateUuid } from 'vs/base/common/uuid'; type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void }; -export class FileProtocolHandler extends Disposable { +export class ProtocolMainService extends Disposable implements IProtocolMainService { + + declare readonly _serviceBrand: undefined; private readonly validRoots = TernarySearchTree.forUris(() => !isLinux); private readonly validExtensions = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp']); // https://github.com/microsoft/vscode/issues/119384 @@ -28,16 +30,21 @@ export class FileProtocolHandler extends Disposable { ) { super(); - const { defaultSession } = session; - // Define an initial set of roots we allow loading from // - appRoot : all files installed as part of the app // - extensions : all files shipped from extensions // - storage : all files in global and workspace storage (https://github.com/microsoft/vscode/issues/116735) - this.validRoots.set(URI.file(environmentService.appRoot), true); - this.validRoots.set(URI.file(environmentService.extensionsPath), true); - this.validRoots.set(environmentService.globalStorageHome, true); - this.validRoots.set(environmentService.workspaceStorageHome, true); + this.addValidFileRoot(URI.file(environmentService.appRoot)); + this.addValidFileRoot(URI.file(environmentService.extensionsPath)); + this.addValidFileRoot(environmentService.globalStorageHome); + this.addValidFileRoot(environmentService.workspaceStorageHome); + + // Handle protocols + this.handleProtocols(); + } + + private handleProtocols(): void { + const { defaultSession } = session; // Register vscode-file:// handler defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback)); @@ -52,28 +59,7 @@ export class FileProtocolHandler extends Disposable { })); } - injectWindowsMainService(windowsMainService: IWindowsMainService): void { - this._register(windowsMainService.onDidSignalReadyWindow(window => { - if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) { - const disposables = new DisposableStore(); - disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose())); - - // Allow access to extension development path - if (window.config.extensionDevelopmentPath) { - for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) { - disposables.add(this.addValidRoot(URI.file(extensionDevelopmentPath))); - } - } - - // Allow access to extension tests path - if (window.config.extensionTestsPath) { - disposables.add(this.addValidRoot(URI.file(window.config.extensionTestsPath))); - } - } - })); - } - - private addValidRoot(root: URI): IDisposable { + addValidFileRoot(root: URI): IDisposable { if (!this.validRoots.get(root)) { this.validRoots.set(root, true); @@ -83,7 +69,9 @@ export class FileProtocolHandler extends Disposable { return Disposable.None; } - private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { + //#region file:// + + private handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback): void { const fileUri = URI.parse(request.url); // isPreferringBrowserCodeLoad: false @@ -118,7 +106,11 @@ export class FileProtocolHandler extends Disposable { } } - private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) { + //#endregion + + //#region vscode-file:// + + private handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback): void { const uri = URI.parse(request.url); // Restore the `vscode-file` URI to a `file` URI so that we can @@ -145,4 +137,30 @@ export class FileProtocolHandler extends Disposable { return callback({ error: -3 /* ABORTED */ }); } + + //#endregion + + //#region IPC Object URLs + + createIPCObjectUrl(obj: object): IIPCObjectUrl { + + // Create unique URI + const resource = URI.from({ + scheme: 'vscode', // used for all our IPC communication (vscode:) + path: generateUuid() + }); + + // Install IPC handler + const channel = resource.toString(); + const handler = async (): Promise => obj; + ipcMain.handle(channel, handler); + + return { + resource, + update: updatedObj => obj = updatedObj, + dispose: () => ipcMain.removeHandler(channel) + }; + } + + //#endregion } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 47a2c82aa85..cba0358873e 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -16,8 +16,9 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp'; import { assertIsDefined } from 'vs/base/common/types'; import { Emitter, Event } from 'vs/base/common/event'; -import { toWindowUrl, WindowError } from 'vs/platform/windows/electron-main/windows'; +import { WindowError } from 'vs/platform/windows/electron-main/windows'; import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; export class SharedProcess extends Disposable implements ISharedProcess { @@ -35,7 +36,8 @@ export class SharedProcess extends Disposable implements ISharedProcess { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, - @IThemeMainService private readonly themeMainService: IThemeMainService + @IThemeMainService private readonly themeMainService: IThemeMainService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { super(); @@ -158,6 +160,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { } private createWindow(): void { + const configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); // shared process is a hidden window by default this.window = new BrowserWindow({ @@ -165,6 +168,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + additionalArguments: [`--vscode-window-config=${configObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, nodeIntegration: true, enableWebSQL: false, @@ -188,8 +192,11 @@ export class SharedProcess extends Disposable implements ISharedProcess { logLevel: this.logService.getLevel() }; + // Store into config object URL + configObjectUrl.update(config); + // Load with config - this.window.loadURL(toWindowUrl('vs/code/electron-browser/sharedProcess/sharedProcess.html', config)); + this.window.loadURL(FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require).toString(true)); } private registerWindowListeners(): void { @@ -212,7 +219,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { this.window.on('close', this.windowCloseListener); - // Crashes & Unrsponsive & Failed to load + // Crashes & Unresponsive & Failed to load // We use `onUnexpectedError` explicitly because the error handler // will send the error to the active window to log in devtools too this.window.webContents.on('render-process-gone', (event, details) => this._onDidError.fire({ type: WindowError.CRASHED, details })); diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index ffe20c78d61..0f41ec2fa2e 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -18,7 +18,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Disposable } from 'vs/base/common/lifecycle'; import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { defaultWindowState, ICodeWindow, ILoadEvent, IWindowState, toWindowUrl, WindowError, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { defaultWindowState, ICodeWindow, ILoadEvent, IWindowState, WindowError, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -31,11 +31,12 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; -import { ByteSize, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { CancellationToken } from 'vs/base/common/cancellation'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; export interface IWindowCreationOptions { state: IWindowState; @@ -77,8 +78,6 @@ const enum ReadyState { export class CodeWindow extends Disposable implements ICodeWindow { - private static readonly MAX_URL_LENGTH = 2 * ByteSize.MB; // https://source.chromium.org/chromium/chromium/src/+/master:url/url_constants.cc;l=37 - //#region Events private readonly _onWillLoad = this._register(new Emitter()); @@ -132,6 +131,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { private currentConfig: INativeWindowConfiguration | undefined; get config(): INativeWindowConfiguration | undefined { return this.currentConfig; } + private readonly configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); + get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; } get isExtensionDevelopmentHost(): boolean { return !!(this.currentConfig?.extensionDevelopmentPath); } @@ -154,7 +155,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { super(); @@ -182,6 +184,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { title: this.productService.nameLong, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, + additionalArguments: [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, enableRemoteModule: false, @@ -408,7 +411,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private registerListeners(): void { - // Crashes & Unrsponsive & Failed to load + // Crashes & Unresponsive & Failed to load this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details)); this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.onWindowError(WindowError.LOAD, errorDescription)); @@ -736,9 +739,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setTitle(this.productService.nameLong); } + // Update config object URL + this.updateConfigObjectUrl(configuration, options); + // Load URL mark('code/willOpenNewWindow'); - this._win.loadURL(this.getUrl(configuration, options)); + this._win.loadURL(FileAccess.asBrowserUri(this.environmentMainService.sandbox ? + 'vs/code/electron-sandbox/workbench/workbench.html' : + 'vs/code/electron-browser/workbench/workbench.html', require + ).toString(true)); // Make window visible if it did not open in N seconds because this indicates an error // Only do this when running out of sources and not when running tests @@ -813,7 +822,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return configuration.workspace; } - private getUrl(baseConfig: INativeWindowConfiguration, options: ILoadOptions): string { + private updateConfigObjectUrl(baseConfig: INativeWindowConfiguration, options: ILoadOptions): void { // Config is a combination of native CLI args and window configuration const configuration: { [key: string]: unknown } = { ...this.environmentMainService.args, ...baseConfig }; @@ -862,32 +871,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { release: release() }; - // In the unlikely event of the URL becoming larger than 2MB, remove parts of - // it that are not under our control. Mainly, the user environment can be very - // large depending on user configuration, so we can only remove it in that case. - let configUrl = this.doGetUrl(configuration); - if (configUrl.length > CodeWindow.MAX_URL_LENGTH) { - this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.'); - - configUrl = this.doGetUrl({ ...configuration, userEnv: undefined }); - - if (configUrl.length > CodeWindow.MAX_URL_LENGTH) { - this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.'); - } - } - - return configUrl; - } - - private doGetUrl(config: object): string { - let workbench: string; - if (this.environmentMainService.sandbox) { - workbench = 'vs/code/electron-sandbox/workbench/workbench.html'; - } else { - workbench = 'vs/code/electron-browser/workbench/workbench.html'; - } - - return toWindowUrl(workbench, config); + // Store into config object URL + this.configObjectUrl.update(configuration); } serializeWindowState(): IWindowState { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 5b93768fbae..255a84e5157 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -14,7 +14,6 @@ import { URI } from 'vs/base/common/uri'; import { Rectangle, BrowserWindow, WebContents } from 'electron'; import { IDisposable } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { FileAccess } from 'vs/base/common/network'; export const enum OpenContext { @@ -207,28 +206,3 @@ export interface IOpenConfiguration extends IBaseOpenConfiguration { } export interface IOpenEmptyConfiguration extends IBaseOpenConfiguration { } - -/** - * Utility to produce a window URL that includes the provided configuration - * as query parameter. - * - * @param modulePathToHtml path to the HTML to load - * @param config the configuration for the window - * @returns the url to use for loading - */ -export function toWindowUrl(modulePathToHtml: string, config: object, forceCodeFileUri?: boolean): string { - const configuration: { [key: string]: unknown } = { ...config }; - - // Remove values from config that are falsy to reduce the size of of the URL we create - for (const key in configuration) { - const value = configuration[key]; - if (value === undefined || value === null || value === '' || value === false) { - delete configuration[key]; - } - } - - return FileAccess - .asBrowserUri(modulePathToHtml, require, forceCodeFileUri) - .with({ query: `config=${encodeURIComponent(JSON.stringify(configuration))}` }) - .toString(true); -} diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 283bbc93c80..0566e37a67f 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -14,12 +14,12 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow } from 'vs/platform/windows/electron-main/window'; import { BrowserWindow, MessageBoxOptions, WebContents } from 'electron'; -import { ILifecycleMainService, UnloadReason, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { ILifecycleMainService, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration, INativeOpenFileRequest } from 'vs/platform/windows/common/windows'; import { findWindowOnFile, findWindowOnWorkspaceOrFolder, findWindowOnExtensionDevelopmentPath } from 'vs/platform/windows/electron-main/windowsFinder'; -import { Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IOpenEmptyConfiguration, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; @@ -33,7 +33,7 @@ import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler'; import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { once } from 'vs/base/common/functional'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; @@ -42,6 +42,7 @@ import { getPathLabel } from 'vs/base/common/labels'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IFileService } from 'vs/platform/files/common/files'; import { cwd } from 'vs/base/common/process'; +import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; //#region Helper Interfaces @@ -151,17 +152,38 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @IFileService private readonly fileService: IFileService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { super(); - this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.registerListeners(); } private registerListeners(): void { // Signal a window is ready after having entered a workspace this._register(this.workspacesManagementMainService.onDidEnterWorkspace(event => this._onDidSignalReadyWindow.fire(event.window))); + + // Update valid roots in protocol service for extension dev windows + this._register(this.onDidSignalReadyWindow(window => { + if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) { + const disposables = new DisposableStore(); + disposables.add(Event.any(window.onDidClose, window.onDidDestroy)(() => disposables.dispose())); + + // Allow access to extension development path + if (window.config.extensionDevelopmentPath) { + for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) { + disposables.add(this.protocolMainService.addValidFileRoot(URI.file(extensionDevelopmentPath))); + } + } + + // Allow access to extension tests path + if (window.config.extensionTestsPath) { + disposables.add(this.protocolMainService.addValidFileRoot(URI.file(window.config.extensionTestsPath))); + } + } + })); } openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 864e97efd2d..542896a70ba 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -684,7 +684,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#endregion - //region IEditorGroupView + //#region IEditorGroupView private readonly _group: EditorGroup; get group(): EditorGroup { diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 219a673c370..6993e08a59f 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -173,6 +173,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['app.isReady => window.loadUrl()', metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['window.loadUrl() => begin to require(workbench.desktop.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); table.push(['require(workbench.desktop.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); + table.push(['wait for window config', metrics.timers.ellapsedWaitForWindowConfig, '[renderer]', undefined]); table.push(['wait for shell environment', metrics.timers.ellapsedWaitForShellEnv, '[renderer]', undefined]); table.push(['init storage (global & workspace)', metrics.timers.ellapsedStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index 534cc553e97..0e94af6bcb6 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -56,6 +56,7 @@ export interface IMemoryInfo { "timers.ellapsedEditorRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedNlsGeneration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedWaitForWindowConfig" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWaitForShellEnv" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "platform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "release" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, @@ -198,6 +199,15 @@ export interface IStartupMetrics { */ readonly ellapsedWindowLoadToRequire: number; + /** + * The time it took to wait for resolving the window configuration. This time the workbench + * will not continue to load and be blocked entirely. + * + * * Happens in the renderer-process + * * Measured with the `willWaitForWindowConfig` and `didWaitForWindowConfig` performance marks. + */ + readonly ellapsedWaitForWindowConfig: number; + /** * The time it took to wait for resolving the shell environment. This time the workbench * will not continue to load and be blocked entirely. @@ -521,6 +531,7 @@ export abstract class AbstractTimerService implements ITimerService { ellapsedWindowLoad: initialStartup ? this._marks.getDuration('code/mainAppReady', 'code/willOpenNewWindow') : undefined, ellapsedWindowLoadToRequire: this._marks.getDuration('code/willOpenNewWindow', 'code/willLoadWorkbenchMain'), ellapsedRequire: this._marks.getDuration('code/willLoadWorkbenchMain', 'code/didLoadWorkbenchMain'), + ellapsedWaitForWindowConfig: this._marks.getDuration('code/willWaitForWindowConfig', 'code/didWaitForWindowConfig'), ellapsedWaitForShellEnv: this._marks.getDuration('code/willWaitForShellEnv', 'code/didWaitForShellEnv'), ellapsedStorageInit: this._marks.getDuration('code/willInitStorage', 'code/didInitStorage'), ellapsedSharedProcesConnected: this._marks.getDuration('code/willConnectSharedProcess', 'code/didConnectSharedProcess'),