diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 20153fb49aa1f..278f8b121aec9 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -435,6 +435,8 @@ export class HostedPluginSupport { if (!manager) { const pluginId = getPluginId(hostContributions[0].plugin.metadata.model); const rpc = this.initRpc(host, pluginId); + const apiStartfunction = setUpPluginApi(rpc, this.container); + this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, this.container)); toDisconnect.push(rpc); manager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT); @@ -475,6 +477,8 @@ export class HostedPluginSupport { }, jsonValidation }); + // now the ext services are fully set up + apiStartfunction(); if (toDisconnect.disposed) { return undefined; } @@ -483,10 +487,7 @@ export class HostedPluginSupport { } protected initRpc(host: PluginHost, pluginId: string): RPCProtocol { - const rpc = host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(pluginId, host); - setUpPluginApi(rpc, this.container); - this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, this.container)); - return rpc; + return host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(pluginId, host); } private createServerRpc(pluginID: string, hostID: string): RPCProtocol { diff --git a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts index 50c0214e0e5d0..438666c1d3a33 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts @@ -17,22 +17,13 @@ import { Emitter } from '@theia/core/lib/common/event'; import { RPCProtocolImpl } from '../../../common/rpc-protocol'; import { PluginManagerExtImpl } from '../../../plugin/plugin-manager'; -import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin, TerminalServiceExt } from '../../../common/plugin-api-rpc'; -import { createAPIFactory } from '../../../plugin/plugin-context'; +import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin, PluginAPIFactory } from '../../../common/plugin-api-rpc'; import { getPluginId, PluginMetadata } from '../../../common/plugin-protocol'; import * as theia from '@theia/plugin'; -import { PreferenceRegistryExtImpl } from '../../../plugin/preference-registry'; import { ExtPluginApi } from '../../../common/plugin-ext-api-contribution'; import { createDebugExtStub } from './debug-stub'; -import { EditorsAndDocumentsExtImpl } from '../../../plugin/editors-and-documents'; -import { WorkspaceExtImpl } from '../../../plugin/workspace'; -import { MessageRegistryExt } from '../../../plugin/message-registry'; import { WorkerEnvExtImpl } from './worker-env-ext'; -import { ClipboardExt } from '../../../plugin/clipboard-ext'; -import { KeyValueStorageProxy } from '../../../plugin/plugin-storage'; -import { WebviewsExtImpl } from '../../../plugin/webviews'; import { loadManifest } from './plugin-manifest-loader'; -import { TerminalServiceExtImpl } from '../../../plugin/terminal-ext'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const ctx = self as any; @@ -55,15 +46,7 @@ function initialize(contextPath: string, pluginMetadata: PluginMetadata): void { ctx.importScripts('/context/' + contextPath); } const envExt = new WorkerEnvExtImpl(rpc); -const storageProxy = new KeyValueStorageProxy(rpc); -const editorsAndDocuments = new EditorsAndDocumentsExtImpl(rpc); -const messageRegistryExt = new MessageRegistryExt(rpc); -const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments, messageRegistryExt); -const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt); const debugExt = createDebugExtStub(rpc); -const clipboardExt = new ClipboardExt(rpc); -const webviewExt = new WebviewsExtImpl(rpc, workspaceExt); -const terminalService: TerminalServiceExt = new TerminalServiceExtImpl(rpc); const pluginManager = new PluginManagerExtImpl({ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -84,7 +67,7 @@ const pluginManager = new PluginManagerExtImpl({ return ctx[plugin.lifecycle.frontendModuleName]; } }, - async init(rawPluginData: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> { + async init(apiFactory: PluginAPIFactory, rawPluginData: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> { const result: Plugin[] = []; const foreign: Plugin[] = []; // Process the plugins concurrently, making sure to keep the order. @@ -148,20 +131,8 @@ const pluginManager = new PluginManagerExtImpl({ } } } -}, envExt, terminalService, storageProxy, preferenceRegistryExt, webviewExt, rpc); +}, envExt, debugExt, rpc); -const apiFactory = createAPIFactory( - rpc, - pluginManager, - envExt, - debugExt, - preferenceRegistryExt, - editorsAndDocuments, - workspaceExt, - messageRegistryExt, - clipboardExt, - webviewExt -); let defaultApi: typeof theia; const handler = { @@ -174,7 +145,7 @@ const handler = { } if (!defaultApi) { - defaultApi = apiFactory(emptyPlugin); + defaultApi = pluginManager.apiFactory(emptyPlugin); } return defaultApi; @@ -183,11 +154,6 @@ const handler = { ctx['theia'] = new Proxy(Object.create(null), handler); rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, pluginManager); -rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocuments); -rpc.set(MAIN_RPC_CONTEXT.WORKSPACE_EXT, workspaceExt); -rpc.set(MAIN_RPC_CONTEXT.PREFERENCE_REGISTRY_EXT, preferenceRegistryExt); -rpc.set(MAIN_RPC_CONTEXT.STORAGE_EXT, storageProxy); -rpc.set(MAIN_RPC_CONTEXT.WEBVIEWS_EXT, webviewExt); function isElectron(): boolean { if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts index e6ae378e13fe0..e545630223780 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-process.ts @@ -155,6 +155,8 @@ export class HostedPluginProcess implements ServerPluginRunner { this.childProcess.on('message', message => { if (this.client) { this.client.postMessage(message); + } else { + this.logger.error('Dropping message: ' + JSON.stringify(message)); } }); } diff --git a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts index 64b3ab35ae7fb..8c88deba075da 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts @@ -17,28 +17,15 @@ import { PluginManagerExtImpl } from '../../plugin/plugin-manager'; import { MAIN_RPC_CONTEXT, Plugin, PluginAPIFactory } from '../../common/plugin-api-rpc'; import { PluginMetadata } from '../../common/plugin-protocol'; -import { createAPIFactory } from '../../plugin/plugin-context'; -import { EnvExtImpl } from '../../plugin/env'; -import { PreferenceRegistryExtImpl } from '../../plugin/preference-registry'; +import { loadManifest } from './plugin-manifest-loader'; import { ExtPluginApi } from '../../common/plugin-ext-api-contribution'; -import { DebugExtImpl } from '../../plugin/node/debug/debug'; -import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents'; -import { WorkspaceExtImpl } from '../../plugin/workspace'; -import { MessageRegistryExt } from '../../plugin/message-registry'; import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext'; -import { ClipboardExt } from '../../plugin/clipboard-ext'; -import { loadManifest } from './plugin-manifest-loader'; -import { KeyValueStorageProxy } from '../../plugin/plugin-storage'; -import { WebviewsExtImpl } from '../../plugin/webviews'; -import { TerminalServiceExtImpl } from '../../plugin/terminal-ext'; +import { DebugExtImpl } from '../../plugin/node/debug/debug'; /** * Handle the RPC calls. */ export class PluginHostRPC { - - private apiFactory: PluginAPIFactory; - private pluginManager: PluginManagerExtImpl; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -46,36 +33,8 @@ export class PluginHostRPC { } initialize(): void { - const envExt = new EnvNodeExtImpl(this.rpc); - const storageProxy = new KeyValueStorageProxy(this.rpc); - const debugExt = new DebugExtImpl(this.rpc); - const editorsAndDocumentsExt = new EditorsAndDocumentsExtImpl(this.rpc); - const messageRegistryExt = new MessageRegistryExt(this.rpc); - const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt); - const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt); - const clipboardExt = new ClipboardExt(this.rpc); - const webviewExt = new WebviewsExtImpl(this.rpc, workspaceExt); - const terminalService = new TerminalServiceExtImpl(this.rpc); - this.pluginManager = this.createPluginManager(envExt, terminalService, storageProxy, preferenceRegistryExt, webviewExt, this.rpc); + this.pluginManager = this.createPluginManager(this.rpc); this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager); - this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocumentsExt); - this.rpc.set(MAIN_RPC_CONTEXT.WORKSPACE_EXT, workspaceExt); - this.rpc.set(MAIN_RPC_CONTEXT.PREFERENCE_REGISTRY_EXT, preferenceRegistryExt); - this.rpc.set(MAIN_RPC_CONTEXT.STORAGE_EXT, storageProxy); - this.rpc.set(MAIN_RPC_CONTEXT.WEBVIEWS_EXT, webviewExt); - - this.apiFactory = createAPIFactory( - this.rpc, - this.pluginManager, - envExt, - debugExt, - preferenceRegistryExt, - editorsAndDocumentsExt, - workspaceExt, - messageRegistryExt, - clipboardExt, - webviewExt - ); } async terminate(): Promise { @@ -83,21 +42,22 @@ export class PluginHostRPC { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - initContext(contextPath: string, plugin: Plugin): any { + initContext(apiFactory: PluginAPIFactory, contextPath: string, plugin: Plugin): any { const { name, version } = plugin.rawModel; console.log('PLUGIN_HOST(' + process.pid + '): initializing(' + name + '@' + version + ' with ' + contextPath + ')'); try { const backendInit = require(contextPath); - backendInit.doInitialization(this.apiFactory, plugin); + backendInit.doInitialization(apiFactory, plugin); } catch (e) { console.error(e); } } - createPluginManager( - envExt: EnvExtImpl, terminalService: TerminalServiceExtImpl, storageProxy: KeyValueStorageProxy, preferencesManager: PreferenceRegistryExtImpl, webview: WebviewsExtImpl, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - rpc: any): PluginManagerExtImpl { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createPluginManager(rpc: any): PluginManagerExtImpl { + const envExt = new EnvNodeExtImpl(this.rpc); + const debugExt = new DebugExtImpl(this.rpc); + const { extensionTestsPath } = process.env; const self = this; const pluginManager = new PluginManagerExtImpl({ @@ -143,7 +103,7 @@ export class PluginHostRPC { return require(plugin.pluginPath); } }, - async init(raw: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> { + async init(apiFactory: PluginAPIFactory, raw: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> { console.log('PLUGIN_HOST(' + process.pid + '): PluginManagerExtImpl/init()'); const result: Plugin[] = []; const foreign: Plugin[] = []; @@ -177,7 +137,7 @@ export class PluginHostRPC { rawModel }; - self.initContext(backendInitPath, plugin); + self.initContext(apiFactory, backendInitPath, plugin); result.push(plugin); } @@ -227,7 +187,7 @@ export class PluginHostRPC { `Path ${extensionTestsPath} does not point to a valid extension test runner.` ); } : undefined - }, envExt, terminalService, storageProxy, preferencesManager, webview, rpc); + }, envExt, debugExt, rpc); return pluginManager; } } diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts index e74d0bb9a6793..17324f12949de 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -54,7 +54,7 @@ import { TimelineMainImpl } from './timeline-main'; import { AuthenticationMainImpl } from './authentication-main'; import { ThemingMainImpl } from './theming-main'; -export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { +export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): () => void { const authenticationMain = new AuthenticationMainImpl(rpc, container); rpc.set(PLUGIN_RPC_CONTEXT.AUTHENTICATION_MAIN, authenticationMain); @@ -92,9 +92,6 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container const editorsMain = new TextEditorsMainImpl(editorsAndDocuments, rpc, bulkEditService, monacoEditorService); rpc.set(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN, editorsMain); - // start listening only after all clients are subscribed to events - editorsAndDocuments.listen(); - const statusBarMessageRegistryMain = new StatusBarMessageRegistryMainImpl(container); rpc.set(PLUGIN_RPC_CONTEXT.STATUS_BAR_MESSAGE_REGISTRY_MAIN, statusBarMessageRegistryMain); @@ -163,4 +160,9 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container const themingMain = new ThemingMainImpl(rpc); rpc.set(PLUGIN_RPC_CONTEXT.THEMING_MAIN, themingMain); + + return () => { + // start listening only after all clients are subscribed to events + editorsAndDocuments.listen(); + }; } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 5c4421db63108..e5cb29857c33f 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -175,7 +175,8 @@ export function createAPIFactory( workspaceExt: WorkspaceExtImpl, messageRegistryExt: MessageRegistryExt, clipboard: ClipboardExt, - webviewExt: WebviewsExtImpl + webviewExt: WebviewsExtImpl, + terminalExt: TerminalServiceExtImpl ): PluginAPIFactory { const authenticationExt = rpc.set(MAIN_RPC_CONTEXT.AUTHENTICATION_EXT, new AuthenticationExtImpl(rpc)); @@ -187,7 +188,6 @@ export function createAPIFactory( const editors = rpc.set(MAIN_RPC_CONTEXT.TEXT_EDITORS_EXT, new TextEditorsExtImpl(rpc, editorsAndDocumentsExt)); const documents = rpc.set(MAIN_RPC_CONTEXT.DOCUMENTS_EXT, new DocumentsExtImpl(rpc, editorsAndDocumentsExt)); const statusBarMessageRegistryExt = new StatusBarMessageRegistryExt(rpc); - const terminalExt = rpc.set(MAIN_RPC_CONTEXT.TERMINAL_EXT, new TerminalServiceExtImpl(rpc)); const outputChannelRegistryExt = rpc.set(MAIN_RPC_CONTEXT.OUTPUT_CHANNEL_REGISTRY_EXT, new OutputChannelRegistryExtImpl(rpc)); const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents, commandRegistry)); const treeViewsExt = rpc.set(MAIN_RPC_CONTEXT.TREE_VIEWS_EXT, new TreeViewsExtImpl(rpc, commandRegistry)); @@ -200,7 +200,6 @@ export function createAPIFactory( const labelServiceExt = rpc.set(MAIN_RPC_CONTEXT.LABEL_SERVICE_EXT, new LabelServiceExtImpl(rpc)); const timelineExt = rpc.set(MAIN_RPC_CONTEXT.TIMELINE_EXT, new TimelineExtImpl(rpc, commandRegistry)); const themingExt = rpc.set(MAIN_RPC_CONTEXT.THEMING_EXT, new ThemingExtImpl(rpc)); - rpc.set(MAIN_RPC_CONTEXT.DEBUG_EXT, debugExt); return function (plugin: InternalPlugin): typeof theia { const authentication: typeof theia.authentication = { diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index 274da9e107af5..f02df728b2444 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -26,7 +26,8 @@ import { ConfigStorage, PluginManagerInitializeParams, PluginManagerStartParams, - TerminalServiceExt + MAIN_RPC_CONTEXT, + PluginAPIFactory } from '../common/plugin-api-rpc'; import { PluginMetadata, PluginJsonValidationContribution } from '../common/plugin-protocol'; import * as theia from '@theia/plugin'; @@ -38,13 +39,20 @@ import { ExtPluginApi } from '../common/plugin-ext-api-contribution'; import { RPCProtocol } from '../common/rpc-protocol'; import { Emitter } from '@theia/core/lib/common/event'; import { WebviewsExtImpl } from './webviews'; +import { ClipboardExt } from './clipboard-ext'; +import { EditorsAndDocumentsExtImpl } from './editors-and-documents'; +import { MessageRegistryExt } from './message-registry'; +import { DebugExtImpl } from './node/debug/debug'; +import { createAPIFactory } from './plugin-context'; +import { TerminalServiceExtImpl } from './terminal-ext'; +import { WorkspaceExtImpl } from './workspace'; export interface PluginHost { // eslint-disable-next-line @typescript-eslint/no-explicit-any loadPlugin(plugin: Plugin): any; - init(data: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> | [Plugin[], Plugin[]]; + init(apiFactory: PluginAPIFactory, data: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> | [Plugin[], Plugin[]]; initExtApi(extApi: ExtPluginApi[]): void; @@ -96,6 +104,14 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { private onDidChangeEmitter = new Emitter(); private messageRegistryProxy: MessageRegistryMain; private notificationMain: NotificationMain; + + private terminalService: TerminalServiceExtImpl; + private storageProxy: KeyValueStorageProxy; + private preferencesManager: PreferenceRegistryExtImpl; + private webview: WebviewsExtImpl; + + apiFactory: PluginAPIFactory; + protected fireOnDidChange(): void { this.onDidChangeEmitter.fire(undefined); } @@ -105,14 +121,12 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { constructor( private readonly host: PluginHost, private readonly envExt: EnvExtImpl, - private readonly terminalService: TerminalServiceExt, - private readonly storageProxy: KeyValueStorageProxy, - private readonly preferencesManager: PreferenceRegistryExtImpl, - private readonly webview: WebviewsExtImpl, + private readonly debugExt: DebugExtImpl, private readonly rpc: RPCProtocol ) { this.messageRegistryProxy = this.rpc.getProxy(PLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN); this.notificationMain = this.rpc.getProxy(PLUGIN_RPC_CONTEXT.NOTIFICATION_MAIN); + this.rpc.set(MAIN_RPC_CONTEXT.DEBUG_EXT, this.debugExt); } async $stop(pluginId?: string): Promise { @@ -182,6 +196,29 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { } async $init(params: PluginManagerInitializeParams): Promise { + this.storageProxy = this.rpc.set(MAIN_RPC_CONTEXT.STORAGE_EXT, new KeyValueStorageProxy(this.rpc)); + this.terminalService = this.rpc.set(MAIN_RPC_CONTEXT.TERMINAL_EXT, new TerminalServiceExtImpl(this.rpc)); + const editorsAndDocumentsExt = this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, new EditorsAndDocumentsExtImpl(this.rpc)); + const messageRegistryExt = new MessageRegistryExt(this.rpc); + const workspaceExt = this.rpc.set(MAIN_RPC_CONTEXT.WORKSPACE_EXT, new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt)); + this.preferencesManager = this.rpc.set(MAIN_RPC_CONTEXT.PREFERENCE_REGISTRY_EXT, new PreferenceRegistryExtImpl(this.rpc, workspaceExt)); + this.webview = this.rpc.set(MAIN_RPC_CONTEXT.WEBVIEWS_EXT, new WebviewsExtImpl(this.rpc, workspaceExt)); + const clipboardExt = new ClipboardExt(this.rpc); + + this.apiFactory = createAPIFactory( + this.rpc, + this, + this.envExt, + this.debugExt, + this.preferencesManager, + editorsAndDocumentsExt, + workspaceExt, + messageRegistryExt, + clipboardExt, + this.webview, + this.terminalService + ); + this.storageProxy.init(params.globalState, params.workspaceState); this.envExt.setQueryParameters(params.env.queryParams); @@ -203,7 +240,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { async $start(params: PluginManagerStartParams): Promise { this.configStorage = params.configStorage; - const [plugins, foreignPlugins] = await this.host.init(params.plugins); + const [plugins, foreignPlugins] = await this.host.init(this.apiFactory, params.plugins); // add foreign plugins for (const plugin of foreignPlugins) { this.registerPlugin(plugin);