Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Support webview in dedicate container #947

Merged
merged 1 commit into from
Dec 21, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { PluginReaderExtension } from './plugin-reader-extension';
import { PluginRemoteNodeImpl } from './plugin-remote-node-impl';
import { RPCProtocolImpl } from '@theia/plugin-ext/lib/common/rpc-protocol';
import { TerminalContainerAware } from './terminal-container-aware';
import { WebviewsContentAware } from './webviews-content-aware';
import { logger } from '@theia/core';
import pluginExtBackendModule from '@theia/plugin-ext/lib/plugin-ext-backend-module';
import pluginRemoteBackendModule from './plugin-remote-backend-module';
Expand Down Expand Up @@ -247,6 +248,10 @@ to pick-up automatically a free port`)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(webSocketClient.rpc as any).locals.get(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT.id)
);
WebviewsContentAware.makeWebviewsContentAware(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(webSocketClient.rpc as any).locals.get(MAIN_RPC_CONTEXT.WEBVIEWS_EXT.id)
);

let channelName = '';
if (process.env.CHE_MACHINE_NAME) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**********************************************************************
* Copyright (c) 2020 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
***********************************************************************/

import * as theia from '@theia/plugin';

import { WebviewImpl, WebviewsExtImpl } from '@theia/plugin-ext/lib/plugin/webviews';

import { Plugin } from '@theia/plugin-ext/src/common/plugin-api-rpc';
import { Uri } from '@theia/plugin';
import { overrideUri } from './che-content-aware-utils';

export class RemoteWebview extends WebviewImpl {}

export class WebviewsContentAware {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably will need to spend some times on bringing DI in upstream for the plug-in host

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to digg into this thing and from what I saw, this requires some refactoring in plugin initializing mechanism. Am I right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static makeWebviewsContentAware(webviewExt: WebviewsExtImpl): void {
const webviewsContentAware = new WebviewsContentAware();
webviewsContentAware.overrideVSCodeResourceScheme(webviewExt);
}

overrideVSCodeResourceScheme(webviewExt: WebviewsExtImpl): void {
this.rebind$createWebview(webviewExt);
}

// Modify WebviewOptions#localResourceRoots by setting remote side car scheme instead of default file
// during webview panel create step. This method activates only when plugin call theia.createWebview
// method from remote sidecar. Normally from theia sidecar this method should not be executed.
//
// localResourceRoots provides paths where extension hosts own resources, styles, scripts, fonts, etc.
private rebind$createWebview(webviewExt: WebviewsExtImpl): void {
const original$createWebview = webviewExt.createWebview.bind(webviewExt);
webviewExt.createWebview = (
viewType: string,
title: string,
showOptions: theia.ViewColumn | theia.WebviewPanelShowOptions,
options: theia.WebviewPanelOptions & theia.WebviewOptions,
plugin: Plugin
) => {
const webviewPanel: theia.WebviewPanel = original$createWebview(
viewType,
title,
showOptions,
options.localResourceRoots
? ({
enableFindWidget: options.enableFindWidget,
retainContextWhenHidden: options.retainContextWhenHidden,
enableScripts: options.enableScripts,
enableCommandUris: options.enableCommandUris,
localResourceRoots: (() => options.localResourceRoots.map(root => overrideUri(root)))(),
portMapping: options.portMapping,
} as theia.WebviewPanelOptions & theia.WebviewOptions)
: options,
plugin
);

this.rebind$asWebviewUri(webviewPanel.webview);
this.rebind$_htmlSetter(webviewPanel.webview);

return webviewPanel;
};
}

// Method theia.Webview#asWebviewUri is being called from theia container.
// In default flow method returns the path like: /webview/theia-resource/file:///path/to/directory
// with https scheme and authority
private rebind$asWebviewUri(webview: theia.Webview): void {
const original$asWebviewUri = webview.asWebviewUri.bind(webview);
webview.asWebviewUri = (resource: Uri) => original$asWebviewUri(overrideUri(resource));
}

// Browser part perform preprocess initial html content by replacing vscode-resource:/path/to/file
// to https://authority/webview/theia-resource/file/path/to/file. To be able to operate with custom
Copy link
Contributor

@benoitf benoitf Dec 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this solution, but it seems, that this won't work. The problem is extending the WebviewWidget won't get a proper effect, because this code is run on browser part on Theia sidecar and we need to provide the container name from environment variable on the sidecar from plugin.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this solution, but it seems, that this won't work.

sorry @vzhukovs but it's not clear to me if
you think that this will not work OR you have tried and this didn't work
Could you please clarify it? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried. The main problem here is that rebound class will be executing on Theia's container where we don't have any sidecar specific information. As far as rebound class will execute from the browser part we also don't have ability to get environment information. So it better to intercept this method's calls from server side on sidecar directly:

VS Code extension (sidecar server side) > Method intercept and link modification (sidecar server side) > Plugin Ext (sidecar server side > theia browser side) > Theia (theia browser side).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't only rebind only in sidecar usecase ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I saw, I couldn't get this worked

// provided scheme, we can perform replacing the initial html by appending remote sidecar scheme, so
// we will get links look like vscode-resource://scheme/path/to/file. Webview.html organized via
// getter and setter, so we cant really bind method, instead of this, we redefine setter property.
// We need to leave backward compatibility with vscode resources that loads remotely.
private rebind$_htmlSetter(webview: theia.Webview): void {
Object.defineProperty(webview, '_html', {
get: function () {
// @ts-ignore
return this._html;
}.bind(this),
set: function (value: string) {
const sideCarScheme = `file-sidecar-${process.env.CHE_MACHINE_NAME}`;
// @ts-ignore
this._html = value.replace(
/(["'])(vscode|theia)-resource:(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi,
(_, startQuote, resourceType, _1, scheme, path, endQuote) => {
if (scheme) {
return _;
}
return `${startQuote}${resourceType}-resource://${sideCarScheme}${path}${endQuote}`;
}
);
}.bind(this),
});
}
}