Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web worker extension host #100729

Merged
merged 7 commits into from Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/gulpfile.vscode.js
Expand Up @@ -47,6 +47,7 @@ const nodeModules = ['electron', 'original-fs']
const vscodeEntryPoints = _.flatten([
buildfile.entrypoint('vs/workbench/workbench.desktop.main'),
buildfile.base,
buildfile.workerExtensionHost,
buildfile.workbenchDesktop,
buildfile.code
]);
Expand All @@ -72,6 +73,7 @@ const vscodeResources = [
'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt',
'out-build/vs/workbench/contrib/webview/browser/pre/*.js',
'out-build/vs/workbench/contrib/webview/electron-browser/pre/*.js',
'out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js',
'out-build/vs/**/markdown.css',
'out-build/vs/workbench/contrib/tasks/**/*.json',
'out-build/vs/platform/files/**/*.exe',
Expand Down
Expand Up @@ -194,6 +194,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'array',
description: localize('handleUriConfirmedExtensions', "When an extension is listed here, a confirmation prompt will not be shown when that extension handles a URI."),
default: []
},
'extensions.webWorker': {
type: 'boolean',
description: localize('extensionsWebWorker', "Enable web worker extension host."),
default: false
}
}
});
Expand Down
Expand Up @@ -18,6 +18,7 @@ import { getExtensionKind } from 'vs/workbench/services/extensions/common/extens
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
import { StorageManager } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';

const SOURCE = 'IWorkbenchExtensionEnablementService';

Expand Down Expand Up @@ -151,8 +152,13 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
}
if (extensionKind === 'web') {
// Web extensions are not yet supported to be disabled by kind. Enable them always on web.
const enableLocalWebWorker = this.configurationService.getValue<boolean>(webWorkerExtHostConfig);
if (enableLocalWebWorker) {
// Web extensions are enabled on all configurations
return false;
}
if (this.extensionManagementServerService.localExtensionManagementServer === null) {
// Web extensions run only in the web
return false;
}
}
Expand Down
47 changes: 33 additions & 14 deletions src/vs/workbench/services/extensions/browser/extensionService.ts
Expand Up @@ -14,11 +14,9 @@ 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, parseScannedExtension } from 'vs/workbench/services/extensions/common/abstractExtensionService';
import { RemoteExtensionHost, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
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';
import { URI } from 'vs/base/common/uri';
import { canExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
Expand All @@ -31,7 +29,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot
export class ExtensionService extends AbstractExtensionService implements IExtensionService {

private _disposables = new DisposableStore();
private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null = null;
private _remoteInitData: IRemoteExtensionHostInitData | null = null;

constructor(
@IInstantiationService instantiationService: IInstantiationService,
Expand Down Expand Up @@ -71,29 +69,38 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._disposables.add(this._fileService.registerProvider(Schemas.https, provider));
}

private _createProvider(remoteAuthority: string): IInitDataProvider {
private _createLocalExtensionHostDataProvider() {
return {
getInitData: async () => {
const allExtensions = await this.getExtensions();
const webExtensions = allExtensions.filter(ext => canExecuteOnWeb(ext, this._productService, this._configService));
return {
autoStart: true,
extensions: webExtensions
};
}
};
}

private _createRemoteExtensionHostDataProvider(remoteAuthority: string): IRemoteExtensionHostDataProvider {
return {
remoteAuthority: remoteAuthority,
getInitData: async () => {
await this.whenInstalledExtensionsRegistered();
const connectionData = this._remoteAuthorityResolverService.getConnectionData(remoteAuthority);
const remoteEnvironment = this._remoteExtensionsEnvironmentData!;
return { connectionData, remoteEnvironment };
return this._remoteInitData!;
}
};
}

protected _createExtensionHosts(_isInitialStart: boolean): IExtensionHost[] {
const result: IExtensionHost[] = [];

const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => canExecuteOnWeb(ext, this._productService, this._configService)));
const webWorkerExtHost = this._instantiationService.createInstance(WebWorkerExtensionHost, webExtensions, URI.file(this._environmentService.logsPath).with({ scheme: this._environmentService.logFile.scheme }));
const webWorkerExtHost = this._instantiationService.createInstance(WebWorkerExtensionHost, this._createLocalExtensionHostDataProvider());
result.push(webWorkerExtHost);

const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !canExecuteOnWeb(ext, this._productService, this._configService)));
const remoteExtHost = this._instantiationService.createInstance(RemoteExtensionHost, remoteExtensions, this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
const remoteExtHost = this._instantiationService.createInstance(RemoteExtensionHost, this._createRemoteExtensionHostDataProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
result.push(remoteExtHost);
}

Expand All @@ -107,13 +114,15 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._webExtensionsScannerService.scanExtensions().then(extensions => extensions.map(parseScannedExtension))
]);

const remoteAgentConnection = this._remoteAgentService.getConnection();

let result: DeltaExtensionsResult;

