Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898 (#15681)

* Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898

* Fixes and cleanup

* Distro

* Fix hygiene yarn

* delete no yarn lock changes file

* Fix hygiene

* Fix layer check

* Fix CI

* Skip lib checks

* Remove tests deleted in vs code

* Fix tests

* Distro

* Fix tests and add removed extension point

* Skip failing notebook tests for now

* Disable broken tests and cleanup build folder

* Update yarn.lock and fix smoke tests

* Bump sqlite

* fix contributed actions and file spacing

* Fix user data path

* Update yarn.locks

Co-authored-by: ADS Merger <karlb@microsoft.com>
This commit is contained in:
Charles Gagnon
2021-06-17 08:17:11 -07:00
committed by GitHub
parent fdcb97c7f7
commit 3cb2f552a6
2582 changed files with 124827 additions and 87099 deletions

View File

@@ -0,0 +1,82 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<script>
performance.mark('code/didStartRenderer')
</script>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
<!-- Workbench Auth Session -->
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
<!-- Builtin Extensions (running out of sources) -->
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{WORKBENCH_WEB_BASE_URL}}/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="{{WORKBENCH_WEB_BASE_URL}}/manifest.json">
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet"
href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.css">
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
<script>
var baseUrl = '{{WORKBENCH_WEB_BASE_URL}}';
self.require = {
baseUrl: `${baseUrl}/out`,
recordStats: true,
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
createScriptURL(value) {
if (value.startsWith(baseUrl)) {
return value;
}
throw new Error(`Invalid script url: ${value}`)
}
}),
paths: {
'vscode-textmate': `${baseUrl}/node_modules/vscode-textmate/release/main`,
'vscode-oniguruma': `${baseUrl}/node_modules/vscode-oniguruma/release/main`,
'xterm': `${baseUrl}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${baseUrl}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-unicode11': `${baseUrl}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
'xterm-addon-webgl': `${baseUrl}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'tas-client-umd': `${baseUrl}/node_modules/tas-client-umd/lib/tas-client-umd.js`,
'iconv-lite-umd': `${baseUrl}/node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
'jschardet': `${baseUrl}/node_modules/jschardet/dist/jschardet.min.js`,
}
};
</script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script>
performance.mark('code/willLoadWorkbenchMain');
function injectScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.addEventListener('load', resolve);
script.addEventListener('error', e => reject(e.error));
document.head.appendChild(script);
});
}
</script>
<script>
require(['vs/code/browser/workbench/workbench'], function () { });
</script>
</html>

View File

@@ -0,0 +1,82 @@
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<script>
performance.mark('code/didStartRenderer')
</script>
<meta charset="utf-8" />
<!-- Disable pinch zooming -->
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
<!-- Workbench Auth Session -->
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
<!-- Builtin Extensions (running out of sources) -->
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="{{WORKBENCH_WEB_BASE_URL}}/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="{{WORKBENCH_WEB_BASE_URL}}/manifest.json">
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet"
href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.css">
</head>
<body aria-label="">
</body>
<!-- Startup (do not modify order of script tags!) -->
<script>
var baseUrl = '{{WORKBENCH_WEB_BASE_URL}}';
self.require = {
baseUrl: `${baseUrl}/out`,
recordStats: true,
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
createScriptURL(value) {
if (value.startsWith(baseUrl)) {
return value;
}
throw new Error(`Invalid script url: ${value}`)
}
}),
paths: {
'vscode-textmate': `${baseUrl}/node_modules/vscode-textmate/release/main`,
'vscode-oniguruma': `${baseUrl}/node_modules/vscode-oniguruma/release/main`,
'xterm': `${baseUrl}/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${baseUrl}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-unicode11': `${baseUrl}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
'xterm-addon-webgl': `${baseUrl}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'tas-client-umd': `${baseUrl}/node_modules/tas-client-umd/lib/tas-client-umd.js`,
'iconv-lite-umd': `${baseUrl}/node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
'jschardet': `${baseUrl}/node_modules/jschardet/dist/jschardet.min.js`,
}
};
</script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script>
performance.mark('code/willLoadWorkbenchMain');
function injectScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.addEventListener('load', resolve);
script.addEventListener('error', e => reject(e.error));
document.head.appendChild(script);
});
}
</script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.nls.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.api.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/code/browser/workbench/workbench.js"></script>
</html>

View File

