Path: blob/main/src/vs/platform/browserView/electron-main/browserSession.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { session } from 'electron';6import { joinPath } from '../../../base/common/resources.js';7import { URI } from '../../../base/common/uri.js';8import { IApplicationStorageMainService } from '../../storage/electron-main/storageMainService.js';9import { BrowserViewStorageScope } from '../common/browserView.js';10import { BrowserSessionTrust, IBrowserSessionTrust } from './browserSessionTrust.js';11import { FileAccess } from '../../../base/common/network.js';1213// Same as webviews, minus clipboard-read14const allowedPermissions = new Set([15'pointerLock',16'notifications',17'clipboard-sanitized-write'18]);1920/**21* Holds an Electron session along with its storage scope and unique browser22* context identifier. Each instance maps one-to-one to an Electron23* {@link Electron.Session} -- the {@link id} is derived from what makes the24* Electron session unique (scope + workspace), **not** from any view id.25* Multiple browser views may reference the same `BrowserSession`.26*27* The class centralises the permission configuration. The {@link id}28* doubles as the CDP `browserContextId`.29*30* This class uses a private constructor with static factory methods31* ({@link getOrCreate}, {@link getOrCreateGlobal}, etc.) and maintains32* an internal registry of live sessions. Use the static methods to33* obtain instances.34*/35export class BrowserSession {3637// #region Static registry3839/**40* Primary store — keyed by Electron session so entries are41* automatically removed when the Electron session is GC'd.42*43* The goal is to ensure that BrowserSessions have the exact same lifespan as their Electron sessions.44*/45private static readonly _bySession = new WeakMap<Electron.Session, BrowserSession>();4647/**48* String-keyed lookup for {@link get} and {@link getBrowserContextIds}.49* Values are weak references so they don't prevent GC of the50* {@link BrowserSession} (and transitively the Electron session).51*52* ID derivation rules (one-to-one with Electron sessions):53* - Global scope -> `"global"`54* - Workspace scope -> `"workspace:${workspaceId}"`55* - Ephemeral scope -> `"ephemeral:${viewId}"` or `"${type}:${viewId}"` for custom types56*/57private static readonly _byId = new Map<string, WeakRef<BrowserSession>>();5859/**60* Cleans up stale {@link _byId} entries when the Electron session61* they point to is garbage-collected.62*/63private static readonly _finalizer = new FinalizationRegistry<string>((id) => {64BrowserSession._byId.delete(id);65});6667/**68* Weak set mirroring the Electron sessions owned by any BrowserSession.69* Useful for quickly checking whether a given {@link Electron.WebContents}70* belongs to the integrated browser.71*/72static readonly knownSessions = new WeakSet<Electron.Session>();7374/**75* Check if a {@link Electron.WebContents} belongs to an integrated browser76* view backed by a BrowserSession.77*/78static isBrowserViewWebContents(contents: Electron.WebContents): boolean {79return BrowserSession.knownSessions.has(contents.session);80}8182/**83* Return an existing session for the given id, or `undefined`.84*/85static get(id: string): BrowserSession | undefined {86const ref = BrowserSession._byId.get(id);87if (!ref) {88return undefined;89}90const bs = ref.deref();91if (!bs) {92BrowserSession._byId.delete(id);93}94return bs;95}9697/**98* Return all live browser context IDs (i.e. all session {@link id}s).99*/100static getBrowserContextIds(): string[] {101const ids: string[] = [];102for (const [id, ref] of BrowserSession._byId) {103if (ref.deref()) {104ids.push(id);105} else {106BrowserSession._byId.delete(id);107}108}109return ids;110}111112/**113* Get or create the singleton global-scope session.114*/115static getOrCreateGlobal(): BrowserSession {116const electronSession = session.fromPartition('persist:vscode-browser');117return BrowserSession._bySession.get(electronSession)118?? new BrowserSession('global', electronSession, BrowserViewStorageScope.Global);119}120121/**122* Get or create a workspace-scope session for the given workspace.123*/124static getOrCreateWorkspace(workspaceId: string, workspaceStorageHome: URI): BrowserSession {125const storage = joinPath(workspaceStorageHome, workspaceId, 'browserStorage');126const electronSession = session.fromPath(storage.fsPath);127return BrowserSession._bySession.get(electronSession)128?? new BrowserSession(`workspace:${workspaceId}`, electronSession, BrowserViewStorageScope.Workspace);129}130131/**132* Get or create an ephemeral session for the given view / target id.133*/134static getOrCreateEphemeral(viewId: string, type?: string): BrowserSession {135if (type === 'workspace' || type === 'ephemeral') {136throw new Error(`Cannot create session with reserved type '${type}'`);137}138139const sessionId = `${type ?? 'ephemeral'}:${viewId}`;140const electronSession = session.fromPartition(`vscode-browser-${type}${viewId}`);141return BrowserSession._bySession.get(electronSession)142?? new BrowserSession(sessionId, electronSession, BrowserViewStorageScope.Ephemeral);143}144145/**146* Get or create a session for a workbench-originated browser view.147* The session id is derived from the *scope* -- not the view id -- so148* multiple views that share a scope (e.g. two Global views) get the149* same `BrowserSession`.150*151* @param viewId Used only for ephemeral sessions where every view152* needs its own Electron session.153* @param scope Desired storage scope.154* @param workspaceStorageHome Root folder under which per-workspace155* browser storage is created156* (`IEnvironmentMainService.workspaceStorageHome`).157* @param workspaceId Only required when `scope` is `workspace`.158*/159static getOrCreate(160viewId: string,161scope: BrowserViewStorageScope,162workspaceStorageHome: URI,163workspaceId?: string,164): BrowserSession {165switch (scope) {166case BrowserViewStorageScope.Global:167return BrowserSession.getOrCreateGlobal();168case BrowserViewStorageScope.Workspace:169if (workspaceId) {170return BrowserSession.getOrCreateWorkspace(workspaceId, workspaceStorageHome);171}172// fallthrough -- no workspace context -> ephemeral173case BrowserViewStorageScope.Ephemeral:174default:175return BrowserSession.getOrCreateEphemeral(viewId);176}177}178179// #endregion180181// #region Instance182183private readonly _trust: BrowserSessionTrust;184185private constructor(186/**187* Unique identifier for this session. Derived from what makes the188* underlying Electron session unique (scope key, workspace id, view189* id, or context uuid) -- NOT from any particular view id.190*/191readonly id: string,192/** The underlying Electron session. */193readonly electronSession: Electron.Session,194/** Resolved storage scope. */195readonly storageScope: BrowserViewStorageScope,196) {197this._trust = new BrowserSessionTrust(this);198this.configure();199BrowserSession.knownSessions.add(electronSession);200BrowserSession._bySession.set(electronSession, this);201BrowserSession._byId.set(id, new WeakRef(this));202BrowserSession._finalizer.register(electronSession, id);203}204205/** Public trust interface for consumers that need cert operations. */206get trust(): IBrowserSessionTrust {207return this._trust;208}209210/**211* Connect application storage to this session so that preferences212* (trusted certificates, permissions, etc.) are persisted across213* restarts. Restores any previously-saved data on first call;214* subsequent calls are no-ops.215*/216connectStorage(storage: IApplicationStorageMainService): void {217this._trust.connectStorage(storage);218}219220/**221* Apply the permission policy and preload scripts to the session.222*/223private configure(): void {224this.electronSession.setPermissionRequestHandler((_webContents, permission, callback) => {225return callback(allowedPermissions.has(permission));226});227this.electronSession.setPermissionCheckHandler((_webContents, permission, _origin) => {228return allowedPermissions.has(permission);229});230this.electronSession.registerPreloadScript({231type: 'frame',232filePath: FileAccess.asFileUri('vs/platform/browserView/electron-browser/preload-browserView.js').fsPath233});234}235236/**237* Clear all session data including trust state and all browsing data.238*/239async clearData(): Promise<void> {240await this._trust.clear();241await this.electronSession.clearData();242}243244// #endregion245}246247248