// local: only enabled and web'ish extension
localExtensions = localExtensions!.filter(ext => this._isEnabled(ext) && canExecuteOnWeb(ext, this._productService, this._configService));
this._checkEnableProposedApi(localExtensions);

if (!remoteEnv) {
if (!remoteEnv || !remoteAgentConnection) {
result = this._registry.deltaExtensions(localExtensions, []);

} else {
Expand All @@ -127,7 +136,17 @@ export class ExtensionService extends AbstractExtensionService implements IExten
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));

// save for remote extension's init data
this._remoteExtensionsEnvironmentData = remoteEnv;
this._remoteInitData = {
connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority),
pid: remoteEnv.pid,
appRoot: remoteEnv.appRoot,
appSettingsHome: remoteEnv.appSettingsHome,
extensionHostLogsPath: remoteEnv.extensionHostLogsPath,
globalStorageHome: remoteEnv.globalStorageHome,
userHome: remoteEnv.userHome,
extensions: remoteEnv.extensions,
allExtensions: remoteEnv.extensions.concat(localExtensions)
};

result = this._registry.deltaExtensions(remoteEnv.extensions.concat(localExtensions), []);
}
Expand Down
Expand Up @@ -25,6 +25,15 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { localize } from 'vs/nls';

export interface IWebWorkerExtensionHostInitData {
readonly autoStart: boolean;
readonly extensions: IExtensionDescription[];
}

export interface IWebWorkerExtensionHostDataProvider {
getInitData(): Promise<IWebWorkerExtensionHostInitData>;
}

export class WebWorkerExtensionHost implements IExtensionHost {

public readonly kind = ExtensionHostKind.LocalWebWorker;
Expand All @@ -37,18 +46,19 @@ export class WebWorkerExtensionHost implements IExtensionHost {
private readonly _onDidExit = new Emitter<[number, string | null]>();
readonly onExit: Event<[number, string | null]> = this._onDidExit.event;

private readonly _extensionHostLogsLocation: URI;
private readonly _extensionHostLogFile: URI;

constructor(
private readonly _extensions: Promise<IExtensionDescription[]>,
private readonly _extensionHostLogsLocation: URI,
private readonly _initDataProvider: IWebWorkerExtensionHostDataProvider,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@ILabelService private readonly _labelService: ILabelService,
@ILogService private readonly _logService: ILogService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IProductService private readonly _productService: IProductService,
) {
this._extensionHostLogsLocation = URI.file(this._environmentService.logsPath).with({ scheme: this._environmentService.logFile.scheme });
this._extensionHostLogFile = joinPath(this._extensionHostLogsLocation, `${ExtensionHostLogFileName}.log`);
}

Expand Down Expand Up @@ -129,7 +139,7 @@ export class WebWorkerExtensionHost implements IExtensionHost {
}

private async _createExtHostInitData(): Promise<IInitData> {
const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]);
const [telemetryInfo, initData] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]);
const workspace = this._contextService.getWorkspace();
return {
commit: this._productService.commit,
Expand All @@ -154,12 +164,12 @@ export class WebWorkerExtensionHost implements IExtensionHost {
},
resolvedExtensions: [],
hostExtensions: [],
extensions: extensionDescriptions,
extensions: initData.extensions,
telemetryInfo,
logLevel: this._logService.getLevel(),
logsLocation: this._extensionHostLogsLocation,
logFile: this._extensionHostLogFile,
autoStart: true,
autoStart: initData.autoStart,
remote: {
authority: this._environmentService.configuration.remoteAuthority,
connectionData: null,
Expand Down
Expand Up @@ -286,6 +286,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
}

protected _checkEnabledAndProposedAPI(extensions: IExtensionDescription[]): IExtensionDescription[] {
// enable or disable proposed API per extension
this._checkEnableProposedApi(extensions);

// keep only enabled extensions
return extensions.filter(extension => this._isEnabled(extension));
}

private _isExtensionUnderDevelopment(extension: IExtensionDescription): boolean {
if (this._environmentService.isExtensionDevelopment) {
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
Expand All @@ -302,21 +310,17 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}

protected _isEnabled(extension: IExtensionDescription): boolean {
return !this._isDisabled(extension);
}

protected _isDisabled(extension: IExtensionDescription): boolean {
if (this._isExtensionUnderDevelopment(extension)) {
// Never disable extensions under development
return false;
return true;
}

if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) {
// Check if this is the better merge extension which was migrated to a built-in extension
return true;
return false;
}

return !this._extensionEnablementService.isEnabled(toExtension(extension));
return this._extensionEnablementService.isEnabled(toExtension(extension));
}

protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void {
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/services/extensions/common/extensions.ts
Expand Up @@ -24,6 +24,8 @@ export const nullExtensionDescription = Object.freeze(<IExtensionDescription>{
isBuiltin: false,
});

export const webWorkerExtHostConfig = 'extensions.webWorker';

export const IExtensionService = createDecorator<IExtensionService>('extensionService');

export interface IMessage {
Expand Down