Merge vscode source through 1.62 release (#19981)

* Build breaks 1

* Build breaks

* Build breaks

* Build breaks

* More build breaks

* Build breaks (#2512)

* Runtime breaks

* Build breaks

* Fix dialog location break

* Update typescript

* Fix ASAR break issue

* Unit test breaks

* Update distro

* Fix breaks in ADO builds (#2513)

* Bump to node 16

* Fix hygiene errors

* Bump distro

* Remove reference to node type

* Delete vscode specific extension

* Bump to node 16 in CI yaml

* Skip integration tests in CI builds (while fixing)

* yarn.lock update

* Bump moment dependency in remote yarn

* Fix drop-down chevron style

* Bump to node 16

* Remove playwrite from ci.yaml

* Skip building build scripts in hygine check
This commit is contained in:
Karl Burtram
2022-07-11 14:09:32 -07:00
committed by GitHub
parent fa0fcef303
commit 26455e9113
1876 changed files with 72050 additions and 37997 deletions

View File

@@ -9,11 +9,11 @@ import { IWorkbenchExtensionEnablementService, IWebExtensionsScannerService } fr
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionService, IExtensionHost, toExtensionDescription, ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, IExtensionHost, toExtensionDescription, ExtensionRunningLocation, extensionRunningLocationToString } from 'vs/workbench/services/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IProductService } from 'vs/platform/product/common/productService';
import { AbstractExtensionService, ExtensionRunningLocationClassifier, ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { AbstractExtensionService, ExtensionRunningPreference, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { RemoteExtensionHost, IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
@@ -29,6 +29,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
import { IAutomatedWindow } from 'vs/platform/log/browser/log';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@@ -52,12 +53,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService,
@ILogService private readonly _logService: ILogService,
) {
super(
new ExtensionRunningLocationClassifier(
(extension) => this._getExtensionKind(extension),
(extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference)
),
instantiationService,
notificationService,
environmentService,
@@ -130,6 +128,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten
};
}
protected _pickRunningLocation(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation {
const result = ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference);
this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionRunningLocationToString(result)}`);
return result;
}
public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation {
const result: ExtensionRunningLocation[] = [];
let canRunRemotely = false;

View File

@@ -27,6 +27,10 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings';
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
@@ -73,6 +77,14 @@ export interface IExtensionUrlHandler {
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void;
}
export interface ExtensionUrlHandlerEvent {
readonly extensionId: string;
}
export interface ExtensionUrlHandlerClassification extends GDPRClassification<ExtensionUrlHandlerEvent> {
readonly extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight'; };
}
/**
* This class handles URLs which are directed towards extensions.
* If a URL is directed towards an inactive extension, it buffers it,
@@ -103,6 +115,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProgressService private readonly progressService: IProgressService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IExtensionUrlTrustService private readonly extensionUrlTrustService: IExtensionUrlTrustService
) {
this.userTrustedExtensionsStorage = new UserTrustedExtensionIdStorage(storageService);
@@ -129,6 +142,8 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
}
const extensionId = uri.authority;
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/start', { extensionId });
const wasHandlerAvailable = this.extensionHandlers.has(ExtensionIdentifier.toKey(extensionId));
const extension = await this.extensionService.getExtension(extensionId);
@@ -167,6 +182,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
});
if (!result.confirmed) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/cancel', { extensionId });
return true;
}
@@ -180,7 +196,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return await handler.handleURL(uri, options);
return await this.handleURLByExtension(extensionId, handler, uri, options);
}
// let the ExtensionUrlHandler instance handle this
@@ -209,7 +225,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
const uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId)) || [];
for (const { uri } of uris) {
handler.handleURL(uri);
this.handleURLByExtension(extensionId, handler, uri);
}
this.uriBuffer.delete(ExtensionIdentifier.toKey(extensionId));
@@ -219,6 +235,11 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
this.extensionHandlers.delete(ExtensionIdentifier.toKey(extensionId));
}
private async handleURLByExtension(extensionId: ExtensionIdentifier | string, handler: IURLHandler, uri: URI, options?: IOpenURLOptions): Promise<boolean> {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/end', { extensionId: ExtensionIdentifier.toKey(extensionId) });
return await handler.handleURL(uri, options);
}
private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const extension = installedExtensions.filter(e => areSameExtensions(e.identifier, extensionIdentifier))[0];
@@ -229,6 +250,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
// Extension is not running. Reload the window to handle.
if (enabled) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/activate_extension/start', { extensionId: extensionIdentifier.id });
const result = await this.dialogService.confirm({
message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
@@ -237,14 +259,17 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
});
if (!result.confirmed) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/activate_extension/cancel', { extensionId: extensionIdentifier.id });
return;
}
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/activate_extension/accept', { extensionId: extensionIdentifier.id });
await this.reloadAndHandle(uri);
}
// Extension is disabled. Enable the extension and reload the window to handle.
else if (this.extensionEnablementService.canChangeEnablement(extension)) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/enable_extension/start', { extensionId: extensionIdentifier.id });
const result = await this.dialogService.confirm({
message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
@@ -253,9 +278,11 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
});
if (!result.confirmed) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/enable_extension/cancel', { extensionId: extensionIdentifier.id });
return;
}
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/enable_extension/accept', { extensionId: extensionIdentifier.id });
await this.extensionEnablementService.setEnablement([extension], EnablementState.EnabledGlobally);
await this.reloadAndHandle(uri);
}
@@ -266,7 +293,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
let galleryExtension: IGalleryExtension | undefined;
try {
galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier) ?? undefined;
galleryExtension = (await this.galleryService.getExtensions([extensionIdentifier], CancellationToken.None))[0] ?? undefined;
} catch (err) {
return;
}
@@ -275,6 +302,8 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
return;
}
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/install_extension/start', { extensionId: extensionIdentifier.id });
// Install the Extension and reload the window to handle.
const result = await this.dialogService.confirm({
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
@@ -284,9 +313,12 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
});
if (!result.confirmed) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/install_extension/cancel', { extensionId: extensionIdentifier.id });
return;
}
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/install_extension/accept', { extensionId: extensionIdentifier.id });
try {
await this.progressService.withProgress({
location: ProgressLocation.Notification,
@@ -296,7 +328,12 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
this.notificationService.prompt(
Severity.Info,
localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString()),
[{ label: localize('Reload', "Reload Window and Open"), run: () => this.reloadAndHandle(uri) }],
[{
label: localize('Reload', "Reload Window and Open"), run: async () => {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/install_extension/reload', { extensionId: extensionIdentifier.id });
await this.reloadAndHandle(uri);
}
}],
{ sticky: true }
);
} catch (error) {

View File

@@ -93,7 +93,11 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
const forceHTTPS = (location.protocol === 'https:');
if (this._environmentService.options && this._environmentService.options.__uniqueWebWorkerExtensionHostOrigin) {
let uniqueWebWorkerExtensionHostOrigin = true;
if (this._environmentService.options && typeof this._environmentService.options.__uniqueWebWorkerExtensionHostOrigin !== 'undefined') {
uniqueWebWorkerExtensionHostOrigin = this._environmentService.options.__uniqueWebWorkerExtensionHostOrigin;
}
if (uniqueWebWorkerExtensionHostOrigin) {
const webEndpointUrlTemplate = this._productService.webEndpointUrlTemplate;
const commit = this._productService.commit;
const quality = this._productService.quality;
@@ -384,7 +388,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
environment: {
isExtensionDevelopmentDebug: this._environmentService.debugRenderer,
appName: this._productService.nameLong,
embedderIdentifier: this._productService.embedderIdentifier || 'web',
appHost: this._productService.embedderIdentifier ?? (platform.isWeb ? 'web' : 'desktop'),
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
@@ -395,7 +399,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
configuration: workspace.configuration || undefined,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
name: this._labelService.getWorkspaceLabel(workspace),
transient: workspace.transient
},
resolvedExtensions: [],
hostExtensions: [],

View File

@@ -15,7 +15,7 @@ import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService } fr
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, toExtensionDescription, ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensions';
import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, toExtensionDescription, ExtensionRunningLocation, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
@@ -51,6 +51,17 @@ export const enum ExtensionRunningPreference {
Remote
}
export function extensionRunningPreferenceToString(preference: ExtensionRunningPreference) {
switch (preference) {
case ExtensionRunningPreference.None:
return 'None';
case ExtensionRunningPreference.Local:
return 'Local';
case ExtensionRunningPreference.Remote:
return 'Remote';
}
}
class LockCustomer {
public readonly promise: Promise<IDisposable>;
private _resolve!: (value: IDisposable) => void;
@@ -133,6 +144,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
protected readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
protected readonly _runningLocationClassifier: ExtensionRunningLocationClassifier;
protected readonly _registry: ExtensionDescriptionRegistry;
private readonly _registryLock: Lock;
@@ -156,7 +168,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
private _extensionHostExtensionRuntimeErrors: Map<string, Error[]>;
constructor(
protected readonly _runningLocationClassifier: ExtensionRunningLocationClassifier,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@INotificationService protected readonly _notificationService: INotificationService,
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
@@ -172,9 +183,16 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
) {
super();
this._runningLocationClassifier = new ExtensionRunningLocationClassifier(
(extension) => this._getExtensionKind(extension),
(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._pickRunningLocation(extensionId, extensionKinds, isInstalledLocally, isInstalledRemotely, preference)
);
// help the file service to activate providers by activating extensions by file system event
this._register(this._fileService.onWillActivateFileSystemProvider(e => {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
if (e.scheme !== Schemas.vscodeRemote) {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}
}));
this._registry = new ExtensionDescriptionRegistry([]);
@@ -234,7 +252,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}));
}
protected _getExtensionKind(extensionDescription: IExtensionDescription): ExtensionKind[] {
private _getExtensionKind(extensionDescription: IExtensionDescription): ExtensionKind[] {
if (extensionDescription.isUnderDevelopment && this._environmentService.extensionDevelopmentKind) {
return this._environmentService.extensionDevelopmentKind;
}
@@ -242,6 +260,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
return this._extensionManifestPropertiesService.getExtensionKind(extensionDescription);
}
protected abstract _pickRunningLocation(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation;
protected _getExtensionHostManager(kind: ExtensionHostKind): IExtensionHostManager | null {
for (const extensionHostManager of this._extensionHostManagers) {
if (extensionHostManager.kind === kind) {
@@ -263,6 +283,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
let lock: IDisposable | null = null;
try {
this._inHandleDeltaExtensions = true;
// wait for _initialize to finish before hanlding any delta extension events
await this._installedExtensionsReady.wait();
lock = await this._registryLock.acquire('handleDeltaExtensions');
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift()!;
@@ -364,7 +388,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
for (const extension of toAdd) {
const extensionKind = this._getExtensionKind(extension);
const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;
const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None);
const runningLocation = this._pickRunningLocation(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None);
this._runningLocation.set(ExtensionIdentifier.toKey(extension.identifier), runningLocation);
}
groupAdd(ExtensionHostKind.LocalProcess, ExtensionRunningLocation.LocalProcess);
@@ -401,7 +425,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
const extensionKind = this._getExtensionKind(extension);
const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;
const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None);
const runningLocation = this._pickRunningLocation(extension.identifier, extensionKind, !isRemote, isRemote, ExtensionRunningPreference.None);
if (runningLocation === ExtensionRunningLocation.None) {
return false;
}
@@ -490,7 +514,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
protected async _initialize(): Promise<void> {
perf.mark('code/willLoadExtensions');
this._startExtensionHosts(true, []);
await this._scanAndHandleExtensions();
const lock = await this._registryLock.acquire('_initialize');
try {
await this._scanAndHandleExtensions();
} finally {
lock.dispose();
}
this._releaseBarrier();
perf.mark('code/didLoadExtensions');
await this._handleExtensionTests();
@@ -605,7 +636,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
protected _onExtensionHostCrashed(extensionHost: IExtensionHostManager, code: number, signal: string | null): void {
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
console.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) terminated unexpectedly. Code: ${code}, Signal: ${signal}`);
if (extensionHost.kind === ExtensionHostKind.LocalProcess) {
this.stopExtensionHosts();
} else if (extensionHost.kind === ExtensionHostKind.Remote) {
@@ -980,44 +1011,94 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
public abstract _onExtensionHostExit(code: number): void;
}
export class ExtensionRunningLocationClassifier {
class ExtensionWithKind {
constructor(
public readonly getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[],
public readonly pickRunningLocation: (extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference) => ExtensionRunningLocation,
public readonly desc: IExtensionDescription,
public readonly kind: ExtensionKind[]
) { }
public get key(): string {
return ExtensionIdentifier.toKey(this.desc.identifier);
}
public get isUnderDevelopment(): boolean {
return this.desc.isUnderDevelopment;
}
}
class ExtensionInfo {
constructor(
public readonly local: ExtensionWithKind | null,
public readonly remote: ExtensionWithKind | null,
) { }
public get key(): string {
if (this.local) {
return this.local.key;
}
return this.remote!.key;
}
public get identifier(): ExtensionIdentifier {
if (this.local) {
return this.local.desc.identifier;
}
return this.remote!.desc.identifier;
}
public get kind(): ExtensionKind[] {
// in case of disagreements between extension kinds, it is always
// better to pick the local extension because it has a much higher
// chance of being up-to-date
if (this.local) {
return this.local.kind;
}
return this.remote!.kind;
}
}
class ExtensionRunningLocationClassifier {
constructor(
private readonly getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[],
private readonly pickRunningLocation: (extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference) => ExtensionRunningLocation,
) {
}
public determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[]): Map<string, ExtensionRunningLocation> {
const allExtensionKinds = new Map<string, ExtensionKind[]>();
localExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), this.getExtensionKind(ext)));
remoteExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), this.getExtensionKind(ext)));
const localExtensionsSet = new Set<string>();
localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const localUnderDevelopmentExtensionsSet = new Set<string>();
localExtensions.forEach((ext) => {
if (ext.isUnderDevelopment) {
localUnderDevelopmentExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier));
}
private _toExtensionWithKind(extensions: IExtensionDescription[]): Map<string, ExtensionWithKind> {
const result = new Map<string, ExtensionWithKind>();
extensions.forEach((desc) => {
const ext = new ExtensionWithKind(desc, this.getExtensionKind(desc));
result.set(ext.key, ext);
});
return result;
}
const remoteExtensionsSet = new Set<string>();
remoteExtensions.forEach(ext => remoteExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
public determineRunningLocation(_localExtensions: IExtensionDescription[], _remoteExtensions: IExtensionDescription[]): Map<string, ExtensionRunningLocation> {
const localExtensions = this._toExtensionWithKind(_localExtensions);
const remoteExtensions = this._toExtensionWithKind(_remoteExtensions);
const remoteUnderDevelopmentExtensionsSet = new Set<string>();
remoteExtensions.forEach((ext) => {
if (ext.isUnderDevelopment) {
remoteUnderDevelopmentExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier));
const allExtensions = new Map<string, ExtensionInfo>();
const collectExtension = (ext: ExtensionWithKind) => {
if (allExtensions.has(ext.key)) {
return;
}
});
const local = localExtensions.get(ext.key) || null;
const remote = remoteExtensions.get(ext.key) || null;
const info = new ExtensionInfo(local, remote);
allExtensions.set(info.key, info);
};
localExtensions.forEach((ext) => collectExtension(ext));
remoteExtensions.forEach((ext) => collectExtension(ext));
const pickRunningLocation = (extensionIdentifier: ExtensionIdentifier): ExtensionRunningLocation => {
const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier));
const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier));
const runningLocation = new Map<string, ExtensionRunningLocation>();
allExtensions.forEach((ext) => {
const isInstalledLocally = Boolean(ext.local);
const isInstalledRemotely = Boolean(ext.remote);
const isLocallyUnderDevelopment = localUnderDevelopmentExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier));
const isRemotelyUnderDevelopment = remoteUnderDevelopmentExtensionsSet.has(ExtensionIdentifier.toKey(extensionIdentifier));
const isLocallyUnderDevelopment = Boolean(ext.local && ext.local.isUnderDevelopment);
const isRemotelyUnderDevelopment = Boolean(ext.remote && ext.remote.isUnderDevelopment);
let preference = ExtensionRunningPreference.None;
if (isLocallyUnderDevelopment && !isRemotelyUnderDevelopment) {
@@ -1026,13 +1107,9 @@ export class ExtensionRunningLocationClassifier {
preference = ExtensionRunningPreference.Remote;
}
const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extensionIdentifier)) || [];
return this.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference);
};
runningLocation.set(ext.key, this.pickRunningLocation(ext.identifier, ext.kind, isInstalledLocally, isInstalledRemotely, preference));
});
const runningLocation = new Map<string, ExtensionRunningLocation>();
localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext.identifier)));
remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext.identifier)));
return runningLocation;
}
}

