diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 97e91c1f7d1..2590c31ace2 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -506,7 +506,7 @@ export interface WebviewPanelShowOptions { export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void; - $createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: modes.IWebviewOptions, extensionLocation: UriComponents | undefined): void; + $createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier | undefined, extensionLocation: UriComponents | undefined): void; $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index 16433f0a226..a9202f1f5e5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -94,7 +94,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn); } - const webview = this._webviewService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), URI.revive(extensionLocation), this.createWebviewEventDelegate(handle)); + const webview = this._webviewService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), { + location: URI.revive(extensionLocation), + id: extensionId + }, this.createWebviewEventDelegate(handle)); webview.state = { viewType: viewType, state: undefined @@ -111,7 +114,13 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extensionId.value }); } - $createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: IWebviewOptions, extensionLocation: UriComponents): void { + $createWebviewCodeInset( + handle: WebviewInsetHandle, + symbolId: string, + options: IWebviewOptions, + extensionId: ExtensionIdentifier, + extensionLocation: UriComponents + ): void { // todo@joh main is for the lack of a code-inset service // which we maybe wanna have... this is how it now works // 1) create webview element @@ -122,7 +131,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews WebviewElement, this._layoutService.getContainer(Parts.EDITOR_PART), { - extensionLocation: URI.revive(extensionLocation), + extension: { + location: URI.revive(extensionLocation), + id: extensionId + }, enableFindWidget: false, }, { diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index d99356d64d9..328f1b2b02c 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -1211,7 +1211,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { const webviewHandle = Math.random(); const webview = new ExtHostWebview(webviewHandle, this._webviewProxy, { enableScripts: true }); return this._withAdapter(handle, CodeInsetAdapter, async (adapter, extension) => { - await this._webviewProxy.$createWebviewCodeInset(webviewHandle, symbol.id, { enableCommandUris: true, enableScripts: true }, extension ? extension.extensionLocation : undefined); + await this._webviewProxy.$createWebviewCodeInset(webviewHandle, symbol.id, { enableCommandUris: true, enableScripts: true }, extension ? extension.identifier : undefined, extension ? extension.extensionLocation : undefined); return adapter.resolveCodeInset(symbol, webview, token); }, symbol); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts index c99ae0e8e48..ff987ed90e2 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts @@ -257,9 +257,9 @@ export class WebviewEditor extends BaseEditor { private getDefaultLocalResourceRoots(): URI[] { const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri); - const extensionLocation = (this.input as WebviewEditorInput).extensionLocation; - if (extensionLocation) { - rootPaths.push(extensionLocation); + const extension = (this.input as WebviewEditorInput).extension; + if (extension) { + rootPaths.push(extension.location); } return rootPaths; } @@ -283,7 +283,7 @@ export class WebviewEditor extends BaseEditor { this._layoutService.getContainer(Parts.EDITOR_PART), { allowSvgs: true, - extensionLocation: input.extensionLocation, + extension: input.extension, enableFindWidget: input.options.enableFindWidget }, {}); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts index 93b7532ed29..791a9b9ca3c 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts @@ -7,6 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { WebviewEvents, WebviewInputOptions } from './webviewEditorService'; @@ -63,7 +64,10 @@ export class WebviewEditorInput extends EditorInput { private _scrollYPercentage: number = 0; private _state: any; - public readonly extensionLocation: URI | undefined; + public readonly extension?: { + readonly location: URI; + readonly id: ExtensionIdentifier; + }; private readonly _id: number; constructor( @@ -73,7 +77,10 @@ export class WebviewEditorInput extends EditorInput { options: WebviewInputOptions, state: any, events: WebviewEvents, - extensionLocation: URI | undefined, + extension: undefined | { + readonly location: URI; + readonly id: ExtensionIdentifier; + }, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -89,7 +96,7 @@ export class WebviewEditorInput extends EditorInput { this._options = options; this._events = events; this._state = state; - this.extensionLocation = extensionLocation; + this.extension = extension; } public getTypeId(): string { @@ -313,11 +320,14 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput { options: WebviewInputOptions, state: any, events: WebviewEvents, - extensionLocation: URI | undefined, + extension: undefined | { + readonly location: URI; + readonly id: ExtensionIdentifier + }, public readonly reviver: (input: WebviewEditorInput) => Promise, @IWorkbenchLayoutService partService: IWorkbenchLayoutService, ) { - super(viewType, id, name, options, state, events, extensionLocation, partService); + super(viewType, id, name, options, state, events, extension, partService); } public async resolve(): Promise { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts index cd2b716c90a..d79b00c8037 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts @@ -8,6 +8,7 @@ import { IEditorInputFactory } from 'vs/workbench/common/editor'; 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'; interface SerializedIconPath { light: string | UriComponents; @@ -20,6 +21,7 @@ interface SerializedWebview { readonly title: string; readonly options: WebviewInputOptions; readonly extensionLocation: string | UriComponents | undefined; + readonly extensionId: string | undefined; readonly state: any; readonly iconPath: SerializedIconPath | undefined; readonly group?: number; @@ -45,7 +47,8 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { id: input.getId(), title: input.getName(), options: input.options, - extensionLocation: input.extensionLocation, + extensionLocation: input.extension ? input.extension.location : undefined, + extensionId: input.extension ? input.extension.id.value : undefined, state: input.state, iconPath: input.iconPath ? { light: input.iconPath.light, dark: input.iconPath.dark, } : undefined, group: input.group @@ -64,8 +67,12 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { ): WebviewEditorInput { const data: SerializedWebview = JSON.parse(serializedEditorInput); 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.id, data.title, iconPath, data.state, data.options, extensionLocation, data.group); + return this._webviewService.reviveWebview(data.viewType, data.id, data.title, iconPath, data.state, data.options, extensionLocation ? { + location: extensionLocation, + id: extensionId + } : undefined, data.group); } } function reviveIconPath(data: SerializedIconPath | undefined) { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts index 623eb976b40..8581f7b8975 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts @@ -7,12 +7,13 @@ import { equals } from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; +import { IWebviewOptions, IWebviewPanelOptions } from 'vs/editor/common/modes'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput'; -import { IWebviewOptions, IWebviewPanelOptions } from 'vs/editor/common/modes'; export const IWebviewEditorService = createDecorator('webviewEditorService'); @@ -29,7 +30,10 @@ export interface IWebviewEditorService { title: string, showOptions: ICreateWebViewShowOptions, options: WebviewInputOptions, - extensionLocation: URI | undefined, + extension: undefined | { + location: URI, + id: ExtensionIdentifier + }, events: WebviewEvents ): WebviewEditorInput; @@ -40,7 +44,10 @@ export interface IWebviewEditorService { iconPath: { light: URI, dark: URI } | undefined, state: any, options: WebviewInputOptions, - extensionLocation: URI | undefined, + extension: undefined | { + readonly location: URI, + readonly id?: ExtensionIdentifier + }, group: number | undefined ): WebviewEditorInput; @@ -130,10 +137,13 @@ export class WebviewEditorService implements IWebviewEditorService { title: string, showOptions: ICreateWebViewShowOptions, options: IWebviewOptions, - extensionLocation: URI | undefined, + extension: undefined | { + location: URI, + id: ExtensionIdentifier + }, events: WebviewEvents ): WebviewEditorInput { - const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, undefined, title, options, {}, events, extensionLocation); + const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, undefined, title, options, {}, events, extension); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group); return webviewInput; } @@ -160,10 +170,13 @@ export class WebviewEditorService implements IWebviewEditorService { iconPath: { light: URI, dark: URI } | undefined, state: any, options: WebviewInputOptions, - extensionLocation: URI, + extension: undefined | { + readonly location: URI, + readonly id?: ExtensionIdentifier + }, group: number | undefined, ): WebviewEditorInput { - const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, viewType, id, title, options, state, {}, extensionLocation, async (webview: WebviewEditorInput): Promise => { + const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, viewType, id, 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/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index d6dc03875ec..3e56ee15843 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -19,6 +19,8 @@ import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/the import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; import { areWebviewInputOptionsEqual } from './webviewEditorService'; import { WebviewFindWidget } from './webviewFindWidget'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export interface WebviewPortMapping { readonly port: number; @@ -32,7 +34,10 @@ export interface WebviewPortMapping { export interface WebviewOptions { readonly allowSvgs?: boolean; - readonly extensionLocation?: URI; + readonly extension?: { + readonly location: URI; + readonly id?: ExtensionIdentifier; + }; readonly enableFindWidget?: boolean; } @@ -145,10 +150,14 @@ class WebviewPortMappingProvider extends Disposable { constructor( session: WebviewSession, - mappings: () => ReadonlyArray + mappings: () => ReadonlyArray, + extensionId: ExtensionIdentifier | undefined, + @ITelemetryService telemetryService: ITelemetryService, ) { super(); + let hasLogged = false; + session.onBeforeRequest(async (details) => { const uri = URI.parse(details.url); if (uri.scheme !== 'http' && uri.scheme !== 'https') { @@ -157,6 +166,17 @@ class WebviewPortMappingProvider extends Disposable { const localhostMatch = /^localhost:(\d+)$/.exec(uri.authority); if (localhostMatch) { + if (!hasLogged && extensionId) { + hasLogged = true; + + /* __GDPR__ + "webview.accessLocalhost" : { + "extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryService.publicLog('webview.accessLocalhost', { extension: extensionId.value }); + } + const port = +localhostMatch[1]; for (const mapping of mappings()) { if (mapping.port === port && mapping.port !== mapping.resolvedPort) { @@ -317,6 +337,7 @@ export class WebviewElement extends Disposable { @IThemeService themeService: IThemeService, @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, + @ITelemetryService telemetryService: ITelemetryService, ) { super(); this._webview = document.createElement('webview'); @@ -348,15 +369,16 @@ export class WebviewElement extends Disposable { this._register(new WebviewProtocolProvider( this._webview, - this._options.extensionLocation, + this._options.extension ? this._options.extension.location : undefined, () => (this._contentOptions.localResourceRoots || []), environmentService, fileService)); this._register(new WebviewPortMappingProvider( session, - () => (this._contentOptions.portMappings || []) - )); + () => (this._contentOptions.portMappings || []), + _options.extension ? _options.extension.id : undefined, + telemetryService)); if (!this._options.allowSvgs) {