mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Prepopulate the Find in Page search text with the currently selected text in the page (#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
This commit is contained in:
@@ -61,6 +61,12 @@ const RULES: IRule[] = [
|
||||
disallowedTypes: NATIVE_TYPES,
|
||||
},
|
||||
|
||||
// Browser view preload script
|
||||
{
|
||||
target: '**/vs/platform/browserView/electron-browser/preload-browserView.ts',
|
||||
disallowedTypes: NATIVE_TYPES,
|
||||
},
|
||||
|
||||
// Common
|
||||
{
|
||||
target: '**/vs/**/common/**',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"../../src/**/test/**",
|
||||
"../../src/**/fixtures/**",
|
||||
"../../src/vs/base/parts/sandbox/electron-browser/preload.ts", // Preload scripts for Electron sandbox
|
||||
"../../src/vs/base/parts/sandbox/electron-browser/preload-aux.ts" // have limited access to node.js APIs
|
||||
"../../src/vs/base/parts/sandbox/electron-browser/preload-aux.ts", // have limited access to node.js APIs
|
||||
"../../src/vs/platform/browserView/electron-browser/preload-browserView.ts" // Browser view preload script
|
||||
]
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ const vscodeResourceIncludes = [
|
||||
// Electron Preload
|
||||
'out-build/vs/base/parts/sandbox/electron-browser/preload.js',
|
||||
'out-build/vs/base/parts/sandbox/electron-browser/preload-aux.js',
|
||||
'out-build/vs/platform/browserView/electron-browser/preload-browserView.js',
|
||||
|
||||
// Node Scripts
|
||||
'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}',
|
||||
|
||||
@@ -117,6 +117,11 @@ export enum BrowserViewStorageScope {
|
||||
|
||||
export const ipcBrowserViewChannelName = 'browserView';
|
||||
|
||||
/**
|
||||
* This should match the isolated world ID defined in `preload-browserView.ts`.
|
||||
*/
|
||||
export const browserViewIsolatedWorldId = 999;
|
||||
|
||||
export interface IBrowserViewService {
|
||||
/**
|
||||
* Dynamic events that return an Event for a specific browser view ID.
|
||||
@@ -246,6 +251,14 @@ export interface IBrowserViewService {
|
||||
*/
|
||||
stopFindInPage(id: string, keepSelection?: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the currently selected text in the browser view.
|
||||
* Returns immediately with empty string if the page is still loading.
|
||||
* @param id The browser view identifier
|
||||
* @returns The selected text, or empty string if no selection or page is loading
|
||||
*/
|
||||
getSelectedText(id: string): Promise<string>;
|
||||
|
||||
/**
|
||||
* Clear all storage data for the global browser session
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-globals */
|
||||
|
||||
/**
|
||||
* Preload script for pages loaded in Integrated Browser
|
||||
*
|
||||
* It runs in an isolated context that Electron calls an "isolated world".
|
||||
* Specifically the isolated world with worldId 999, which shows in DevTools as "Electron Isolated Context".
|
||||
* Despite being isolated, it still runs on the same page as the JS from the actual loaded website
|
||||
* which runs on the so-called "main world" (worldId 0. In DevTools as "top").
|
||||
*
|
||||
* Learn more: see Electron docs for Security, contextBridge, and Context Isolation.
|
||||
*/
|
||||
(function () {
|
||||
|
||||
const { contextBridge } = require('electron');
|
||||
|
||||
// #######################################################################
|
||||
// ### ###
|
||||
// ### !!! DO NOT USE GET/SET PROPERTIES ANYWHERE HERE !!! ###
|
||||
// ### !!! UNLESS THE ACCESS IS WITHOUT SIDE EFFECTS !!! ###
|
||||
// ### (https://github.com/electron/electron/issues/25516) ###
|
||||
// ### ###
|
||||
// #######################################################################
|
||||
const globals = {
|
||||
/**
|
||||
* Get the currently selected text in the page.
|
||||
*/
|
||||
getSelectedText(): string {
|
||||
try {
|
||||
// Even if the page has overridden window.getSelection, our call here will still reach the original
|
||||
// implementation. That's because Electron proxies functions, such as getSelectedText here, that are
|
||||
// exposed to a different context via exposeInIsolatedWorld or exposeInMainWorld.
|
||||
return window.getSelection()?.toString() ?? '';
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Use `contextBridge` APIs to expose globals to the same isolated world where this preload script runs (worldId 999).
|
||||
// The globals object will be recursively frozen (and for functions also proxied) by Electron to prevent
|
||||
// modification within the given context.
|
||||
contextBridge.exposeInIsolatedWorld(999, 'browserViewAPI', globals);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}());
|
||||
@@ -4,10 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { WebContentsView, webContents } from 'electron';
|
||||
import { FileAccess } from '../../../base/common/network.js';
|
||||
import { Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { VSBuffer } from '../../../base/common/buffer.js';
|
||||
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, BrowserNewPageLocation } from '../common/browserView.js';
|
||||
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, BrowserNewPageLocation, browserViewIsolatedWorldId } from '../common/browserView.js';
|
||||
import { EVENT_KEY_CODE_MAP, KeyCode, KeyMod, SCAN_CODE_STR_TO_EVENT_KEY_CODE } from '../../../base/common/keyCodes.js';
|
||||
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
|
||||
import { IBaseWindow, ICodeWindow } from '../../window/electron-main/window.js';
|
||||
@@ -96,6 +97,7 @@ export class BrowserView extends Disposable {
|
||||
sandbox: true,
|
||||
webviewTag: false,
|
||||
session: viewSession,
|
||||
preload: FileAccess.asFileUri('vs/platform/browserView/electron-browser/preload-browserView.js').fsPath,
|
||||
|
||||
// TODO@kycutler: Remove this once https://github.com/electron/electron/issues/42578 is fixed
|
||||
type: 'browserView'
|
||||
@@ -535,6 +537,23 @@ export class BrowserView extends Disposable {
|
||||
this._view.webContents.stopFindInPage(keepSelection ? 'keepSelection' : 'clearSelection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected text in the browser view.
|
||||
* Returns immediately with empty string if the page is still loading.
|
||||
*/
|
||||
async getSelectedText(): Promise<string> {
|
||||
// we don't want to wait for the page to finish loading, which executeJavaScript normally does.
|
||||
if (this._view.webContents.isLoading()) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
// Uses our preloaded contextBridge-exposed API.
|
||||
return await this._view.webContents.executeJavaScriptInIsolatedWorld(browserViewIsolatedWorldId, [{ code: 'window.browserViewAPI?.getSelectedText?.() ?? ""' }]);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all storage data for this browser view's session
|
||||
*/
|
||||
|
||||
@@ -243,6 +243,10 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
|
||||
return this._getBrowserView(id).stopFindInPage(keepSelection);
|
||||
}
|
||||
|
||||
async getSelectedText(id: string): Promise<string> {
|
||||
return this._getBrowserView(id).getSelectedText();
|
||||
}
|
||||
|
||||
async clearStorage(id: string): Promise<void> {
|
||||
return this._getBrowserView(id).clearStorage();
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ export interface IBrowserViewModel extends IDisposable {
|
||||
focus(): Promise<void>;
|
||||
findInPage(text: string, options?: IBrowserViewFindInPageOptions): Promise<void>;
|
||||
stopFindInPage(keepSelection?: boolean): Promise<void>;
|
||||
getSelectedText(): Promise<string>;
|
||||
clearStorage(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -336,6 +337,10 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
|
||||
return this.browserViewService.stopFindInPage(this.id, keepSelection);
|
||||
}
|
||||
|
||||
async getSelectedText(): Promise<string> {
|
||||
return this.browserViewService.getSelectedText(this.id);
|
||||
}
|
||||
|
||||
async clearStorage(): Promise<void> {
|
||||
return this.browserViewService.clearStorage(this.id);
|
||||
}
|
||||
|
||||
@@ -600,10 +600,15 @@ export class BrowserEditor extends EditorPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the find widget
|
||||
* Show the find widget, optionally pre-populated with selected text from the browser view
|
||||
*/
|
||||
showFind(): void {
|
||||
this._findWidget.value.reveal();
|
||||
async showFind(): Promise<void> {
|
||||
// Get selected text from the browser view to pre-populate the search box.
|
||||
const selectedText = await this._model?.getSelectedText();
|
||||
|
||||
// Only use the selected text if it doesn't contain newlines (single line selection)
|
||||
const textToReveal = selectedText && !/[\r\n]/.test(selectedText) ? selectedText : undefined;
|
||||
this._findWidget.value.reveal(textToReveal);
|
||||
this._findWidget.value.layout(this._findWidgetContainer.clientWidth);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user