View File

@@ -70,7 +70,11 @@ export class ExtensionHostMain {
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
performance.mark(`code/extHost/didCreateServices`);
this._logService.info('extension host started');
if (this._hostUtils.pid) {
this._logService.info(`Extension host with pid ${this._hostUtils.pid} started`);
} else {
this._logService.info(`Extension host started`);
}
this._logService.trace('initData', initData);
// ugly self - inject
@@ -91,7 +95,7 @@ export class ExtensionHostMain {
stackTraceMessage += `\n\tat ${call.toString()}`;
fileName = call.getFileName();
if (!extension && fileName) {
extension = map.findSubstr(fileName);
extension = map.findSubstr(URI.file(fileName));
}
}
@@ -136,7 +140,11 @@ export class ExtensionHostMain {
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
setTimeout(() => {
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => {
this._logService.info(`exiting with code 0`);
if (this._hostUtils.pid) {
this._logService.info(`Extension host with pid ${this._hostUtils.pid} exiting with code 0`);
} else {
this._logService.info(`Extension host exiting with code 0`);
}
this._logService.flush();
this._logService.dispose();
this._hostUtils.exit(0);

View File

@@ -20,12 +20,13 @@ import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { StopWatch } from 'vs/base/common/stopwatch';
import { VSBuffer } from 'vs/base/common/buffer';
import { IExtensionHost, ExtensionHostKind, ActivationKind } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionHost, ExtensionHostKind, ActivationKind, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { Barrier, timeout } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
@@ -56,6 +57,24 @@ export function createExtensionHostManager(instantiationService: IInstantiationS
return instantiationService.createInstance(ExtensionHostManager, extensionHost, initialActivationEvents);
}
export type ExtensionHostStartupClassification = {
time: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
action: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
kind: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
errorName?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
errorMessage?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
errorStack?: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
export type ExtensionHostStartupEvent = {
time: number;
action: 'starting' | 'success' | 'error';
kind: string;
errorName?: string;
errorMessage?: string;
errorStack?: string;
};
class ExtensionHostManager extends Disposable implements IExtensionHostManager {
public readonly kind: ExtensionHostKind;
@@ -83,6 +102,8 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
initialActivationEvents: string[],
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ILogService private readonly _logService: ILogService,
) {
super();
this._cachedActivationEvents = new Map<string, Promise<void>>();
@@ -92,14 +113,50 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
this._extensionHost = extensionHost;
this.kind = this._extensionHost.kind;
this.onDidExit = this._extensionHost.onExit;
const startingTelemetryEvent: ExtensionHostStartupEvent = {
time: Date.now(),
action: 'starting',
kind: extensionHostKindToString(this.kind)
};
this._telemetryService.publicLog2<ExtensionHostStartupEvent, ExtensionHostStartupClassification>('extensionHostStartup', startingTelemetryEvent);
this._proxy = this._extensionHost.start()!.then(
(protocol) => {
this._hasStarted = true;
// Track healthy extension host startup
const successTelemetryEvent: ExtensionHostStartupEvent = {
time: Date.now(),
action: 'success',
kind: extensionHostKindToString(this.kind)
};
this._telemetryService.publicLog2<ExtensionHostStartupEvent, ExtensionHostStartupClassification>('extensionHostStartup', successTelemetryEvent);
return { value: this._createExtensionHostCustomers(protocol) };
},
(err) => {
console.error(`Error received from starting extension host (kind: ${this.kind})`);
console.error(err);
this._logService.error(`Error received from starting extension host (kind: ${extensionHostKindToString(this.kind)})`);
this._logService.error(err);
// Track errors during extension host startup
const failureTelemetryEvent: ExtensionHostStartupEvent = {
time: Date.now(),
action: 'error',
kind: extensionHostKindToString(this.kind)
};
if (err && err.name) {
failureTelemetryEvent.errorName = err.name;
}
if (err && err.message) {
failureTelemetryEvent.errorMessage = err.message;
}
if (err && err.stack) {
failureTelemetryEvent.errorStack = err.stack;
}
this._telemetryService.publicLog2<ExtensionHostStartupEvent, ExtensionHostStartupClassification>('extensionHostStartup', failureTelemetryEvent, true);
return null;
}
);
@@ -327,6 +384,11 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
}
public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI> {
const authorityPlusIndex = remoteAuthority.indexOf('+');
if (authorityPlusIndex === -1) {
// This authority does not use a resolver
return uri;
}
const proxy = await this._getProxy();
if (!proxy) {
throw new Error(`Cannot resolve canonical URI`);
@@ -437,7 +499,7 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost
const extensionHostAlreadyStarted = Boolean(this._actual);
const shouldStartExtensionHost = (toAdd.length > 0);
if (extensionHostAlreadyStarted || shouldStartExtensionHost) {
const actual = await this._getOrCreateActualAndStart(`contains ${toAdd.length} new extension(s) (installed or enabled)`);
const actual = await this._getOrCreateActualAndStart(`contains ${toAdd.length} new extension(s) (installed or enabled): ${toAdd.map(ext => ext.identifier.value)}`);
return actual.deltaExtensions(toAdd, toRemove);
}
}
@@ -449,6 +511,13 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost
return false;
}
public async activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
if (activationKind === ActivationKind.Immediate) {
// this is an immediate request, so we cannot wait for start to be called
if (this._actual) {
return this._actual.activateByEvent(activationEvent, activationKind);
}
return;
}
await this._startCalled.wait();
if (this._actual) {
return this._actual.activateByEvent(activationEvent, activationKind);
@@ -478,7 +547,7 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost
public async start(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
if (enabledExtensionIds.length > 0) {
// there are actual extensions, so let's launch the extension host
const actual = this._createActual(`contains ${enabledExtensionIds.length} extension(s).`);
const actual = this._createActual(`contains ${enabledExtensionIds.length} extension(s): ${enabledExtensionIds.map(extId => extId.value)}.`);
const result = actual.start(enabledExtensionIds);
this._startCalled.open();
return result;

View File

@@ -17,6 +17,7 @@ import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspa
import { isBoolean } from 'vs/base/common/types';
import { IWorkspaceTrustEnablementService } from 'vs/platform/workspace/common/workspaceTrust';
import { ILogService } from 'vs/platform/log/common/log';
import { isWeb } from 'vs/base/common/platform';
export const IExtensionManifestPropertiesService = createDecorator<IExtensionManifestPropertiesService>('extensionManifestPropertiesService');
@@ -109,7 +110,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
const deducedExtensionKind = this.deduceExtensionKind(manifest);
const configuredExtensionKind = this.getConfiguredExtensionKind(manifest);
if (configuredExtensionKind) {
if (configuredExtensionKind && configuredExtensionKind.length > 0) {
const result: ExtensionKind[] = [];
for (const extensionKind of configuredExtensionKind) {
if (extensionKind !== '-web') {
@@ -124,7 +125,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
}
// Add web kind if not opted out from web and can run in web
if (!configuredExtensionKind.includes('-web') && !configuredExtensionKind.includes('web') && deducedExtensionKind.includes('web')) {
if (isWeb && !configuredExtensionKind.includes('-web') && !configuredExtensionKind.includes('web') && deducedExtensionKind.includes('web')) {
result.push('web');
}
@@ -221,7 +222,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
// Not an UI extension if it has main
if (manifest.main) {
if (manifest.browser) {
return ['workspace', 'web'];
return isWeb ? ['workspace', 'web'] : ['workspace'];
}
return ['workspace'];
}
@@ -232,9 +233,9 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
let result = [...ALL_EXTENSION_KINDS];
// Extension pack defaults to workspace extensionKind
if (isNonEmptyArray(manifest.extensionPack) || isNonEmptyArray(manifest.extensionDependencies)) {
result = ['workspace'];
// Extension pack defaults to [workspace, web] in web and only [workspace] in desktop
result = isWeb ? ['workspace', 'web'] : ['workspace'];
}
if (manifest.contributes) {
@@ -270,7 +271,8 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
return extensionPointExtensionKind;
}
return ['workspace', 'web'] /* Unknown extension point => workspace, web */;
/* Unknown extension point */
return isWeb ? ['workspace', 'web'] : ['workspace'];
}
private getConfiguredExtensionKind(manifest: IExtensionManifest): (ExtensionKind | '-web')[] | null {
@@ -292,7 +294,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
result = manifest.extensionKind;
if (typeof result !== 'undefined') {
result = this.toArray(result);
return result.filter(r => ALL_EXTENSION_KINDS.includes(r));
return result.filter(r => ['ui', 'workspace'].includes(r));
}
return null;

View File

@@ -43,6 +43,19 @@ export const enum ExtensionRunningLocation {
Remote
}
export function extensionRunningLocationToString(location: ExtensionRunningLocation) {
switch (location) {
case ExtensionRunningLocation.None:
return 'None';
case ExtensionRunningLocation.LocalProcess:
return 'LocalProcess';
case ExtensionRunningLocation.LocalWebWorker:
return 'LocalWebWorker';
case ExtensionRunningLocation.Remote:
return 'Remote';
}
}
export interface IExtensionsStatus {
messages: IMessage[];
activationTimes: ActivationTimes | undefined;
@@ -100,6 +113,14 @@ export const enum ExtensionHostKind {
Remote
}
export function extensionHostKindToString(kind: ExtensionHostKind): string {
switch (kind) {
case ExtensionHostKind.LocalProcess: return 'LocalProcess';
case ExtensionHostKind.LocalWebWorker: return 'LocalWebWorker';
case ExtensionHostKind.Remote: return 'Remote';
}
}
export interface IExtensionHost {
readonly kind: ExtensionHostKind;
readonly remoteAuthority: string | null;
@@ -267,10 +288,30 @@ export interface IExtensionService {
*/
setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
/**
* Please do not use!
* (This is public such that the extension host process can coordinate with and call back in the IExtensionService)
*/
_activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
/**
* Please do not use!
* (This is public such that the extension host process can coordinate with and call back in the IExtensionService)
*/
_onWillActivateExtension(extensionId: ExtensionIdentifier): void;
/**
* Please do not use!
* (This is public such that the extension host process can coordinate with and call back in the IExtensionService)
*/
_onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
/**
* Please do not use!
* (This is public such that the extension host process can coordinate with and call back in the IExtensionService)
*/
_onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void;
/**
* Please do not use!
* (This is public such that the extension host process can coordinate with and call back in the IExtensionService)
*/
_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
}

View File

@@ -148,13 +148,11 @@ const extensionKindSchema: IJSONSchema = {
type: 'string',
enum: [
'ui',
'workspace',
'web'
'workspace'
],
enumDescriptions: [
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote."),
nls.localize('web', "Web worker extension kind. Such an extension can execute in a web worker extension host.")
],
};

View File

@@ -59,7 +59,7 @@ export function getStringIdentifierForProxy(nid: number): string {
}
/**
* Marks the object as containing buffers that should be serialized more efficently.
* Marks the object as containing buffers that should be serialized more efficiently.
*/
export class SerializableObjectWithBuffers<T> {
constructor(

View File

@@ -6,9 +6,9 @@
import { IRemoteConsoleLog, parse } from 'vs/base/common/console';
import { ILogService } from 'vs/platform/log/common/log';
export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog): void {
export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog, label: string | null = null): void {
const args = parse(entry).args;
const firstArg = args.shift();
let firstArg = args.shift();
if (typeof firstArg !== 'string') {
return;
}
@@ -17,6 +17,16 @@ export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog
entry.severity = 'info';
}
if (label) {
if (!/^\[/.test(label)) {
label = `[${label}]`;
}
if (!/ $/.test(label)) {
label = `${label} `;
}
firstArg = label + firstArg;
}
switch (entry.severity) {
case 'log':
case 'info':
@@ -30,3 +40,20 @@ export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog
break;
}
}
export function logRemoteEntryIfError(logService: ILogService, entry: IRemoteConsoleLog, label: string): void {
const args = parse(entry).args;
const firstArg = args.shift();
if (typeof firstArg !== 'string' || entry.severity !== 'error') {
return;
}
if (!/^\[/.test(label)) {
label = `[${label}]`;
}
if (!/ $/.test(label)) {
label = `${label} `;
}
logService.error(label + firstArg, ...args);
}

View File

@@ -3,34 +3,34 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as platform from 'vs/base/common/platform';
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { localize } from 'vs/nls';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { IProductService } from 'vs/platform/product/common/productService';
import { Registry } from 'vs/platform/registry/common/platform';
import { connectRemoteAgentExtensionHost, IConnectionOptions, IRemoteExtensionHostStartParams, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ISignService } from 'vs/platform/sign/common/sign';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { IExtensionHost, ExtensionHostLogFileName, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IRemoteAuthorityResolverService, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as platform from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { createMessageOfType, isMessageOfType, MessageType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostKind, ExtensionHostLogFileName, IExtensionHost } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { VSBuffer } from 'vs/base/common/buffer';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { IProductService } from 'vs/platform/product/common/productService';
import { ISignService } from 'vs/platform/sign/common/sign';
import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { localize } from 'vs/nls';
import { Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output';
export interface IRemoteExtensionHostInitData {
readonly connectionData: IRemoteConnectionData | null;
@@ -109,7 +109,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
debugId: this._environmentService.debugExtensionHost.debugId,
break: this._environmentService.debugExtensionHost.break,
port: this._environmentService.debugExtensionHost.port,
env: resolverResult.options && resolverResult.options.extensionHostEnv
env: { ...this._environmentService.debugExtensionHost.env, ...resolverResult.options?.extensionHostEnv },
};
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
@@ -148,7 +148,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
let handle = setTimeout(() => {
reject('timeout');
reject('The remote extenion host took longer than 60s to send its ready message.');
}, 60 * 1000);
let logFile: URI;
@@ -232,7 +232,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
isExtensionDevelopmentDebug,
appRoot: remoteInitData.appRoot,
appName: this._productService.nameLong,
embedderIdentifier: this._productService.embedderIdentifier || 'desktop',
appHost: this._productService.embedderIdentifier || 'desktop',
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
@@ -243,7 +243,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace)
name: this._labelService.getWorkspaceLabel(workspace),
transient: workspace.transient
},
remote: {
isRemote: true,

View File

@@ -544,6 +544,8 @@ class MessageBuffer {
return 1;
}
public static readonly sizeUInt32 = 4;
public writeUInt8(n: number): void {
this._buff.writeUInt8(n, this._offset); this._offset += 1;
}
@@ -628,7 +630,7 @@ class MessageBuffer {
size += this.sizeVSBuffer(el.value);
break;
case ArgType.SerializedObjectWithBuffers:
size += this.sizeUInt8(); // buffer count
size += this.sizeUInt32; // buffer count
size += this.sizeLongString(el.value);
for (let i = 0; i < el.buffers.length; ++i) {
size += this.sizeVSBuffer(el.buffers[i]);
@@ -657,7 +659,7 @@ class MessageBuffer {
break;
case ArgType.SerializedObjectWithBuffers:
this.writeUInt8(ArgType.SerializedObjectWithBuffers);
this.writeUInt8(el.buffers.length);
this.writeUInt32(el.buffers.length);
this.writeLongString(el.value);
for (let i = 0; i < el.buffers.length; ++i) {
this.writeBuffer(el.buffers[i]);
@@ -683,7 +685,7 @@ class MessageBuffer {
arr[i] = this.readVSBuffer();
break;
case ArgType.SerializedObjectWithBuffers:
const bufferCount = this.readUInt8();
const bufferCount = this.readUInt32();
const jsonString = this.readLongString();
const buffers: VSBuffer[] = [];
for (let i = 0; i < bufferCount; ++i) {
@@ -878,14 +880,14 @@ class MessageIO {
const resBuff = VSBuffer.fromString(res);
let len = 0;
len += MessageBuffer.sizeUInt8(); // buffer count
len += MessageBuffer.sizeUInt32; // buffer count
len += MessageBuffer.sizeLongString(resBuff);
for (const buffer of buffers) {
len += MessageBuffer.sizeVSBuffer(buffer);
}
let result = MessageBuffer.alloc(MessageType.ReplyOKJSONWithBuffers, req, len);
result.writeUInt8(buffers.length);
result.writeUInt32(buffers.length);
result.writeLongString(resBuff);
for (const buffer of buffers) {
result.writeBuffer(buffer);
@@ -900,7 +902,7 @@ class MessageIO {
}
public static deserializeReplyOKJSONWithBuffers(buff: MessageBuffer, uriTransformer: IURITransformer | null): SerializableObjectWithBuffers<any> {
const bufferCount = buff.readUInt8();
const bufferCount = buff.readUInt32();
const res = buff.readLongString();
const buffers: VSBuffer[] = [];

View File

@@ -5,8 +5,9 @@
import { LocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/localProcessExtensionHost';
import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { AbstractExtensionService, ExtensionRunningLocationClassifier, ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { AbstractExtensionService, ExtensionRunningPreference, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import * as nls from 'vs/nls';
import { runWhenIdle } from 'vs/base/common/async';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -21,7 +22,7 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig, ExtensionRunningLocation, WebWorkerExtHostConfigValue } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig, ExtensionRunningLocation, WebWorkerExtHostConfigValue, extensionRunningLocationToString, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
@@ -43,6 +44,7 @@ import { updateProxyConfigurationsScope } from 'vs/platform/request/common/reque
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { CancellationToken } from 'vs/base/common/cancellation';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@@ -75,10 +77,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
) {
super(
new ExtensionRunningLocationClassifier(
(extension) => this._getExtensionKind(extension),
(extensionKinds, isInstalledLocally, isInstalledRemotely, preference) => this._pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference)
),
instantiationService,
notificationService,
_environmentService,
@@ -183,8 +181,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
};
}
private _pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation {
return ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker);
protected _pickRunningLocation(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionRunningLocation {
const result = ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker);
this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionRunningLocationToString(result)}`);
return result;
}
public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference, hasRemoteExtHost: boolean, hasWebWorkerExtHost: boolean): ExtensionRunningLocation {
@@ -268,8 +268,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return;
}
const message = `Extension host terminated unexpectedly. The following extensions were running: ${activatedExtensions.map(id => id.value).join(', ')}`;
this._logService.error(message);
if (activatedExtensions.length > 0) {
this._logService.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) terminated unexpectedly. The following extensions were running: ${activatedExtensions.map(id => id.value).join(', ')}`);
} else {
this._logService.error(`Extension host (${extensionHostKindToString(extensionHost.kind)}) terminated unexpectedly. No extensions were activated.`);
}
this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."),
[{
@@ -526,7 +529,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
label: nls.localize('install', 'Install and Reload'),
run: async () => {
sendTelemetry('install');
const galleryExtension = await this._extensionGalleryService.getCompatibleExtension({ id: resolverExtensionId });
const [galleryExtension] = await this._extensionGalleryService.getExtensions([{ id: resolverExtensionId }], CancellationToken.None);
if (galleryExtension) {
await this._extensionManagementService.installFromGallery(galleryExtension);
await this._hostService.reload();

View File

@@ -3,11 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { ChildProcess, fork } from 'child_process';
import { Server, Socket, createServer } from 'net';
import { findFreePort } from 'vs/base/node/ports';
import { createRandomIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import * as nls from 'vs/nls';
import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
import { FileAccess } from 'vs/base/common/network';
import { timeout } from 'vs/base/common/async';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
@@ -16,11 +17,9 @@ import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IRemoteConsoleLog, log } from 'vs/base/common/console';
import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
import { findFreePort } from 'vs/base/node/ports';
import { logRemoteEntry, logRemoteEntryIfError } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
import { createRandomIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
@@ -45,9 +44,11 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { isUUID } from 'vs/base/common/uuid';
import { join } from 'vs/base/common/path';
import { Readable, Writable } from 'stream';
import { StringDecoder } from 'string_decoder';
import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService';
import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter';
import { SerializedError } from 'vs/base/common/errors';
import { StopWatch } from 'vs/base/common/stopwatch';
import { removeDangerousEnvVariables } from 'vs/base/node/processes';
export interface ILocalProcessExtensionHostInitData {
readonly autoStart: boolean;
@@ -63,6 +64,50 @@ const enum NativeLogMarkers {
End = 'END_NATIVE_LOG',
}
class ExtensionHostProcess {
private readonly _id: string;
public get onStdout(): Event<string> {
return this._extensionHostStarter.onDynamicStdout(this._id);
}
public get onStderr(): Event<string> {
return this._extensionHostStarter.onDynamicStderr(this._id);
}
public get onMessage(): Event<any> {
return this._extensionHostStarter.onDynamicMessage(this._id);
}
public get onError(): Event<{ error: SerializedError; }> {
return this._extensionHostStarter.onDynamicError(this._id);
}
public get onExit(): Event<{ code: number; signal: string }> {
return this._extensionHostStarter.onDynamicExit(this._id);
}
constructor(
id: string,
private readonly _extensionHostStarter: IExtensionHostStarter,
) {
this._id = id;
}
public start(opts: IExtensionHostProcessOptions): Promise<{ pid: number; }> {
return this._extensionHostStarter.start(this._id, opts);
}
public enableInspectPort(): Promise<boolean> {
return this._extensionHostStarter.enableInspectPort(this._id);
}
public kill(): Promise<void> {
return this._extensionHostStarter.kill(this._id);
}
}
export class LocalProcessExtensionHost implements IExtensionHost {
public readonly kind = ExtensionHostKind.LocalProcess;
@@ -88,7 +133,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// Resources, in order they get acquired/created when .start() is called:
private _namedPipeServer: Server | null;
private _inspectPort: number | null;
private _extensionHostProcess: ChildProcess | null;
private _extensionHostProcess: ExtensionHostProcess | null;
private _extensionHostConnection: Socket | null;
private _messageProtocol: Promise<PersistentProtocol> | null;
@@ -107,7 +152,8 @@ export class LocalProcessExtensionHost implements IExtensionHost {
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
@IHostService private readonly _hostService: IHostService,
@IProductService private readonly _productService: IProductService,
@IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService
@IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService,
@IExtensionHostStarter private readonly _extensionHostStarter: IExtensionHostStarter,
) {
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
@@ -151,18 +197,34 @@ export class LocalProcessExtensionHost implements IExtensionHost {
this.terminate();
}
private async _createExtensionHost(): Promise<{ id: string; }> {
const sw = new StopWatch(false);
const result = await this._extensionHostStarter.createExtensionHost();
if (sw.elapsed() > 20) {
// communicating to the shared process took more than 20ms
this._logService.info(`[LocalProcessExtensionHost]: IExtensionHostStarter.createExtensionHost() took ${sw.elapsed()} ms.`);
}
return result;
}
public start(): Promise<IMessagePassingProtocol> | null {
if (this._terminating) {
// .terminate() was called
return null;
}
const timer = new LocalProcessExtensionHostStartupTimer();
if (!this._messageProtocol) {
this._messageProtocol = Promise.all([
this._tryListenOnPipe(),
this._tryFindDebugPort(),
this._shellEnvironmentService.getShellEnv()
]).then(([pipeName, portNumber, processEnv]) => {
spyPromise(this._createExtensionHost(), () => timer.markDidCreateExtensionHost()),
spyPromise(this._tryListenOnPipe(), () => timer.markDidListenOnPipe()),
spyPromise(this._tryFindDebugPort(), () => timer.markDidFindDebugPort()),
spyPromise(this._shellEnvironmentService.getShellEnv(), () => timer.markDidGetShellEnv()),
]).then(([extensionHostCreationResult, pipeName, portNumber, processEnv]) => {
this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter);
const env = objects.mixin(processEnv, {
VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
VSCODE_PIPE_LOGGING: 'true',
@@ -174,12 +236,12 @@ export class LocalProcessExtensionHost implements IExtensionHost {
VSCODE_LOG_LEVEL: this._environmentService.verbose ? 'trace' : this._environmentService.log
});
if (platform.isMacintosh) {
// Unset `DYLD_LIBRARY_PATH`, as it leads to extension host crashes
// See https://github.com/microsoft/vscode/issues/104525
delete env['DYLD_LIBRARY_PATH'];
if (this._environmentService.debugExtensionHost.env) {
objects.mixin(env, this._environmentService.debugExtensionHost.env);
}
removeDangerousEnvVariables(env);
if (this._isExtensionDevHost) {
// Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might
// be that dependencies, that otherwise would be cached, get modified.
@@ -238,13 +300,10 @@ export class LocalProcessExtensionHost implements IExtensionHost {
opts.env.VSCODE_CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
}
// Run Extension Host as fork of current process
this._extensionHostProcess = fork(FileAccess.asFileUri('bootstrap-fork', require).fsPath, ['--type=extensionHost', '--skipWorkspaceStorageLock'], opts);
// Catch all output coming from the extension host process
type Output = { data: string, format: string[] };
const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.stdout!);
const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.stderr!);
const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.onStdout);
const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.onStderr);
const onOutput = Event.any(
Event.map(onStdout.event, o => ({ data: `%c${o}`, format: [''] })),
Event.map(onStderr.event, o => ({ data: `%c${o}`, format: ['color: red'] }))
@@ -278,15 +337,16 @@ export class LocalProcessExtensionHost implements IExtensionHost {
});
// Support logging from extension host
this._extensionHostProcess.on('message', msg => {
this._extensionHostProcess.onMessage(msg => {
if (msg && (<IRemoteConsoleLog>msg).type === '__$console') {
this._logExtensionHostMessage(<IRemoteConsoleLog>msg);
}
});
// Lifecycle
this._extensionHostProcess.on('error', (err) => this._onExtHostProcessError(err));
this._extensionHostProcess.on('exit', (code: number, signal: string) => this._onExtHostProcessExit(code, signal));
this._extensionHostProcess.onError((e) => this._onExtHostProcessError(e.error));
this._extensionHostProcess.onExit(({ code, signal }) => this._onExtHostProcessExit(code, signal));
// Notify debugger that we are ready to attach to the process if we run a development extension
if (portNumber) {
@@ -316,7 +376,12 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
// Initialize extension host process with hand shakes
return this._tryExtHostHandshake().then((protocol) => {
return this._tryExtHostHandshake(opts, timer).then((protocol) => {
timer.markDidFinishHandhsake();
const localProcessExtensionHostStartupTimesEvent = timer.toEvent();
this._telemetryService.publicLog2<LocalProcessExtensionHostStartupTimesEvent, LocalProcessExtensionHostStartupTimesClassification>('localProcessExtensionHostStartupTimes', localProcessExtensionHostStartupTimesEvent);
clearTimeout(startupTimeoutHandle);
return protocol;
});
@@ -354,7 +419,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
const expected = this._environmentService.debugExtensionHost.port;
const port = await findFreePort(expected, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */);
const port = await findFreePort(expected, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */, 2048 /* skip 2048 ports between attempts */);
if (!this._isExtensionDevTestFromCli) {
if (!port) {
@@ -374,7 +439,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
return port || 0;
}
private _tryExtHostHandshake(): Promise<PersistentProtocol> {
private _tryExtHostHandshake(opts: IExtensionHostProcessOptions, timer: LocalProcessExtensionHostStartupTimer): Promise<PersistentProtocol> {
return new Promise<PersistentProtocol>((resolve, reject) => {
@@ -385,10 +450,12 @@ export class LocalProcessExtensionHost implements IExtensionHost {
this._namedPipeServer.close();
this._namedPipeServer = null;
}
reject('timeout');
reject('The local extension host took longer than 60s to connect.');
}, 60 * 1000);
this._namedPipeServer!.on('connection', socket => {
timer.markDidReceiveConnection();
clearTimeout(handle);
if (this._namedPipeServer) {
this._namedPipeServer.close();
@@ -402,6 +469,18 @@ export class LocalProcessExtensionHost implements IExtensionHost {
resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection)));
});
// Now that the named pipe listener is installed, start the ext host process
const sw = new StopWatch(false);
this._extensionHostProcess!.start(opts).then(() => {
sw.stop();
timer.markDidStartExtensionHost();
this._logService.info(`[LocalProcessExtensionHost]: IExtensionHostStarter.start() took ${sw.elapsed()} ms.`);
}, (err) => {
// Starting the ext host process resulted in an error
reject(err);
});
}).then((protocol) => {
// 1) wait for the incoming `ready` event and send the initialization data.
@@ -411,7 +490,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
let timeoutHandle: NodeJS.Timer;
const installTimeoutCheck = () => {
timeoutHandle = setTimeout(() => {
reject('timeout');
reject('The local extenion host took longer than 60s to send its ready message.');
}, 60 * 1000);
};
const uninstallTimeoutCheck = () => {
@@ -424,6 +503,8 @@ export class LocalProcessExtensionHost implements IExtensionHost {
const disposable = protocol.onMessage(msg => {
if (isMessageOfType(msg, MessageType.Ready)) {
timer.markDidReceiveReady();
// 1) Extension Host is ready to receive messages, initialize it
uninstallTimeoutCheck();
@@ -438,6 +519,8 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
if (isMessageOfType(msg, MessageType.Initialized)) {
timer.markDidReceiveInitialized();
// 2) Extension Host is initialized
uninstallTimeoutCheck();
@@ -472,7 +555,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
appName: this._productService.nameLong,
embedderIdentifier: this._productService.embedderIdentifier || 'desktop',
appHost: this._productService.embedderIdentifier || 'desktop',
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
@@ -484,7 +567,8 @@ export class LocalProcessExtensionHost implements IExtensionHost {
configuration: withNullAsUndefined(workspace.configuration),
id: workspace.id,
name: this._labelService.getWorkspaceLabel(workspace),
isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false
isUntitled: workspace.configuration ? isUntitledWorkspace(workspace.configuration, this._environmentService) : false,
transient: workspace.transient
},
remote: {
authority: this._environmentService.remoteAuthority,
@@ -504,19 +588,25 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
private _logExtensionHostMessage(entry: IRemoteConsoleLog) {
if (this._isExtensionDevTestFromCli) {
// Log on main side if running tests from cli
// If running tests from cli, log to the log service everything
logRemoteEntry(this._logService, entry);
} else {
// Send to local console
// Log to the log service only errors and log everything to local console
logRemoteEntryIfError(this._logService, entry, 'Extension Host');
log(entry, 'Extension Host');
}
}
private _onExtHostProcessError(err: any): void {
private _onExtHostProcessError(_err: SerializedError): void {
let err: any = _err;
if (_err && _err.$isError) {
err = new Error();
err.name = _err.name;
err.message = _err.message;
err.stack = _err.stack;
}
let errorMessage = toErrorMessage(err);
if (errorMessage === this._lastExtensionHostError) {
return; // prevent error spam
@@ -536,40 +626,35 @@ export class LocalProcessExtensionHost implements IExtensionHost {
this._onExit.fire([code, signal]);
}
private _handleProcessOutputStream(stream: Readable) {
private _handleProcessOutputStream(stream: Event<string>) {
let last = '';
let isOmitting = false;
const event = new Emitter<string>();
const decoder = new StringDecoder('utf-8');
stream.pipe(new Writable({
write(chunk, _encoding, callback) {
// not a fancy approach, but this is the same approach used by the split2
// module which is well-optimized (https://github.com/mcollina/split2)
last += typeof chunk === 'string' ? chunk : decoder.write(chunk);
let lines = last.split(/\r?\n/g);
last = lines.pop()!;
stream((chunk) => {
// not a fancy approach, but this is the same approach used by the split2
// module which is well-optimized (https://github.com/mcollina/split2)
last += chunk;
let lines = last.split(/\r?\n/g);
last = lines.pop()!;
// protected against an extension spamming and leaking memory if no new line is written.
if (last.length > 10_000) {
lines.push(last);
last = '';
}
for (const line of lines) {
if (isOmitting) {
if (line === NativeLogMarkers.End) {
isOmitting = false;
}
} else if (line === NativeLogMarkers.Start) {
isOmitting = true;
} else if (line.length) {
event.fire(line + '\n');
}
}
callback();
// protected against an extension spamming and leaking memory if no new line is written.
if (last.length > 10_000) {
lines.push(last);
last = '';
}
}));
for (const line of lines) {
if (isOmitting) {
if (line === NativeLogMarkers.End) {
isOmitting = false;
}
} else if (line === NativeLogMarkers.Start) {
isOmitting = true;
} else if (line.length) {
event.fire(line + '\n');
}
}
});
return event;
}
@@ -583,26 +668,13 @@ export class LocalProcessExtensionHost implements IExtensionHost {
return false;
}
interface ProcessExt {
_debugProcess?(n: number): any;
}
if (typeof (<ProcessExt>process)._debugProcess === 'function') {
// use (undocumented) _debugProcess feature of node
(<ProcessExt>process)._debugProcess!(this._extensionHostProcess.pid);
await Promise.race([Event.toPromise(this._onDidSetInspectPort.event), timeout(1000)]);
return typeof this._inspectPort === 'number';
} else if (!platform.isWindows) {
// use KILL USR1 on non-windows platforms (fallback)
this._extensionHostProcess.kill('SIGUSR1');
await Promise.race([Event.toPromise(this._onDidSetInspectPort.event), timeout(1000)]);
return typeof this._inspectPort === 'number';
} else {
// not supported...
const result = await this._extensionHostProcess.enableInspectPort();
if (!result) {
return false;
}
await Promise.race([Event.toPromise(this._onDidSetInspectPort.event), timeout(1000)]);
return typeof this._inspectPort === 'number';
}
public getInspectPort(): number | undefined {
@@ -669,3 +741,100 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
}
}
async function spyPromise<T>(p: Promise<T>, whenDone: () => void): Promise<T> {
const result = await p;
whenDone();
return result;
}
type LocalProcessExtensionHostStartupTimesClassification = {
didCreateExtensionHost: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didListenOnPipe: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didFindDebugPort: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didGetShellEnv: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didStartExtensionHost: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didReceiveConnection: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didReceiveReady: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didReceiveInitialized: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
didFinishHandhsake: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
};
type LocalProcessExtensionHostStartupTimesEvent = {
didCreateExtensionHost: number;
didListenOnPipe: number;
didFindDebugPort: number;
didGetShellEnv: number;
didStartExtensionHost: number;
didReceiveConnection: number;
didReceiveReady: number;
didReceiveInitialized: number;
didFinishHandhsake: number;
};
class LocalProcessExtensionHostStartupTimer {
private readonly _sw: StopWatch;
constructor() {
this._sw = new StopWatch(false);
}
public toEvent(): LocalProcessExtensionHostStartupTimesEvent {
return {
didCreateExtensionHost: this.didCreateExtensionHost,
didListenOnPipe: this.didListenOnPipe,
didFindDebugPort: this.didFindDebugPort,
didGetShellEnv: this.didGetShellEnv,
didStartExtensionHost: this.didStartExtensionHost,
didReceiveConnection: this.didReceiveConnection,
didReceiveReady: this.didReceiveReady,
didReceiveInitialized: this.didReceiveInitialized,
didFinishHandhsake: this.didFinishHandhsake,
};
}
private didCreateExtensionHost = 0;
public markDidCreateExtensionHost() {
this.didCreateExtensionHost = this._sw.elapsed();
}
private didListenOnPipe = 0;
public markDidListenOnPipe() {
this.didListenOnPipe = this._sw.elapsed();
}
private didFindDebugPort = 0;
public markDidFindDebugPort() {
this.didFindDebugPort = this._sw.elapsed();
}
private didGetShellEnv = 0;
public markDidGetShellEnv() {
this.didGetShellEnv = this._sw.elapsed();
}
private didStartExtensionHost = 0;
public markDidStartExtensionHost() {
this.didStartExtensionHost = this._sw.elapsed();
}
private didReceiveConnection = 0;
public markDidReceiveConnection() {
this.didReceiveConnection = this._sw.elapsed();
}
private didReceiveReady = 0;
public markDidReceiveReady() {
this.didReceiveReady = this._sw.elapsed();
}
private didReceiveInitialized = 0;
public markDidReceiveInitialized() {
this.didReceiveInitialized = this._sw.elapsed();
}
private didFinishHandhsake = 0;
public markDidFinishHandhsake() {
this.didFinishHandhsake = this._sw.elapsed();
}
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter';
registerSharedProcessRemoteService(IExtensionHostStarter, ipcExtensionHostStarterChannelName, { supportsDelayedInstantiation: true });

View File

@@ -21,7 +21,7 @@ import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/com
import { Promises } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ProcessTimeRunOnceScheduler } from 'vs/base/common/async';
import { boolean } from 'vs/editor/common/config/editorOptions';
import 'vs/workbench/api/common/extHost.common.services';
@@ -115,8 +115,8 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime;
const reconnectionShortGraceTime = ProtocolConstants.ReconnectionShortGraceTime;
const disconnectRunner1 = new RunOnceScheduler(() => onTerminate('renderer disconnected for too long (1)'), reconnectionGraceTime);
const disconnectRunner2 = new RunOnceScheduler(() => onTerminate('renderer disconnected for too long (2)'), reconnectionShortGraceTime);
const disconnectRunner1 = new ProcessTimeRunOnceScheduler(() => onTerminate('renderer disconnected for too long (1)'), reconnectionGraceTime);
const disconnectRunner2 = new ProcessTimeRunOnceScheduler(() => onTerminate('renderer disconnected for too long (2)'), reconnectionShortGraceTime);
process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => {
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
@@ -333,6 +333,7 @@ export async function startExtensionHostProcess(): Promise<void> {
// host abstraction
const hostUtils = new class NodeHost implements IHostUtils {
declare readonly _serviceBrand: undefined;
public readonly pid = process.pid;
exit(code: number) { nativeExit(code); }
exists(path: string) { return Promises.exists(path); }
realpath(path: string) { return realpath(path); }

View File

@@ -122,7 +122,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku
}
const modules = lookup[request];
const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
const ext = extensionPaths.findSubstr(URI.file(parent.filename));
let cache = modulesCache.get(ext);
if (!cache) {
modulesCache.set(ext, cache = {});

View File

@@ -20,24 +20,24 @@ suite('ExtensionManifestPropertiesService - ExtensionKind', () => {
let testObject = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustEnablementService(), new NullLogService());
test('declarative with extension dependencies => workspace', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionDependencies: ['ext1'] }), ['workspace']);
test('declarative with extension dependencies', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionDependencies: ['ext1'] }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('declarative extension pack => workspace', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionPack: ['ext1', 'ext2'] }), ['workspace']);
test('declarative extension pack', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionPack: ['ext1', 'ext2'] }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('declarative extension pack and extension dependencies => workspace', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionPack: ['ext1', 'ext2'], extensionDependencies: ['ext1', 'ext2'] }), ['workspace']);
test('declarative extension pack and extension dependencies', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionPack: ['ext1', 'ext2'], extensionDependencies: ['ext1', 'ext2'] }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('declarative with unknown contribution point => workspace, web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ contributes: <any>{ 'unknownPoint': { something: true } } }), ['workspace', 'web']);
test('declarative with unknown contribution point => workspace, web in web and => workspace in desktop', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ contributes: <any>{ 'unknownPoint': { something: true } } }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('declarative extension pack with unknown contribution point => workspace', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionPack: ['ext1', 'ext2'], contributes: <any>{ 'unknownPoint': { something: true } } }), ['workspace']);
test('declarative extension pack with unknown contribution point', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionPack: ['ext1', 'ext2'], contributes: <any>{ 'unknownPoint': { something: true } } }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('simple declarative => ui, workspace, web', () => {
@@ -52,16 +52,20 @@ suite('ExtensionManifestPropertiesService - ExtensionKind', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ main: 'main.js' }), ['workspace']);
});
test('main and browser => workspace, web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ main: 'main.js', browser: 'main.browser.js' }), ['workspace', 'web']);
test('main and browser => workspace, web in web and workspace in desktop', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ main: 'main.js', browser: 'main.browser.js' }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('browser entry point with workspace extensionKind => workspace, web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ main: 'main.js', browser: 'main.browser.js', extensionKind: ['workspace'] }), ['workspace', 'web']);
test('browser entry point with workspace extensionKind => workspace, web in web and workspace in desktop', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ main: 'main.js', browser: 'main.browser.js', extensionKind: ['workspace'] }), isWeb ? ['workspace', 'web'] : ['workspace']);
});
test('simple descriptive with workspace, ui extensionKind => workspace, ui, web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionKind: ['workspace', 'ui'] }), ['workspace', 'ui', 'web']);
test('only browser entry point with out extensionKind => web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ browser: 'main.browser.js' }), ['web']);
});
test('simple descriptive with workspace, ui extensionKind => workspace, ui, web in web and workspace, ui in desktop', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<IExtensionManifest>{ extensionKind: ['workspace', 'ui'] }), isWeb ? ['workspace', 'ui', 'web'] : ['workspace', 'ui']);
});
test('opt out from web through settings even if it can run in web', () => {
@@ -77,6 +81,14 @@ suite('ExtensionManifestPropertiesService - ExtensionKind', () => {
test('extension cannot opt out from web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<any>{ browser: 'main.browser.js', extensionKind: ['-web'] }), ['web']);
});
test('extension cannot opt into web', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<any>{ main: 'main.js', extensionKind: ['web', 'workspace', 'ui'] }), ['workspace', 'ui']);
});
test('extension cannot opt into web only', () => {
assert.deepStrictEqual(testObject.getExtensionKind(<any>{ main: 'main.js', extensionKind: ['web'] }), ['workspace']);
});
});

View File

@@ -23,6 +23,8 @@ import { URI } from 'vs/base/common/uri';
declare function postMessage(data: any, transferables?: Transferable[]): void;
declare type _Fetch = typeof fetch;
declare namespace self {
let close: any;
let postMessage: any;
@@ -32,6 +34,9 @@ declare namespace self {
let indexedDB: { open: any, [k: string]: any };
let caches: { open: any, [k: string]: any };
let importScripts: any;
let fetch: _Fetch;
let XMLHttpRequest: any;
let trustedTypes: any;
}
const nativeClose = self.close.bind(self);
@@ -40,6 +45,27 @@ self.close = () => console.trace(`'close' has been blocked`);
const nativePostMessage = postMessage.bind(self);
self.postMessage = () => console.trace(`'postMessage' has been blocked`);
const nativeFetch = fetch.bind(self);
self.fetch = function (input, init) {
if (input instanceof Request) {
// Request object - massage not supported
return nativeFetch(input, init);
}
if (/^file:/i.test(String(input))) {
input = FileAccess.asBrowserUri(URI.parse(String(input))).toString(true);
}
return nativeFetch(input, init);
};
self.XMLHttpRequest = class extends XMLHttpRequest {
override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void {
if (/^file:/i.test(url.toString())) {
url = FileAccess.asBrowserUri(URI.parse(url.toString())).toString(true);
}
return super.open(method, url, async ?? true, username, password);
}
};
self.importScripts = () => { throw new Error(`'importScripts' has been blocked`); };
// const nativeAddEventListener = addEventListener.bind(self);
@@ -63,12 +89,41 @@ if ((<any>self).Worker) {
if (/^file:/i.test(stringUrl.toString())) {
stringUrl = FileAccess.asBrowserUri(URI.parse(stringUrl.toString())).toString(true);
}
const js = `(function() {
const ttPolicy = self.trustedTypes ? self.trustedTypes.createPolicy('extensionHostWorker', { createScriptURL: (value) => value }) : undefined;
const stringUrl = '${stringUrl}';
importScripts(ttPolicy ? ttPolicy.createScriptURL(stringUrl) : stringUrl);
})();
`;
// IMPORTANT: bootstrapFn is stringified and injected as worker blob-url. Because of that it CANNOT
// have dependencies on other functions or variables. Only constant values are supported. Due to
// that logic of FileAccess.asBrowserUri had to be copied, see `asWorkerBrowserUrl` (below).
const bootstrapFnSource = (function bootstrapFn(workerUrl: string) {
function asWorkerBrowserUrl(url: string | URL | TrustedScriptURL): any {
if (typeof url === 'string' || url instanceof URL) {
return String(url).replace(/^file:\/\//i, 'vscode-file://vscode-app');
}
return url;
}
const nativeFetch = fetch.bind(self);
self.fetch = function (input, init) {
if (input instanceof Request) {
// Request object - massage not supported
return nativeFetch(input, init);
}
return nativeFetch(asWorkerBrowserUrl(input), init);
};
self.XMLHttpRequest = class extends XMLHttpRequest {
override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void {
return super.open(method, asWorkerBrowserUrl(url), async ?? true, username, password);
}
};
const nativeImportScripts = importScripts.bind(self);
self.importScripts = (...urls: string[]) => {
nativeImportScripts(...urls.map(asWorkerBrowserUrl));
};
const ttPolicy = self.trustedTypes ? self.trustedTypes.createPolicy('extensionHostWorker', { createScriptURL: (value: string) => value }) : undefined;
nativeImportScripts(ttPolicy ? ttPolicy.createScriptURL(workerUrl) : workerUrl);
}).toString();
const js = `(${bootstrapFnSource}('${stringUrl}'))`;
options = options || {};
options.name = options.name || path.basename(stringUrl.toString());
const blob = new Blob([js], { type: 'application/javascript' });
@@ -88,6 +143,7 @@ if ((<any>self).Worker) {
const hostUtil = new class implements IHostUtils {
declare readonly _serviceBrand: undefined;
public readonly pid = undefined;
exit(_code?: number | undefined): void {
nativeClose();
}