From c848d2853a76e360f95b0f8898218ecd22b96c7d Mon Sep 17 00:00:00 2001 From: Vladyslav Zhukovskyi Date: Mon, 21 Dec 2020 01:53:01 +0200 Subject: [PATCH] Support webview in dedicate container Signed-off-by: Vladyslav Zhukovskyi --- .../src/node/plugin-remote-init.ts | 5 + .../src/node/webviews-content-aware.ts | 104 ++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 extensions/eclipse-che-theia-plugin-remote/src/node/webviews-content-aware.ts diff --git a/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts b/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts index 29d4ea81e..f54e36c7f 100644 --- a/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts +++ b/extensions/eclipse-che-theia-plugin-remote/src/node/plugin-remote-init.ts @@ -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'; @@ -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) { diff --git a/extensions/eclipse-che-theia-plugin-remote/src/node/webviews-content-aware.ts b/extensions/eclipse-che-theia-plugin-remote/src/node/webviews-content-aware.ts new file mode 100644 index 000000000..a9d292cf0 --- /dev/null +++ b/extensions/eclipse-che-theia-plugin-remote/src/node/webviews-content-aware.ts @@ -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 { + 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 + // 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), + }); + } +}