Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4bd5ae0
[WIP] Integrated Browser
kycutler Nov 20, 2025
8c37204
clean
kycutler Nov 21, 2025
feea74d
refactor
kycutler Nov 21, 2025
4b29dda
structure
kycutler Nov 22, 2025
e1ebea2
focus
kycutler Nov 24, 2025
aa1e9f1
tooltips
kycutler Nov 24, 2025
e2c43db
rename
kycutler Nov 24, 2025
80b1b68
polish
kycutler Nov 24, 2025
33efb28
start unpinned
kycutler Nov 24, 2025
9ecfeda
More polish
kycutler Nov 25, 2025
d62b23d
commands
kycutler Nov 26, 2025
b34d92e
tweaks, new tab support
kycutler Nov 26, 2025
759a495
clean
kycutler Nov 26, 2025
7557c13
shortcut fixes
kycutler Nov 26, 2025
a1336ab
warnings
kycutler Nov 26, 2025
39031a3
Update src/vs/workbench/contrib/browserView/electron-browser/browserE…
kycutler Nov 27, 2025
bc6de66
Telemetry
kycutler Nov 27, 2025
d2ec0ec
load errors
kycutler Nov 27, 2025
d7736eb
PR feedback
kycutler Nov 27, 2025
0245ed4
PR feedback
kycutler Dec 1, 2025
1e54da1
Merge branch 'main' into kycutler/rich-browser
kycutler Dec 1, 2025
faf1c6c
Permissions, unloads, trust
kycutler Dec 4, 2025
7c805eb
Storage controls
kycutler Dec 4, 2025
58d54aa
Handle render process gone
kycutler Dec 4, 2025
5a5bf55
Merge branch 'main' into kycutler/rich-browser
kycutler Dec 4, 2025
8229bec
devtools
kycutler Dec 5, 2025
9c00484
Screenshot rect
kycutler Dec 5, 2025
d8b430c
close
kycutler Dec 6, 2025
0e95ee2
Fix focused context
kycutler Dec 8, 2025
5b6cfd1
Merge branch 'main' into kycutler/rich-browser
kycutler Dec 9, 2025
86167ee
Fix merge
kycutler Dec 9, 2025
aca4396
disposables
kycutler Dec 9, 2025
077294b
Merge branch 'main' into kycutler/rich-browser
bpasero Dec 19, 2025
97e1fad
:lipstick:
bpasero Dec 19, 2025
a656469
Merge branch 'main' into kycutler/rich-browser
kycutler Dec 17, 2025
ac7df40
Multi-window improvements
kycutler Dec 17, 2025
b6a19a8
Fix reopen
kycutler Dec 19, 2025
ca8dcbb
PR feedback
kycutler Dec 19, 2025
39beb94
Actions fixes
kycutler Dec 19, 2025
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
2 changes: 2 additions & 0 deletions src/vs/base/common/keyCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ const userSettingsUSMap = new KeyCodeStrMap();
const userSettingsGeneralMap = new KeyCodeStrMap();
export const EVENT_KEY_CODE_MAP: { [keyCode: number]: KeyCode } = new Array(230);
export const NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE: { [nativeKeyCode: string]: KeyCode } = {};
export const SCAN_CODE_STR_TO_EVENT_KEY_CODE: { [scanCodeStr: string]: number } = {};
const scanCodeIntToStr: string[] = [];
const scanCodeStrToInt: { [code: string]: number } = Object.create(null);
const scanCodeLowerCaseStrToInt: { [code: string]: number } = Object.create(null);
Expand Down Expand Up @@ -754,6 +755,7 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) {
}
if (eventKeyCode) {
EVENT_KEY_CODE_MAP[eventKeyCode] = keyCode;
SCAN_CODE_STR_TO_EVENT_KEY_CODE[scanCodeStr] = eventKeyCode;
Comment thread
kycutler marked this conversation as resolved.
}
if (vkey) {
NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE[vkey] = keyCode;
Expand Down
5 changes: 5 additions & 0 deletions src/vs/base/common/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export namespace Schemas {
*/
export const vscodeWebview = 'vscode-webview';

/**
* Scheme used for integrated browser tabs using WebContentsView.
*/
export const vscodeBrowser = 'vscode-browser';

/**
* Scheme used for extension pages
*/
Expand Down
15 changes: 15 additions & 0 deletions src/vs/code/electron-main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import { DialogMainService, IDialogMainService } from '../../platform/dialogs/el
import { IEncryptionMainService } from '../../platform/encryption/common/encryptionService.js';
import { EncryptionMainService } from '../../platform/encryption/electron-main/encryptionMainService.js';
import { NativeBrowserElementsMainService, INativeBrowserElementsMainService } from '../../platform/browserElements/electron-main/nativeBrowserElementsMainService.js';
import { IBrowserViewService } from '../../platform/browserView/common/browserView.js';
import { BrowserViewMainService } from '../../platform/browserView/electron-main/browserViewMainService.js';
import { NativeParsedArgs } from '../../platform/environment/common/argv.js';
import { IEnvironmentMainService } from '../../platform/environment/electron-main/environmentMainService.js';
import { isLaunchedFromCli } from '../../platform/environment/node/argvHelper.js';
Expand Down Expand Up @@ -413,6 +415,12 @@ export class CodeApplication extends Disposable {

// Block any in-page navigation
contents.on('will-navigate', event => {
const browserViewService = this.mainInstantiationService.invokeFunction(accessor => accessor.get(IBrowserViewService));
// Allow navigation in integrated browser views
if (browserViewService?.isBrowserViewWebContents(contents)) {
return;
}

this.logService.error('webContents#will-navigate: Prevented webcontent navigation');

event.preventDefault();
Expand All @@ -421,6 +429,7 @@ export class CodeApplication extends Disposable {
// All Windows: only allow about:blank auxiliary windows to open
// For all other URLs, delegate to the OS.
contents.setWindowOpenHandler(details => {
// TODO: for browser views, open in a new browser tab.

// about:blank windows can open as window witho our default options
Comment thread
kycutler marked this conversation as resolved.
if (details.url === 'about:blank') {
Expand Down Expand Up @@ -1106,6 +1115,8 @@ export class CodeApplication extends Disposable {
// MCP
services.set(INativeMcpDiscoveryHelperService, new SyncDescriptor(NativeMcpDiscoveryHelperService));

// Browser View
Comment thread
kycutler marked this conversation as resolved.
Outdated
services.set(IBrowserViewService, new SyncDescriptor(BrowserViewMainService, undefined, true));

// Dev Only: CSS service (for ESM)
services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true));
Expand Down Expand Up @@ -1168,6 +1179,10 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel('browserElements', browserElementsChannel);
sharedProcessClient.then(client => client.registerChannel('browserElements', browserElementsChannel));

// Browser View
const browserViewChannel = ProxyChannel.fromService(accessor.get(IBrowserViewService), disposables);
mainProcessElectronServer.registerChannel('browserView', browserViewChannel);

// Signing
const signChannel = ProxyChannel.fromService(accessor.get(ISignService), disposables);
mainProcessElectronServer.registerChannel('sign', signChannel);
Expand Down
205 changes: 205 additions & 0 deletions src/vs/platform/browserView/common/browserView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { VSBuffer } from '../../../base/common/buffer.js';
import { Event } from '../../../base/common/event.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';

export const IBrowserViewService = createDecorator<IBrowserViewService>('browserViewService');

export interface IBrowserViewBounds {
x: number;
y: number;
width: number;
height: number;
}

export interface IBrowserViewState {
url: string;
title: string;
canGoBack: boolean;
canGoForward: boolean;
loading: boolean;
}

export interface IBrowserViewNavigationEvent {
id: string;
url: string;
canGoBack: boolean;
canGoForward: boolean;
}

export interface IBrowserViewLoadingEvent {
id: string;
loading: boolean;
}

export interface IBrowserViewPaintEvent {
id: string;
dataUrl: string;
}

export interface IBrowserViewFocusEvent {
id: string;
focused: boolean;
}

export interface IBrowserViewKeyDownEvent {
viewId: string;
key: string;
keyCode: number;
code: string;
ctrlKey: boolean;
shiftKey: boolean;
altKey: boolean;
metaKey: boolean;
repeat: boolean;
}

export interface IBrowserViewTitleChangeEvent {
id: string;
title: string;
}

export interface IBrowserViewFaviconChangeEvent {
id: string;
favicon: string;
}

export interface IBrowserViewCreateOptions {
offscreen?: boolean;
}

export interface IBrowserViewService {
readonly _serviceBrand: undefined;

readonly onDidPaint: Event<IBrowserViewPaintEvent>;
readonly onDidNavigate: Event<IBrowserViewNavigationEvent>;
readonly onDidChangeLoadingState: Event<IBrowserViewLoadingEvent>;
readonly onDidChangeFocus: Event<IBrowserViewFocusEvent>;
readonly onDidKeyCommand: Event<IBrowserViewKeyDownEvent>;
readonly onDidChangeTitle: Event<IBrowserViewTitleChangeEvent>;
readonly onDidChangeFavicons: Event<IBrowserViewFaviconChangeEvent>;

/**
* Get or create a browser view instance
* @param id The browser view identifier
* @param windowId The window identifier to host the view
* @param offscreen Whether to create the view in offscreen mode
*/
getOrCreateBrowserView(id: string, windowId: number, options?: IBrowserViewCreateOptions): Promise<IBrowserViewState>;

/**
* Destroy a browser view instance
* @param id The browser view identifier
*/
destroyBrowserView(id: string): Promise<void>;

/**
* Update the bounds of a browser view
* @param id The browser view identifier
* @param bounds The new bounds for the view
*/
setBounds(id: string, bounds: IBrowserViewBounds): Promise<void>;

/**
* Set the visibility of a browser view
* @param id The browser view identifier
* @param visible Whether the view should be visible
*/
setVisible(id: string, visible: boolean, keepRendering?: boolean): Promise<void>;

/**
* Navigate the browser view to a URL
* @param id The browser view identifier
* @param url The URL to navigate to
*/
loadURL(id: string, url: string): Promise<void>;

/**
* Get the current URL of a browser view
* @param id The browser view identifier
*/
getURL(id: string): Promise<string>;

/**
* Go back in navigation history
* @param id The browser view identifier
*/
goBack(id: string): Promise<void>;

/**
* Go forward in navigation history
* @param id The browser view identifier
*/
goForward(id: string): Promise<void>;

/**
* Reload the current page
* @param id The browser view identifier
*/
reload(id: string): Promise<void>;

/**
* Check if the view can go back
* @param id The browser view identifier
*/
canGoBack(id: string): Promise<boolean>;

/**
* Check if the view can go forward
* @param id The browser view identifier
*/
canGoForward(id: string): Promise<boolean>;

/**
* Capture a screenshot of the browser view
* @param id The browser view identifier
* @param quality The quality of the screenshot (0-100)
* @returns Data URL of the screenshot, or undefined if capture failed
*/
captureScreenshot(id: string, quality?: number): Promise<VSBuffer | undefined>;

// /**
// * Clear all browsing data (cookies, cache, storage, etc.)
// */
// clearBrowsingData(): Promise<void>;

// /**
// * Clear cookies only
// */
// clearCookies(): Promise<void>;

// /**
// * Clear cache only
// */
// clearCache(): Promise<void>;

// /**
// * Clear local storage and session storage
// */
// clearStorageData(): Promise<void>;

/**
* Dispatch a key event to the browser view
* @param viewId The browser view identifier
* @param keyEvent The key event data
*/
dispatchKeyEvent(viewId: string, keyEvent: IBrowserViewKeyDownEvent): Promise<void>;
Comment thread
kycutler marked this conversation as resolved.
Outdated

/**
* Set the zoom factor for a browser view
* @param id The browser view identifier
* @param zoomFactor The zoom factor to apply
*/
setZoomFactor(id: string, zoomFactor: number): Promise<void>;

/**
* Check if a WebContents instance belongs to a browser view
* @param contents The WebContents instance to check
* @returns True if the WebContents belongs to a browser view
*/
isBrowserViewWebContents(contents: Electron.WebContents): boolean;

Check failure on line 204 in src/vs/platform/browserView/common/browserView.ts

View workflow job for this annotation

GitHub Actions / Monaco Editor checks

Cannot find namespace 'Electron'.
}
Loading
Loading