Skip to content

Commit

Permalink
Empty VSCode plugin webview editor windows #13258 (#13265)
Browse files Browse the repository at this point in the history
* introduce widget manager method to find a widget based on testing its options
* use this in webviews-main to also check pending widgets
* use an origin that is computed based on the view type
* add uuid v5 hash method to core
  • Loading branch information
jfaltermeier authored Jan 18, 2024
1 parent f4ad0cd commit eb8148b
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 24 deletions.
25 changes: 25 additions & 0 deletions packages/core/src/browser/widget-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends Widget>(factoryId: string, predicate: (options?: any) => boolean): Promise<T | undefined> {
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<T extends Widget>(key: string): MaybePromise<T> | undefined {
const pendingWidget = this.widgets.get(key) ?? this.pendingWidgetPromises.get(key);
if (pendingWidget) {
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/common/uuid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -97,3 +99,14 @@ 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 {
// as opposed to v4, v5 is deterministic and uses SHA1 hashing
return v5(value, NAMESPACE);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 '@theia/core/lib/common/uuid';

export class WebviewWidgetFactory {

Expand All @@ -32,7 +31,7 @@ export class WebviewWidgetFactory {

async createWidget(identifier: WebviewWidgetIdentifier): Promise<WebviewWidget> {
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);
}
Expand All @@ -42,16 +41,4 @@ export class WebviewWidgetFactory {
return child.get(WebviewWidget);
}

protected async getOrigin(storageService: StorageService, viewId: string): Promise<string> {
const key = 'plugin-view-registry.origin.' + viewId;
const origin = await storageService.getData<string>(key);
if (!origin) {
const newOrigin = v4();
storageService.setData(key, newOrigin);
return newOrigin;
} else {
return origin;
}
}

}
7 changes: 6 additions & 1 deletion packages/plugin-ext/src/main/browser/webviews-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable {
}

private async tryGetWebview(id: string): Promise<WebviewWidget | undefined> {
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>(WebviewWidget.FACTORY_ID, options => {
if (options) {
return options.id === id;
}
return false;
})
|| await this.widgetManager.getWidget<CustomEditorWidget>(CustomEditorWidget.FACTORY_ID, <WebviewWidgetIdentifier>{ id });
return webview;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-ext/src/plugin/webview-views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '@theia/core/lib/common/uuid';

export class WebviewViewsExtImpl implements WebviewViewsExt {

Expand Down Expand Up @@ -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);
Expand Down
17 changes: 10 additions & 7 deletions packages/plugin-ext/src/plugin/webviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '@theia/core/lib/common/uuid';

export class WebviewsExtImpl implements WebviewsExt {
private readonly proxy: WebviewsMain;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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 {
Expand Down

0 comments on commit eb8148b

Please sign in to comment.