Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
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
4 changes: 4 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 @@ -755,6 +756,9 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) {
if (eventKeyCode) {
EVENT_KEY_CODE_MAP[eventKeyCode] = keyCode;
}
if (scanCodeStr) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for such a low level change, it'd be good to have a test.

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,9 @@ 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 { ipcBrowserViewChannelName } from '../../platform/browserView/common/browserView.js';
import { IBrowserViewMainService } from '../../platform/browserView/electron-main/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 +416,11 @@ export class CodeApplication extends Disposable {

// Block any in-page navigation
contents.on('will-navigate', event => {
// Allow navigation in integrated browser views
if (BrowserViewMainService.isBrowserViewWebContents(contents)) {
return;
}

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

event.preventDefault();
Expand Down Expand Up @@ -1021,6 +1029,9 @@ export class CodeApplication extends Disposable {
// Browser Elements
services.set(INativeBrowserElementsMainService, new SyncDescriptor(NativeBrowserElementsMainService, undefined, false /* proxied to other processes */));

// Browser View
services.set(IBrowserViewMainService, new SyncDescriptor(BrowserViewMainService, undefined, false /* proxied to other processes */));

// Keyboard Layout
services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService));

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(IBrowserViewMainService), disposables);
mainProcessElectronServer.registerChannel(ipcBrowserViewChannelName, browserViewChannel);

// Signing
const signChannel = ProxyChannel.fromService(accessor.get(ISignService), disposables);
mainProcessElectronServer.registerChannel('sign', signChannel);
Expand Down
2 changes: 2 additions & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ export class MenuId {
static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar');
static readonly DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar');
static readonly DiffEditorSelectionToolbar = new MenuId('DiffEditorSelectionToolbar');
static readonly BrowserNavigationToolbar = new MenuId('BrowserNavigationToolbar');
static readonly BrowserActionsToolbar = new MenuId('BrowserActionsToolbar');


/**
Expand Down
170 changes: 170 additions & 0 deletions src/vs/platform/browserView/common/browserView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event } from '../../../base/common/event.js';

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

export interface IBrowserViewState {
url: string;
title: string;
canGoBack: boolean;
canGoForward: boolean;
loading: boolean;
lastScreenshot: string | undefined;
lastFavicon: string | undefined;
}

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

export interface IBrowserViewLoadingEvent {
loading: boolean;
}

export interface IBrowserViewFocusEvent {
focused: boolean;
}

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

export interface IBrowserViewTitleChangeEvent {
title: string;
}

export interface IBrowserViewFaviconChangeEvent {
favicon: string;
}

export interface IBrowserViewNewPageRequest {
url: string;
name?: string;
background: boolean;
}

export const ipcBrowserViewChannelName = 'browserView';

export interface IBrowserViewService {
/**
* Dynamic events that return an Event for a specific browser view ID.
*/
onDynamicDidNavigate(id: string): Event<IBrowserViewNavigationEvent>;
onDynamicDidChangeLoadingState(id: string): Event<IBrowserViewLoadingEvent>;
onDynamicDidChangeFocus(id: string): Event<IBrowserViewFocusEvent>;
onDynamicDidKeyCommand(id: string): Event<IBrowserViewKeyDownEvent>;
onDynamicDidChangeTitle(id: string): Event<IBrowserViewTitleChangeEvent>;
onDynamicDidChangeFavicon(id: string): Event<IBrowserViewFaviconChangeEvent>;
onDynamicDidRequestNewPage(id: string): Event<IBrowserViewNewPageRequest>;

/**
* Get or create a browser view instance
* @param id The browser view identifier
* @param workspaceId Optional workspace identifier for session isolation
*/
getOrCreateBrowserView(id: string, workspaceId?: string): 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
*/
layout(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): 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<string>;

/**
* 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

/**
* Focus the browser view
* @param id The browser view identifier
*/
focus(id: string): Promise<void>;
}
62 changes: 62 additions & 0 deletions src/vs/platform/browserView/common/browserViewUri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Schemas } from '../../../base/common/network.js';
import { URI } from '../../../base/common/uri.js';
import { generateUuid } from '../../../base/common/uuid.js';

/**
* Helper for creating and parsing browser view URIs.
*/
export namespace BrowserViewUri {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also seems like a good class for unit tests


export const scheme = Schemas.vscodeBrowser;

/**
* Creates a resource URI for a browser view with the given URL.
* Optionally accepts an ID; if not provided, a new UUID is generated.
*/
export function forUrl(url: string | undefined, id?: string): URI {
const viewId = id ?? generateUuid();
return URI.from({
scheme,
path: `/${viewId}`,
query: url ? `url=${encodeURIComponent(url)}` : undefined
});
}

/**
* Parses a browser view resource URI to extract the ID and URL.
*/
export function parse(resource: URI): { id: string; url: string } | undefined {
if (resource.scheme !== scheme) {
return undefined;
}

// Remove leading slash if present
const id = resource.path.startsWith('/') ? resource.path.substring(1) : resource.path;
if (!id) {
return undefined;
}

const url = resource.query ? new URLSearchParams(resource.query).get('url') ?? '' : '';

return { id, url };
}

/**
* Extracts the ID from a browser view resource URI.
*/
export function getId(resource: URI): string | undefined {
return parse(resource)?.id;
}

/**
* Extracts the URL from a browser view resource URI.
*/
export function getUrl(resource: URI): string | undefined {
return parse(resource)?.url;
}
}
13 changes: 13 additions & 0 deletions src/vs/platform/browserView/electron-main/browserView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IBrowserViewService } from '../common/browserView.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';

export const IBrowserViewMainService = createDecorator<IBrowserViewMainService>('browserViewMainService');
Comment thread
kycutler marked this conversation as resolved.
Outdated

export interface IBrowserViewMainService extends IBrowserViewService {

}
Loading
Loading