Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empty VSCode plugin webview editor windows #13258 #13265

Merged
merged 4 commits into from
Jan 18, 2024
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
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we are using v5 as opposed to v4 here, I assume that we made this choice on purpose to always get the same UUID given the specified value, right? If so I think it would be great to make that even more explicit in the comment. Or is that explicit enough in your opinion, just asking from a very novice point of view.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I've added a comment

}
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
Loading