Theia: [plugins] Plugins are activated after the loading screen has faded out

Created on 30 Jun 2020  路  20Comments  路  Source: eclipse-theia/theia

Bug Description:

A loading screen exists in Theia to cover up the UI being built. However, plugins are activated after this has been hidden meaning users see the UI changes implemented by plugins.
I would expect the loading screen to fade out after everything has been loaded and the UI is ready to use.

Steps to Reproduce:

Launch Theia with external plugins which affect the UI and see the UI change after the loading screen has faded.

I think this is because the plugin loading mechanism isn't waited on for completion. Starting here:

https://github.com/eclipse-theia/theia/blob/c385cc6dad6f9089806752ab06def4039a7494b0/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts#L108

Additional Information

  • Theia Version: 1.3.0
plug-in system question

Most helpful comment

Thanks for all the help here. I can confirm this approach will work to ensure certain plugins are loaded before the UI is available.
This approach also gives great control over when and if certain plugins are loaded:

@injectable()
export class PluginLoader implements FrontendApplicationContribution {

    constructor(@inject(HostedPluginSupport) protected readonly hostedPluginSupport: HostedPluginSupport) {
    }

    public async onDidInitializeLayout(): Promise<void> {
        await this.hostedPluginSupport.didStart;
        for (const plugin of this.hostedPluginSupport.plugins) {
            // Filter plugins here if you want
            await this.hostedPluginSupport.activatePlugin(plugin.model.id);
        }
    }
}

All 20 comments

Is not it by design? Plugins are loaded lazily according to activation events and can contribute new functionality without blocking the user. There is no a contract that they should be loaded on startup. One could though implement onStart hook to eagerly activate the particular plugin.

Activation events doc: https://code.visualstudio.com/api/references/activation-events
How to trigger and await activation of particular plugin: https://github.com/eclipse-theia/theia/blob/250841db7f5de49a1c57ffb437379c8638d35be0/examples/api-tests/src/typescript.spec.js#L138 it will throw if a plugin failed to activate. Maybe you can use it to fail the app startup?

One more note: activated plugin does not mean that it revealed all contributed views, it could reveal it at later time. It is a secret of the plugin. You could control for your plugins, but not for 3rd party.

what about making this behavior configurable?
BTW I think that plugins deployment may also run in parallel to theia fully loaded (before activation).

what about making this behavior configurable?

It is already configurable. You can provide a custom ApplicationFrontendContribution.onStart hook which awaits loading and activation of all plugins. DI is primary mechanism of configuration in Theia. But as I said even that won't ensure that everything is present, plugins can decide after loading/activation to run event loop several times for some reason before revealing a view, to fetch required data for instance.

BTW I think that plugins deployment may also run in parallel to theia fully loaded (before activation).

Plugin deployment is running on Theia backend startup already, even before any frontend is opened.

This code snippet shows how to await startup and activation of some plugins: https://github.com/eclipse-theia/theia/blob/250841db7f5de49a1c57ffb437379c8638d35be0/examples/api-tests/src/typescript.spec.js#L133-L139 You can use HostedPluginSupport.plugins to access all loaded plugins after didStart event.

Recently we tried to acheive such behavior and the only way was overriding revealShell which seems fragile:

  protected async revealShell(host: HTMLElement): Promise<void> {
    // Wait for loading important plugins
    await this.hostedPluginSupport().didStart;
    return super.revealShell(host);
  }

Is there a better way?

Plugin deployment is running on Theia backend startup already, even before any frontend is opened.

I think it starts with theia but if it take long time to download and extract the plugins then you can open the UI before it is finished.

I think it starts with theia but if it take long time to download and extract the plugins then you can open the UI before it is finished.

That's fine: the whole idea is that a user can use an editor before plugins are there. Awaiting on didStart should cover it as well, since it awaits for the first deployment. Although it guarantees only that static contribution were registered, views are still can appear later. I don't think you can ensure it 100%, since it is not how plugins activation is designed. You can improve it a bit by calling activatePlugin as well, some plugins may have async activate function and call reveal in it.

