diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 9351d89b4bdce..132d36358cd98 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -153,7 +153,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc return; } - webviewInput.webview.onDispose(() => { + webviewInput.webview.onDidDispose(() => { // If the model is still dirty, make sure we have time to save it if (modelRef.object.isDirty()) { const sub = modelRef.object.onDidChangeDirty(() => { diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 84107c980a3c3..f8541ed9a804f 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -140,7 +140,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc this._webviewInputs.add(handle, input); this._mainThreadWebviews.addWebview(handle, input.webview); - input.webview.onDispose(() => { + input.webview.onDidDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index 8180b9972ee7e..904279d527a9b 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -69,7 +69,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); - disposables.add(webview.onDispose(() => { + disposables.add(webview.onDidDispose(() => { disposables.dispose(); this._webviews.delete(handle); })); diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 4423555621a10..04ebf3bfae03c 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -171,8 +171,10 @@ export abstract class BaseWebview extends Disposable { if (this.element) { this.element.remove(); } - this._element = undefined; + + this._onDidDispose.fire(); + super.dispose(); } @@ -203,6 +205,9 @@ export abstract class BaseWebview extends Disposable { private readonly _onDidBlur = this._register(new Emitter()); public readonly onDidBlur = this._onDidBlur.event; + private readonly _onDidDispose = this._register(new Emitter()); + public readonly onDidDispose = this._onDidDispose.event; + public postMessage(data: any): void { this._send('message', data); } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 7d2046676ff61..72c446f70049d 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -59,12 +59,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv return !!this._webview.value?.isFocused; } - private readonly _onDispose = this._register(new Emitter()); - public onDispose = this._onDispose.event; + private readonly _onDidDispose = this._register(new Emitter()); + public onDidDispose = this._onDidDispose.event; dispose() { this.container.remove(); - this._onDispose.fire(); + this._onDidDispose.fire(); super.dispose(); } diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index 25d4ea314fd2a..9144fa8eea0ff 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MultiCommand, RedoCommand, SelectAllCommand, ServicesAccessor, UndoCommand } from 'vs/editor/browser/editorExtensions'; +import { MultiCommand, RedoCommand, SelectAllCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; @@ -12,11 +12,12 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { Webview, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { getActiveWebviewEditor, HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; +import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; +import { WebviewService } from './webviewService'; import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; (Registry.as(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create( @@ -29,6 +30,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor WebviewEditorInputFactory.ID, WebviewEditorInputFactory); +registerSingleton(IWebviewService, WebviewService, true); registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); registerAction2(ShowWebViewEditorFindWidgetAction); @@ -38,32 +40,13 @@ registerAction2(WebViewEditorFindPreviousCommand); registerAction2(ReloadWebviewAction); -function getInnerActiveWebview(accessor: ServicesAccessor): Webview | undefined { - const webview = getActiveWebviewEditor(accessor); - if (!webview) { - return undefined; - } - - // Make sure we are really focused on the webview - if (!['WEBVIEW', 'IFRAME'].includes(document.activeElement?.tagName ?? '')) { - return undefined; - } - - if ('getInnerWebview' in (webview as WebviewOverlay)) { - const innerWebview = (webview as WebviewOverlay).getInnerWebview(); - return innerWebview; - } - - return webview; -} - - const PRIORITY = 100; function overrideCommandForWebview(command: MultiCommand | undefined, f: (webview: Webview) => void) { command?.addImplementation(PRIORITY, accessor => { - const webview = getInnerActiveWebview(accessor); - if (webview && webview.isFocused) { + const webviewService = accessor.get(IWebviewService); + const webview = webviewService.activeWebview; + if (webview?.isFocused) { f(webview); return true; } diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 4df30f8be7b9a..61c085a241c1f 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -36,6 +36,8 @@ export interface WebviewIcons { export interface IWebviewService { readonly _serviceBrand: undefined; + readonly activeWebview: Webview | undefined; + createWebviewElement( id: string, options: WebviewOptions, @@ -100,6 +102,8 @@ export interface Webview extends IDisposable { readonly onDidFocus: Event; readonly onDidBlur: Event; + readonly onDidDispose: Event; + readonly onDidClickLink: Event; readonly onDidScroll: Event<{ scrollYPercentage: number }>; readonly onDidWheel: Event; @@ -142,8 +146,6 @@ export interface WebviewOverlay extends Webview { readonly container: HTMLElement; options: WebviewOptions; - readonly onDispose: Event; - claim(owner: any): void; release(owner: any): void; diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 688b513948d22..a26e64ab978b5 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -3,34 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWebviewService, WebviewContentOptions, WebviewOverlay, WebviewElement, WebviewIcons, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; -import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; import { WebviewIconManager } from './webviewIconManager'; export class WebviewService implements IWebviewService { declare readonly _serviceBrand: undefined; - private readonly _webviewThemeDataProvider: WebviewThemeDataProvider; + protected readonly _webviewThemeDataProvider: WebviewThemeDataProvider; + private readonly _iconManager: WebviewIconManager; constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, ) { this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); this._iconManager = this._instantiationService.createInstance(WebviewIconManager); } + private _activeWebview?: Webview; + public get activeWebview() { return this._activeWebview; } + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewElement { - return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + const webview = this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + this.addWebviewListeners(webview); + return webview; } createWebviewOverlay( @@ -39,12 +44,27 @@ export class WebviewService implements IWebviewService { contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewOverlay { - return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); + const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); + this.addWebviewListeners(webview); + return webview; } setIcons(id: string, iconPath: WebviewIcons | undefined): void { this._iconManager.setIcons(id, iconPath); } -} -registerSingleton(IWebviewService, WebviewService, true); + protected addWebviewListeners(webview: Webview) { + webview.onDidFocus(() => { + this._activeWebview = webview; + }); + + const onBlur = () => { + if (this._activeWebview === webview) { + this._activeWebview = undefined; + } + }; + + webview.onDidBlur(onBlur); + webview.onDidDispose(onBlur); + } +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index 44f3d38b9e9e3..480f221276845 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -6,24 +6,19 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; -import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; -import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewIconManager } from 'vs/workbench/contrib/webview/browser/webviewIconManager'; +import { WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; import { ElectronIframeWebview } from 'vs/workbench/contrib/webview/electron-browser/iframeWebviewElement'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -export class ElectronWebviewService implements IWebviewService { +export class ElectronWebviewService extends WebviewService { declare readonly _serviceBrand: undefined; - private readonly _webviewThemeDataProvider: WebviewThemeDataProvider; - private readonly _iconManager: WebviewIconManager; - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService private readonly _configService: IConfigurationService, ) { - this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); - this._iconManager = this._instantiationService.createInstance(WebviewIconManager); + super(instantiationService); } createWebviewElement( @@ -33,7 +28,9 @@ export class ElectronWebviewService implements IWebviewService { extension: WebviewExtensionDescription | undefined, ): WebviewElement { const useIframes = this._configService.getValue('webview.experimental.useIframes'); - return this._instantiationService.createInstance(useIframes ? ElectronIframeWebview : ElectronWebviewBasedWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + const webview = this._instantiationService.createInstance(useIframes ? ElectronIframeWebview : ElectronWebviewBasedWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + this.addWebviewListeners(webview); + return webview; } createWebviewOverlay( @@ -42,10 +39,8 @@ export class ElectronWebviewService implements IWebviewService { contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewOverlay { - return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); - } - - setIcons(id: string, iconPath: WebviewIcons | undefined): void { - this._iconManager.setIcons(id, iconPath); + const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); + this.addWebviewListeners(webview); + return webview; } } diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index 1159fe856e7ac..181d779e37655 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -599,6 +599,8 @@ registerSingleton(IKeymapService, SimpleKeymapService); class SimpleWebviewService implements IWebviewService { declare readonly _serviceBrand: undefined; + readonly activeWebview = undefined; + createWebviewElement(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewElement { throw new Error('Method not implemented.'); } createWebviewOverlay(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewOverlay { throw new Error('Method not implemented.'); } setIcons(id: string, value: WebviewIcons | undefined): void { }