From be86ef4bfbcaa4eb04ef5b37b278cc42f97ae8f8 Mon Sep 17 00:00:00 2001 From: Kyle Cutler Date: Fri, 21 Aug 2026 14:57:05 -0700 Subject: [PATCH] Browser: support tracking the host session id separately from agentic ownership --- .../browserView/common/browserView.ts | 10 ++++++-- .../browserView/electron-main/browserView.ts | 8 +++---- .../electron-main/browserViewGroup.ts | 6 ++--- .../electron-main/browserViewMainService.ts | 24 +++++++++---------- .../browserView/node/playwrightService.ts | 4 +++- .../contrib/browserView/common/browserView.ts | 3 +++ .../electron-browser/browserViewCDPService.ts | 4 +++- .../browserViewWorkbenchService.ts | 8 ++++--- 8 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/vs/platform/browserView/common/browserView.ts b/src/vs/platform/browserView/common/browserView.ts index dc63ab79e47..8e1f7c00814 100644 --- a/src/vs/platform/browserView/common/browserView.ts +++ b/src/vs/platform/browserView/common/browserView.ts @@ -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. */ diff --git a/src/vs/platform/browserView/electron-main/browserView.ts b/src/vs/platform/browserView/electron-main/browserView.ts index 3d5e5a67985..6831bae3194 100644 --- a/src/vs/platform/browserView/electron-main/browserView.ts +++ b/src/vs/platform/browserView/electron-main/browserView.ts @@ -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) => { diff --git a/src/vs/platform/browserView/electron-main/browserViewGroup.ts b/src/vs/platform/browserView/electron-main/browserViewGroup.ts index 9808c0edaea..cca6ee680dc 100644 --- a/src/vs/platform/browserView/electron-main/browserViewGroup.ts +++ b/src/vs/platform/browserView/electron-main/browserViewGroup.ts @@ -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, diff --git a/src/vs/platform/browserView/electron-main/browserViewMainService.ts b/src/vs/platform/browserView/electron-main/browserViewMainService.ts index 85d3b21fc7b..4e96fe60a55 100644 --- a/src/vs/platform/browserView/electron-main/browserViewMainService.ts +++ b/src/vs/platform/browserView/electron-main/browserViewMainService.ts @@ -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 { 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'); diff --git a/src/vs/platform/browserView/node/playwrightService.ts b/src/vs/platform/browserView/node/playwrightService.ts index 135069b93b3..3be826992a4 100644 --- a/src/vs/platform/browserView/node/playwrightService.ts +++ b/src/vs/platform/browserView/node/playwrightService.ts @@ -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) } ); diff --git a/src/vs/workbench/contrib/browserView/common/browserView.ts b/src/vs/workbench/contrib/browserView/common/browserView.ts index 6cd181bc509..bba3637e8f0 100644 --- a/src/vs/workbench/contrib/browserView/common/browserView.ts +++ b/src/vs/workbench/contrib/browserView/common/browserView.ts @@ -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, diff --git a/src/vs/workbench/contrib/browserView/electron-browser/browserViewCDPService.ts b/src/vs/workbench/contrib/browserView/electron-browser/browserViewCDPService.ts index abf2c74f1e0..b90f439389b 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/browserViewCDPService.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/browserViewCDPService.ts @@ -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 } } diff --git a/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts b/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts index 99f97400015..3e987b284d8 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/browserViewWorkbenchService.ts @@ -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;