From ecb45f5207c126ff5083ba8a746ddd5fbcc126d0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 8 Apr 2021 20:05:22 -0700 Subject: [PATCH] Move shared webview focus implementation into base class --- .../webview/browser/baseWebviewElement.ts | 52 +++++++++++++++++ .../contrib/webview/browser/webviewElement.ts | 55 ++---------------- .../electron-browser/webviewElement.ts | 56 ++----------------- 3 files changed, 60 insertions(+), 103 deletions(-) diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 29b3100c87e..55910cf0bee 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -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 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 extends Disposable { location: redirect }); } + + public focus(): void { + this.doFocus(); + + // Handle focus change programmatically (do not rely on event from ) + 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; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index fe31f78258d..2077463f139 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -23,9 +23,6 @@ export class IFrameWebview extends BaseWebview 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 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 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 implements Web } }); } - - public focus(): void { - this.doFocus(); - - // Handle focus change programmatically (do not rely on event from ) - 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'); - }); - } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 4a46bde28b1..a6e2cfb2931 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -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 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 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 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 impleme this.element?.send(channel, data); } - public focus(): void { - this.doFocus(); - - // Handle focus change programmatically (do not rely on event from ) - 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();