Skip to content

Commit 8e3bfab

Browse files
committed
Initial support for the webview find widget on web
Fixes microsoft#136473
1 parent dd075fa commit 8e3bfab

File tree

3 files changed

+125
-79
lines changed

3 files changed

+125
-79
lines changed

src/vs/workbench/contrib/webview/browser/pre/main.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,36 @@ onDomReady(() => {
980980
assertIsDefined(target.contentDocument).execCommand(data);
981981
});
982982

983+
hostMessaging.onMessage('find', (_event, data) => {
984+
const target = getActiveFrame();
985+
if (!target) {
986+
return;
987+
}
988+
const didFind = (/** @type {any} */ (target.contentWindow)).find(
989+
data.value,
990+
false,
991+
/* backwards*/ data.previous,
992+
/* wrapAround*/ true,
993+
false,
994+
/*aSearchInFrames*/ true,
995+
false);
996+
hostMessaging.postMessage('did-find', didFind);
997+
});
998+
999+
hostMessaging.onMessage('find-stop', (_event, data) => {
1000+
const target = getActiveFrame();
1001+
if (!target) {
1002+
return;
1003+
}
1004+
1005+
if (!data.clearSelection) {
1006+
const selection = target.contentWindow.getSelection();
1007+
for (let i = 0; i < selection.rangeCount; i++) {
1008+
selection.removeRange(selection.getRangeAt(i));
1009+
}
1010+
}
1011+
});
1012+
9831013
trackFocus({
9841014
onFocus: () => hostMessaging.postMessage('did-focus'),
9851015
onBlur: () => hostMessaging.postMessage('did-blur')

src/vs/workbench/contrib/webview/browser/webviewElement.ts

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { IAction } from 'vs/base/common/actions';
1010
import { ThrottledDelayer } from 'vs/base/common/async';
1111
import { streamToBuffer } from 'vs/base/common/buffer';
1212
import { CancellationTokenSource } from 'vs/base/common/cancellation';
13-
import { Emitter } from 'vs/base/common/event';
13+
import { Emitter, Event } from 'vs/base/common/event';
1414
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1515
import { Schemas } from 'vs/base/common/network';
1616
import { URI } from 'vs/base/common/uri';
@@ -22,6 +22,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2222
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
2323
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
2424
import { IFileService } from 'vs/platform/files/common/files';
25+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2526
import { ILogService } from 'vs/platform/log/common/log';
2627
import { INotificationService } from 'vs/platform/notification/common/notification';
2728
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
@@ -32,6 +33,7 @@ import { asWebviewUri, decodeAuthority, webviewGenericCspSource, webviewRootReso
3233
import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading';
3334
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
3435
import { areWebviewContentOptionsEqual, Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
36+
import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget';
3537
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
3638

3739
export const enum WebviewMessageChannels {
@@ -41,6 +43,7 @@ export const enum WebviewMessageChannels {
4143
didFocus = 'did-focus',
4244
didBlur = 'did-blur',
4345
didLoad = 'did-load',
46+
didFind = 'did-find',
4447
doUpdateState = 'do-update-state',
4548
doReload = 'do-reload',
4649
setConfirmBeforeClose = 'set-confirm-before-close',
@@ -88,7 +91,7 @@ namespace WebviewState {
8891
export type State = typeof Ready | Initializing;
8992
}
9093

91-
export class IFrameWebview extends Disposable implements Webview {
94+
export class IFrameWebview extends Disposable implements Webview, WebviewFindDelegate {
9295

9396
protected get platform(): string { return 'browser'; }
9497

@@ -129,6 +132,9 @@ export class IFrameWebview extends Disposable implements Webview {
129132

130133
private readonly _messageHandlers = new Map<string, Set<(data: any) => void>>();
131134

135+
protected readonly _webviewFindWidget: WebviewFindWidget | undefined;
136+
public readonly checkImeCompletionState = true;
137+
132138
constructor(
133139
public readonly id: string,
134140
private readonly options: WebviewOptions,
@@ -145,6 +151,7 @@ export class IFrameWebview extends Disposable implements Webview {
145151
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
146152
@ITelemetryService private readonly _telemetryService: ITelemetryService,
147153
@ITunnelService private readonly _tunnelService: ITunnelService,
154+
@IInstantiationService instantiationService: IInstantiationService,
148155
) {
149156
super();
150157

@@ -215,6 +222,10 @@ export class IFrameWebview extends Disposable implements Webview {
215222
this.handleFocusChange(false);
216223
}));
217224

225+
this._register(this.on(WebviewMessageChannels.didFind, (didFind: boolean) => {
226+
this._hasFindResult.fire(didFind);
227+
}));
228+
218229
this._register(this.on<{ message: string }>(WebviewMessageChannels.fatalError, (e) => {
219230
notificationService.error(localize('fatalErrorMessage', "Error loading webview: {0}", e.message));
220231
}));
@@ -312,6 +323,11 @@ export class IFrameWebview extends Disposable implements Webview {
312323
}
313324
}));
314325

326+
if (options.enableFindWidget) {
327+
this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this));
328+
this.styledFindWidget();
329+
}
330+
315331
this.initElement(extension, options);
316332
}
317333

@@ -416,9 +432,14 @@ export class IFrameWebview extends Disposable implements Webview {
416432
}
417433

418434
public mountTo(parent: HTMLElement) {
419-
if (this.element) {
420-
parent.appendChild(this.element);
435+
if (!this.element) {
436+
return;
421437
}
438+
439+
if (this._webviewFindWidget) {
440+
parent.appendChild(this._webviewFindWidget.getDomNode()!);
441+
}
442+
parent.appendChild(this.element);
422443
}
423444

424445
protected get webviewContentEndpoint(): string {
@@ -584,6 +605,12 @@ export class IFrameWebview extends Disposable implements Webview {
584605
}
585606

586607
this._send('styles', { styles, activeTheme, themeName: themeLabel });
608+
609+
this.styledFindWidget();
610+
}
611+
612+
private styledFindWidget() {
613+
this._webviewFindWidget?.updateTheme(this.webviewThemeDataProvider.getTheme());
587614
}
588615

589616
private handleFocusChange(isFocused: boolean): void {
@@ -755,15 +782,51 @@ export class IFrameWebview extends Disposable implements Webview {
755782
});
756783
}
757784

758-
public showFind(): void {
759-
// noop
785+
protected readonly _hasFindResult = this._register(new Emitter<boolean>());
786+
public readonly hasFindResult: Event<boolean> = this._hasFindResult.event;
787+
788+
protected readonly _onDidStopFind = this._register(new Emitter<void>());
789+
public readonly onDidStopFind: Event<void> = this._onDidStopFind.event;
790+
791+
/**
792+
* Webviews expose a stateful find API.
793+
* Successive calls to find will move forward or backward through onFindResults
794+
* depending on the supplied options.
795+
*
796+
* @param value The string to search for. Empty strings are ignored.
797+
*/
798+
public find(value: string, previous: boolean): void {
799+
if (!this.element) {
800+
return;
801+
}
802+
803+
this._send('find', { value, previous });
804+
}
805+
806+
public startFind(value: string) {
807+
if (!value || !this.element) {
808+
return;
809+
}
810+
this._send('find', { value });
811+
}
812+
813+
public stopFind(keepSelection?: boolean): void {
814+
if (!this.element) {
815+
return;
816+
}
817+
this._send('find-stop', { keepSelection });
818+
this._onDidStopFind.fire();
819+
}
820+
821+
public showFind() {
822+
this._webviewFindWidget?.reveal();
760823
}
761824

762-
public hideFind(): void {
763-
// noop
825+
public hideFind() {
826+
this._webviewFindWidget?.hide();
764827
}
765828

766-
public runFindAction(previous: boolean): void {
767-
// noop
829+
public runFindAction(previous: boolean) {
830+
this._webviewFindWidget?.find(previous);
768831
}
769832
}

src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts

Lines changed: 22 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Delayer } from 'vs/base/common/async';
7-
import { Emitter, Event } from 'vs/base/common/event';
87
import { Schemas } from 'vs/base/common/network';
98
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
109
import { IMenuService } from 'vs/platform/actions/common/actions';
@@ -23,26 +22,22 @@ import { FindInFrameOptions, IWebviewManagerService } from 'vs/platform/webview/
2322
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
2423
import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
2524
import { IFrameWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/webviewElement';
26-
import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget';
2725
import { WindowIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager';
2826
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
2927

3028
/**
3129
* Webview backed by an iframe but that uses Electron APIs to power the webview.
3230
*/
33-
export class ElectronIframeWebview extends IFrameWebview implements WebviewFindDelegate {
31+
export class ElectronIframeWebview extends IFrameWebview {
3432

3533
private readonly _webviewKeyboardHandler: WindowIgnoreMenuShortcutsManager;
3634

37-
private _webviewFindWidget: WebviewFindWidget | undefined;
3835
private _findStarted: boolean = false;
3936
private _cachedHtmlContent: string | undefined;
4037

4138
private readonly _webviewMainService: IWebviewManagerService;
4239
private readonly _iframeDelayer = this._register(new Delayer<void>(200));
4340

44-
public readonly checkImeCompletionState = true;
45-
4641
protected override get platform() { return 'electron'; }
4742

4843
constructor(
@@ -67,7 +62,7 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
6762
) {
6863
super(id, options, contentOptions, extension, webviewThemeDataProvider,
6964
configurationService, contextMenuService, menuService, notificationService, environmentService,
70-
fileService, logService, remoteAuthorityResolverService, telemetryService, tunnelService);
65+
fileService, logService, remoteAuthorityResolverService, telemetryService, tunnelService, instantiationService);
7166

7267
this._webviewKeyboardHandler = new WindowIgnoreMenuShortcutsManager(configurationService, mainProcessService, nativeHostService);
7368

@@ -82,8 +77,6 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
8277
}));
8378

8479
if (options.enableFindWidget) {
85-
this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this));
86-
8780
this._register(this.onDidHtmlChange((newContent) => {
8881
if (this._findStarted && this._cachedHtmlContent !== newContent) {
8982
this.stopFind(false);
@@ -94,67 +87,21 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
9487
this._register(this._webviewMainService.onFoundInFrame((result) => {
9588
this._hasFindResult.fire(result.matches > 0);
9689
}));
97-
98-
this.styledFindWidget();
99-
}
100-
}
101-
102-
public override mountTo(parent: HTMLElement) {
103-
if (!this.element) {
104-
return;
105-
}
106-
107-
if (this._webviewFindWidget) {
108-
parent.appendChild(this._webviewFindWidget.getDomNode()!);
10990
}
110-
parent.appendChild(this.element);
11191
}
11292

11393
protected override get webviewContentEndpoint(): string {
11494
return `${Schemas.vscodeWebview}://${this.id}`;
11595
}
11696

117-
protected override style(): void {
118-
super.style();
119-
this.styledFindWidget();
120-
}
121-
122-
private styledFindWidget() {
123-
this._webviewFindWidget?.updateTheme(this.webviewThemeDataProvider.getTheme());
124-
}
125-
126-
private readonly _hasFindResult = this._register(new Emitter<boolean>());
127-
public readonly hasFindResult: Event<boolean> = this._hasFindResult.event;
128-
129-
private readonly _onDidStopFind = this._register(new Emitter<void>());
130-
public readonly onDidStopFind: Event<void> = this._onDidStopFind.event;
131-
132-
public startFind(value: string) {
133-
if (!value || !this.element) {
134-
return;
135-
}
136-
137-
// FindNext must be true for a first request
138-
const options: FindInFrameOptions = {
139-
forward: true,
140-
findNext: true,
141-
matchCase: false
142-
};
143-
144-
this._iframeDelayer.trigger(() => {
145-
this._findStarted = true;
146-
this._webviewMainService.findInFrame({ windowId: this.nativeHostService.windowId }, this.id, value, options);
147-
});
148-
}
149-
15097
/**
15198
* Webviews expose a stateful find API.
15299
* Successive calls to find will move forward or backward through onFindResults
153100
* depending on the supplied options.
154101
*
155102
* @param value The string to search for. Empty strings are ignored.
156103
*/
157-
public find(value: string, previous: boolean): void {
104+
public override find(value: string, previous: boolean): void {
158105
if (!this.element) {
159106
return;
160107
}
@@ -168,7 +115,25 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
168115
}
169116
}
170117

171-
public stopFind(keepSelection?: boolean): void {
118+
public override startFind(value: string) {
119+
if (!value || !this.element) {
120+
return;
121+
}
122+
123+
// FindNext must be true for a first request
124+
const options: FindInFrameOptions = {
125+
forward: true,
126+
findNext: true,
127+
matchCase: false
128+
};
129+
130+
this._iframeDelayer.trigger(() => {
131+
this._findStarted = true;
132+
this._webviewMainService.findInFrame({ windowId: this.nativeHostService.windowId }, this.id, value, options);
133+
});
134+
}
135+
136+
public override stopFind(keepSelection?: boolean): void {
172137
if (!this.element) {
173138
return;
174139
}
@@ -179,16 +144,4 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
179144
});
180145
this._onDidStopFind.fire();
181146
}
182-
183-
public override showFind() {
184-
this._webviewFindWidget?.reveal();
185-
}
186-
187-
public override hideFind() {
188-
this._webviewFindWidget?.hide();
189-
}
190-
191-
public override runFindAction(previous: boolean) {
192-
this._webviewFindWidget?.find(previous);
193-
}
194147
}

0 commit comments

Comments
 (0)