Skip to content

Commit

Permalink
Enable webview commands for webview views
Browse files Browse the repository at this point in the history
Fixes #105670

Previously our webview commands assumed that webviews were always going to be in an editor. This is no longer true with webview views.

To fix this, I've added an `activeWebview` to the `IWebviewService`. This  tracks the currently focused webview.
  • Loading branch information
mjbvz committed Sep 9, 2020
1 parent 6cf52e3 commit 80b1a77
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadCustomEditors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadWebviewPanels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadWebviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,10 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
if (this.element) {
this.element.remove();
}

this._element = undefined;

this._onDidDispose.fire();

super.dispose();
}

Expand Down Expand Up @@ -203,6 +205,9 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
private readonly _onDidBlur = this._register(new Emitter<void>());
public readonly onDidBlur = this._onDidBlur.event;

private readonly _onDidDispose = this._register(new Emitter<void>());
public readonly onDidDispose = this._onDidDispose.event;

public postMessage(data: any): void {
this._send('message', data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv
return !!this._webview.value?.isFocused;
}

private readonly _onDispose = this._register(new Emitter<void>());
public onDispose = this._onDispose.event;
private readonly _onDidDispose = this._register(new Emitter<void>());
public onDidDispose = this._onDidDispose.event;

dispose() {
this.container.remove();
this._onDispose.fire();
this._onDidDispose.fire();
super.dispose();
}

Expand Down
33 changes: 8 additions & 25 deletions src/vs/workbench/contrib/webview/browser/webview.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<IEditorRegistry>(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create(
Expand All @@ -29,6 +30,7 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
WebviewEditorInputFactory.ID,
WebviewEditorInputFactory);

registerSingleton(IWebviewService, WebviewService, true);
registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true);

registerAction2(ShowWebViewEditorFindWidgetAction);
Expand All @@ -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;
}
Expand Down
6 changes: 4 additions & 2 deletions src/vs/workbench/contrib/webview/browser/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface WebviewIcons {
export interface IWebviewService {
readonly _serviceBrand: undefined;

readonly activeWebview: Webview | undefined;

createWebviewElement(
id: string,
options: WebviewOptions,
Expand Down Expand Up @@ -100,6 +102,8 @@ export interface Webview extends IDisposable {

readonly onDidFocus: Event<void>;
readonly onDidBlur: Event<void>;
readonly onDidDispose: Event<void>;

readonly onDidClickLink: Event<string>;
readonly onDidScroll: Event<{ scrollYPercentage: number }>;
readonly onDidWheel: Event<IMouseWheelEvent>;
Expand Down Expand Up @@ -142,8 +146,6 @@ export interface WebviewOverlay extends Webview {
readonly container: HTMLElement;
options: WebviewOptions;

readonly onDispose: Event<void>;

claim(owner: any): void;
release(owner: any): void;

Expand Down
38 changes: 29 additions & 9 deletions src/vs/workbench/contrib/webview/browser/webviewService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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);
}
}
27 changes: 11 additions & 16 deletions src/vs/workbench/contrib/webview/electron-browser/webviewService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -33,7 +28,9 @@ export class ElectronWebviewService implements IWebviewService {
extension: WebviewExtensionDescription | undefined,
): WebviewElement {
const useIframes = this._configService.getValue<string>('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(
Expand All @@ -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;
}
}
2 changes: 2 additions & 0 deletions src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { }
Expand Down

0 comments on commit 80b1a77

Please sign in to comment.