diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index e52a8969269ae639991c41e391c73f3723ba4654..05cfa4cdc4c2d7e995ffe22aff8e94c89d107916 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 e87a48fc9d91605213f2ec1991933ffb04128865..d26b5440d4791fda895fb5e1f14e97fe3f273b28 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 cc83d78934055a1d4b4b6a30103e611f320809ee..aaa6a49734cea47ce9a1aab32cd84f7c60b9b9b6 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 273eaec4fcee835d6317dc726a839ec1b8a09586..b4803e191fa05f0b69605036d4e7607a94e9f281 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 af7c52ebdac1e74f01a747a53447b8d8de094fa6..ac53ce590e20e0f75016ae2bb88e9db314102046 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 1dc234fe681c9670836675a637666f02e52a5144..63585fc25c2109a28657a15e94de700e9325340a 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 23f08be0281628e30be8acdfe1b2310cfdd89b2e..8d26680d73ab40c3c286f32407bfd91f95b21bca 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 77a8253ff661f518f2c092f6a873abeb40467477..9dfa9721021f851bd5af2b9690a98cbc2ae1bc2a 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 2b1140644202469c988b15ec7a8ac59ccb7eeeef..e882669a573ec334a7fbb2382d4b17dd79d11a8b 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 66f9f0b8e253ce8a717a75a2b6212d04203359be..6c565b87ada7522113eabc528d76ce0fa27b8472 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 6064f07dd959f07f076af3dec7218247a1ba9ee8..1a5fa997a1e824f4ab00b4cd542a7a0ba685130b 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 ed7e22ab84d3d63ce612c52aaf93d5be8e322491..4c202ecef2cc10bb20ecc3ef00fb40552fa7d185 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 58448a143cb76b2e0a8e13b821710a4150a21037..a4125927d7b49c757c0816d4ed2088752fe1dbbe 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 bf459a3c69409d682f8a8c44a1c7f189fcba6b9d..bcc186c375ec6724203fe4c041e04e659c5cf1e3 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 e8ccaee18816086103338479b85b1358e74fb892..aa7ffc5d9bcb0849baf9f36c94278383299dbda4 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 {