From c6c47da886cfd859bc53567d0e3337dc1f467464 Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Thu, 11 Jan 2024 15:45:19 +0100 Subject: [PATCH 1/4] Empty VSCode plugin webview editor windows #13258 * introduce widget manager method to find a widget based on testing its options * use this in webviews-main to also check pending widgets --- packages/core/src/browser/widget-manager.ts | 25 +++++++++++++++++++ .../src/main/browser/webviews-main.ts | 7 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/core/src/browser/widget-manager.ts b/packages/core/src/browser/widget-manager.ts index 56c1ba61e2dbb..77a4a2bb2e265 100644 --- a/packages/core/src/browser/widget-manager.ts +++ b/packages/core/src/browser/widget-manager.ts @@ -194,6 +194,31 @@ export class WidgetManager { return widget; } + /** + * Finds a widget that matches the given test predicate. + * @param factoryId The widget factory id. + * @param predicate The test predicate. + * + * @returns a promise resolving to the widget if available, else `undefined`. + */ + async findWidget(factoryId: string, predicate: (options?: any) => boolean): Promise { + for (const [key, widget] of this.widgets.entries()) { + if (this.testPredicate(key, factoryId, predicate)) { + return widget as T; + } + } + for (const [key, widget] of this.pendingWidgetPromises.entries()) { + if (this.testPredicate(key, factoryId, predicate)) { + return widget as T; + } + } + } + + protected testPredicate(key: string, factoryId: string, predicate: (options?: any) => boolean): boolean { + const constructionOptions = this.fromKey(key); + return constructionOptions.factoryId === factoryId && predicate(constructionOptions.options); + } + protected doGetWidget(key: string): MaybePromise | undefined { const pendingWidget = this.widgets.get(key) ?? this.pendingWidgetPromises.get(key); if (pendingWidget) { diff --git a/packages/plugin-ext/src/main/browser/webviews-main.ts b/packages/plugin-ext/src/main/browser/webviews-main.ts index aa534de603d0e..cf0f319950ba4 100644 --- a/packages/plugin-ext/src/main/browser/webviews-main.ts +++ b/packages/plugin-ext/src/main/browser/webviews-main.ts @@ -269,7 +269,12 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable { } private async tryGetWebview(id: string): Promise { - const webview = this.widgetManager.getWidgets(WebviewWidget.FACTORY_ID).find(widget => widget instanceof WebviewWidget && widget.identifier.id === id) as WebviewWidget + const webview = await this.widgetManager.findWidget(WebviewWidget.FACTORY_ID, options => { + if (options) { + return options.id === id; + } + return false; + }) || await this.widgetManager.getWidget(CustomEditorWidget.FACTORY_ID, { id }); return webview; } From 50fc6b0f3ff3765ca85de4a13c7d1d9b766ffb37 Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Fri, 12 Jan 2024 13:40:13 +0100 Subject: [PATCH 2/4] Empty VSCode plugin webview editor windows #13258 * fix {{uuid}} replacements * use an origin that is computed based on the view type --- packages/plugin-ext/src/common/hash-uuid.ts | 27 +++++++++++++++++++ .../browser/webview/webview-widget-factory.ts | 17 ++---------- .../plugin-ext/src/plugin/webview-views.ts | 3 ++- packages/plugin-ext/src/plugin/webviews.ts | 17 +++++++----- 4 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 packages/plugin-ext/src/common/hash-uuid.ts diff --git a/packages/plugin-ext/src/common/hash-uuid.ts b/packages/plugin-ext/src/common/hash-uuid.ts new file mode 100644 index 0000000000000..db6a7a7086fc1 --- /dev/null +++ b/packages/plugin-ext/src/common/hash-uuid.ts @@ -0,0 +1,27 @@ +// ***************************************************************************** +// Copyright (C) 2024 EclipseSource 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 +// ***************************************************************************** + +import { v5 } from 'uuid'; + +const NAMESPACE = '4c90ee4f-d952-44b1-83ca-f04121ab8e05'; +/** + * This function will hash the given value. The result will be a uuid. + * @param value the string to hash + * @returns a uuid + */ +export function hashValue(value: string): string { + return v5(value, NAMESPACE); +} diff --git a/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts b/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts index aec77f37c448e..3df41ad91a5e3 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts @@ -17,8 +17,7 @@ import { interfaces } from '@theia/core/shared/inversify'; import { WebviewWidget, WebviewWidgetIdentifier, WebviewWidgetExternalEndpoint } from './webview'; import { WebviewEnvironment } from './webview-environment'; -import { StorageService } from '@theia/core/lib/browser'; -import { v4 } from 'uuid'; +import { hashValue } from '../../../common/hash-uuid'; export class WebviewWidgetFactory { @@ -32,7 +31,7 @@ export class WebviewWidgetFactory { async createWidget(identifier: WebviewWidgetIdentifier): Promise { const externalEndpoint = await this.container.get(WebviewEnvironment).externalEndpoint(); - let endpoint = externalEndpoint.replace('{{uuid}}', identifier.viewId ? await this.getOrigin(this.container.get(StorageService), identifier.viewId) : identifier.id); + let endpoint = externalEndpoint.replace('{{uuid}}', identifier.viewId ? hashValue(identifier.viewId) : identifier.id); if (endpoint[endpoint.length - 1] === '/') { endpoint = endpoint.slice(0, endpoint.length - 1); } @@ -42,16 +41,4 @@ export class WebviewWidgetFactory { return child.get(WebviewWidget); } - protected async getOrigin(storageService: StorageService, viewId: string): Promise { - const key = 'plugin-view-registry.origin.' + viewId; - const origin = await storageService.getData(key); - if (!origin) { - const newOrigin = v4(); - storageService.setData(key, newOrigin); - return newOrigin; - } else { - return origin; - } - } - } diff --git a/packages/plugin-ext/src/plugin/webview-views.ts b/packages/plugin-ext/src/plugin/webview-views.ts index 04d66415b79e3..259c7fa38730c 100644 --- a/packages/plugin-ext/src/plugin/webview-views.ts +++ b/packages/plugin-ext/src/plugin/webview-views.ts @@ -27,6 +27,7 @@ import { WebviewImpl, WebviewsExtImpl } from './webviews'; import { WebviewViewProvider } from '@theia/plugin'; import { Emitter, Event } from '@theia/core/lib/common/event'; import * as theia from '@theia/plugin'; +import { hashValue } from '../common/hash-uuid'; export class WebviewViewsExtImpl implements WebviewViewsExt { @@ -82,7 +83,7 @@ export class WebviewViewsExtImpl implements WebviewViewsExt { const { provider, plugin } = entry; - const webviewNoPanel = this.webviewsExt.createNewWebview({}, plugin, handle); + const webviewNoPanel = this.webviewsExt.createNewWebview({}, plugin, handle, hashValue(viewType)); const revivedView = new WebviewViewExtImpl(handle, this.proxy, viewType, title, webviewNoPanel, true); this.webviewViews.set(handle, revivedView); await provider.resolveWebviewView(revivedView, { state }, cancellation); diff --git a/packages/plugin-ext/src/plugin/webviews.ts b/packages/plugin-ext/src/plugin/webviews.ts index c107ced1af6be..90959d9bda290 100644 --- a/packages/plugin-ext/src/plugin/webviews.ts +++ b/packages/plugin-ext/src/plugin/webviews.ts @@ -23,6 +23,7 @@ import { fromViewColumn, toViewColumn, toWebviewPanelShowOptions } from './type- import { Disposable, WebviewPanelTargetArea, URI } from './types-impl'; import { WorkspaceExtImpl } from './workspace'; import { PluginIconPath } from './plugin-icon-path'; +import { hashValue } from '../common/hash-uuid'; export class WebviewsExtImpl implements WebviewsExt { private readonly proxy: WebviewsMain; @@ -96,7 +97,7 @@ export class WebviewsExtImpl implements WebviewsExt { } const { serializer, plugin } = entry; - const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin); + const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin, hashValue(viewType)); const revivedPanel = new WebviewPanelImpl(viewId, this.proxy, viewType, title, toViewColumn(viewState.position)!, options, webview); revivedPanel.setActive(viewState.active); revivedPanel.setVisible(viewState.visible); @@ -131,7 +132,7 @@ export class WebviewsExtImpl implements WebviewsExt { throw new Error('Webviews are not initialized'); } const webviewShowOptions = toWebviewPanelShowOptions(showOptions); - const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin); + const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin, hashValue(viewType)); const panel = new WebviewPanelImpl(viewId, this.proxy, viewType, title, webviewShowOptions, options, webview); this.webviewPanels.set(viewId, panel); return panel; @@ -140,12 +141,13 @@ export class WebviewsExtImpl implements WebviewsExt { createNewWebview( options: theia.WebviewPanelOptions & theia.WebviewOptions, plugin: Plugin, - viewId: string + viewId: string, + origin?: string ): WebviewImpl { if (!this.initData) { throw new Error('Webviews are not initialized'); } - const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin); + const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin, origin); this.webviews.set(viewId, webview); return webview; } @@ -201,7 +203,8 @@ export class WebviewImpl implements theia.Webview { options: theia.WebviewOptions, private readonly initData: WebviewInitData, private readonly workspace: WorkspaceExtImpl, - readonly plugin: Plugin + readonly plugin: Plugin, + private readonly origin?: string ) { this._options = options; } @@ -219,12 +222,12 @@ export class WebviewImpl implements theia.Webview { .replace('{{scheme}}', resource.scheme) .replace('{{authority}}', resource.authority) .replace('{{path}}', resource.path.replace(/^\//, '')) - .replace('{{uuid}}', this.viewId); + .replace('{{uuid}}', this.origin ?? this.viewId); return URI.parse(uri); } get cspSource(): string { - return this.initData.webviewCspSource.replace('{{uuid}}', this.viewId); + return this.initData.webviewCspSource.replace('{{uuid}}', this.origin ?? this.viewId); } get html(): string { From 63efe7a0123f2115f1bf2f64a50d8e3605a4950a Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Mon, 15 Jan 2024 13:33:20 +0100 Subject: [PATCH 3/4] Empty VSCode plugin webview editor windows #13258 * move uuid v5 hash method to core --- packages/core/src/common/uuid.ts | 12 +++++++++ packages/plugin-ext/src/common/hash-uuid.ts | 27 ------------------- .../browser/webview/webview-widget-factory.ts | 2 +- .../plugin-ext/src/plugin/webview-views.ts | 2 +- packages/plugin-ext/src/plugin/webviews.ts | 2 +- 5 files changed, 15 insertions(+), 30 deletions(-) delete mode 100644 packages/plugin-ext/src/common/hash-uuid.ts diff --git a/packages/core/src/common/uuid.ts b/packages/core/src/common/uuid.ts index 1325fa7249d83..d99de4b73ab16 100644 --- a/packages/core/src/common/uuid.ts +++ b/packages/core/src/common/uuid.ts @@ -21,6 +21,8 @@ // based on https://github.com/microsoft/vscode/blob/1.72.2/src/vs/base/common/uuid.ts +import { v5 } from 'uuid'; + const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; export function isUUID(value: string): boolean { @@ -97,3 +99,13 @@ export const generateUuid = (function (): () => string { return result; }; })(); + +const NAMESPACE = '4c90ee4f-d952-44b1-83ca-f04121ab8e05'; +/** + * This function will hash the given value using SHA1. The result will be a uuid. + * @param value the string to hash + * @returns a uuid + */ +export function hashValue(value: string): string { + return v5(value, NAMESPACE); +} diff --git a/packages/plugin-ext/src/common/hash-uuid.ts b/packages/plugin-ext/src/common/hash-uuid.ts deleted file mode 100644 index db6a7a7086fc1..0000000000000 --- a/packages/plugin-ext/src/common/hash-uuid.ts +++ /dev/null @@ -1,27 +0,0 @@ -// ***************************************************************************** -// Copyright (C) 2024 EclipseSource 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 -// ***************************************************************************** - -import { v5 } from 'uuid'; - -const NAMESPACE = '4c90ee4f-d952-44b1-83ca-f04121ab8e05'; -/** - * This function will hash the given value. The result will be a uuid. - * @param value the string to hash - * @returns a uuid - */ -export function hashValue(value: string): string { - return v5(value, NAMESPACE); -} diff --git a/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts b/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts index 3df41ad91a5e3..6b029253af7da 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview-widget-factory.ts @@ -17,7 +17,7 @@ import { interfaces } from '@theia/core/shared/inversify'; import { WebviewWidget, WebviewWidgetIdentifier, WebviewWidgetExternalEndpoint } from './webview'; import { WebviewEnvironment } from './webview-environment'; -import { hashValue } from '../../../common/hash-uuid'; +import { hashValue } from '@theia/core/lib/common/uuid'; export class WebviewWidgetFactory { diff --git a/packages/plugin-ext/src/plugin/webview-views.ts b/packages/plugin-ext/src/plugin/webview-views.ts index 259c7fa38730c..16d659c691c29 100644 --- a/packages/plugin-ext/src/plugin/webview-views.ts +++ b/packages/plugin-ext/src/plugin/webview-views.ts @@ -27,7 +27,7 @@ import { WebviewImpl, WebviewsExtImpl } from './webviews'; import { WebviewViewProvider } from '@theia/plugin'; import { Emitter, Event } from '@theia/core/lib/common/event'; import * as theia from '@theia/plugin'; -import { hashValue } from '../common/hash-uuid'; +import { hashValue } from '@theia/core/lib/common/uuid'; export class WebviewViewsExtImpl implements WebviewViewsExt { diff --git a/packages/plugin-ext/src/plugin/webviews.ts b/packages/plugin-ext/src/plugin/webviews.ts index 90959d9bda290..8a1fe9da7f6a0 100644 --- a/packages/plugin-ext/src/plugin/webviews.ts +++ b/packages/plugin-ext/src/plugin/webviews.ts @@ -23,7 +23,7 @@ import { fromViewColumn, toViewColumn, toWebviewPanelShowOptions } from './type- import { Disposable, WebviewPanelTargetArea, URI } from './types-impl'; import { WorkspaceExtImpl } from './workspace'; import { PluginIconPath } from './plugin-icon-path'; -import { hashValue } from '../common/hash-uuid'; +import { hashValue } from '@theia/core/lib/common/uuid'; export class WebviewsExtImpl implements WebviewsExt { private readonly proxy: WebviewsMain; From b39dd636f42b68cd599c9c25b92f0f2880df50b4 Mon Sep 17 00:00:00 2001 From: Johannes Faltermeier Date: Mon, 15 Jan 2024 13:33:20 +0100 Subject: [PATCH 4/4] Empty VSCode plugin webview editor windows #13258 * Add comment --- packages/core/src/common/uuid.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/common/uuid.ts b/packages/core/src/common/uuid.ts index d99de4b73ab16..1994e29282d30 100644 --- a/packages/core/src/common/uuid.ts +++ b/packages/core/src/common/uuid.ts @@ -107,5 +107,6 @@ const NAMESPACE = '4c90ee4f-d952-44b1-83ca-f04121ab8e05'; * @returns a uuid */ export function hashValue(value: string): string { + // as opposed to v4, v5 is deterministic and uses SHA1 hashing return v5(value, NAMESPACE); }