Is there a better way?

I would use the standard contribution point for it: https://github.com/eclipse-theia/theia/blob/250841db7f5de49a1c57ffb437379c8638d35be0/packages/core/src/browser/frontend-application.ts#L49 but overriding revealShell is fine as well. If you are concerned that this method can be removed then use onStart hook.

I would use the standard contribution point for it

I tried this approach first and as far as I remember it got me into a deadlock with other contribution we have.

If you are concerned that this method can be removed then use onStart hook.

startContributions is only called on start method. I think the only place this customization worked for me was in the reveal override and not before.

I tried this approach first and as far as I remember it got me into a deadlock with other contribution we have.

Start up of plugins has some preconditions like the layout should be restored, preferences and workspace are loaded. Maybe it is causing a deadlock. One will need to look carefully how it could be untangled without affecting restoration of plugin views and initialisation of the host process.

So maybe you can try ApplicationFrontendContribution.onDidInitializeLayout instead.

You reminded me. Yes, it was about the hostedPluginSupport waiting for the layout which is sorted out after the startContributions but not before reveal.

Some really useful information and pointers here, thanks!

I'll investigate controlling and loading plugins in onStart() and report back.

BTW, a quick read of the activation document suggests to me that any * activation events should be executed on startup and block loading, otherwise there would be no difference with the onStartupFinished event (which explicitly says it doesn't affect startup).

However, VSCode may not actually be doing this.

How does Theia treat these events differently?

BTW, a quick read of the activation document suggests to me that any * activation events should be executed on startup and block loading, otherwise there would be no difference with the onStartupFinished event (which explicitly says it doesn't affect startup).

I think they mean on start of extension host process: https://github.com/microsoft/vscode/blob/921e66b0639830cd1f6182c67fd8f3ed701c6e17/src/vs/workbench/api/common/extHostExtensionService.ts#L460 I don't see that VS Code awaits anywhere that this method is resolved. I won't expect it either VS Code should work even if the extension host process is crashed. It is the same in Theia.

onStartupFinished is fired after all eager extensions (* and initial workspaceContains activation events) are activated or some timeout: https://github.com/microsoft/vscode/blob/921e66b0639830cd1f6182c67fd8f3ed701c6e17/src/vs/workbench/api/common/extHostExtensionService.ts#L470-L472

OK, so in theory, the onStart() approach works:

    public async onStart(): Promise<void> {
        await this.hostedPluginSupport.didStart;
        for (const plugin of this.hostedPluginSupport.plugins) {
            await this.hostedPluginSupport.activatePlugin(plugin.model.id);
        }
    }

However, I had to disable this line because plugin loading and activation explicitly blocks until the system is laid out.

So this approach currently creates a deadlock. Any ideas?

Have you tried to use ApplicationFrontendContribution.onDidInitializeLayout instead? It should allow to await before the shell is revealed but the layout should be already initialised to unblock plugins startup.

However, I had to disable this line because plugin loading and activation explicitly blocks until the system is laid out.

Disabling the line will break restoration of plugin webviews and views.

Thanks for all the help here. I can confirm this approach will work to ensure certain plugins are loaded before the UI is available.
This approach also gives great control over when and if certain plugins are loaded:

@injectable()
export class PluginLoader implements FrontendApplicationContribution {

    constructor(@inject(HostedPluginSupport) protected readonly hostedPluginSupport: HostedPluginSupport) {
    }

    public async onDidInitializeLayout(): Promise<void> {
        await this.hostedPluginSupport.didStart;
        for (const plugin of this.hostedPluginSupport.plugins) {
            // Filter plugins here if you want
            await this.hostedPluginSupport.activatePlugin(plugin.model.id);
        }
    }
}
Was this page helpful?
0 / 5 - 0 ratings