mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.")
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
@@ -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); }
|
||||
|
||||
@@ -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 = {});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user