@@ -275,28 +275,35 @@ class WorkspaceProvider implements IWorkspaceProvider {
static QUERY_PARAM_PAYLOAD = 'payload';
readonly trusted = true;
constructor(
public readonly workspace: IWorkspace,
public readonly payload: object
) { }
async open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise<void> {
async open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise<boolean> {
if (options?.reuse && !options.payload && this.isSame(this.workspace, workspace)) {
return; // return early if workspace and environment is not changing and we are reusing window
return true; // return early if workspace and environment is not changing and we are reusing window
}
const targetHref = this.createTargetUrl(workspace, options);
if (targetHref) {
if (options?.reuse) {
window.location.href = targetHref;
return true;
} else {
let result;
if (isStandalone) {
window.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window!
result = window.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window!
} else {
window.open(targetHref);
result = window.open(targetHref);
}
return !!result;
}
}
return false;
}
private createTargetUrl(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): string | undefined {
@@ -406,13 +413,6 @@ class WindowIndicator implements IWindowIndicator {
const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
// Revive static extension locations
if (Array.isArray(config.staticExtensions)) {
config.staticExtensions.forEach(extension => {
extension.extensionLocation = URI.revive(extension.extensionLocation);
});
}
// Find workspace to open and payload
let foundWorkspace = false;
let workspace: IWorkspace;
@@ -520,7 +520,10 @@ class WindowIndicator implements IWindowIndicator {
// Finally create workbench
create(document.body, {
...config,
logLevel: logLevel ? parseLogLevel(logLevel) : undefined,
developmentOptions: {
logLevel: logLevel ? parseLogLevel(logLevel) : undefined,
...config.developmentOptions
},
settingsSyncOptions,
homeIndicator,
windowIndicator,

View File

@@ -7,7 +7,7 @@ import * as fs from 'fs';
import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
import { IStringDictionary } from 'vs/base/common/collections';
import product from 'vs/platform/product/common/product';
import { IProductService } from 'vs/platform/product/common/productService';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
@@ -32,9 +32,14 @@ interface LanguagePackFile {
export class LanguagePackCachedDataCleaner extends Disposable {
private readonly _DataMaxAge = this._productService.quality !== 'stable'
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
constructor(
@INativeEnvironmentService private readonly _environmentService: INativeEnvironmentService,
@ILogService private readonly _logService: ILogService
@ILogService private readonly _logService: ILogService,
@IProductService private readonly _productService: IProductService
) {
super();
// We have no Language pack support for dev version (run from source)
@@ -48,9 +53,6 @@ export class LanguagePackCachedDataCleaner extends Disposable {
let handle: any = setTimeout(async () => {
handle = undefined;
this._logService.info('Starting to clean up unused language packs.');
const maxAge = product.nameLong.indexOf('Insiders') >= 0
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
try {
const installed: IStringDictionary<boolean> = Object.create(null);
const metaData: LanguagePackFile = JSON.parse(await fs.promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
@@ -84,7 +86,7 @@ export class LanguagePackCachedDataCleaner extends Disposable {
const stat = await fs.promises.stat(candidate);
if (stat.isDirectory()) {
const diff = now - stat.mtime.getTime();
if (diff > maxAge) {
if (diff > this._DataMaxAge) {
this._logService.info('Removing language pack cache entry: ', path.join(packEntry, entry));
await pfs.rimraf(candidate);
}

View File

@@ -8,18 +8,19 @@ import { basename, dirname, join } from 'vs/base/common/path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { readdir, rimraf } from 'vs/base/node/pfs';
import product from 'vs/platform/product/common/product';
import { IProductService } from 'vs/platform/product/common/productService';
export class NodeCachedDataCleaner {
private static readonly _DataMaxAge = product.nameLong.indexOf('Insiders') >= 0
private readonly _DataMaxAge = this.productService.quality !== 'stable'
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
private readonly _disposables = new DisposableStore();
constructor(
private readonly nodeCachedDataDir: string | undefined
private readonly nodeCachedDataDir: string | undefined,
@IProductService private readonly productService: IProductService
) {
this._manageCachedDataSoon();
}
@@ -61,7 +62,7 @@ export class NodeCachedDataCleaner {
// * only when old enough
if (stats.isDirectory()) {
const diff = now - stats.mtime.getTime();
if (diff > NodeCachedDataCleaner._DataMaxAge) {
if (diff > this._DataMaxAge) {
return rimraf(path);
}
}

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
(function () {
'use strict';
const bootstrap = bootstrapLib();
const bootstrapWindow = bootstrapWindowLib();
@@ -16,10 +16,15 @@
// Load shared process into window
bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) {
return sharedProcess.main(configuration);
});
//#region Globals
},
{
configureDeveloperSettings: function () {
return {
disallowReloadKeybinding: true
};
}
}
);
/**
* @returns {{ avoidMonkeyPatchFromAppInsights: () => void; }}
@@ -30,13 +35,27 @@
}
/**
* @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }}
* @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration
*
* @returns {{
* load: (
* modules: string[],
* resultCallback: (result, configuration: ISandboxConfiguration) => unknown,
* options?: {
* configureDeveloperSettings?: (config: ISandboxConfiguration) => {
* forceEnableDeveloperKeybindings?: boolean,
* disallowReloadKeybinding?: boolean,
* removeDeveloperKeybindingsAfterLoad?: boolean
* },
* canModifyDOM?: (config: ISandboxConfiguration) => void,
* beforeLoaderConfig?: (loaderConfig: object) => void,
* beforeRequire?: () => void
* }
* ) => Promise<unknown>
* }}
*/
function bootstrapWindowLib() {
// @ts-ignore (defined in bootstrap-window.js)
return window.MonacoBootstrapWindow;
}
//#endregion
}());

View File

@@ -3,16 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import product from 'vs/platform/product/common/product';
import * as fs from 'fs';
import { release } from 'os';
import { release, hostname } from 'os';
import { gracefulify } from 'graceful-fs';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
import { ipcRenderer } from 'electron';
import product from 'vs/platform/product/common/product';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp';
import { StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -22,15 +23,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/browser/requestService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { combinedAppender, NullTelemetryService, ITelemetryAppender, NullAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogService } from 'vs/platform/log/common/log';
import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { ILogService, ILoggerService, MultiplexLogService, ConsoleLogger } from 'vs/platform/log/common/log';
import { LogLevelChannelClient, FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -40,9 +40,10 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
@@ -51,13 +52,11 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService, UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncMachinesServiceChannel, UserDataSyncAccountServiceChannel, UserDataSyncStoreManagementServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService';
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
@@ -78,6 +77,15 @@ import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/con
import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/deprecatedExtensionsCleaner';
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { TerminalIpcChannels } from 'vs/platform/terminal/common/terminal';
import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService';
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc';
import { IChecksumService } from 'vs/platform/checksum/common/checksumService';
import { ChecksumService } from 'vs/platform/checksum/node/checksumService';
import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
class SharedProcessMain extends Disposable {
@@ -123,7 +131,7 @@ class SharedProcessMain extends Disposable {
// Instantiate Contributions
this._register(combinedDisposable(
new NodeCachedDataCleaner(this.configuration.nodeCachedDataDir),
instantiationService.createInstance(NodeCachedDataCleaner, this.configuration.nodeCachedDataDir),
instantiationService.createInstance(LanguagePackCachedDataCleaner),
instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath),
instantiationService.createInstance(LogsDataCleaner),
@@ -135,26 +143,33 @@ class SharedProcessMain extends Disposable {
private async initServices(): Promise<IInstantiationService> {
const services = new ServiceCollection();
// Environment
const environmentService = new NativeEnvironmentService(this.configuration.args);
services.set(IEnvironmentService, environmentService);
services.set(INativeEnvironmentService, environmentService);
// Log
const mainRouter = new StaticRouter(ctx => ctx === 'main');
const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); // we only use this for log levels
const multiplexLogger = this._register(new MultiplexLogService([
this._register(new ConsoleLogService(this.configuration.logLevel)),
this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel))
]));
const logService = this._register(new FollowerLogService(loggerClient, multiplexLogger));
services.set(ILogService, logService);
// Product
const productService = { _serviceBrand: undefined, ...product };
services.set(IProductService, productService);
// Main Process
const mainRouter = new StaticRouter(ctx => ctx === 'main');
const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter);
services.set(IMainProcessService, mainProcessService);
// Environment
const environmentService = new NativeEnvironmentService(this.configuration.args, productService);
services.set(INativeEnvironmentService, environmentService);
// Logger
const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter));
const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger'));
services.set(ILoggerService, loggerService);
// Log
const multiplexLogger = this._register(new MultiplexLogService([
this._register(new ConsoleLogger(this.configuration.logLevel)),
this._register(loggerService.createLogger(joinPath(URI.file(environmentService.logsPath), 'sharedprocess.log'), { name: 'sharedprocess' }))
]));
const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger));
services.set(ILogService, logService);
// Files
const fileService = this._register(new FileService(logService));
services.set(IFileService, fileService);
@@ -168,21 +183,21 @@ class SharedProcessMain extends Disposable {
await configurationService.initialize();
// Storage
const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService);
// Storage (global access only)
const storageService = new NativeStorageService(undefined, mainProcessService, environmentService);
services.set(IStorageService, storageService);
await storageService.initialize();
this._register(toDisposable(() => storageService.flush()));
// Product
services.set(IProductService, { _serviceBrand: undefined, ...product });
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
// Checksum
services.set(IChecksumService, new SyncDescriptor(ChecksumService));
// Native Host
const nativeHostService = createChannelSender<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
const nativeHostService = ProxyChannel.toService<INativeHostService>(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId });
services.set(INativeHostService, nativeHostService);
// Download
@@ -193,28 +208,24 @@ class SharedProcessMain extends Disposable {
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
services.set(IExtensionRecommendationNotificationService, new ExtensionRecommendationNotificationServiceChannelClient(this.server.getChannel('extensionRecommendationNotification', activeWindowRouter)));
// Logger
const loggerService = this._register(new LoggerService(logService, fileService));
services.set(ILoggerService, loggerService);
// Telemetry
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
let telemetryService: ITelemetryService;
let telemetryAppender: ITelemetryAppender;
if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
if (!environmentService.isExtensionDevelopment && !environmentService.disableTelemetry && productService.enableTelemetry) {
telemetryAppender = new TelemetryLogAppender(loggerService, environmentService);
const { appRoot, extensionsPath, isBuilt, installSourcePath } = environmentService;
// Application Insights
if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) {
const appInsightsAppender = new AppInsightsAppender('adsworkbench', null, product.aiConfig.asimovKey); // {{SQL CARBON EDIT}} Use our own event prefix
if (productService.aiConfig && productService.aiConfig.asimovKey && isBuilt) {
const appInsightsAppender = new AppInsightsAppender('adsworkbench', null, productService.aiConfig.asimovKey); // {{SQL CARBON EDIT}} Use our own event prefix
this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
telemetryAppender = combinedAppender(appInsightsAppender, telemetryAppender);
}
telemetryService = new TelemetryService({
appender: telemetryAppender,
commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, this.configuration.machineId, product.msftInternalDomains, installSourcePath),
commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath),
sendErrorTelemetry: true,
piiPaths: [appRoot, extensionsPath]
}, configurationService);
@@ -226,6 +237,10 @@ class SharedProcessMain extends Disposable {
this.server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(telemetryAppender));
services.set(ITelemetryService, telemetryService);
// Custom Endpoint Telemetry
const customEndpointTelemetryService = new CustomEndpointTelemetryService(configurationService, telemetryService);
services.set(ICustomEndpointTelemetryService, customEndpointTelemetryService);
// Extension Management
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
@@ -256,51 +271,59 @@ class SharedProcessMain extends Disposable {
services.set(IUserDataSyncResourceEnablementService, new SyncDescriptor(UserDataSyncResourceEnablementService));
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
// Terminal
services.set(ILocalPtyService, this._register(new PtyHostService(logService, telemetryService)));
return new InstantiationService(services);
}
private initChannels(accessor: ServicesAccessor): void {
// Extensions Management
const extensionManagementService = accessor.get(IExtensionManagementService);
const channel = new ExtensionManagementChannel(extensionManagementService, () => null);
const channel = new ExtensionManagementChannel(accessor.get(IExtensionManagementService), () => null);
this.server.registerChannel('extensions', channel);
// Localizations
const localizationsService = accessor.get(ILocalizationsService);
const localizationsChannel = createChannelReceiver(localizationsService);
const localizationsChannel = ProxyChannel.fromService(accessor.get(ILocalizationsService));
this.server.registerChannel('localizations', localizationsChannel);
// Diagnostics
const diagnosticsService = accessor.get(IDiagnosticsService);
const diagnosticsChannel = createChannelReceiver(diagnosticsService);
const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService));
this.server.registerChannel('diagnostics', diagnosticsChannel);
// Extension Tips
const extensionTipsService = accessor.get(IExtensionTipsService);
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
const extensionTipsChannel = new ExtensionTipsChannel(accessor.get(IExtensionTipsService));
this.server.registerChannel('extensionTipsService', extensionTipsChannel);
// Checksum
const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService));
this.server.registerChannel('checksum', checksumChannel);
// Settings Sync
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(accessor.get(IUserDataSyncMachinesService));
this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
const authTokenService = accessor.get(IUserDataSyncAccountService);
const authTokenChannel = new UserDataSyncAccountServiceChannel(authTokenService);
this.server.registerChannel('userDataSyncAccount', authTokenChannel);
// Custom Endpoint Telemetry
const customEndpointTelemetryChannel = ProxyChannel.fromService(accessor.get(ICustomEndpointTelemetryService));
this.server.registerChannel('customEndpointTelemetry', customEndpointTelemetryChannel);
const userDataSyncStoreManagementService = accessor.get(IUserDataSyncStoreManagementService);
const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(userDataSyncStoreManagementService);
const userDataSyncAccountChannel = new UserDataSyncAccountServiceChannel(accessor.get(IUserDataSyncAccountService));
this.server.registerChannel('userDataSyncAccount', userDataSyncAccountChannel);
const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(accessor.get(IUserDataSyncStoreManagementService));
this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel);
const userDataSyncService = accessor.get(IUserDataSyncService);
const userDataSyncChannel = new UserDataSyncChannel(this.server, userDataSyncService, accessor.get(ILogService));
const userDataSyncChannel = new UserDataSyncChannel(accessor.get(IUserDataSyncService), accessor.get(ILogService));
this.server.registerChannel('userDataSync', userDataSyncChannel);
const userDataAutoSync = this._register(accessor.get(IInstantiationService).createInstance(UserDataAutoSyncService));
const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync);
this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel);
// Terminal
const localPtyService = accessor.get(ILocalPtyService);
const localPtyChannel = ProxyChannel.fromService(localPtyService);
this.server.registerChannel(TerminalIpcChannels.LocalPty, localPtyChannel);
}
private registerErrorHandler(logService: ILogService): void {

View File

@@ -3,7 +3,8 @@
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview: https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote-resource:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview:; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
<!-- <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types default TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget domLineBreaksComputer editorViewLayer diffReview extensionHostWorker insane notebookOutputRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost webWorkerExtensionHost;"> {{SQL CARBON EDIT}} TODO @chgagnon Comment out until we can investigate a proper fix for ADS not starting up -->
</head>
<body aria-label="">
</body>

View File

@@ -6,9 +6,9 @@
/// <reference path="../../../../typings/require.d.ts" />
//@ts-check
'use strict';
(function () {
'use strict';
const bootstrapWindow = bootstrapWindowLib();
// Add a perf entry right from the top
@@ -33,15 +33,38 @@
return require('vs/workbench/electron-browser/desktop.main').main(configuration);
},
{
removeDeveloperKeybindingsAfterLoad: true,
configureDeveloperSettings: function (windowConfig) {
return {
// disable automated devtools opening on error when running extension tests
// as this can lead to undeterministic test exectuion (devtools steals focus)
forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string',
// enable devtools keybindings in extension development window
forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0,
removeDeveloperKeybindingsAfterLoad: true
};
},
canModifyDOM: function (windowConfig) {
showPartsSplash(windowConfig);
},
beforeLoaderConfig: function (windowConfig, loaderConfig) {
beforeLoaderConfig: function (loaderConfig) {
loaderConfig.recordStats = true;
},
beforeRequire: function () {
performance.mark('code/willLoadWorkbenchMain');
// It looks like browsers only lazily enable
// the <canvas> element when needed. Since we
// leverage canvas elements in our code in many
// locations, we try to help the browser to
// initialize canvas when it is idle, right
// before we wait for the scripts to be loaded.
// @ts-ignore
window.requestIdleCallback(() => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.remove();
}, { timeout: 50 });
}
}
);
@@ -63,12 +86,27 @@
}
});
//region Helpers
//#region Helpers
/**
* @typedef {import('../../../platform/windows/common/windows').INativeWindowConfiguration} INativeWindowConfiguration
*
* @returns {{
* load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown,
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals')
* load: (
* modules: string[],
* resultCallback: (result, configuration: INativeWindowConfiguration) => unknown,
* options?: {
* configureDeveloperSettings?: (config: INativeWindowConfiguration & object) => {
* forceDisableShowDevtoolsOnError?: boolean,
* forceEnableDeveloperKeybindings?: boolean,
* disallowReloadKeybinding?: boolean,
* removeDeveloperKeybindingsAfterLoad?: boolean
* },
* canModifyDOM?: (config: INativeWindowConfiguration & object) => void,
* beforeLoaderConfig?: (loaderConfig: object) => void,
* beforeRequire?: () => void
* }
* ) => Promise<unknown>
* }}
*/
function bootstrapWindowLib() {
@@ -180,5 +218,4 @@
}
//#endregion
}());

View File

@@ -3,25 +3,26 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { release, hostname } from 'os';
import { statSync } from 'fs';
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
import { release } from 'os';
import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform';
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { resolveShellEnv } from 'vs/code/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
import { getDelayedChannel, StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron';
import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcess';
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService } from 'vs/platform/log/common/log';
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -32,11 +33,10 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil
import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
import product from 'vs/platform/product/common/product';
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 } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, ICodeWindow, OpenContext, WindowError } from 'vs/platform/windows/electron-main/windows';
import { URI } from 'vs/base/common/uri';
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
@@ -45,28 +45,25 @@ import { Win32UpdateService } from 'vs/platform/update/electron-main/updateServi
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
import { IssueMainService, IIssueMainService } from 'vs/platform/issue/electron-main/issueMainService';
import { LoggerChannel } from 'vs/platform/log/common/logIpc';
import { LoggerChannel, LogLevelChannel } from 'vs/platform/log/common/logIpc';
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService';
import { RunOnceScheduler } from 'vs/base/common/async';
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
import { sep, posix, join, isAbsolute } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { localize } from 'vs/nls';
import { Schemas } from 'vs/base/common/network';
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { NativeURLService } from 'vs/platform/url/common/urlService';
import { WorkspacesManagementMainService, IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
import { statSync } from 'fs';
import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
import { ElectronExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/electron-main/extensionHostDebugIpc';
import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
@@ -74,7 +71,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { IFileService } from 'vs/platform/files/common/files';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { stripComments } from 'vs/base/common/json';
import { generateUuid } from 'vs/base/common/uuid';
import { VSBuffer } from 'vs/base/common/buffer';
@@ -82,29 +79,35 @@ import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encry
import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker';
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { isEqualOrParent } from 'vs/base/common/extpath';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
import { once } from 'vs/base/common/functional';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { ISignService } from 'vs/platform/sign/common/sign';
/**
* The main VS Code application. There will only ever be one instance,
* even if the user starts many instances (e.g. from the command line).
*/
export class CodeApplication extends Disposable {
private windowsMainService: IWindowsMainService | undefined;
private dialogMainService: IDialogMainService | undefined;
private nativeHostMainService: INativeHostMainService | undefined;
constructor(
private readonly mainIpcServer: NodeIPCServer,
private readonly mainProcessNodeIpcServer: NodeIPCServer,
private readonly userEnv: IProcessEnvironment,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService private readonly mainInstantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStateService private readonly stateService: IStateService,
@IFileService private readonly fileService: IFileService
@IFileService private readonly fileService: IFileService,
@IProductService private readonly productService: IProductService
) {
super();
@@ -166,7 +169,7 @@ export class CodeApplication extends Disposable {
event.preventDefault();
});
app.on('remote-get-current-web-contents', event => {
if (this.environmentService.args.driver) {
if (this.environmentMainService.args.driver) {
return; // the driver needs access to web contents
}
@@ -184,11 +187,11 @@ export class CodeApplication extends Disposable {
const uri = URI.parse(source);
if (uri.scheme === Schemas.vscodeWebview) {
return uri.path === '/index.html' || uri.path === '/electron-browser/index.html';
return uri.path === '/index.html' || uri.path === '/electron-browser-index.html';
}
const srcUri = uri.fsPath.toLowerCase();
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
const rootUri = URI.file(this.environmentMainService.appRoot).fsPath.toLowerCase();
return srcUri.startsWith(rootUri + sep);
};
@@ -223,11 +226,20 @@ export class CodeApplication extends Disposable {
this.nativeHostMainService?.openExternal(undefined, url);
});
session.defaultSession.setPermissionRequestHandler((webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback) => {
const isUrlFromWebview = (requestingUrl: string) =>
requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return callback(permission === 'clipboard-read');
}
return callback(false);
});
session.defaultSession.setPermissionCheckHandler((webContents, permission /* 'media' */) => {
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return permission === 'clipboard-read';
}
return false;
});
});
@@ -253,7 +265,7 @@ export class CodeApplication extends Disposable {
runningTimeout = setTimeout(() => {
this.windowsMainService?.open({
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
cli: this.environmentService.args,
cli: this.environmentMainService.args,
urisToOpen: macOpenFileURIs,
gotoLineMode: false,
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
@@ -271,71 +283,73 @@ export class CodeApplication extends Disposable {
//#region Bootstrap IPC Handlers
let slowShellResolveWarningShown = false;
ipcMain.on('vscode:fetchShellEnv', async event => {
ipcMain.handle('vscode:fetchShellEnv', event => {
return new Promise(async resolve => {
// DO NOT remove: not only usual windows are fetching the
// shell environment but also shared process, issue reporter
// etc, so we need to reply via `webContents` always
const webContents = event.sender;
// DO NOT remove: not only usual windows are fetching the
// shell environment but also shared process, issue reporter
// etc, so we need to reply via `webContents` always
const webContents = event.sender;
let replied = false;
let replied = false;
function acceptShellEnv(env: NodeJS.ProcessEnv): void {
clearTimeout(shellEnvSlowWarningHandle);
clearTimeout(shellEnvTimeoutErrorHandle);
function acceptShellEnv(env: IProcessEnvironment): void {
clearTimeout(shellEnvSlowWarningHandle);
clearTimeout(shellEnvTimeoutErrorHandle);
if (!replied) {
replied = true;
if (!replied) {
replied = true;
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', env);
if (!webContents.isDestroyed()) {
resolve(env);
}
}
}
}
// Handle slow shell environment resolve calls:
// - a warning after 3s but continue to resolve (only once in active window)
// - an error after 10s and stop trying to resolve (in every window where this happens)
const cts = new CancellationTokenSource();
// Handle slow shell environment resolve calls:
// - a warning after 3s but continue to resolve (only once in active window)
// - an error after 10s and stop trying to resolve (in every window where this happens)
const cts = new CancellationTokenSource();
const shellEnvSlowWarningHandle = setTimeout(() => {
if (!slowShellResolveWarningShown) {
this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token);
slowShellResolveWarningShown = true;
const shellEnvSlowWarningHandle = setTimeout(() => {
if (!slowShellResolveWarningShown) {
this.windowsMainService?.sendToFocused('vscode:showShellEnvSlowWarning', cts.token);
slowShellResolveWarningShown = true;
}
}, 3000);
const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!!
const shellEnvTimeoutErrorHandle = setTimeout(() => {
cts.dispose(true);
window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None);
acceptShellEnv({});
}, 10000);
// Prefer to use the args and env from the target window
// when resolving the shell env. It is possible that
// a first window was opened from the UI but a second
// from the CLI and that has implications for whether to
// resolve the shell environment or not.
//
// Window can be undefined for e.g. the shared process
// that is not part of our windows registry!
let args: NativeParsedArgs;
let env: IProcessEnvironment;
if (window?.config) {
args = window.config;
env = { ...process.env, ...window.config.userEnv };
} else {
args = this.environmentMainService.args;
env = process.env;
}
}, 3000);
const window = this.windowsMainService?.getWindowByWebContents(event.sender); // Note: this can be `undefined` for the shared process!!
const shellEnvTimeoutErrorHandle = setTimeout(() => {
cts.dispose(true);
window?.sendWhenReady('vscode:showShellEnvTimeoutError', CancellationToken.None);
acceptShellEnv({});
}, 10000);
// Prefer to use the args and env from the target window
// when resolving the shell env. It is possible that
// a first window was opened from the UI but a second
// from the CLI and that has implications for whether to
// resolve the shell environment or not.
//
// Window can be undefined for e.g. the shared process
// that is not part of our windows registry!
let args: NativeParsedArgs;
let env: NodeJS.ProcessEnv;
if (window?.config) {
args = window.config;
env = { ...process.env, ...window.config.userEnv };
} else {
args = this.environmentService.args;
env = process.env;
}
// Resolve shell env
const shellEnv = await resolveShellEnv(this.logService, args, env);
acceptShellEnv(shellEnv);
// Resolve shell env
const shellEnv = await resolveShellEnv(this.logService, args, env);
acceptShellEnv(shellEnv);
});
});
ipcMain.handle('vscode:writeNlsFile', async (event, path: unknown, data: unknown) => {
ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
const uri = this.validateNlsPath([path]);
if (!uri || typeof data !== 'string') {
throw new Error('Invalid operation (vscode:writeNlsFile)');
@@ -374,7 +388,7 @@ export class CodeApplication extends Disposable {
}
}
if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentService.cachedLanguagesPath, !isLinux)) {
if (typeof path !== 'string' || !isAbsolute(path) || !isEqualOrParent(path, this.environmentMainService.cachedLanguagesPath, !isLinux)) {
return undefined;
}
@@ -386,7 +400,7 @@ export class CodeApplication extends Disposable {
// take only the message and stack property
const friendlyError = {
message: err.message,
message: `[uncaught exception in main]: ${err.message}`,
stack: err.stack
};
@@ -402,14 +416,26 @@ export class CodeApplication extends Disposable {
async startup(): Promise<void> {
this.logService.debug('Starting VS Code');
this.logService.debug(`from: ${this.environmentService.appRoot}`);
this.logService.debug('args:', this.environmentService.args);
this.logService.debug(`from: ${this.environmentMainService.appRoot}`);
this.logService.debug('args:', this.environmentMainService.args);
// TODO@bpasero TODO@deepak1556 workaround for #120655
try {
const cachedDataPath = URI.file(this.environmentMainService.chromeCachedDataDir);
this.logService.trace(`Deleting Chrome cached data path: ${cachedDataPath.fsPath}`);
await this.fileService.del(cachedDataPath, { recursive: true });
} catch (error) {
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
this.logService.error(error);
}
}
// Make sure we associate the program with the app user model id
// This will help Windows to associate the running program with
// any shortcut that is pinned to the taskbar and prevent showing
// two icons in the taskbar for the same app.
const win32AppUserModelId = product.win32AppUserModelId;
const win32AppUserModelId = this.productService.win32AppUserModelId;
if (isWindows && win32AppUserModelId) {
app.setAppUserModelId(win32AppUserModelId);
}
@@ -428,62 +454,43 @@ export class CodeApplication extends Disposable {
this.logService.error(error);
}
// Setup Protocol Handler
const fileProtocolHandler = this._register(this.instantiationService.createInstance(FileProtocolHandler));
// Create Electron IPC Server
const electronIpcServer = new ElectronIPCServer();
// Main process server (electron IPC based)
const mainProcessElectronServer = new ElectronIPCServer();
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
const machineId = await this.resolveMachineId();
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process after the first window has opened and 3s have passed
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
const sharedProcessClient = (async () => {
this.logService.trace('Main->SharedProcess#connect');
const port = await sharedProcess.connect();
this.logService.trace('Main->SharedProcess#connect: connection established');
return new MessagePortClient(port, 'main');
})();
const sharedProcessReady = (async () => {
await sharedProcess.whenReady();
return sharedProcessClient;
})();
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
this._register(new RunOnceScheduler(async () => {
sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env));
}, 3000)).schedule();
});
// Shared process
const { sharedProcess, sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId);
// Services
const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessReady);
const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady);
// Create driver
if (this.environmentService.driverHandle) {
const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService);
if (this.environmentMainService.driverHandle) {
const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, this.environmentMainService, appInstantiationService);
this.logService.info('Driver started at:', this.environmentService.driverHandle);
this.logService.info('Driver started at:', this.environmentMainService.driverHandle);
this._register(server);
}
// Setup Auth Handler
this._register(appInstantiationService.createInstance(<any>ProxyAuthHandler)); // {{SQL CARBON EDIT}} Cast here to avoid compilation error (not finding constructor?)
// Init Channels
appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient));
// Open Windows
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler));
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess));
// Tracing: Stop tracing after windows are ready if enabled
if (this.environmentService.args.trace) {
this.stopTracingEventually(windows);
if (this.environmentMainService.args.trace) {
appInstantiationService.invokeFunction(accessor => this.stopTracingEventually(accessor, windows));
}
}
@@ -501,9 +508,32 @@ export class CodeApplication extends Disposable {
return machineId;
}
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
private setupSharedProcess(machineId: string): { sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>, sharedProcessClient: Promise<MessagePortClient> } {
const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, this.userEnv));
const sharedProcessClient = (async () => {
this.logService.trace('Main->SharedProcess#connect');
const port = await sharedProcess.connect();
this.logService.trace('Main->SharedProcess#connect: connection established');
return new MessagePortClient(port, 'main');
})();
const sharedProcessReady = (async () => {
await sharedProcess.whenReady();
return sharedProcessClient;
})();
return { sharedProcess, sharedProcessReady, sharedProcessClient };
}
private async initServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
const services = new ServiceCollection();
// Update
switch (process.platform) {
case 'win32':
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
@@ -522,38 +552,60 @@ export class CodeApplication extends Disposable {
break;
}
// Windows
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv]));
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
services.set(IDiagnosticsService, createChannelSender(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
// Dialogs
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
// Launch
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
// Diagnostics
services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')))));
// Issues
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [this.userEnv]));
// Encryption
services.set(IEncryptionMainService, new SyncDescriptor(EncryptionMainService, [machineId]));
// Keyboard Layout
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));
services.set(IDisplayMainService, new SyncDescriptor(DisplayMainService));
// Native Host
services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess]));
// Webview Manager
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
// Workspaces
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService));
services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService));
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
// Menubar
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
// Extension URL Trust
services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService));
const storageMainService = new StorageMainService(this.logService, this.environmentService);
services.set(IStorageMainService, storageMainService);
this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close()));
// Storage
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService);
// Backups
const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService);
services.set(IBackupMainService, backupMainService);
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
// URL handling
services.set(IURLService, new SyncDescriptor(NativeURLService));
services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService));
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!this.productService.enableTelemetry) {
const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender')));
const appender = new TelemetryAppenderClient(channel);
const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath);
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, this.productService.msftInternalDomains, this.environmentMainService.installSourcePath);
const piiPaths = [this.environmentMainService.appRoot, this.environmentMainService.extensionsPath];
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true };
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config]));
@@ -564,123 +616,97 @@ export class CodeApplication extends Disposable {
// Init services that require it
await backupMainService.initialize();
return this.instantiationService.createChild(services);
return this.mainInstantiationService.createChild(services);
}
private stopTracingEventually(windows: ICodeWindow[]): void {
this.logService.info(`Tracing: waiting for windows to get ready...`);
private initChannels(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>): void {
let recordingStopped = false;
const stopRecording = async (timeout: boolean) => {
if (recordingStopped) {
return;
}
// Launch: this one is explicitly registered to the node.js
// server because when a second instance starts up, that is
// the only possible connection between the first and the
// second instance. Electron IPC does not work across apps.
const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), { disableMarshalling: true });
this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel);
recordingStopped = true; // only once
// Update
const updateChannel = new UpdateChannel(accessor.get(IUpdateService));
mainProcessElectronServer.registerChannel('update', updateChannel);
const path = await contentTracing.stopRecording(joinPath(this.environmentService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
// Issues
const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService));
mainProcessElectronServer.registerChannel('issue', issueChannel);
if (!timeout) {
this.dialogMainService?.showMessageBox({
type: 'info',
message: localize('trace.message', "Successfully created trace."),
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
buttons: [localize('trace.ok', "OK")]
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
} else {
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
}
};
// Encryption
const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService));
mainProcessElectronServer.registerChannel('encryption', encryptionChannel);
// Wait up to 30s before creating the trace anyways
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
// Signing
const signChannel = ProxyChannel.fromService(accessor.get(ISignService));
mainProcessElectronServer.registerChannel('sign', signChannel);
// Wait for all windows to get ready and stop tracing then
Promise.all(windows.map(window => window.ready())).then(() => {
clearTimeout(timeoutHandle);
stopRecording(false);
});
}
// Keyboard Layout
const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService));
mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
// Register more Main IPC services
const launchMainService = accessor.get(ILaunchMainService);
const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
this.mainIpcServer.registerChannel('launch', launchChannel);
// Register more Electron IPC services
const updateService = accessor.get(IUpdateService);
const updateChannel = new UpdateChannel(updateService);
electronIpcServer.registerChannel('update', updateChannel);
const issueMainService = accessor.get(IIssueMainService);
const issueChannel = createChannelReceiver(issueMainService);
electronIpcServer.registerChannel('issue', issueChannel);
const encryptionMainService = accessor.get(IEncryptionMainService);
const encryptionChannel = createChannelReceiver(encryptionMainService);
electronIpcServer.registerChannel('encryption', encryptionChannel);
const keyboardLayoutMainService = accessor.get(IKeyboardLayoutMainService);
const keyboardLayoutChannel = createChannelReceiver(keyboardLayoutMainService);
electronIpcServer.registerChannel('keyboardLayout', keyboardLayoutChannel);
const displayMainService = accessor.get(IDisplayMainService);
const displayChannel = createChannelReceiver(displayMainService);
electronIpcServer.registerChannel('display', displayChannel);
const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService);
const nativeHostChannel = createChannelReceiver(this.nativeHostMainService);
electronIpcServer.registerChannel('nativeHost', nativeHostChannel);
// Native host (main & shared process)
this.nativeHostMainService = accessor.get(INativeHostMainService);
const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService);
mainProcessElectronServer.registerChannel('nativeHost', nativeHostChannel);
sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel));
const workspacesService = accessor.get(IWorkspacesService);
const workspacesChannel = createChannelReceiver(workspacesService);
electronIpcServer.registerChannel('workspaces', workspacesChannel);
// Workspaces
const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService));
mainProcessElectronServer.registerChannel('workspaces', workspacesChannel);
const menubarMainService = accessor.get(IMenubarMainService);
const menubarChannel = createChannelReceiver(menubarMainService);
electronIpcServer.registerChannel('menubar', menubarChannel);
// Menubar
const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService));
mainProcessElectronServer.registerChannel('menubar', menubarChannel);
const urlService = accessor.get(IURLService);
const urlChannel = createChannelReceiver(urlService);
electronIpcServer.registerChannel('url', urlChannel);
// URL handling
const urlChannel = ProxyChannel.fromService(accessor.get(IURLService));
mainProcessElectronServer.registerChannel('url', urlChannel);
const extensionUrlTrustService = accessor.get(IExtensionUrlTrustService);
const extensionUrlTrustChannel = createChannelReceiver(extensionUrlTrustService);
electronIpcServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel);
// Extension URL Trust
const extensionUrlTrustChannel = ProxyChannel.fromService(accessor.get(IExtensionUrlTrustService));
mainProcessElectronServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel);
const webviewManagerService = accessor.get(IWebviewManagerService);
const webviewChannel = createChannelReceiver(webviewManagerService);
electronIpcServer.registerChannel('webview', webviewChannel);
// Webview Manager
const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService));
mainProcessElectronServer.registerChannel('webview', webviewChannel);
const storageMainService = accessor.get(IStorageMainService);
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
electronIpcServer.registerChannel('storage', storageChannel);
// Storage (main & shared process)
const storageChannel = this._register(new StorageDatabaseChannel(this.logService, accessor.get(IStorageMainService)));
mainProcessElectronServer.registerChannel('storage', storageChannel);
sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
const loggerChannel = new LoggerChannel(accessor.get(ILogService));
electronIpcServer.registerChannel('logger', loggerChannel);
// Log Level (main & shared process)
const logLevelChannel = new LogLevelChannel(accessor.get(ILogService));
mainProcessElectronServer.registerChannel('logLevel', logLevelChannel);
sharedProcessClient.then(client => client.registerChannel('logLevel', logLevelChannel));
// Logger
const loggerChannel = new LoggerChannel(accessor.get(ILoggerService),);
mainProcessElectronServer.registerChannel('logger', loggerChannel);
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
fileProtocolHandler.injectWindowsMainService(windowsMainService);
// Extension Host Debug Broadcasting
const electronExtensionHostDebugBroadcastChannel = new ElectronExtensionHostDebugBroadcastChannel(accessor.get(IWindowsMainService));
mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel);
}
// ExtensionHost Debug broadcast service
electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] {
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
const urlService = accessor.get(IURLService);
const nativeHostMainService = accessor.get(INativeHostMainService);
// Signal phase: ready (services set)
this.lifecycleMainService.phase = LifecycleMainPhase.Ready;
// Propagate to clients
this.dialogMainService = accessor.get(IDialogMainService);
// Check for initial URLs to handle from protocol link invocations
const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = [];
const pendingProtocolLinksToHandle = [
// Windows/Linux: protocol handler invokes CLI with --open-url
...this.environmentService.args['open-url'] ? this.environmentService.args._urls || [] : [],
...this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [],
// macOS: open-url events
...((<any>global).getOpenUrls() || []) as string[]
@@ -717,7 +743,7 @@ export class CodeApplication extends Disposable {
// or open new windows. The URL handler will be invoked from
// protocol invocations outside of VSCode.
const app = this;
const environmentService = this.environmentService;
const environmentService = this.environmentMainService;
urlService.registerHandler({
async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
@@ -734,6 +760,7 @@ export class CodeApplication extends Disposable {
cli: { ...environmentService.args },
urisToOpen: [windowOpenableFromProtocolLink],
gotoLineMode: true
/* remoteAuthority will be determined based on windowOpenableFromProtocolLink */
});
window.focus(); // this should help ensuring that the right window gets focus when multiple are opened
@@ -748,7 +775,8 @@ export class CodeApplication extends Disposable {
context: OpenContext.API,
cli: { ...environmentService.args },
forceEmpty: true,
gotoLineMode: true
gotoLineMode: true,
remoteAuthority: getRemoteAuthority(uri)
});
await window.ready();
@@ -768,14 +796,14 @@ export class CodeApplication extends Disposable {
}));
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
const urlHandlerChannel = mainProcessElectronServer.getChannel('urlHandler', urlHandlerRouter);
urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel));
// Watch Electron URLs and forward them to the UrlService
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentService));
this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService, this.productService));
// Open our first window
const args = this.environmentService.args;
const args = this.environmentMainService.args;
const macOpenFiles: string[] = (<any>global).macOpenFiles;
const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP;
const hasCliArgs = args._.length;
@@ -783,6 +811,7 @@ export class CodeApplication extends Disposable {
const hasFileURIs = !!args['file-uri'];
const noRecentEntry = args['skip-add-to-recently-opened'] === true;
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
const remoteAuthority = args.remote || undefined;
// check for a pending window to open from URI
// e.g. when running code with --open-uri from
@@ -794,6 +823,7 @@ export class CodeApplication extends Disposable {
urisToOpen: pendingWindowOpenablesFromProtocolLinks,
gotoLineMode: true,
initialStartup: true
/* remoteAuthority will be determined based on pendingWindowOpenablesFromProtocolLinks */
});
}
@@ -806,7 +836,8 @@ export class CodeApplication extends Disposable {
forceEmpty: true,
noRecentEntry,
waitMarkerFileURI,
initialStartup: true
initialStartup: true,
remoteAuthority
});
}
@@ -818,7 +849,8 @@ export class CodeApplication extends Disposable {
urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
noRecentEntry,
waitMarkerFileURI,
initialStartup: true
initialStartup: true,
/* remoteAuthority will be determined based on macOpenFiles */
});
}
@@ -831,21 +863,22 @@ export class CodeApplication extends Disposable {
noRecentEntry,
waitMarkerFileURI,
gotoLineMode: args.goto,
initialStartup: true
initialStartup: true,
remoteAuthority
});
}
private shouldBlockURI(uri: URI): boolean {
if (uri.authority === Schemas.file && isWindows) {
const res = dialog.showMessageBoxSync({
title: product.nameLong,
title: this.productService.nameLong,
type: 'question',
buttons: [
mnemonicButtonLabel(localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes")),
mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")),
],
cancelId: 1,
message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentService), product.nameShort),
message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentMainService), this.productService.nameShort),
detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"),
noLink: true
});
@@ -883,6 +916,8 @@ export class CodeApplication extends Disposable {
if (hasWorkspaceFileExtension(path)) {
return { workspaceUri: remoteUri };
} else if (/:[\d]+$/.test(path)) { // path with :line:column syntax
return { fileUri: remoteUri };
} else {
return { folderUri: remoteUri };
}
@@ -909,13 +944,49 @@ export class CodeApplication extends Disposable {
return { fileUri: URI.file(path) };
}
private async afterWindowOpen(accessor: ServicesAccessor): Promise<void> {
private async afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): Promise<void> {
// Signal phase: after window open
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
// Observe shared process for errors
let willShutdown = false;
once(this.lifecycleMainService.onWillShutdown)(() => willShutdown = true);
const telemetryService = accessor.get(ITelemetryService);
this._register(sharedProcess.onDidError(({ type, details }) => {
// Logging
let message: string;
if (typeof details === 'string') {
message = details;
} else {
message = `SharedProcess: crashed (detail: ${details.reason})`;
}
onUnexpectedError(new Error(message));
// Telemetry
type SharedProcessErrorClassification = {
type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
reason: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
visible: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
shuttingdown: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
};
type SharedProcessErrorEvent = {
type: WindowError;
reason: string | undefined;
visible: boolean;
shuttingdown: boolean;
};
telemetryService.publicLog2<SharedProcessErrorEvent, SharedProcessErrorClassification>('sharedprocesserror', {
type,
reason: typeof details !== 'string' ? details?.reason : undefined,
visible: sharedProcess.isVisible(),
shuttingdown: willShutdown
});
}));
// Windows: install mutex
const win32MutexName = product.win32MutexName;
const win32MutexName = this.productService.win32MutexName;
if (isWindows && win32MutexName) {
try {
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
@@ -941,13 +1012,13 @@ export class CodeApplication extends Disposable {
}
// Start to fetch shell environment (if needed) after window has opened
resolveShellEnv(this.logService, this.environmentService.args, process.env);
resolveShellEnv(this.logService, this.environmentMainService.args, process.env);
// If enable-crash-reporter argv is undefined then this is a fresh start,
// based on telemetry.enableCrashreporter settings, generate a UUID which
// will be used as crash reporter id and also update the json file.
try {
const argvContent = await this.fileService.readFile(this.environmentService.argvResource);
const argvContent = await this.fileService.readFile(this.environmentMainService.argvResource);
const argvString = argvContent.value.toString();
const argvJSON = JSON.parse(stripComments(argvString));
if (argvJSON['enable-crash-reporter'] === undefined) {
@@ -965,10 +1036,47 @@ export class CodeApplication extends Disposable {
];
const newArgvString = argvString.substring(0, argvString.length - 2).concat(',\n', additionalArgvContent.join('\n'));
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(newArgvString));
await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString));
}
} catch (error) {
this.logService.error(error);
}
}
private stopTracingEventually(accessor: ServicesAccessor, windows: ICodeWindow[]): void {
this.logService.info(`Tracing: waiting for windows to get ready...`);
const dialogMainService = accessor.get(IDialogMainService);
let recordingStopped = false;
const stopRecording = async (timeout: boolean) => {
if (recordingStopped) {
return;
}
recordingStopped = true; // only once
const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${this.productService.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath);
if (!timeout) {
dialogMainService.showMessageBox({
type: 'info',
message: localize('trace.message', "Successfully created trace."),
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
buttons: [localize('trace.ok', "OK")]
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
} else {
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
}
};
// Wait up to 30s before creating the trace anyways
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
// Wait for all windows to get ready and stop tracing then
Promise.all(windows.map(window => window.ready())).then(() => {
clearTimeout(timeoutHandle);
stopRecording(false);
});
}
}

View File

@@ -12,7 +12,7 @@ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
import { generateUuid } from 'vs/base/common/uuid';
import product from 'vs/platform/product/common/product';
import { IProductService } from 'vs/platform/product/common/productService';
import { CancellationToken } from 'vs/base/common/cancellation';
interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails {
@@ -56,7 +56,7 @@ enum ProxyAuthState {
export class ProxyAuthHandler extends Disposable {
private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`;
private readonly PROXY_CREDENTIALS_SERVICE_KEY = `${this.productService.urlProtocol}.proxy-credentials`;
private pendingProxyResolve: Promise<Credentials | undefined> | undefined = undefined;
@@ -65,10 +65,12 @@ export class ProxyAuthHandler extends Disposable {
private sessionCredentials: Credentials | undefined = undefined;
constructor(
any,
@ILogService private readonly logService: ILogService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService
@IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService,
@IProductService private readonly productService: IProductService
) {
super();
@@ -153,7 +155,7 @@ export class ProxyAuthHandler extends Disposable {
let storedUsername: string | undefined = undefined;
let storedPassword: string | undefined = undefined;
try {
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
if (encryptedSerializedProxyCredentials) {
const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials));
@@ -201,7 +203,7 @@ export class ProxyAuthHandler extends Disposable {
const proxyAuthResponseHandler = async (event: ElectronEvent, channel: string, reply: Credentials & { remember: boolean } | undefined /* canceled */) => {
if (channel === payload.replyChannel) {
this.logService.trace(`auth#doResolveProxyCredentials - exit - received credentials from window ${window.id}`);
window.win.webContents.off('ipc-message', proxyAuthResponseHandler);
window.win?.webContents.off('ipc-message', proxyAuthResponseHandler);
// We got credentials from the window
if (reply) {
@@ -211,9 +213,9 @@ export class ProxyAuthHandler extends Disposable {
try {
if (reply.remember) {
const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials));
await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
await this.nativeHostMainService.setPassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials);
} else {
await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
await this.nativeHostMainService.deletePassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash);
}
} catch (error) {
this.logService.error(error); // handle gracefully
@@ -229,7 +231,7 @@ export class ProxyAuthHandler extends Disposable {
}
};
window.win.webContents.on('ipc-message', proxyAuthResponseHandler);
window.win?.webContents.on('ipc-message', proxyAuthResponseHandler);
});
// Remember credentials for the session in case

