Move shared webview focus implementation into base class

This commit is contained in:
Matt Bierner
2021-04-08 20:05:22 -07:00
parent ed655c2d2e
commit ecb45f5207
3 changed files with 60 additions and 103 deletions
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { ThrottledDelayer } from 'vs/base/common/async';
import { streamToBuffer } from 'vs/base/common/buffer';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
@@ -98,6 +99,8 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
private readonly _tunnelService: ITunnelService;
protected readonly _environmentService: IWorkbenchEnvironmentService;
private readonly _focusDelayer = this._register(new ThrottledDelayer(10));
constructor(
public readonly id: string,
private readonly options: WebviewOptions,
@@ -528,4 +531,53 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
location: redirect
});
}
public focus(): void {
this.doFocus();
// Handle focus change programmatically (do not rely on event from <webview>)
this.handleFocusChange(true);
}
protected doFocus() {
if (!this.element) {
return;
}
// Clear the existing focus first if not already on the webview.
// This is required because the next part where we set the focus is async.
if (document.activeElement && document.activeElement instanceof HTMLElement && document.activeElement !== this.element) {
// Don't blur if on the webview because this will also happen async and may unset the focus
// after the focus trigger fires below.
document.activeElement.blur();
}
// Workaround for https://github.com/microsoft/vscode/issues/75209
// Electron's webview.focus is async so for a sequence of actions such as:
//
// 1. Open webview
// 1. Show quick pick from command palette
//
// We end up focusing the webview after showing the quick pick, which causes
// the quick pick to instantly dismiss.
//
// Workaround this by debouncing the focus and making sure we are not focused on an input
// when we try to re-focus.
this._focusDelayer.trigger(async () => {
if (!this.isFocused || !this.element) {
return;
}
if (document.activeElement && document.activeElement?.tagName !== 'BODY') {
return;
}
try {
this.elementFocusImpl();
} catch {
// noop
}
this._send('focus');
});
}
protected abstract elementFocusImpl(): void;
}
@@ -23,9 +23,6 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
private _confirmBeforeClose: string;
private readonly _focusDelayer = this._register(new ThrottledDelayer(10));
private _elementFocusImpl!: (options?: FocusOptions | undefined) => void;
constructor(
id: string,
options: WebviewOptions,
@@ -87,7 +84,6 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
element.style.width = '100%';
element.style.height = '100%';
this._elementFocusImpl = () => element.contentWindow?.focus();
element.focus = () => {
this.doFocus();
};
@@ -95,6 +91,10 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
return element;
}
protected elementFocusImpl() {
this.element?.contentWindow?.focus();
}
protected initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions, extraParams?: object) {
const params = {
id: this.id,
@@ -162,51 +162,4 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
}
});
}
public focus(): void {
this.doFocus();
// Handle focus change programmatically (do not rely on event from <webview>)
this.handleFocusChange(true);
}
private doFocus() {
if (!this.element) {
return;
}
// Clear the existing focus first if not already on the webview.
// This is required because the next part where we set the focus is async.
if (document.activeElement && document.activeElement instanceof HTMLElement && document.activeElement !== this.element) {
// Don't blur if on the webview because this will also happen async and may unset the focus
// after the focus trigger fires below.
document.activeElement.blur();
}
// Workaround for https://github.com/microsoft/vscode/issues/75209
// Electron's webview.focus is async so for a sequence of actions such as:
//
// 1. Open webview
// 1. Show quick pick from command palette
//
// We end up focusing the webview after showing the quick pick, which causes
// the quick pick to instantly dismiss.
//
// Workaround this by debouncing the focus and making sure we are not focused on an input
// when we try to re-focus.
this._focusDelayer.trigger(async () => {
if (!this.isFocused || !this.element) {
return;
}
if (document.activeElement && document.activeElement?.tagName !== 'BODY') {
return;
}
try {
this._elementFocusImpl();
} catch {
// noop
}
this._send('focus');
});
}
}
@@ -5,7 +5,6 @@
import { FindInPageOptions, WebviewTag } from 'electron';
import { addDisposableListener } from 'vs/base/browser/dom';
import { ThrottledDelayer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { once } from 'vs/base/common/functional';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -45,9 +44,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
private _webviewFindWidget: WebviewFindWidget | undefined;
private _findStarted: boolean = false;
private readonly _focusDelayer = this._register(new ThrottledDelayer(10));
private _elementFocusImpl!: (options?: FocusOptions | undefined) => void;
constructor(
id: string,
options: WebviewOptions,
@@ -168,7 +164,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
// Wait the end of the ctor when all listeners have been hooked up.
const element = document.createElement('webview');
this._elementFocusImpl = element.focus.bind(element);
element.focus = () => {
this.doFocus();
};
@@ -185,6 +180,10 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
return element;
}
protected elementFocusImpl() {
this.element?.focus();
}
public override set contentOptions(options: WebviewContentOptions) {
this._myLogService.debug(`Webview(${this.id}): will set content options`);
super.contentOptions = options;
@@ -212,53 +211,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
this.element?.send(channel, data);
}
public focus(): void {
this.doFocus();
// Handle focus change programmatically (do not rely on event from <webview>)
this.handleFocusChange(true);
}
private doFocus() {
if (!this.element) {
return;
}
// Clear the existing focus first if not already on the webview.
// This is required because the next part where we set the focus is async.
if (document.activeElement && document.activeElement instanceof HTMLElement && document.activeElement !== this.element) {
// Don't blur if on the webview because this will also happen async and may unset the focus
// after the focus trigger fires below.
document.activeElement.blur();
}
// Workaround for https://github.com/microsoft/vscode/issues/75209
// Electron's webview.focus is async so for a sequence of actions such as:
//
// 1. Open webview
// 1. Show quick pick from command palette
//
// We end up focusing the webview after showing the quick pick, which causes
// the quick pick to instantly dismiss.
//
// Workaround this by debouncing the focus and making sure we are not focused on an input
// when we try to re-focus.
this._focusDelayer.trigger(async () => {
if (!this.isFocused || !this.element) {
return;
}
if (document.activeElement && document.activeElement?.tagName !== 'BODY') {
return;
}
try {
this._elementFocusImpl();
} catch {
// noop
}
this._send('focus');
});
}
protected override style(): void {
super.style();
this.styledFindWidget();