// ***************************************************************************** // Copyright (C) 2021 SAP SE or an SAP affiliate company and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // some code copied and modified from https://github.com/microsoft/vscode/blob/e1f0f8f51390dea5df9096718fb6b647ed5a9534/src/vs/workbench/api/browser/mainThreadWebviewViews.ts import { inject, interfaces } from '@theia/core/shared/inversify'; import { WebviewViewsMain, MAIN_RPC_CONTEXT, WebviewViewsExt } from '../../../common/plugin-api-rpc'; import { RPCProtocol } from '../../../common/rpc-protocol'; import { Disposable, DisposableCollection, ILogger } from '@theia/core'; import { WebviewView } from './webview-views'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; import { WebviewsMainImpl } from '../webviews-main'; import { BadgeService, Widget, WidgetManager } from '@theia/core/lib/browser'; import { PluginViewRegistry } from '../view/plugin-view-registry'; import { ViewBadge } from '@theia/plugin'; import { PluginViewWidget } from '../view/plugin-view-widget'; export class WebviewViewsMainImpl implements WebviewViewsMain, Disposable { protected readonly proxy: WebviewViewsExt; protected readonly toDispose = new DisposableCollection( Disposable.create(() => { /* mark as not disposed */ }) ); protected readonly webviewViews = new Map(); protected readonly webviewViewProviders = new Map(); protected readonly widgetManager: WidgetManager; protected readonly pluginViewRegistry: PluginViewRegistry; protected readonly badgeService: BadgeService; @inject(ILogger) protected readonly logger: ILogger; constructor(rpc: RPCProtocol, container: interfaces.Container, readonly webviewsMain: WebviewsMainImpl ) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WEBVIEW_VIEWS_EXT); this.widgetManager = container.get(WidgetManager); this.pluginViewRegistry = container.get(PluginViewRegistry); this.badgeService = container.get(BadgeService); } dispose(): void { this.toDispose.dispose(); } async $registerWebviewViewProvider(viewType: string, options: { retainContextWhenHidden?: boolean, serializeBuffersForPostMessage: boolean }): Promise { if (this.webviewViewProviders.has(viewType)) { throw new Error(`View provider for ${viewType} already registered`); } const registration = await this.pluginViewRegistry.registerWebviewView(viewType, { resolve: async (webviewView: WebviewView, cancellation: CancellationToken) => { const handle = webviewView.webview.identifier.id; this.webviewViews.set(handle, webviewView); this.webviewsMain.hookWebview(webviewView.webview); let state: string | undefined; if (webviewView.webview.state) { try { state = JSON.parse(webviewView.webview.state); console.log(state); } catch (e) { console.error('Could not load webview state', e, webviewView.webview.state); } } if (options) { webviewView.webview.options = options; } webviewView.onDidChangeVisibility(async visible => { if (visible) { await webviewView.resolve(); } this.proxy.$onDidChangeWebviewViewVisibility(handle, visible); }); webviewView.onDidDispose(() => { this.proxy.$disposeWebviewView(handle); this.webviewViews.delete(handle); }); try { await this.proxy.$resolveWebviewView(handle, viewType, webviewView.title, state, cancellation); } catch (error) { this.logger.error(`Error resolving webview view '${viewType}': ${error}`); webviewView.webview.setHTML('failed to load plugin webview view'); } } }); this.webviewViewProviders.set(viewType, registration); } protected getWebview(handle: string): Widget | undefined { return this.widgetManager.tryGetWidget(handle); } $unregisterWebviewViewProvider(viewType: string): void { const provider = this.webviewViewProviders.get(viewType); if (!provider) { throw new Error(`No view provider for ${viewType} registered`); } provider.dispose(); this.webviewViewProviders.delete(viewType); } $setWebviewViewTitle(handle: string, value: string | undefined): void { const webviewView = this.getWebviewView(handle); webviewView.title = value; } $setWebviewViewDescription(handle: string, value: string | undefined): void { const webviewView = this.getWebviewView(handle); webviewView.description = value; } async $setBadge(handle: string, badge: ViewBadge | undefined): Promise { const webviewView = this.getWebviewView(handle); if (webviewView) { if (!(webviewView.webview.parent instanceof PluginViewWidget)) { throw new Error('Unexpected parent of WebviewViewWidget'); } this.badgeService.showBadge(webviewView.webview.parent, badge); } } $show(handle: string, preserveFocus: boolean): void { const webviewView = this.getWebviewView(handle); webviewView.show(preserveFocus); } protected getWebviewView(handle: string): WebviewView { const webviewView = this.webviewViews.get(handle); if (!webviewView) { throw new Error(`No webview view registered for handle '${handle}'`); } return webviewView; } }