View File

@@ -8,11 +8,12 @@ import { app, dialog } from 'electron';
import { promises, unlinkSync } from 'fs';
import { localize } from 'vs/nls';
import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
import { mark } from 'vs/base/common/performance';
import product from 'vs/platform/product/common/product';
import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Server as NodeIPCServer, serve as nodeIPCServe, connect as nodeIPCConnect, XDG_RUNTIME_DIR } from 'vs/base/parts/ipc/node/ipc.net';
import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net';
import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
@@ -20,10 +21,9 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
import { ILogService, ConsoleMainLogger, MultiplexLogService, getLogLevel, ILoggerService } from 'vs/platform/log/common/log';
import { StateService } from 'vs/platform/state/node/stateService';
import { IStateService } from 'vs/platform/state/node/state';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
@@ -31,9 +31,9 @@ import { IRequestService } from 'vs/platform/request/common/request';
import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService';
import { CodeApplication } from 'vs/code/electron-main/app';
import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { ExpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { once } from 'vs/base/common/functional';
import { ISignService } from 'vs/platform/sign/common/sign';
@@ -47,158 +47,146 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
import { IProductService } from 'vs/platform/product/common/productService';
import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath';
import { isNumber } from 'vs/base/common/types';
import { rtrim, trim } from 'vs/base/common/strings';
import { basename, resolve } from 'vs/base/common/path';
import { basename, join, resolve } from 'vs/base/common/path';
import { coalesce, distinct } from 'vs/base/common/arrays';
import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
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';
class ExpectedError extends Error {
readonly isExpected = true;
}
/**
* The main VS Code entry point.
*
* Note: This class can exist more than once for example when VS Code is already
* running and a second instance is started from the command line. It will always
* try to communicate with an existing instance to prevent that 2 VS Code instances
* are running at the same time.
*/
class CodeMain {
main(): void {
try {
this.startup();
} catch (error) {
console.error(error.message);
app.exit(1);
}
}
private async startup(): Promise<void> {
// Set the error handler early enough so that we are not getting the
// default electron error dialog popping up
setUnexpectedErrorHandler(err => console.error(err));
// Parse arguments
let args: NativeParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
args = this.validatePaths(args);
} catch (err) {
console.error(err.message);
app.exit(1);
// Create services
const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService, productService] = this.createServices();
return;
}
// If we are started with --wait create a random temporary file
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
if (args.wait && !args.waitMarkerFilePath) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
}
}
// Launch
this.startup(args);
}
private async startup(args: NativeParsedArgs): Promise<void> {
// We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows (https://github.com/microsoft/vscode/issues/41218)
const bufferLogService = new BufferLogService();
const [instantiationService, instanceEnvironment, environmentService] = this.createServices(args, bufferLogService);
try {
// Init services
await instantiationService.invokeFunction(async accessor => {
const configurationService = accessor.get(IConfigurationService);
const stateService = accessor.get(IStateService);
try {
await this.initServices(environmentService, configurationService, stateService);
} catch (error) {
try {
await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);
} catch (error) {
// Show a dialog for errors that can be resolved by the user
this.handleStartupDataDirError(environmentService, productService.nameLong, error);
// Show a dialog for errors that can be resolved by the user
this.handleStartupDataDirError(environmentService, error);
throw error;
}
});
throw error;
}
// Startup
await instantiationService.invokeFunction(async accessor => {
const logService = accessor.get(ILogService);
const lifecycleMainService = accessor.get(ILifecycleMainService);
const fileService = accessor.get(IFileService);
const configurationService = accessor.get(IConfigurationService);
const mainIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true);
// Create the main IPC server by trying to be the server
// If this throws an error it means we are not the first
// instance of VS Code running and so we would quit.
const mainProcessNodeIpcServer = await this.claimInstance(logService, environmentService, lifecycleMainService, instantiationService, productService, true);
bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)
bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, 'main.log'), true, bufferLogService.getLevel());
// Lifecycle
once(lifecycleMainService.onWillShutdown)(() => {
fileService.dispose();
(configurationService as ConfigurationService).dispose();
configurationService.dispose();
});
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
});
} catch (error) {
instantiationService.invokeFunction(this.quit, error);
}
}
private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService] {
private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService, IProductService] {
const services = new ServiceCollection();
const environmentService = new EnvironmentMainService(args);
const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
services.set(IEnvironmentService, environmentService);
services.set(IEnvironmentMainService, environmentService);
// Product
const productService = { _serviceBrand: undefined, ...product };
services.set(IProductService, productService);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
// Environment
const environmentMainService = new EnvironmentMainService(this.resolveArgs(), productService);
const instanceEnvironment = this.patchEnvironment(environmentMainService); // Patch `process.env` with the instance's environment
services.set(IEnvironmentMainService, environmentMainService);
// Log: We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows (https://github.com/microsoft/vscode/issues/41218)
const bufferLogService = new BufferLogService();
const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService]);
process.once('exit', () => logService.dispose());
services.set(ILogService, logService);
// Files
const fileService = new FileService(logService);
services.set(IFileService, fileService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource, fileService));
// Logger
services.set(ILoggerService, new LoggerService(logService, fileService));
// Configuration
const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService);
services.set(IConfigurationService, configurationService);
// Lifecycle
services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
services.set(IStateService, new SyncDescriptor(StateService));
// State
const stateService = new StateService(environmentMainService, logService);
services.set(IStateService, stateService);
// Request
services.set(IRequestService, new SyncDescriptor(RequestMainService));
// Themes
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
// Signing
services.set(ISignService, new SyncDescriptor(SignService));
services.set(IProductService, { _serviceBrand: undefined, ...product });
// Tunnel
services.set(ITunnelService, new SyncDescriptor(TunnelService));
return [new InstantiationService(services, true), instanceEnvironment, environmentService];
// Protocol
services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService));
return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogService, productService];
}
private initServices(environmentService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise<unknown> {
// Environment service (paths)
const environmentServiceInitialization = Promise.all<void | undefined>([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome.fsPath,
environmentService.workspaceStorageHome.fsPath,
environmentService.backupHome
].map((path): undefined | Promise<void> => path ? promises.mkdir(path, { recursive: true }) : undefined));
// Configuration service
const configurationServiceInitialization = configurationService.initialize();
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
}
private patchEnvironment(environmentService: IEnvironmentMainService): IProcessEnvironment {
private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment {
const instanceEnvironment: IProcessEnvironment = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle
VSCODE_IPC_HOOK: environmentMainService.mainIPCHandle
};
['VSCODE_NLS_CONFIG', 'VSCODE_LOGS', 'VSCODE_PORTABLE', 'ADS_LOGS'].forEach(key => { // {{SQL CARBON EDIT}} add ads logs
@@ -213,15 +201,38 @@ class CodeMain {
return instanceEnvironment;
}
private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise<NodeIPCServer> {
private initServices(environmentMainService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise<unknown> {
// Environment service (paths)
const environmentServiceInitialization = Promise.all<string | undefined>([
environmentMainService.extensionsPath,
environmentMainService.nodeCachedDataDir,
environmentMainService.logsPath,
environmentMainService.globalStorageHome.fsPath,
environmentMainService.workspaceStorageHome.fsPath,
environmentMainService.backupHome
].map(path => path ? promises.mkdir(path, { recursive: true }) : undefined));
// Configuration service
const configurationServiceInitialization = configurationService.initialize();
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]);
}
private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise<NodeIPCServer> {
// Try to setup a server for running. If that succeeds it means
// we are the first instance to startup. Otherwise it is likely
// that another instance is already running.
let server: NodeIPCServer;
let mainProcessNodeIpcServer: NodeIPCServer;
try {
server = await nodeIPCServe(environmentService.mainIPCHandle);
once(lifecycleMainService.onWillShutdown)(() => server.dispose());
mark('code/willStartMainServer');
mainProcessNodeIpcServer = await nodeIPCServe(environmentMainService.mainIPCHandle);
mark('code/didStartMainServer');
once(lifecycleMainService.onWillShutdown)(() => mainProcessNodeIpcServer.dispose());
} catch (error) {
// Handle unexpected errors (the only expected error is EADDRINUSE that
@@ -229,7 +240,7 @@ class CodeMain {
if (error.code !== 'EADDRINUSE') {
// Show a dialog for errors that can be resolved by the user
this.handleStartupDataDirError(environmentService, error);
this.handleStartupDataDirError(environmentMainService, productService.nameLong, error);
// Any other runtime error is just printed to the console
throw error;
@@ -238,15 +249,16 @@ class CodeMain {
// there's a running instance, let's connect to it
let client: NodeIPCClient<string>;
try {
client = await nodeIPCConnect(environmentService.mainIPCHandle, 'main');
client = await nodeIPCConnect(environmentMainService.mainIPCHandle, 'main');
} catch (error) {
// Handle unexpected connection errors by showing a dialog to the user
if (!retry || isWindows || error.code !== 'ECONNREFUSED') {
if (error.code === 'EPERM') {
this.showStartupWarningDialog(
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort),
localize('secondInstanceAdminDetail', "Please close the other instance and try again.")
localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", productService.nameShort),
localize('secondInstanceAdminDetail', "Please close the other instance and try again."),
productService.nameLong
);
}
@@ -257,18 +269,18 @@ class CodeMain {
// let's delete it, since we can't connect to it and then
// retry the whole thing
try {
unlinkSync(environmentService.mainIPCHandle);
unlinkSync(environmentMainService.mainIPCHandle);
} catch (error) {
logService.warn('Could not delete obsolete instance handle', error);
throw error;
}
return this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, false);
return this.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, false);
}
// Tests from CLI require to be the only instance currently
if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) {
if (environmentMainService.extensionTestsLocationURI && !environmentMainService.debugExtensionHost.break) {
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
logService.error(msg);
client.dispose();
@@ -280,21 +292,22 @@ class CodeMain {
// Skip this if we are running with --wait where it is expected that we wait for a while.
// Also skip when gathering diagnostics (--status) which can take a longer time.
let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined;
if (!args.wait && !args.status) {
if (!environmentMainService.args.wait && !environmentMainService.args.status) {
startupWarningDialogHandle = setTimeout(() => {
this.showStartupWarningDialog(
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort),
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.")
localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", productService.nameShort),
localize('secondInstanceNoResponseDetail', "Please close all other instances and try again."),
productService.nameLong
);
}, 10000);
}
const launchService = createChannelSender<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
const launchService = ProxyChannel.toService<ILaunchMainService>(client.getChannel('launch'), { disableMarshalling: true });
// Process Info
if (args.status) {
if (environmentMainService.args.status) {
return instantiationService.invokeFunction(async () => {
const diagnosticsService = new DiagnosticsService(NullTelemetryService);
const diagnosticsService = new DiagnosticsService(NullTelemetryService, productService);
const mainProcessInfo = await launchService.getMainProcessInfo();
const remoteDiagnostics = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true });
const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics);
@@ -311,7 +324,7 @@ class CodeMain {
// Send environment over...
logService.trace('Sending env to running instance...');
await launchService.start(args, process.env as IProcessEnvironment);
await launchService.start(environmentMainService.args, process.env as IProcessEnvironment);
// Cleanup
client.dispose();
@@ -325,7 +338,7 @@ class CodeMain {
}
// Print --status usage info
if (args.status) {
if (environmentMainService.args.status) {
logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.');
throw new ExpectedError('Terminating...');
@@ -335,26 +348,27 @@ class CodeMain {
// instance to startup. Otherwise we would wrongly overwrite the PID
process.env['VSCODE_PID'] = String(process.pid);
return server;
return mainProcessNodeIpcServer;
}
private handleStartupDataDirError(environmentService: IEnvironmentMainService, error: NodeJS.ErrnoException): void {
private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, title: string, error: NodeJS.ErrnoException): void {
if (error.code === 'EACCES' || error.code === 'EPERM') {
const directories = coalesce([environmentService.userDataPath, environmentService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentService));
const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentMainService));
this.showStartupWarningDialog(
localize('startupDataDirError', "Unable to write program user data."),
localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n'))
localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')),
title
);
}
}
private showStartupWarningDialog(message: string, detail: string): void {
private showStartupWarningDialog(message: string, detail: string, title: string): void {
// use sync variant here because we likely exit after this method
// due to startup issues and otherwise the dialog seems to disappear
// https://github.com/microsoft/vscode/issues/104493
dialog.showMessageBoxSync({
title: product.nameLong,
title,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
@@ -363,9 +377,9 @@ class CodeMain {
});
}
private async windowsAllowSetForegroundWindow(launchService: ILaunchMainService, logService: ILogService): Promise<void> {
private async windowsAllowSetForegroundWindow(launchMainService: ILaunchMainService, logService: ILogService): Promise<void> {
if (isWindows) {
const processId = await launchService.getMainProcessId();
const processId = await launchMainService.getMainProcessId();
logService.trace('Sending some foreground love to the running instance:', processId);
@@ -402,7 +416,30 @@ class CodeMain {
lifecycleMainService.kill(exitCode);
}
//#region Helpers
//#region Command line arguments utilities
private resolveArgs(): NativeParsedArgs {
// Parse arguments
const args = this.validatePaths(parseMainProcessArgv(process.argv));
// If we are started with --wait create a random temporary file
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
if (args.wait && !args.waitMarkerFilePath) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
}
}
return args;
}
private validatePaths(args: NativeParsedArgs): NativeParsedArgs {
@@ -422,7 +459,7 @@ class CodeMain {
}
private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
const cwd = process.env['VSCODE_CWD'] || process.cwd();
const currentWorkingDir = cwd();
const result = args.map(arg => {
let pathCandidate = String(arg);
@@ -433,10 +470,10 @@ class CodeMain {
}
if (pathCandidate) {
pathCandidate = this.preparePath(cwd, pathCandidate);
pathCandidate = this.preparePath(currentWorkingDir, pathCandidate);
}
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
const sanitizedFilePath = sanitizeFilePath(pathCandidate, currentWorkingDir);
const filePathBasename = basename(sanitizedFilePath);
if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) {
@@ -483,11 +520,11 @@ class CodeMain {
private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string {
const segments = [pathWithLineAndCol.path];
if (isNumber(pathWithLineAndCol.line)) {
if (typeof pathWithLineAndCol.line === 'number') {
segments.push(String(pathWithLineAndCol.line));
}
if (isNumber(pathWithLineAndCol.column)) {
if (typeof pathWithLineAndCol.column === 'number') {
segments.push(String(pathWithLineAndCol.column));
}

View File

@@ -1,110 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 { 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 { 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';
type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void };
export class FileProtocolHandler extends Disposable {
private readonly validRoots = TernarySearchTree.forUris<boolean>(() => !isLinux);
constructor(
@INativeEnvironmentService environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService
) {
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
this.validRoots.set(URI.file(environmentService.appRoot), true);
this.validRoots.set(URI.file(environmentService.extensionsPath), true);
// Register vscode-file:// handler
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback));
// Block any file:// access (explicitly enabled only)
if (isPreferringBrowserCodeLoad) {
this.logService.info(`Intercepting ${Schemas.file}: protocol and blocking it`);
defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback));
}
// Cleanup
this._register(toDisposable(() => {
defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource);
if (isPreferringBrowserCodeLoad) {
defaultSession.protocol.uninterceptProtocol(Schemas.file);
}
}));
}
injectWindowsMainService(windowsMainService: IWindowsMainService): void {
this._register(windowsMainService.onWindowReady(window => {
if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) {
const disposables = new DisposableStore();
disposables.add(Event.any(window.onClose, window.onDestroy)(() => 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 {
if (!this.validRoots.get(root)) {
this.validRoots.set(root, true);
return toDisposable(() => this.validRoots.delete(root));
}
return Disposable.None;
}
private async handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
const uri = URI.parse(request.url);
this.logService.error(`Refused to load resource ${uri.fsPath} from ${Schemas.file}: protocol`);
callback({ error: -3 /* ABORTED */ });
}
private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
const uri = URI.parse(request.url);
// Restore the `vscode-file` URI to a `file` URI so that we can
// ensure the root is valid and properly tell Chrome where the
// resource is at.
const fileUri = FileAccess.asFileUri(uri);
if (this.validRoots.findSubstr(fileUri)) {
return callback({
path: fileUri.fsPath
});
}
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath}}`);
callback({ error: -3 /* ABORTED */ });
}
}

View File

@@ -1,244 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { FileAccess } from 'vs/base/common/network';
import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform';
import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
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';
export class SharedProcess extends Disposable implements ISharedProcess {
private readonly whenSpawnedBarrier = new Barrier();
private window: BrowserWindow | undefined = undefined;
private windowCloseListener: ((event: Event) => void) | undefined = undefined;
constructor(
private readonly machineId: string,
private userEnv: NodeJS.ProcessEnv,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@ILogService private readonly logService: ILogService,
@IThemeMainService private readonly themeMainService: IThemeMainService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
// Lifecycle
this._register(this.lifecycleMainService.onWillShutdown(() => this.onWillShutdown()));
// Shared process connections from workbench windows
ipcMain.on('vscode:createSharedProcessMessageChannel', async (e, nonce: string) => {
this.logService.trace('SharedProcess: on vscode:createSharedProcessMessageChannel');
// await the shared process to be overall ready
// we do not just wait for IPC ready because the
// workbench window will communicate directly
await this.whenReady();
// connect to the shared process window
const port = await this.connect();
// Check back if the requesting window meanwhile closed
// Since shared process is delayed on startup there is
// a chance that the window close before the shared process
// was ready for a connection.
if (e.sender.isDestroyed()) {
return port.close();
}
// send the port back to the requesting window
e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]);
});
}
private onWillShutdown(): void {
const window = this.window;
if (!window) {
return; // possibly too early before created
}
// Signal exit to shared process when shutting down
if (!window.isDestroyed() && !window.webContents.isDestroyed()) {
window.webContents.send('vscode:electron-main->shared-process=exit');
}
// Shut the shared process down when we are quitting
//
// Note: because we veto the window close, we must first remove our veto.
// Otherwise the application would never quit because the shared process
// window is refusing to close!
//
if (this.windowCloseListener) {
window.removeListener('close', this.windowCloseListener);
this.windowCloseListener = undefined;
}
// Electron seems to crash on Windows without this setTimeout :|
setTimeout(() => {
try {
window.close();
} catch (err) {
// ignore, as electron is already shutting down
}
this.window = undefined;
}, 0);
}
private _whenReady: Promise<void> | undefined = undefined;
whenReady(): Promise<void> {
if (!this._whenReady) {
// Overall signal that the shared process window was loaded and
// all services within have been created.
this._whenReady = new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=init-done', () => {
this.logService.trace('SharedProcess: Overall ready');
resolve();
}));
}
return this._whenReady;
}
private _whenIpcReady: Promise<void> | undefined = undefined;
private get whenIpcReady() {
if (!this._whenIpcReady) {
this._whenIpcReady = (async () => {
// Always wait for `spawn()`
await this.whenSpawnedBarrier.wait();
// Create window for shared process
this.createWindow();
// Listeners
this.registerWindowListeners();
// Wait for window indicating that IPC connections are accepted
await new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => {
this.logService.trace('SharedProcess: IPC ready');
resolve();
}));
})();
}
return this._whenIpcReady;
}
private createWindow(): void {
// shared process is a hidden window by default
this.window = new BrowserWindow({
show: false,
backgroundColor: this.themeMainService.getBackgroundColor(),
webPreferences: {
preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath,
v8CacheOptions: browserCodeLoadingCacheStrategy,
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
images: false,
webgl: false,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});
const config: ISharedProcessConfiguration = {
machineId: this.machineId,
windowId: this.window.id,
appRoot: this.environmentService.appRoot,
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
backupWorkspacesPath: this.environmentService.backupWorkspacesPath,
userEnv: this.userEnv,
sharedIPCHandle: this.environmentService.sharedIPCHandle,
args: this.environmentService.args,
logLevel: this.logService.getLevel()
};
// Load with config
this.window.loadURL(FileAccess
.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
.toString(true)
);
}
private registerWindowListeners(): void {
if (!this.window) {
return;
}
// Prevent the window from closing
this.windowCloseListener = (e: Event) => {
this.logService.trace('SharedProcess#close prevented');
// We never allow to close the shared process unless we get explicitly disposed()
e.preventDefault();
// Still hide the window though if visible
if (this.window?.isVisible()) {
this.window.hide();
}
};
this.window.on('close', this.windowCloseListener);
// Crashes & Unrsponsive & Failed to load
this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`SharedProcess: crashed (detail: ${details?.reason})`));
this.window.on('unresponsive', () => this.logService.error('SharedProcess: detected unresponsive window'));
this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('SharedProcess: failed to load window, ', errorDescription));
}
spawn(userEnv: NodeJS.ProcessEnv): void {
this.userEnv = { ...this.userEnv, ...userEnv };
// Release barrier
this.whenSpawnedBarrier.open();
}
async connect(): Promise<MessagePortMain> {
// Wait for shared process being ready to accept connection
await this.whenIpcReady;
// Connect and return message port
const window = assertIsDefined(this.window);
return connectMessagePort(window);
}
async toggle(): Promise<void> {
// wait for window to be created
await this.whenIpcReady;
if (!this.window) {
return; // possibly disposed already
}
if (this.window.isVisible()) {
this.window.webContents.closeDevTools();
this.window.hide();
} else {
this.window.show();
this.window.webContents.openDevTools();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,26 +4,47 @@
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
(function () {
'use strict';
const bootstrapWindow = bootstrapWindowLib();
// Load issue reporter into window
bootstrapWindow.load(['vs/code/electron-sandbox/issue/issueReporterMain'], function (issueReporter, configuration) {
issueReporter.startup(configuration);
}, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true });
//#region Globals
return issueReporter.startup(configuration);
},
{
configureDeveloperSettings: function () {
return {
forceEnableDeveloperKeybindings: true,
disallowReloadKeybinding: true
};
}
}
);
/**
* @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }}
* @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration
*
* @returns {{
* load: (
* modules: string[],
* resultCallback: (result, configuration: ISandboxConfiguration) => unknown,
* options?: {
* configureDeveloperSettings?: (config: ISandboxConfiguration) => {
* forceEnableDeveloperKeybindings?: boolean,
* disallowReloadKeybinding?: boolean,
* removeDeveloperKeybindingsAfterLoad?: boolean
* },
* canModifyDOM?: (config: ISandboxConfiguration) => void,
* beforeLoaderConfig?: (loaderConfig: object) => void,
* beforeRequire?: () => void
* }
* ) => Promise<unknown>
* }}
*/
function bootstrapWindowLib() {
// @ts-ignore (defined in bootstrap-window.js)
return window.MonacoBootstrapWindow;
}
//#endregion
}());

View File

@@ -11,10 +11,10 @@ import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window';
import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import * as collections from 'vs/base/common/collections';
import { groupBy } from 'vs/base/common/collections';
import { debounce } from 'vs/base/common/decorators';
import { Disposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { isWindows, isLinux, isLinuxSnap, isMacintosh } from 'vs/base/common/platform';
import { escape } from 'vs/base/common/strings';
import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel';
@@ -22,11 +22,11 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage';
import { localize } from 'vs/nls';
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { IssueReporterWindowConfiguration, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
import { Codicon } from 'vs/base/common/codicons';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
const MAX_URL_LENGTH = 2045;
@@ -36,27 +36,14 @@ interface SearchResult {
state?: string;
}
export interface IssueReporterConfiguration extends IWindowConfiguration {
windowId: number;
disableExtensions: boolean;
data: IssueReporterData;
features: IssueReporterFeatures;
os: {
type: string;
arch: string;
release: string;
},
product: {
nameShort: string;
version: string;
commit: string | undefined;
date: string | undefined;
reportIssueUrl: string | undefined;
}
enum IssueSource {
VSCode = 'vscode',
Extension = 'extension',
Marketplace = 'marketplace'
}
export function startup(configuration: IssueReporterConfiguration) {
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
export function startup(configuration: IssueReporterWindowConfiguration) {
const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac';
document.body.classList.add(platformClass); // used by our fonts
safeInnerHtml(document.body, BaseHtml());
@@ -78,7 +65,7 @@ export class IssueReporter extends Disposable {
private readonly previewButton!: Button;
constructor(private readonly configuration: IssueReporterConfiguration) {
constructor(private readonly configuration: IssueReporterWindowConfiguration) {
super();
this.initServices(configuration);
@@ -87,8 +74,8 @@ export class IssueReporter extends Disposable {
this.issueReporterModel = new IssueReporterModel({
issueType: configuration.data.issueType || IssueType.Bug,
versionInfo: {
vscodeVersion: `${configuration.product.nameShort} ${configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`,
os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${platform.isLinuxSnap ? ' snap' : ''}`
vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`,
os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}`
},
extensionsDisabled: !!configuration.disableExtensions,
fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined,
@@ -249,7 +236,7 @@ export class IssueReporter extends Disposable {
private handleExtensionData(extensions: IssueReporterExtensionData[]) {
const installedExtensions = extensions.filter(x => !x.isBuiltin);
const { nonThemes, themes } = collections.groupBy(installedExtensions, ext => {
const { nonThemes, themes } = groupBy(installedExtensions, ext => {
return ext.isTheme ? 'themes' : 'nonThemes';
});
@@ -264,7 +251,7 @@ export class IssueReporter extends Disposable {
this.updateExtensionSelector(installedExtensions);
}
private initServices(configuration: IssueReporterConfiguration): void {
private initServices(configuration: IssueReporterWindowConfiguration): void {
const serviceCollection = new ServiceCollection();
const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId);
serviceCollection.set(IMainProcessService, mainProcessService);
@@ -325,17 +312,18 @@ export class IssueReporter extends Disposable {
hide(problemSourceHelpText);
}
const fileOnExtension = JSON.parse(value);
this.issueReporterModel.update({ fileOnExtension: fileOnExtension });
let fileOnExtension, fileOnMarketplace = false;
if (value === IssueSource.Extension) {
fileOnExtension = true;
} else if (value === IssueSource.Marketplace) {
fileOnMarketplace = true;
}
this.issueReporterModel.update({ fileOnExtension, fileOnMarketplace });
this.render();
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
if (fileOnExtension) {
this.searchExtensionIssues(title);
} else {
const description = this.issueReporterModel.getData().issueDescription;
this.searchVSCodeIssues(title, description);
}
this.searchIssues(title, fileOnExtension, fileOnMarketplace);
});
this.addEventListener('description', 'input', (e: Event) => {
@@ -352,23 +340,19 @@ export class IssueReporter extends Disposable {
this.addEventListener('issue-title', 'input', (e: Event) => {
const title = (<HTMLInputElement>e.target).value;
const lengthValidationMessage = this.getElementById('issue-title-length-validation-error');
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
const issueUrl = this.getIssueUrl();
if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) {
show(lengthValidationMessage);
} else {
hide(lengthValidationMessage);
}
const fileOnExtension = this.issueReporterModel.fileOnExtension();
if (fileOnExtension === undefined) {
const issueSource = this.getElementById<HTMLSelectElement>('issue-source');
if (!issueSource || issueSource.value === '') {
return;
}
if (fileOnExtension) {
this.searchExtensionIssues(title);
} else {
const description = this.issueReporterModel.getData().issueDescription;
this.searchVSCodeIssues(title, description);
}
const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData();
this.searchIssues(title, fileOnExtension, fileOnMarketplace);
});
this.previewButton.onDidClick(() => this.createIssue());
@@ -395,7 +379,7 @@ export class IssueReporter extends Disposable {
});
document.onkeydown = async (e: KeyboardEvent) => {
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey;
// Cmd/Ctrl+Enter previews issue and closes window
if (cmdOrCtrlKey && e.keyCode === 13) {
if (await this.createIssue()) {
@@ -429,7 +413,7 @@ export class IssueReporter extends Disposable {
// With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac
// Manually perform the selection
if (platform.isMacintosh) {
if (isMacintosh) {
if (cmdOrCtrlKey && e.keyCode === 65 && e.target) {
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
(<HTMLInputElement>e.target).select();
@@ -488,6 +472,19 @@ export class IssueReporter extends Disposable {
}
}
private searchIssues(title: string, fileOnExtension: boolean | undefined, fileOnMarketplace: boolean | undefined): void {
if (fileOnExtension) {
return this.searchExtensionIssues(title);
}
if (fileOnMarketplace) {
return this.searchMarketplaceIssues(title);
}
const description = this.issueReporterModel.getData().issueDescription;
this.searchVSCodeIssues(title, description);
}
private searchExtensionIssues(title: string): void {
const url = this.getExtensionGitHubUrl();
if (title) {
@@ -508,6 +505,15 @@ export class IssueReporter extends Disposable {
this.clearSearchResults();
}
private searchMarketplaceIssues(title: string): void {
if (title) {
const gitHubInfo = this.parseGitHubUrl(this.configuration.product.reportMarketplaceIssueUrl!);
if (gitHubInfo) {
return this.searchGitHub(`${gitHubInfo.owner}/${gitHubInfo.repositoryName}`, title);
}
}
}
private clearSearchResults(): void {
const similarIssues = this.getElementById('similar-issues')!;
similarIssues.innerText = '';
@@ -635,7 +641,7 @@ export class IssueReporter extends Disposable {
reset(typeSelect,
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")),
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue"))
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
);
typeSelect.value = issueType.toString();
@@ -665,19 +671,15 @@ export class IssueReporter extends Disposable {
}
sourceSelect.innerText = '';
if (issueType === IssueType.FeatureRequest) {
sourceSelect.append(...[
this.makeOption('', localize('selectSource', "Select source"), true),
this.makeOption('false', localize('azuredatastudio', "Azure Data Studio"), false),
this.makeOption('true', localize('extension', "An extension"), false)
]);
} else {
sourceSelect.append(...[
this.makeOption('', localize('selectSource', "Select source"), true),
this.makeOption('false', localize('azuredatastudio', "Azure Data Studio"), false),
this.makeOption('true', localize('extension', "An extension"), false),
this.makeOption('', localize('unknown', "Don't Know"), false)
]);
sourceSelect.append(this.makeOption('', localize('selectSource', "Select source"), true));
sourceSelect.append(this.makeOption('azuredatastudio', localize('azuredatastudio', "Azure Data Studio"), false)); // {{SQL CARBON EDIT}} Update name
sourceSelect.append(this.makeOption('extension', localize('extension', "An extension"), false));
if (this.configuration.product.reportMarketplaceIssueUrl) {
sourceSelect.append(this.makeOption('marketplace', localize('marketplace', "Extensions marketplace"), false));
}
if (issueType !== IssueType.FeatureRequest) {
sourceSelect.append(this.makeOption('', localize('unknown', "Don't know"), false));
}
if (selected !== -1 && selected < sourceSelect.options.length) {
@@ -690,7 +692,7 @@ export class IssueReporter extends Disposable {
private renderBlocks(): void {
// Depending on Issue Type, we render different blocks and text
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
const { issueType, fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData();
const blockContainer = this.getElementById('block-container');
const systemBlock = document.querySelector('.block-system');
const processBlock = document.querySelector('.block-process');
@@ -714,29 +716,35 @@ export class IssueReporter extends Disposable {
hide(extensionSelector);
if (issueType === IssueType.Bug) {
show(blockContainer);
show(systemBlock);
show(problemSource);
show(experimentsBlock);
if (!fileOnMarketplace) {
show(blockContainer);
show(systemBlock);
show(experimentsBlock);
}
if (fileOnExtension) {
show(extensionSelector);
} else {
} else if (!fileOnMarketplace) {
show(extensionsBlock);
}
reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*'));
reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."));
} else if (issueType === IssueType.PerformanceIssue) {
show(blockContainer);
show(systemBlock);
show(processBlock);
show(workspaceBlock);
show(problemSource);
show(experimentsBlock);
if (!fileOnMarketplace) {
show(blockContainer);
show(systemBlock);
show(processBlock);
show(workspaceBlock);
show(experimentsBlock);
}
if (fileOnExtension) {
show(extensionSelector);
} else {
} else if (!fileOnMarketplace) {
show(extensionsBlock);
}
@@ -844,13 +852,13 @@ export class IssueReporter extends Disposable {
const issueTitle = (<HTMLInputElement>this.getElementById('issue-title')).value;
const issueBody = this.issueReporterModel.serialize();
const issueUrl = this.issueReporterModel.fileOnExtension() ? this.getExtensionGitHubUrl() : this.configuration.product.reportIssueUrl!;
const issueUrl = this.getIssueUrl();
const gitHubDetails = this.parseGitHubUrl(issueUrl);
if (this.configuration.data.githubAccessToken && gitHubDetails) {
return this.submitToGitHub(issueTitle, issueBody, gitHubDetails);
}
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value, issueUrl);
let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`;
if (url.length > MAX_URL_LENGTH) {
@@ -880,6 +888,14 @@ export class IssueReporter extends Disposable {
});
}
private getIssueUrl(): string {
return this.issueReporterModel.fileOnExtension()
? this.getExtensionGitHubUrl()
: this.issueReporterModel.getData().fileOnMarketplace
? this.configuration.product.reportMarketplaceIssueUrl!
: this.configuration.product.reportIssueUrl!;
}
private parseGitHubUrl(url: string): undefined | { repositoryName: string, owner: string } {
// Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner.
// Repository name and owner cannot contain '/'
@@ -908,16 +924,12 @@ export class IssueReporter extends Disposable {
return repositoryUrl;
}
private getIssueUrlWithTitle(issueTitle: string): string {
let repositoryUrl = this.configuration.product.reportIssueUrl;
private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string {
if (this.issueReporterModel.fileOnExtension()) {
const extensionGitHubUrl = this.getExtensionGitHubUrl();
if (extensionGitHubUrl) {
repositoryUrl = extensionGitHubUrl + '/issues/new';
}
repositoryUrl = repositoryUrl + '/issues/new';
}
const queryStringPrefix = repositoryUrl && repositoryUrl.indexOf('?') === -1 ? '?' : '&'; // {{SQL CARBON EDIT}}
const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&';
return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
}
@@ -1155,7 +1167,7 @@ export class IssueReporter extends Disposable {
),
...extensions.map(extension => $('tr', undefined,
$('td', undefined, extension.name),
$('td', undefined, extension.publisher.substr(0, 3)),
$('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'),
$('td', undefined, extension.version),
))
);

View File

@@ -26,6 +26,7 @@ export interface IssueReporterData {
enabledNonThemeExtesions?: IssueReporterExtensionData[];
extensionsDisabled?: boolean;
fileOnExtension?: boolean;
fileOnMarketplace?: boolean;
selectedExtension?: IssueReporterExtensionData;
actualSearchResults?: ISettingSearchResult[];
query?: string;
@@ -111,30 +112,30 @@ ${this.getInfos()}
let info = '';
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
if (this._data.includeSystemInfo && this._data.systemInfo) {
if (!this._data.fileOnMarketplace && this._data.includeSystemInfo && this._data.systemInfo) {
info += this.generateSystemInfoMd();
}
}
if (this._data.issueType === IssueType.PerformanceIssue) {
if (this._data.includeProcessInfo) {
if (!this._data.fileOnMarketplace && this._data.includeProcessInfo) {
info += this.generateProcessInfoMd();
}
if (this._data.includeWorkspaceInfo) {
if (!this._data.fileOnMarketplace && this._data.includeWorkspaceInfo) {
info += this.generateWorkspaceInfoMd();
}
}
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
if (!this._data.fileOnExtension && this._data.includeExtensions) {
if (!this._data.fileOnMarketplace && !this._data.fileOnExtension && this._data.includeExtensions) {
info += this.generateExtensionsMd();
}
}
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
if (this._data.includeExperiments && this._data.experimentInfo) {
if (!this._data.fileOnMarketplace && this._data.includeExperiments && this._data.experimentInfo) {
info += this.generateExperimentsInfoMd();
}
}
@@ -239,7 +240,7 @@ ${this._data.experimentInfo}
const tableHeader = `Extension|Author (truncated)|Version
---|---|---`;
const table = this._data.enabledNonThemeExtesions.map(e => {
return `${e.name}|${e.publisher.substr(0, 3)}|${e.version}`;
return `${e.name}|${e.publisher?.substr(0, 3) ?? 'N/A'}|${e.version}`;
}).join('\n');
return `<details><summary>Extensions (${this._data.enabledNonThemeExtesions.length})</summary>

View File

@@ -12,7 +12,7 @@ suite('IssueReporter', () => {
test('sets defaults to include all data', () => {
const issueReporterModel = new IssueReporterModel();
assert.deepEqual(issueReporterModel.getData(), {
assert.deepStrictEqual(issueReporterModel.getData(), {
allExtensions: [],
includeSystemInfo: true,
includeWorkspaceInfo: true,
@@ -25,7 +25,7 @@ suite('IssueReporter', () => {
test('serializes model skeleton when no data is provided', () => { // {{SQL CARBON EDIT}} modify test for ads
const issueReporterModel = new IssueReporterModel({});
assert.equal(issueReporterModel.serialize(),
assert.strictEqual(issueReporterModel.serialize(),
`
Issue Type: <b>Bug</b>
@@ -55,7 +55,7 @@ Extensions: none
}
}
});
assert.equal(issueReporterModel.serialize(),
assert.strictEqual(issueReporterModel.serialize(),
`
Issue Type: <b>Bug</b>
@@ -77,6 +77,58 @@ OS version: undefined
|Screen Reader|no|
|VM|0%|
</details>Extensions: none
<!-- generated by issue reporter -->`);
});
test.skip('serializes experiment info when data is provided', () => { // {{SQL CARBON EDIT}} skip test
const issueReporterModel = new IssueReporterModel({
issueType: 0,
systemInfo: {
os: 'Darwin',
cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)',
memory: '16.00GB',
vmHint: '0%',
processArgs: '',
screenReader: 'no',
remoteData: [],
gpuStatus: {
'2d_canvas': 'enabled',
'checker_imaging': 'disabled_off'
}
},
experimentInfo: 'vsliv695:30137379\nvsins829:30139715'
});
assert.strictEqual(issueReporterModel.serialize(),
`
Issue Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
<details>
<summary>System Info</summary>
|Item|Value|
|---|---|
|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)|
|GPU Status|2d_canvas: enabled<br>checker_imaging: disabled_off|
|Load (avg)|undefined|
|Memory (System)|16.00GB|
|Process Argv||
|Screen Reader|no|
|VM|0%|
</details>Extensions: none<details>
<summary>A/B Experiments</summary>
\`\`\`
vsliv695:30137379
vsins829:30139715
\`\`\`
</details>
<!-- generated by issue reporter -->`);
});
@@ -100,7 +152,7 @@ OS version: undefined
}
}
});
assert.equal(issueReporterModel.serialize(),
assert.strictEqual(issueReporterModel.serialize(),
`
Issue Type: <b>Bug</b>
@@ -156,7 +208,7 @@ OS version: undefined
]
}
});
assert.equal(issueReporterModel.serialize(),
assert.strictEqual(issueReporterModel.serialize(),
`
Issue Type: <b>Bug</b>
@@ -204,7 +256,7 @@ Remote OS version: Linux x64 4.18.0
gpuStatus: {}
}
});
assert.equal(issueReporterModel.serialize(),
assert.strictEqual(issueReporterModel.serialize(),
`
Issue Type: <b>Bug</b>
@@ -239,7 +291,7 @@ OS version: undefined
'https://github.com/repo/issues/new',
'https://github.com/repo/issues/new/'
].forEach(url => {
assert.equal('https://github.com/repo', normalizeGitHubUrl(url));
assert.strictEqual('https://github.com/repo', normalizeGitHubUrl(url));
});
});
@@ -254,7 +306,7 @@ OS version: undefined
fileOnExtension: true
});
assert.equal(issueReporterModel.fileOnExtension(), true);
assert.strictEqual(issueReporterModel.fileOnExtension(), true);
});
});
});

View File

@@ -4,26 +4,44 @@
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
(function () {
'use strict';
const bootstrapWindow = bootstrapWindowLib();
// Load process explorer into window
bootstrapWindow.load(['vs/code/electron-sandbox/processExplorer/processExplorerMain'], function (processExplorer, configuration) {
processExplorer.startup(configuration.windowId, configuration.data);
}, { forceEnableDeveloperKeybindings: true });
//#region Globals
return processExplorer.startup(configuration);
}, {
configureDeveloperSettings: function () {
return {
forceEnableDeveloperKeybindings: true
};
},
});
/**
* @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }}
* @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration
*
* @returns {{
* load: (
* modules: string[],
* resultCallback: (result, configuration: ISandboxConfiguration) => unknown,
* options?: {
* configureDeveloperSettings?: (config: ISandboxConfiguration) => {
* forceEnableDeveloperKeybindings?: boolean,
* disallowReloadKeybinding?: boolean,
* removeDeveloperKeybindingsAfterLoad?: boolean
* },
* canModifyDOM?: (config: ISandboxConfiguration) => void,
* beforeLoaderConfig?: (loaderConfig: object) => void,
* beforeRequire?: () => void
* }
* ) => Promise<unknown>
* }}
*/
function bootstrapWindowLib() {
// @ts-ignore (defined in bootstrap-window.js)
return window.MonacoBootstrapWindow;
}
//#endregion
}());

View File

@@ -9,13 +9,12 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { localize } from 'vs/nls';
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
import { ProcessExplorerStyles, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue';
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
import * as dom from 'vs/base/browser/dom';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { append, $ } from 'vs/base/browser/dom';
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ByteSize } from 'vs/platform/files/common/files';
@@ -105,11 +104,11 @@ class ProcessHeaderTreeRenderer implements ITreeRenderer<ProcessInformation, voi
templateId: string = 'header';
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
const data = Object.create(null);
const row = dom.append(container, dom.$('.row'));
data.name = dom.append(row, dom.$('.nameLabel'));
data.CPU = dom.append(row, dom.$('.cpu'));
data.memory = dom.append(row, dom.$('.memory'));
data.PID = dom.append(row, dom.$('.pid'));
const row = append(container, $('.row'));
data.name = append(row, $('.nameLabel'));
data.CPU = append(row, $('.cpu'));
data.memory = append(row, $('.memory'));
data.PID = append(row, $('.pid'));
return data;
}
renderElement(node: ITreeNode<ProcessInformation, void>, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void {
@@ -128,8 +127,8 @@ class MachineRenderer implements ITreeRenderer<MachineProcessInformation, void,
templateId: string = 'machine';
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
const data = Object.create(null);
const row = dom.append(container, dom.$('.row'));
data.name = dom.append(row, dom.$('.nameLabel'));
const row = append(container, $('.row'));
data.name = append(row, $('.nameLabel'));
return data;
}
renderElement(node: ITreeNode<MachineProcessInformation, void>, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void {
@@ -144,8 +143,8 @@ class ErrorRenderer implements ITreeRenderer<IRemoteDiagnosticError, void, IProc
templateId: string = 'error';
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
const data = Object.create(null);
const row = dom.append(container, dom.$('.row'));
data.name = dom.append(row, dom.$('.nameLabel'));
const row = append(container, $('.row'));
data.name = append(row, $('.nameLabel'));
return data;
}
renderElement(node: ITreeNode<IRemoteDiagnosticError, void>, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void {
@@ -163,12 +162,12 @@ class ProcessRenderer implements ITreeRenderer<ProcessItem, void, IProcessItemTe
templateId: string = 'process';
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
const data = <IProcessItemTemplateData>Object.create(null);
const row = dom.append(container, dom.$('.row'));
const row = append(container, $('.row'));
data.name = dom.append(row, dom.$('.nameLabel'));
data.CPU = dom.append(row, dom.$('.cpu'));
data.memory = dom.append(row, dom.$('.memory'));
data.PID = dom.append(row, dom.$('.pid'));
data.name = append(row, $('.nameLabel'));
data.CPU = append(row, $('.cpu'));
data.memory = append(row, $('.memory'));
data.PID = append(row, $('.pid'));
return data;
}
@@ -226,8 +225,6 @@ class ProcessExplorer {
private mapPidToWindowTitle = new Map<number, string>();
private listeners = new DisposableStore();
private nativeHostService: INativeHostService;
private tree: DataTree<any, ProcessTree | MachineProcessInformation | ProcessItem | ProcessInformation | IRemoteDiagnosticError, any> | undefined;
@@ -237,6 +234,7 @@ class ProcessExplorer {
this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService;
this.applyStyles(data.styles);
this.setEventHandlers(data);
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => {
@@ -263,6 +261,32 @@ class ProcessExplorer {
this.lastRequestTime = Date.now();
ipcRenderer.send('vscode:windowsInfoRequest');
ipcRenderer.send('vscode:listProcesses');
}
private setEventHandlers(data: ProcessExplorerData): void {
document.onkeydown = (e: KeyboardEvent) => {
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
// Cmd/Ctrl + w closes issue window
if (cmdOrCtrlKey && e.keyCode === 87) {
e.stopPropagation();
e.preventDefault();
ipcRenderer.send('vscode:closeProcessExplorer');
}
// Cmd/Ctrl + zooms in
if (cmdOrCtrlKey && e.keyCode === 187) {
zoomIn();
}
// Cmd/Ctrl - zooms out
if (cmdOrCtrlKey && e.keyCode === 189) {
zoomOut();
}
};
}
private async createProcessTree(processRoots: MachineProcessInformation[]): Promise<void> {
@@ -449,39 +473,12 @@ class ProcessExplorer {
}
}, 200);
}
public dispose() {
this.listeners.dispose();
}
}
export function startup(windowId: number, data: ProcessExplorerData): void {
const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac';
export function startup(configuration: ProcessExplorerWindowConfiguration): void {
const platformClass = configuration.data.platform === 'win32' ? 'windows' : configuration.data.platform === 'linux' ? 'linux' : 'mac';
document.body.classList.add(platformClass); // used by our fonts
applyZoom(data.zoomLevel);
applyZoom(configuration.data.zoomLevel);
const processExplorer = new ProcessExplorer(windowId, data);
document.onkeydown = (e: KeyboardEvent) => {
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
// Cmd/Ctrl + w closes issue window
if (cmdOrCtrlKey && e.keyCode === 87) {
e.stopPropagation();
e.preventDefault();
processExplorer.dispose();
ipcRenderer.send('vscode:closeProcessExplorer');
}
// Cmd/Ctrl + zooms in
if (cmdOrCtrlKey && e.keyCode === 187) {
zoomIn();
}
// Cmd/Ctrl - zooms out
if (cmdOrCtrlKey && e.keyCode === 189) {
zoomOut();
}
};
new ProcessExplorer(configuration.windowId, configuration.data);
}

View File

@@ -3,7 +3,7 @@
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview: https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview:; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types default TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget domLineBreaksComputer editorViewLayer diffReview extensionHostWorker insane notebookOutputRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost webWorkerExtensionHost;">
</head>
<body aria-label="">

View File

@@ -6,9 +6,9 @@
/// <reference path="../../../../typings/require.d.ts" />
//@ts-check
'use strict';
(function () {
'use strict';
const bootstrapWindow = bootstrapWindowLib();
// Add a perf entry right from the top
@@ -32,15 +32,38 @@
return require('vs/workbench/electron-sandbox/desktop.main').main(configuration);
},
{
removeDeveloperKeybindingsAfterLoad: true,
configureDeveloperSettings: function (windowConfig) {
return {
// disable automated devtools opening on error when running extension tests
// as this can lead to undeterministic test exectuion (devtools steals focus)
forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string',
// enable devtools keybindings in extension development window
forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0,
removeDeveloperKeybindingsAfterLoad: true
};
},
canModifyDOM: function (windowConfig) {
// TODO@sandbox part-splash is non-sandboxed only
},
beforeLoaderConfig: function (windowConfig, loaderConfig) {
beforeLoaderConfig: function (loaderConfig) {
loaderConfig.recordStats = true;
},
beforeRequire: function () {
performance.mark('code/willLoadWorkbenchMain');
// It looks like browsers only lazily enable
// the <canvas> element when needed. Since we
// leverage canvas elements in our code in many
// locations, we try to help the browser to
// initialize canvas when it is idle, right
// before we wait for the scripts to be loaded.
// @ts-ignore
window.requestIdleCallback(() => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
canvas.remove();
}, { timeout: 50 });
}
}
);
@@ -62,12 +85,26 @@
}
});
//region Helpers
//#region Helpers
/**
* @typedef {import('../../../platform/windows/common/windows').INativeWindowConfiguration} INativeWindowConfiguration
*
* @returns {{
* load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown,
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals')
* load: (
* modules: string[],
* resultCallback: (result, configuration: INativeWindowConfiguration) => unknown,
* options?: {
* configureDeveloperSettings?: (config: INativeWindowConfiguration & object) => {
* forceEnableDeveloperKeybindings?: boolean,
* disallowReloadKeybinding?: boolean,
* removeDeveloperKeybindingsAfterLoad?: boolean
* },
* canModifyDOM?: (config: INativeWindowConfiguration & object) => void,
* beforeLoaderConfig?: (loaderConfig: object) => void,
* beforeRequire?: () => void
* }
* ) => Promise<unknown>
* }}
*/
function bootstrapWindowLib() {
@@ -76,5 +113,4 @@
}
//#endregion
}());

View File

@@ -4,17 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { homedir } from 'os';
import { constants, existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
import { existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
import product from 'vs/platform/product/common/product';
import { isAbsolute, join } from 'vs/base/common/path';
import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
import { findFreePort, randomPort } from 'vs/base/node/ports';
import { isWindows, isLinux } from 'vs/base/common/platform';
import { isWindows, isLinux, IProcessEnvironment } from 'vs/base/common/platform';
import type { ProfilingSession, Target } from 'v8-inspect-profiler';
import { isString } from 'vs/base/common/types';
import { hasStdinWithoutTty, stdinDataListener, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin';
@@ -23,7 +23,6 @@ function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean {
return !!argv['install-source']
|| !!argv['list-extensions']
|| !!argv['install-extension']
|| !!argv['install-builtin-extension']
|| !!argv['uninstall-extension']
|| !!argv['locate-extension']
|| !!argv['telemetry'];
@@ -84,8 +83,8 @@ export async function main(argv: string[]): Promise<any> {
let restoreMode = false;
if (!!args['file-chmod']) {
targetMode = statSync(target).mode;
if (!(targetMode & constants.S_IWUSR)) {
chmodSync(target, targetMode | constants.S_IWUSR);
if (!(targetMode & 0o200 /* File mode indicating writable by owner */)) {
chmodSync(target, targetMode | 0o200);
restoreMode = true;
}
}
@@ -117,9 +116,8 @@ export async function main(argv: string[]): Promise<any> {
// Just Code
else {
const env: NodeJS.ProcessEnv = {
const env: IProcessEnvironment = {
...process.env,
'VSCODE_CLI': '1', // this will signal Code that it was spawned from this module
'ELECTRON_NO_ATTACH_CONSOLE': '1'
};

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { release } from 'os';
import { release, hostname } from 'os';
import * as fs from 'fs';
import { gracefulify } from 'graceful-fs';
import { isAbsolute, join } from 'vs/base/common/path';
@@ -13,7 +13,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -30,9 +30,9 @@ import { ConfigurationService } from 'vs/platform/configuration/common/configura
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { IStateService } from 'vs/platform/state/node/state';
import { StateService } from 'vs/platform/state/node/stateService';
import { ILogService, getLogLevel, LogLevel, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log';
import { ILogService, getLogLevel, LogLevel, ConsoleLogger, MultiplexLogService, ILogger } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog';
import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
@@ -46,6 +46,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { VSBuffer } from 'vs/base/common/buffer';
import { cwd } from 'vs/base/common/process';
class CliMain extends Disposable {
@@ -94,9 +95,12 @@ class CliMain extends Disposable {
private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
const services = new ServiceCollection();
// Product
const productService = { _serviceBrand: undefined, ...product };
services.set(IProductService, productService);
// Environment
const environmentService = new NativeEnvironmentService(this.argv);
services.set(IEnvironmentService, environmentService);
const environmentService = new NativeEnvironmentService(this.argv, productService);
services.set(INativeEnvironmentService, environmentService);
// Init folders
@@ -104,10 +108,10 @@ class CliMain extends Disposable {
// Log
const logLevel = getLogLevel(environmentService);
const loggers: ILogService[] = [];
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
const loggers: ILogger[] = [];
loggers.push(new SpdLogLogger('cli', join(environmentService.logsPath, 'cli.log'), true, logLevel));
if (logLevel === LogLevel.Trace) {
loggers.push(new ConsoleLogService(logLevel));
loggers.push(new ConsoleLogger(logLevel));
}
const logService = this._register(new MultiplexLogService(loggers));
@@ -131,11 +135,6 @@ class CliMain extends Disposable {
const stateService = new StateService(environmentService, logService);
services.set(IStateService, stateService);
// Product
services.set(IProductService, { _serviceBrand: undefined, ...product });
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
@@ -149,15 +148,17 @@ class CliMain extends Disposable {
// Telemetry
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender('adsworkbench', null, product.aiConfig.asimovKey)); // {{SQL CARBON EDIT}} Use our own event prefix
if (environmentService.isBuilt && !environmentService.isExtensionDevelopment && !environmentService.disableTelemetry && productService.enableTelemetry) {
if (productService.aiConfig && productService.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender('adsworkbench', null, productService.aiConfig.asimovKey)); // {{SQL CARBON EDIT}} Use our own event prefix
}
const { appRoot, extensionsPath, installSourcePath } = environmentService;
const config: ITelemetryServiceConfig = {
appender: combinedAppender(...appenders),
sendErrorTelemetry: false,
commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath),
commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, stateService.getItem('telemetry.machineId'), productService.msftInternalDomains, installSourcePath),
piiPaths: [appRoot, extensionsPath]
};
@@ -217,7 +218,7 @@ class CliMain extends Disposable {
}
private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input);
}
private async setInstallSource(environmentService: INativeEnvironmentService, fileService: IFileService, installSource: string): Promise<void> {

View File

@@ -1,138 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { spawn } from 'child_process';
import { generateUuid } from 'vs/base/common/uuid';
import { isWindows, platform } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { getSystemShell } from 'vs/base/node/shell';
/**
* We need to get the environment from a user's shell.
* This should only be done when Code itself is not launched
* from within a shell.
*/
export async function resolveShellEnv(logService: ILogService, args: NativeParsedArgs, env: NodeJS.ProcessEnv): Promise<typeof process.env> {
// Skip if --force-disable-user-env
if (args['force-disable-user-env']) {
logService.trace('resolveShellEnv(): skipped (--force-disable-user-env)');
return {};
}
// Skip on windows
else if (isWindows) {
logService.trace('resolveShellEnv(): skipped (Windows)');
return {};
}
// Skip if running from CLI already
else if (isLaunchedFromCli(env) && !args['force-user-env']) {
logService.trace('resolveShellEnv(): skipped (VSCODE_CLI is set)');
return {};
}
// Otherwise resolve (macOS, Linux)
else {
if (isLaunchedFromCli(env)) {
logService.trace('resolveShellEnv(): running (--force-user-env)');
} else {
logService.trace('resolveShellEnv(): running (macOS/Linux)');
}
if (!unixShellEnvPromise) {
unixShellEnvPromise = doResolveUnixShellEnv(logService);
}
return unixShellEnvPromise;
}
}
let unixShellEnvPromise: Promise<typeof process.env> | undefined = undefined;
async function doResolveUnixShellEnv(logService: ILogService): Promise<typeof process.env> {
const promise = new Promise<typeof process.env>(async (resolve, reject) => {
const runAsNode = process.env['ELECTRON_RUN_AS_NODE'];
logService.trace('getUnixShellEnvironment#runAsNode', runAsNode);
const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE'];
logService.trace('getUnixShellEnvironment#noAttach', noAttach);
const mark = generateUuid().replace(/-/g, '').substr(0, 12);
const regex = new RegExp(mark + '(.*)' + mark);
const env = {
...process.env,
ELECTRON_RUN_AS_NODE: '1',
ELECTRON_NO_ATTACH_CONSOLE: '1'
};
const command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`;
logService.trace('getUnixShellEnvironment#env', env);
logService.trace('getUnixShellEnvironment#spawn', command);
const systemShellUnix = await getSystemShell(platform);
const child = spawn(systemShellUnix, ['-ilc', command], {
detached: true,
stdio: ['ignore', 'pipe', process.stderr],
env
});
const buffers: Buffer[] = [];
child.on('error', () => resolve({}));
child.stdout.on('data', b => buffers.push(b));
child.on('close', code => {
if (code !== 0) {
return reject(new Error('Failed to get environment'));
}
const raw = Buffer.concat(buffers).toString('utf8');
logService.trace('getUnixShellEnvironment#raw', raw);
const match = regex.exec(raw);
const rawStripped = match ? match[1] : '{}';
try {
const env = JSON.parse(rawStripped);
if (runAsNode) {
env['ELECTRON_RUN_AS_NODE'] = runAsNode;
} else {
delete env['ELECTRON_RUN_AS_NODE'];
}
if (noAttach) {
env['ELECTRON_NO_ATTACH_CONSOLE'] = noAttach;
} else {
delete env['ELECTRON_NO_ATTACH_CONSOLE'];
}
// https://github.com/microsoft/vscode/issues/22593#issuecomment-336050758
delete env['XDG_RUNTIME_DIR'];
logService.trace('getUnixShellEnvironment#result', env);
resolve(env);
} catch (err) {
logService.error('getUnixShellEnvironment#error', err);
reject(err);
}
});
});
try {
return await promise;
} catch (error) {
logService.error('getUnixShellEnvironment#error', toErrorMessage(error));
return {}; // ignore any errors
}
}