diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 5d5a7d4140d..6e40d8382e1 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -5,6 +5,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -19,14 +20,13 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; -import { ICreateWebViewShowOptions, WebviewInputOptions, IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; -import { Schemas } from 'vs/base/common/network'; -import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; /** * Bi-directional map between webview handles and inputs. @@ -100,10 +100,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews @IExtensionService extensionService: IExtensionService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, - @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @IOpenerService private readonly _openerService: IOpenerService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, @IProductService private readonly _productService: IProductService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, ) { super(); @@ -145,7 +145,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn); } - const webview = this._webviewWorkbenchService.createWebview(handle, webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), reviveWebviewExtension(extensionId, extensionLocation)); + const extension = reviveWebviewExtension(extensionId, extensionLocation); + const webview = this._webviewWorkbenchService.createWebview(handle, webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension); this.hookupWebviewEventDelegate(handle, webview); this._webviewInputs.add(handle, webview); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index aa5b86537fe..1bf33f6314d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -105,7 +105,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -528,7 +528,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTreeViews.createTreeView(viewId, options, extension); }, registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { - return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); + return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer); }, registerWebviewEditorProvider: (viewType: string, provider: vscode.WebviewEditorProvider) => { checkProposedApiEnabled(extension); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index b069b40f56b..2292ebdbf7e 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -7,8 +7,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; -import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; @@ -30,7 +31,8 @@ export class ExtHostWebview implements vscode.Webview { private readonly _proxy: MainThreadWebviewsShape, private _options: vscode.WebviewOptions, private readonly _initData: WebviewInitData, - private readonly _extensionId: ExtensionIdentifier | undefined, + private readonly _workspace: IExtHostWorkspace | undefined, + private readonly _extension: IExtensionDescription, ) { } public dispose() { @@ -56,10 +58,10 @@ export class ExtHostWebview implements vscode.Webview { this.assertNotDisposed(); if (this._html !== value) { this._html = value; - if (this._initData.isExtensionDevelopmentDebug && this._extensionId && !this._hasCalledAsWebviewUri) { + if (this._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { this._hasCalledAsWebviewUri = true; - console.warn(`${this._extensionId.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); + console.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } } this._proxy.$setHtml(this._handle, value); @@ -73,7 +75,7 @@ export class ExtHostWebview implements vscode.Webview { public set options(newOptions: vscode.WebviewOptions) { this.assertNotDisposed(); - this._proxy.$setOptions(this._handle, convertWebviewOptions(newOptions)); + this._proxy.$setOptions(this._handle, convertWebviewOptions(this._extension, this._workspace, newOptions)); this._options = newOptions; } @@ -261,12 +263,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private readonly _proxy: MainThreadWebviewsShape; private readonly _webviewPanels = new Map(); - private readonly _serializers = new Map(); + private readonly _serializers = new Map(); private readonly _editorProviders = new Map(); constructor( mainContext: IMainContext, - private readonly initData: WebviewInitData + private readonly initData: WebviewInitData, + private readonly workspace: IExtHostWorkspace | undefined, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -285,15 +288,16 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }; const handle = ExtHostWebviews.newHandle(); - this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, convertWebviewOptions(options), extension.identifier, extension.extensionLocation); + this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options), extension.identifier, extension.extensionLocation); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, extension.identifier); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; } public registerWebviewPanelSerializer( + extension: IExtensionDescription, viewType: string, serializer: vscode.WebviewPanelSerializer ): vscode.Disposable { @@ -301,7 +305,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { throw new Error(`Serializer for '${viewType}' already registered`); } - this._serializers.set(viewType, serializer); + this._serializers.set(viewType, { serializer, extension }); this._proxy.$registerSerializer(viewType); return new Disposable(() => { @@ -397,12 +401,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions ): Promise { - const serializer = this._serializers.get(viewType); - if (!serializer) { + const entry = this._serializers.get(viewType); + if (!entry) { return Promise.reject(new Error(`No serializer found for '${viewType}'`)); } + const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, undefined); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state)); @@ -425,7 +430,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { return Promise.reject(new Error(`No provider found for '${viewType}'`)); } const { provider, extension } = entry; - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, extension.identifier); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); return Promise.resolve(provider.resolveWebviewEditor(URI.revive(resource), revivedPanel)); @@ -441,7 +446,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } function convertWebviewOptions( - options: vscode.WebviewPanelOptions & vscode.WebviewOptions + extension: IExtensionDescription, + workspace: IExtHostWorkspace | undefined, + options: vscode.WebviewPanelOptions & vscode.WebviewOptions, ): modes.IWebviewOptions { return { ...options, @@ -454,5 +461,17 @@ function convertWebviewOptions( return { webviewPort: x.webviewPort, extensionHostPort: x.extensionHostPort }; }) : undefined, + localResourceRoots: options.localResourceRoots || getDefaultLocalResourceRoots(extension, workspace) }; } + + +function getDefaultLocalResourceRoots( + extension: IExtensionDescription, + workspace: IExtHostWorkspace | undefined, +): URI[] { + return [ + ...(workspace && workspace.getWorkspaceFolders() || []).map(x => x.uri), + extension.extensionLocation, + ]; +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index e5b66c96990..0c7be341038 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -10,10 +10,8 @@ import { values } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -150,7 +148,6 @@ export class WebviewEditorService implements IWebviewWorkbenchService { @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewService private readonly _webviewService: IWebviewService, - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, ) { } public createWebview( @@ -270,24 +267,10 @@ export class WebviewEditorService implements IWebviewWorkbenchService { const webview = this._webviewService.createWebviewEditorOverlay(id, { enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden - }, { - ...options, - localResourceRoots: options.localResourceRoots || this.getDefaultLocalResourceRoots(extension), - }); + }, options); webview.extension = extension; return webview; } - - private getDefaultLocalResourceRoots(extension: undefined | { - location: URI, - id: ExtensionIdentifier - }): URI[] { - const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri); - if (extension) { - rootPaths.push(extension.location); - } - return rootPaths; - } } registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index a2117d7652e..37a88c04dcc 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -11,6 +11,7 @@ import * as vscode from 'vscode'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { URI } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('ExtHostWebview', () => { @@ -22,7 +23,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }); + }, undefined); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -32,21 +33,23 @@ suite('ExtHostWebview', () => { } } + const extension = {} as IExtensionDescription; + const serializerA = new NoopSerializer(); const serializerB = new NoopSerializer(); - const serializerARegistration = extHostWebviews.registerWebviewPanelSerializer(viewType, serializerA); + const serializerARegistration = extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializerA); await extHostWebviews.$deserializeWebviewPanel('x', viewType, 'title', {}, 0 as EditorViewColumn, {}); assert.strictEqual(lastInvokedDeserializer, serializerA); assert.throws( - () => extHostWebviews.registerWebviewPanelSerializer(viewType, serializerB), + () => extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializerB), 'Should throw when registering two serializers for the same view'); serializerARegistration.dispose(); - extHostWebviews.registerWebviewPanelSerializer(viewType, serializerB); + extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializerB); await extHostWebviews.$deserializeWebviewPanel('x', viewType, 'title', {}, 0 as EditorViewColumn, {}); assert.strictEqual(lastInvokedDeserializer, serializerB); @@ -58,7 +61,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }); + }, undefined); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -99,7 +102,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }); + }, undefined); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) {