Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build/checker/layersChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/**',
Expand Down
3 changes: 2 additions & 1 deletion build/checker/tsconfig.electron-browser.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
}
1 change: 1 addition & 0 deletions build/gulpfile.vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}',
Expand Down
10 changes: 2 additions & 8 deletions build/lib/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,9 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream {
}

async function main(arch: string = process.arch): Promise<void> {
const version = electronVersion;
const electronPath = path.join(root, '.build', 'electron');
const versionFile = path.join(electronPath, versionedResourcesFolder, 'version');
const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`;

if (!isUpToDate) {
await util.rimraf(electronPath)();
await util.streamToPromise(getElectron(arch)());
}
await util.rimraf(electronPath)();
await util.streamToPromise(getElectron(arch)());
}

if (import.meta.main) {
Expand Down
2 changes: 2 additions & 0 deletions extensions/vscode-test-resolver/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export function activate(context: vscode.ExtensionContext) {

outputChannel.appendLine(`Launching server: "${serverCommandPath}" ${commandArgs.join(' ')}`);
const shell = (process.platform === 'win32');
// Skip prelaunch to avoid redownloading electron while it may be in use
env['VSCODE_SKIP_PRELAUNCH'] = '1';
extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath, shell });
} else {
const extensionToInstall = process.env['TESTRESOLVER_INSTALL_BUILTIN_EXTENSION'];
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/native-watchdog": "^1.4.6",
"@vscode/policy-watcher": "^1.3.2",
"@vscode/proxy-agent": "^0.37.0",
"@vscode/proxy-agent": "^0.38.0",
"@vscode/ripgrep": "^1.15.13",
"@vscode/spdlog": "^0.15.7",
"@vscode/sqlite3": "5.1.12-vscode",
Expand Down
8 changes: 4 additions & 4 deletions remote/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@vscode/deviceid": "^0.1.1",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/native-watchdog": "^1.4.6",
"@vscode/proxy-agent": "^0.37.0",
"@vscode/proxy-agent": "^0.38.0",
"@vscode/ripgrep": "^1.15.13",
"@vscode/spdlog": "^0.15.7",
"@vscode/tree-sitter-wasm": "^0.3.0",
Expand Down
18 changes: 13 additions & 5 deletions src/vs/platform/actions/browser/menuEntryActionViewItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
private readonly _dropdown: DropdownMenuActionViewItem;
private _container: HTMLElement | null = null;
private readonly _storageKey: string;
private readonly _primaryActionListener = this._register(new MutableDisposable());

get onDidChangeDropdownVisibility(): Event<boolean> {
return this._dropdown.onDidChangeVisibility;
Expand Down Expand Up @@ -468,14 +469,18 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem {

this._dropdown = this._register(new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, dropdownOptions));
if (options?.togglePrimaryAction) {
this._register(this._dropdown.actionRunner.onDidRun((e: IRunEvent) => {
if (e.action instanceof MenuItemAction) {
this.update(e.action);
}
}));
this.registerTogglePrimaryActionListener();
}
}

private registerTogglePrimaryActionListener(): void {
this._primaryActionListener.value = this._dropdown.actionRunner.onDidRun((e: IRunEvent) => {
if (e.action instanceof MenuItemAction) {
this.update(e.action);
}
});
}

private update(lastAction: MenuItemAction): void {
if (this._options?.togglePrimaryAction) {
this._storageService.store(this._storageKey, lastAction.id, StorageScope.WORKSPACE, StorageTarget.MACHINE);
Expand Down Expand Up @@ -516,6 +521,9 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem {

this._defaultAction.actionRunner = actionRunner;
this._dropdown.actionRunner = actionRunner;
if (this._primaryActionListener.value) {
this.registerTogglePrimaryActionListener();
}
}

override get actionRunner(): IActionRunner {
Expand Down
13 changes: 13 additions & 0 deletions src/vs/platform/browserView/common/browserView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}());
21 changes: 20 additions & 1 deletion src/vs/platform/browserView/electron-main/browserView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
23 changes: 14 additions & 9 deletions src/vs/workbench/api/browser/mainThreadHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { URI } from '../../../base/common/uri.js';
import { URI, UriComponents } from '../../../base/common/uri.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
import { ExtHostContext, MainContext, MainThreadHooksShape } from '../common/extHost.protocol.js';
import { HookResultKind, IHookResult, IHooksExecutionProxy, IHooksExecutionService } from '../../contrib/chat/common/hooksExecutionService.js';
import { HookTypeValue } from '../../contrib/chat/common/promptSyntax/hookSchema.js';
import { HookTypeValue, IHookCommand } from '../../contrib/chat/common/promptSyntax/hookSchema.js';
import { CancellationToken } from '../../../base/common/cancellation.js';

@extHostNamedCustomer(MainContext.MainThreadHooks)
export class MainThreadHooks extends Disposable implements MainThreadHooksShape {
Expand All @@ -20,17 +21,21 @@ export class MainThreadHooks extends Disposable implements MainThreadHooksShape
super();
const extHostProxy = extHostContext.getProxy(ExtHostContext.ExtHostHooks);

// Adapter that implements IHooksExecutionProxy by forwarding to ExtHostHooksShape
const proxy: IHooksExecutionProxy = {
executeHook: async (hookType: HookTypeValue, sessionResource: URI, input: unknown): Promise<IHookResult[]> => {
const results = await extHostProxy.$executeHook(hookType, sessionResource, input);
return results.map(r => ({
kind: r.kind as HookResultKind,
result: r.result
}));
runHookCommand: async (hookCommand: IHookCommand, input: unknown, token: CancellationToken): Promise<IHookResult> => {
const result = await extHostProxy.$runHookCommand(hookCommand, input, token);
return {
kind: result.kind as HookResultKind,
result: result.result
};
}
};

this._hooksExecutionService.setProxy(proxy);
}

async $executeHook(hookType: string, sessionResource: UriComponents, input: unknown, token: CancellationToken): Promise<IHookResult[]> {
const uri = URI.revive(sessionResource);
return this._hooksExecutionService.executeHook(hookType as HookTypeValue, uri, { input, token });
}
}
7 changes: 2 additions & 5 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostDialogs = new ExtHostDialogs(rpcProtocol);
const extHostChatStatus = new ExtHostChatStatus(rpcProtocol);
const extHostHooks = accessor.get(IExtHostHooks);
extHostHooks.initialize(extHostChatAgents2);

// Register API-ish commands
ExtHostApiCommands.register(extHostCommands);
Expand Down Expand Up @@ -1595,11 +1594,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatPromptFiles');
return extHostChatAgents2.registerPromptFileProvider(extension, PromptsType.skill, provider);
},
executeHook(hookType: vscode.ChatHookType, options: vscode.ChatHookExecutionOptions, token?: vscode.CancellationToken): Thenable<vscode.ChatHookResult[]> {
async executeHook(hookType: vscode.ChatHookType, options: vscode.ChatHookExecutionOptions, token?: vscode.CancellationToken): Promise<vscode.ChatHookResult[]> {
checkProposedApiEnabled(extension, 'chatHooks');
return extHostHooks.executeHook(hookType, options, token).then(results =>
results.map(r => ({ kind: r.kind as unknown as vscode.ChatHookResultKind, result: r.result }))
);
return extHostHooks.executeHook(hookType, options, token);
},
};

Expand Down
Loading
Loading