Skip to content

Commit d3dbc03

Browse files
authored
Prepopulate the Find in Page search text with the currently selected text in the page (microsoft#291800)
* Prepopulate Find textbox * Add warning taken from preload.ts * Small changes * Refactor preload script comments for clarity and security emphasis * Small comment change * Small comment change * Update comment * Use isolated world instead of main world * Comment update * Update comment * Update comment * PR Feedback * Small comment
1 parent 7d015ab commit d3dbc03

9 files changed

Lines changed: 112 additions & 5 deletions

File tree

build/checker/layersChecker.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ const RULES: IRule[] = [
6161
disallowedTypes: NATIVE_TYPES,
6262
},
6363

64+
// Browser view preload script
65+
{
66+
target: '**/vs/platform/browserView/electron-browser/preload-browserView.ts',
67+
disallowedTypes: NATIVE_TYPES,
68+
},
69+
6470
// Common
6571
{
6672
target: '**/vs/**/common/**',

build/checker/tsconfig.electron-browser.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"../../src/**/test/**",
1717
"../../src/**/fixtures/**",
1818
"../../src/vs/base/parts/sandbox/electron-browser/preload.ts", // Preload scripts for Electron sandbox
19-
"../../src/vs/base/parts/sandbox/electron-browser/preload-aux.ts" // have limited access to node.js APIs
19+
"../../src/vs/base/parts/sandbox/electron-browser/preload-aux.ts", // have limited access to node.js APIs
20+
"../../src/vs/platform/browserView/electron-browser/preload-browserView.ts" // Browser view preload script
2021
]
2122
}

