diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index e52a8969269..05cfa4cdc4c 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -88,7 +88,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const disposables = new DisposableStore(); - const webview = this._webviewService.createWebview({ + const webview = this._webviewService.createWebview('' + handle, { enableFindWidget: false, allowSvgs: false, extension: { id: extensionId, location: URI.revive(extensionLocation) } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index e87a48fc9d9..d26b5440d47 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -93,7 +93,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn); } - const webview = this._webviewEditorService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), { + const webview = this._webviewEditorService.createWebview(handle, this.getInternalWebviewViewType(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), { location: URI.revive(extensionLocation), id: extensionId }, this.createWebviewEventDelegate(handle)) as WebviewEditorInput; @@ -212,7 +212,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._revivers.delete(viewType); } - private getInternalWebviewId(viewType: string): string { + private getInternalWebviewViewType(viewType: string): string { return `mainThreadWebview-${viewType}`; } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index cc83d789340..aaa6a49734c 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -40,7 +40,7 @@ export class ExtHostWebview implements vscode.Webview { } public get cspSource(): string { - return this._initData.webviewCspSource; + return this._initData.webviewCspSource.replace('{{uuid}}', this._handle); } public get html(): string { diff --git a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts index 273eaec4fce..b4803e191fa 100644 --- a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts @@ -26,6 +26,7 @@ import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { generateUuid } from 'vs/base/common/uuid'; function renderBody( body: string, @@ -91,6 +92,7 @@ export class ReleaseNotesManager { this._webviewEditorService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false); } else { this._currentReleaseNotes = this._webviewEditorService.createWebview( + generateUuid(), 'releaseNotes', title, { group: ACTIVE_GROUP, preserveFocus: false }, diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index af7c52ebdac..ac53ce590e2 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -4,7 +4,7 @@ + content="default-src 'none'; script-src 'self'; frame-src 'self'; style-src 'unsafe-inline'; worker-src 'self';" /> diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 1dc234fe681..63585fc25c2 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -367,7 +367,7 @@ // seeing the service worker applying properly. // Fake load an empty on the correct origin and then write real html // into it to get around this. - newFrame.src = `/fake.html?id=${ID}`; + newFrame.src = `./fake.html?id=${ID}`; } newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; document.body.appendChild(newFrame); diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index 23f08be0281..8d26680d73a 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ const VERSION = 1; +const rootPath = self.location.pathname.replace(/\/service-worker.js$/, ''); + /** * Root path for resources */ -const resourceRoot = '/vscode-resource'; +const resourceRoot = rootPath + '/vscode-resource'; const resolveTimeout = 30000; @@ -179,7 +181,7 @@ async function processResourceRequest(event, requestUrl) { } const webviewId = getWebviewIdForClient(client); - const resourcePath = requestUrl.pathname.replace(resourceRoot, ''); + const resourcePath = requestUrl.pathname.startsWith(resourceRoot + '/') ? requestUrl.pathname.slice(resourceRoot.length) : requestUrl.pathname; function resolveResourceEntry(entry) { if (!entry) { @@ -269,6 +271,6 @@ async function getOuterIframeClient(webviewId) { const allClients = await self.clients.matchAll({ includeUncontrolled: true }); return allClients.find(client => { const clientUrl = new URL(client.url); - return clientUrl.pathname === '/' && clientUrl.search.match(new RegExp('\\bid=' + webviewId)); + return (clientUrl.pathname === `${rootPath}/` || clientUrl.pathname === `${rootPath}/index.html`) && clientUrl.search.match(new RegExp('\\bid=' + webviewId)); }); } \ No newline at end of file diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 77a8253ff66..9dfa9721021 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -243,7 +243,7 @@ export class WebviewEditor extends BaseEditor { this.findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService); } - this._webview = this._webviewService.createWebview( + this._webview = this._webviewService.createWebview(input.id, { allowSvgs: true, extension: input.extension, diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 2b114064420..e882669a573 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -14,14 +14,13 @@ import { WebviewEvents, WebviewInputOptions } from './webviewEditorService'; import { Webview, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; export class WebviewEditorInput extends EditorInput { - private static handlePool = 0; private static _styleElement?: HTMLStyleElement; - private static _icons = new Map(); + private static _icons = new Map(); private static updateStyleElement( - id: number, + id: string, iconPath: { light: URI, dark: URI } | undefined ) { if (!this._styleElement) { @@ -68,9 +67,9 @@ export class WebviewEditorInput extends EditorInput { readonly location: URI; readonly id: ExtensionIdentifier; }; - private readonly _id: number; constructor( + public readonly id: string, public readonly viewType: string, name: string, options: WebviewInputOptions, @@ -84,8 +83,6 @@ export class WebviewEditorInput extends EditorInput { ) { super(); - this._id = WebviewEditorInput.handlePool++; - this._name = name; this._options = options; this._events = events; @@ -120,7 +117,7 @@ export class WebviewEditorInput extends EditorInput { public getResource(): URI { return URI.from({ scheme: 'webview-panel', - path: `webview-panel/webview-${this._id}` + path: `webview-panel/webview-${this.id}` }); } @@ -147,7 +144,7 @@ export class WebviewEditorInput extends EditorInput { public set iconPath(value: { light: URI, dark: URI } | undefined) { this._iconPath = value; - WebviewEditorInput.updateStyleElement(this._id, value); + WebviewEditorInput.updateStyleElement(this.id, value); } public matches(other: IEditorInput): boolean { @@ -213,7 +210,7 @@ export class WebviewEditorInput extends EditorInput { public get container(): HTMLElement { if (!this._container) { this._container = document.createElement('div'); - this._container.id = `webview-${this._id}`; + this._container.id = `webview-${this.id}`; const part = this._layoutService.getContainer(Parts.EDITOR_PART); part.appendChild(this._container); } @@ -301,6 +298,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput { private _revived: boolean = false; constructor( + id: string, viewType: string, name: string, options: WebviewInputOptions, @@ -313,7 +311,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput { private readonly reviver: (input: WebviewEditorInput) => Promise, @IWorkbenchLayoutService partService: IWorkbenchLayoutService, ) { - super(viewType, name, options, state, events, extension, partService); + super(id, viewType, name, options, state, events, extension, partService); } public async resolve(): Promise { diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index 66f9f0b8e25..6c565b87ada 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -9,6 +9,7 @@ import { WebviewEditorInput } from './webviewEditorInput'; import { IWebviewEditorService, WebviewInputOptions } from './webviewEditorService'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { generateUuid } from 'vs/base/common/uuid'; interface SerializedIconPath { light: string | UriComponents; @@ -67,7 +68,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { const extensionLocation = reviveUri(data.extensionLocation); const extensionId = data.extensionId ? new ExtensionIdentifier(data.extensionId) : undefined; const iconPath = reviveIconPath(data.iconPath); - return this._webviewService.reviveWebview(data.viewType, data.title, iconPath, data.state, data.options, extensionLocation ? { + return this._webviewService.reviveWebview(generateUuid(), data.viewType, data.title, iconPath, data.state, data.options, extensionLocation ? { location: extensionLocation, id: extensionId } : undefined, data.group); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 6064f07dd95..1a5fa997a1e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -26,6 +26,7 @@ export interface IWebviewEditorService { _serviceBrand: any; createWebview( + id: string, viewType: string, title: string, showOptions: ICreateWebViewShowOptions, @@ -38,6 +39,7 @@ export interface IWebviewEditorService { ): WebviewEditorInput; reviveWebview( + id: string, viewType: string, title: string, iconPath: { light: URI, dark: URI } | undefined, @@ -133,6 +135,7 @@ export class WebviewEditorService implements IWebviewEditorService { ) { } public createWebview( + id: string, viewType: string, title: string, showOptions: ICreateWebViewShowOptions, @@ -143,7 +146,7 @@ export class WebviewEditorService implements IWebviewEditorService { }, events: WebviewEvents ): WebviewEditorInput { - const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, title, options, {}, events, extension); + const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, options, {}, events, extension); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group); return webviewInput; } @@ -164,6 +167,7 @@ export class WebviewEditorService implements IWebviewEditorService { } public reviveWebview( + id: string, viewType: string, title: string, iconPath: { light: URI, dark: URI } | undefined, @@ -175,7 +179,7 @@ export class WebviewEditorService implements IWebviewEditorService { }, group: number | undefined, ): WebviewEditorInput { - const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, viewType, title, options, state, {}, extension, async (webview: WebviewEditorInput): Promise => { + const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, id, viewType, title, options, state, {}, extension, async (webview: WebviewEditorInput): Promise => { const didRevive = await this.tryRevive(webview); if (didRevive) { return Promise.resolve(undefined); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index ed7e22ab84d..4c202ecef2c 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -32,11 +32,10 @@ export class IFrameWebview extends Disposable implements Webview { private content: WebviewContent; private _focused = false; - private readonly id: string; - private readonly _portMappingManager: WebviewPortMappingManager; constructor( + private readonly id: string, private _options: WebviewOptions, contentOptions: WebviewContentOptions, @IThemeService themeService: IThemeService, @@ -62,11 +61,9 @@ export class IFrameWebview extends Disposable implements Webview { state: undefined }; - this.id = `webview-${Date.now()}`; - this.element = document.createElement('iframe'); this.element.sandbox.add('allow-scripts', 'allow-same-origin'); - this.element.setAttribute('src', `${environmentService.webviewEndpoint}?id=${this.id}`); + this.element.setAttribute('src', `${this.endpoint}/index.html?id=${this.id}`); this.element.style.border = 'none'; this.element.style.width = '100%'; this.element.style.height = '100%'; @@ -144,6 +141,14 @@ export class IFrameWebview extends Disposable implements Webview { this._register(themeService.onThemeChange(this.style, this)); } + private get endpoint(): string { + const endpoint = this.environmentService.webviewEndpoint!.replace('{{uuid}}', this.id); + if (endpoint[endpoint.length - 1] === '/') { + return endpoint.slice(0, endpoint.length - 1); + } + return endpoint; + } + public mountTo(parent: HTMLElement) { if (this.element) { parent.appendChild(this.element); @@ -174,7 +179,7 @@ export class IFrameWebview extends Disposable implements Webview { private preprocessHtml(value: string): string { return value.replace(/(["'])vscode-resource:([^\s'"]+?)(["'])/gi, (_, startQuote, path, endQuote) => - `${startQuote}${this.environmentService.webviewEndpoint}/vscode-resource${path}${endQuote}`); + `${startQuote}${this.endpoint}/vscode-resource${path}${endQuote}`); } public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 58448a143cb..a4125927d7b 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -15,10 +15,12 @@ export class WebviewService implements IWebviewService { ) { } createWebview( + id: string, options: WebviewOptions, contentOptions: WebviewContentOptions ): Webview { return this._instantiationService.createInstance(WebviewElement, + id, options, contentOptions); } diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index bf459a3c694..bcc186c375e 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -26,6 +26,7 @@ export interface IWebviewService { _serviceBrand: any; createWebview( + id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, ): Webview; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index e8ccaee1881..aa7ffc5d9bc 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -15,6 +15,7 @@ export class WebviewService implements IWebviewService { ) { } createWebview( + _id: string, options: WebviewOptions, contentOptions: WebviewContentOptions ): Webview {