Browser: support tracking the host session id separately from agentic ownership

This commit is contained in:
Kyle Cutler
2026-08-21 15:22:09 -07:00
committed by GitHub
parent 5776eb9251
commit be86ef4bfb
8 changed files with 41 additions and 26 deletions
@@ -255,13 +255,19 @@ export function matchesBrowserViewAudience(candidate: IBrowserViewAudience, patt
&& (pattern.sessionId === undefined || pattern.sessionId === candidate.sessionId);
}
/** Identifies the workbench window and optional Agents Window session that host a browser view. */
export interface IBrowserViewHost {
readonly windowId: number;
readonly sessionId?: string;
}
/**
* Summary information about a browser view, including its current state and
* ownership. Returned by the main service when listing or creating views.
*/
export interface IBrowserViewInfo {
readonly id: string;
readonly hostWindowId: number;
readonly host: IBrowserViewHost;
readonly owner: IBrowserViewOwner;
readonly associatedResource?: UriComponents;
readonly state: IBrowserViewState;
@@ -287,7 +293,7 @@ export interface IBrowserViewCreatedEvent {
/** Host, ownership, storage, and initial access for a newly created browser view. */
export interface IBrowserViewCreationContext {
readonly hostWindowId: number;
readonly host: IBrowserViewHost;
readonly owner: IBrowserViewOwner;
readonly session: BrowserViewSessionSelector;
/** Grants automation clients access before the view is announced to other processes. */
@@ -7,7 +7,7 @@ import { screen, WebContentsView, webContents } from 'electron';
import { Disposable } from '../../../base/common/lifecycle.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { IBrowserViewAudience, IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, browserViewIsolatedWorldId, browserZoomFactors, browserZoomDefaultIndex, IBrowserViewOwner, IBrowserViewEditorOpenOptions, IBrowserViewPermissionRequestEvent, equalsBrowserViewAudience, isBrowserViewAssociatedResourceNavigation, matchesBrowserViewAudience } from '../common/browserView.js';
import { IBrowserViewAudience, IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, browserViewIsolatedWorldId, browserZoomFactors, browserZoomDefaultIndex, IBrowserViewOwner, IBrowserViewEditorOpenOptions, IBrowserViewPermissionRequestEvent, equalsBrowserViewAudience, isBrowserViewAssociatedResourceNavigation, matchesBrowserViewAudience, IBrowserViewHost } from '../common/browserView.js';
import { BrowserViewEmulator } from './browserViewEmulator.js';
import { BrowserViewInspector } from './browserViewInspector.js';
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
@@ -123,7 +123,7 @@ export class BrowserView extends Disposable {
constructor(
public readonly id: string,
public readonly hostWindowId: number,
public readonly host: IBrowserViewHost,
owner: IBrowserViewOwner,
public readonly associatedResource: URI | undefined,
public readonly session: BrowserSession,
@@ -167,9 +167,9 @@ export class BrowserView extends Disposable {
this._view.setBounds({ x: 0, y: 0, width: 1024, height: 768 });
this._view.setBackgroundColor('#FFFFFF');
this._ownerWindow = this.windowsMainService.getWindowById(hostWindowId)!;
this._ownerWindow = this.windowsMainService.getWindowById(host.windowId)!;
if (!this._ownerWindow) {
throw new Error(`Window with ID ${hostWindowId} not found`);
throw new Error(`Window with ID ${host.windowId} not found`);
}
this._register(this._ownerWindow.onDidClose(() => this.dispose()));
this._register(this._ownerWindow.onWillLoad((e) => {
@@ -58,7 +58,7 @@ export class BrowserViewGroup extends Disposable implements ICDPBrowserTarget, I
super();
this._register(this.browserViewMainService.onDidCreateBrowserView(({ info }) => {
if (info.hostWindowId !== this.targetContext.hostWindowId) {
if (info.host.windowId !== this.targetContext.host.windowId) {
return;
}
@@ -91,7 +91,7 @@ export class BrowserViewGroup extends Disposable implements ICDPBrowserTarget, I
}
this._isActive = true;
const views = await this.browserViewMainService.getBrowserViews(this.targetContext.hostWindowId);
const views = await this.browserViewMainService.getBrowserViews(this.targetContext.host.windowId);
await Promise.all(views.map(async info => {
const view = this.browserViewMainService.tryGetBrowserView(info.id);
if (view) {
@@ -241,7 +241,7 @@ export class BrowserViewGroup extends Disposable implements ICDPBrowserTarget, I
const view = target.view.getWebContentsView();
const viewBounds = view.getBounds();
return {
windowId: this.targetContext.hostWindowId,
windowId: this.targetContext.host.windowId,
bounds: {
left: viewBounds.x,
top: viewBounds.y,
@@ -127,7 +127,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
private _getViewInfo(view: BrowserView): IBrowserViewInfo {
return {
id: view.id,
hostWindowId: view.hostWindowId,
host: view.host,
owner: view.owner,
associatedResource: view.associatedResource,
state: view.getState()
@@ -137,7 +137,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
async getBrowserViews(windowId?: number): Promise<IBrowserViewInfo[]> {
const result: IBrowserViewInfo[] = [];
for (const [, view] of this.browserViews) {
if (windowId !== undefined && view.hostWindowId !== windowId) {
if (windowId !== undefined && view.host.windowId !== windowId) {
continue;
}
result.push(this._getViewInfo(view));
@@ -378,7 +378,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
this._ensureWindowCloseSubscription(windowId);
for (const [, view] of this.browserViews) {
if (view.hostWindowId === windowId) {
if (view.host.windowId === windowId) {
if (didThemeChange) {
view.inspector.setTheme(config.theme);
}
@@ -426,13 +426,13 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
/**
* Create a browser view backed by the given {@link BrowserSession}.
*/
private _createNativeBrowserView(id: string, hostWindowId: number, owner: IBrowserViewOwner, browserSession: BrowserSession, associatedResource?: URI, options?: Electron.WebContentsViewConstructorOptions): BrowserView {
private _createNativeBrowserView(id: string, host: IBrowserViewCreationContext['host'], owner: IBrowserViewOwner, browserSession: BrowserSession, associatedResource?: URI, options?: Electron.WebContentsViewConstructorOptions): BrowserView {
if (this.browserViews.has(id)) {
throw new Error(`Browser view with id ${id} already exists`);
}
browserSession.connectStorage(this.applicationStorageMainService);
const windowConfiguration = this._windowConfigurations.get(hostWindowId);
const windowConfiguration = this._windowConfigurations.get(host.windowId);
if (typeof windowConfiguration?.maxHistoryEntries === 'number') {
browserSession.history.setMaxEntries(windowConfiguration.maxHistoryEntries);
}
@@ -443,14 +443,14 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
const view = this.instantiationService.createInstance(
BrowserView,
id,
hostWindowId,
host,
owner,
associatedResource,
browserSession,
// Child views share their host, owner, and storage, but do not implicitly inherit agent access.
(childOwner, url, electronOptions, editorOptions) => {
return this._createBrowserView(generateUuid(), {
hostWindowId,
host,
owner: childOwner,
session: browserSession.id,
initialUrl: url || undefined
@@ -473,8 +473,8 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
}
private _createBrowserView(id: string, options: IBrowserViewCreateOptions, editorOpenRequest?: IBrowserViewEditorOpenOptions, electronOptions?: Electron.WebContentsViewConstructorOptions): BrowserView {
const browserSession = this._resolveBrowserSession(id, options.hostWindowId, options.session);
const view = this._createNativeBrowserView(id, options.hostWindowId, options.owner, browserSession, URI.revive(options.associatedResource), electronOptions);
const browserSession = this._resolveBrowserSession(id, options.host.windowId, options.session);
const view = this._createNativeBrowserView(id, options.host, options.owner, browserSession, URI.revive(options.associatedResource), electronOptions);
if (options.initialAudiences) {
view.setAudiences(options.initialAudiences);
}
@@ -515,7 +515,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
return;
}
const windowConfiguration = this._windowConfigurations.get(view.hostWindowId);
const windowConfiguration = this._windowConfigurations.get(view.host.windowId);
const inspectTarget = windowConfiguration?.aiFeaturesDisabled
? undefined
: params.frame && await view.inspector.getElementHandle(BrowserViewInspectElementId.ContextMenuTarget, params.frame);
@@ -526,7 +526,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
label: localize('browser.contextMenu.openLinkInNewTab', 'Open Link in New Tab'),
click: () => {
void this.openNew(params.linkURL, {
hostWindowId: view.hostWindowId,
host: view.host,
owner: view.owner,
session: view.session.id,
}, { preserveFocus: true, background: true }, 'browserLinkBackground');
@@ -556,7 +556,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
label: localize('browser.contextMenu.openImageInNewTab', 'Open Image in New Tab'),
click: () => {
void this.openNew(params.srcURL!, {
hostWindowId: view.hostWindowId,
host: view.host,
owner: view.owner,
session: view.session.id,
}, { preserveFocus: true, background: true }, 'browserLinkBackground');
@@ -116,7 +116,9 @@ export class PlaywrightService extends Disposable implements IPlaywrightService
const group = await this.browserViewGroupRemoteService.createGroup(
{ audience: { type: 'agent', sessionId } },
{
hostWindowId: this.windowId,
host: {
windowId: this.windowId
},
...getAgentBrowserViewCreationDefaults(sessionId)
}
);
@@ -56,6 +56,7 @@ import {
IBrowserDeviceProfile,
IBrowserViewPermissionRequestEvent,
IBrowserElementSelectionState,
IBrowserViewHost,
} from '../../../../platform/browserView/common/browserView.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { isLocalhostAuthority } from '../../../../platform/url/common/trustedDomains.js';
@@ -361,6 +362,7 @@ export interface IBrowserViewCDPService {
*/
export interface IBrowserViewModel extends IDisposable {
readonly id: string;
readonly host: IBrowserViewHost;
readonly owner: IBrowserViewOwner;
readonly associatedResource: URI | undefined;
readonly url: string;
@@ -485,6 +487,7 @@ export class BrowserViewModel extends Disposable implements IBrowserViewModel {
constructor(
readonly id: string,
readonly host: IBrowserViewHost,
owner: IBrowserViewOwner,
readonly associatedResource: URI | undefined,
initialState: IBrowserViewState,
@@ -30,7 +30,9 @@ export class BrowserViewCDPService extends Disposable implements IBrowserViewCDP
return this._groupService.createGroup(
{ browserIds: [browserId] },
{
hostWindowId: mainWindow.vscodeWindowId,
host: {
windowId: mainWindow.vscodeWindowId
},
owner: { type: 'user' },
session: { scope: BrowserViewStorageScope.Ephemeral }
}
@@ -175,7 +175,7 @@ export class BrowserViewWorkbenchService extends Disposable implements IBrowserV
// Listen for new browser views
this._register(this._browserViewService.onDidCreateBrowserView(e => {
if (e.info.hostWindowId !== this._mainWindowId) {
if (e.info.host.windowId !== this._mainWindowId) {
return; // Not for this window
}
@@ -363,7 +363,9 @@ export class BrowserViewWorkbenchService extends Disposable implements IBrowserV
const info = await this._browserViewService.getOrCreateBrowserView(
id,
{
hostWindowId: this._mainWindowId,
host: {
windowId: this._mainWindowId
},
owner: createOptions?.owner ?? { type: 'user' },
associatedResource,
session: createOptions?.session ?? { scope: await this._resolveStorageScope() },
@@ -451,7 +453,7 @@ export class BrowserViewWorkbenchService extends Disposable implements IBrowserV
: initialUrl
? { ...info.state, url: initialUrl }
: info.state;
const model = this.instantiationService.createInstance(BrowserViewModel, info.id, info.owner, associatedResource, state, this._browserViewService);
const model = this.instantiationService.createInstance(BrowserViewModel, info.id, info.host, info.owner, associatedResource, state, this._browserViewService);
// Sanity: both pass and assign the model to be sure. It will no-op if already set.
this._getOrCreateLazy({ id: info.id, associatedResource, url: initialUrl }, model).model = model;