build/gulpfile.vscode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const vscodeResourceIncludes = [
6868
// Electron Preload
6969
'out-build/vs/base/parts/sandbox/electron-browser/preload.js',
7070
'out-build/vs/base/parts/sandbox/electron-browser/preload-aux.js',
71+
'out-build/vs/platform/browserView/electron-browser/preload-browserView.js',
7172

7273
// Node Scripts
7374
'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}',

src/vs/platform/browserView/common/browserView.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ export enum BrowserViewStorageScope {
117117

118118
export const ipcBrowserViewChannelName = 'browserView';
119119

120+
/**
121+
* This should match the isolated world ID defined in `preload-browserView.ts`.
122+
*/
123+
export const browserViewIsolatedWorldId = 999;
124+
120125
export interface IBrowserViewService {
121126
/**
122127
* Dynamic events that return an Event for a specific browser view ID.
@@ -246,6 +251,14 @@ export interface IBrowserViewService {
246251
*/
247252
stopFindInPage(id: string, keepSelection?: boolean): Promise<void>;
248253

254+
/**
255+
* Get the currently selected text in the browser view.
256+
* Returns immediately with empty string if the page is still loading.
257+
* @param id The browser view identifier
258+
* @returns The selected text, or empty string if no selection or page is loading
259+
*/
260+
getSelectedText(id: string): Promise<string>;
261+
249262
/**
250263
* Clear all storage data for the global browser session
251264
*/
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
/* eslint-disable no-restricted-globals */
7+
8+
/**
9+
* Preload script for pages loaded in Integrated Browser
10+
*
11+
* It runs in an isolated context that Electron calls an "isolated world".
12+
* Specifically the isolated world with worldId 999, which shows in DevTools as "Electron Isolated Context".
13+
* Despite being isolated, it still runs on the same page as the JS from the actual loaded website
14+
* which runs on the so-called "main world" (worldId 0. In DevTools as "top").
15+
*
16+
* Learn more: see Electron docs for Security, contextBridge, and Context Isolation.
17+
*/
18+
(function () {
19+
20+
const { contextBridge } = require('electron');
21+
22+
// #######################################################################
23+
// ### ###
24+
// ### !!! DO NOT USE GET/SET PROPERTIES ANYWHERE HERE !!! ###
25+
// ### !!! UNLESS THE ACCESS IS WITHOUT SIDE EFFECTS !!! ###
26+
// ### (https://github.com/electron/electron/issues/25516) ###
27+
// ### ###
28+
// #######################################################################
29+
const globals = {
30+
/**
31+
* Get the currently selected text in the page.
32+
*/
33+
getSelectedText(): string {
34+
try {
35+
// Even if the page has overridden window.getSelection, our call here will still reach the original
36+
// implementation. That's because Electron proxies functions, such as getSelectedText here, that are
37+
// exposed to a different context via exposeInIsolatedWorld or exposeInMainWorld.
38+
return window.getSelection()?.toString() ?? '';
39+
} catch {
40+
return '';
41+
}
42+
}
43+
};
44+
45+
try {
46+
// Use `contextBridge` APIs to expose globals to the same isolated world where this preload script runs (worldId 999).
47+
// The globals object will be recursively frozen (and for functions also proxied) by Electron to prevent
48+
// modification within the given context.
49+
contextBridge.exposeInIsolatedWorld(999, 'browserViewAPI', globals);
50+
} catch (error) {
51+
console.error(error);
52+
}
53+
}());

src/vs/platform/browserView/electron-main/browserView.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { WebContentsView, webContents } from 'electron';
7+
import { FileAccess } from '../../../base/common/network.js';
78
import { Disposable } from '../../../base/common/lifecycle.js';
89
import { Emitter, Event } from '../../../base/common/event.js';
910
import { VSBuffer } from '../../../base/common/buffer.js';
10-
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, BrowserNewPageLocation } from '../common/browserView.js';
11+
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, BrowserNewPageLocation, browserViewIsolatedWorldId } from '../common/browserView.js';
1112
import { EVENT_KEY_CODE_MAP, KeyCode, KeyMod, SCAN_CODE_STR_TO_EVENT_KEY_CODE } from '../../../base/common/keyCodes.js';
1213
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
1314
import { IBaseWindow, ICodeWindow } from '../../window/electron-main/window.js';
@@ -96,6 +97,7 @@ export class BrowserView extends Disposable {
9697
sandbox: true,
9798
webviewTag: false,
9899
session: viewSession,
100+
preload: FileAccess.asFileUri('vs/platform/browserView/electron-browser/preload-browserView.js').fsPath,
99101

100102
// TODO@kycutler: Remove this once https://github.com/electron/electron/issues/42578 is fixed
101103
type: 'browserView'
@@ -535,6 +537,23 @@ export class BrowserView extends Disposable {
535537
this._view.webContents.stopFindInPage(keepSelection ? 'keepSelection' : 'clearSelection');
536538
}
537539

540+
/**
541+
* Get the currently selected text in the browser view.
542+
* Returns immediately with empty string if the page is still loading.
543+
*/
544+
async getSelectedText(): Promise<string> {
545+
// we don't want to wait for the page to finish loading, which executeJavaScript normally does.
546+
if (this._view.webContents.isLoading()) {
547+
return '';
548+
}
549+
try {
550+
// Uses our preloaded contextBridge-exposed API.
551+
return await this._view.webContents.executeJavaScriptInIsolatedWorld(browserViewIsolatedWorldId, [{ code: 'window.browserViewAPI?.getSelectedText?.() ?? ""' }]);
552+
} catch {
553+
return '';
554+
}
555+
}
556+
538557
/**
539558
* Clear all storage data for this browser view's session
540559
*/

src/vs/platform/browserView/electron-main/browserViewMainService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
243243
return this._getBrowserView(id).stopFindInPage(keepSelection);
244244
}
245245

246+
async getSelectedText(id: string): Promise<string> {
247+
return this._getBrowserView(id).getSelectedText();
248+
}
249+
246250
async clearStorage(id: string): Promise<void> {
247251
return this._getBrowserView(id).clearStorage();
248252
}

src/vs/workbench/contrib/browserView/common/browserView.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export interface IBrowserViewModel extends IDisposable {
118118
focus(): Promise<void>;
119119
findInPage(text: string, options?: IBrowserViewFindInPageOptions): Promise<void>;
120120
stopFindInPage(keepSelection?: boolean): Promise<void>;
121+
getSelectedText(): Promise<string>;
121122
clearStorage(): Promise<void>;
122123
}
123124

@@ -336,6 +337,10 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
336337
return this.browserViewService.stopFindInPage(this.id, keepSelection);
337338
}
338339

340+
async getSelectedText(): Promise<string> {
341+
return this.browserViewService.getSelectedText(this.id);
342+
}
343+
339344
async clearStorage(): Promise<void> {
340345
return this.browserViewService.clearStorage(this.id);
341346
}

src/vs/workbench/contrib/browserView/electron-browser/browserEditor.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,10 +600,15 @@ export class BrowserEditor extends EditorPane {
600600
}
601601

602602
/**
603-
* Show the find widget
603+
* Show the find widget, optionally pre-populated with selected text from the browser view
604604
*/
605-
showFind(): void {
606-
this._findWidget.value.reveal();
605+
async showFind(): Promise<void> {
606+
// Get selected text from the browser view to pre-populate the search box.
607+
const selectedText = await this._model?.getSelectedText();
608+
609+
// Only use the selected text if it doesn't contain newlines (single line selection)
610+
const textToReveal = selectedText && !/[\r\n]/.test(selectedText) ? selectedText : undefined;
611+
this._findWidget.value.reveal(textToReveal);
607612
this._findWidget.value.layout(this._findWidgetContainer.clientWidth);
608613
}
609614

0 commit comments

Comments
 (0)