Files
vscode/src/vs/platform/browserView/electron-main/browserViewMainService.ts
T
Kyle Cutler 6f458a11a9 Browser: make paused state less jarring (#291637)
* Browser: make paused state less jarring

* Update src/vs/workbench/contrib/browserView/electron-browser/browserEditor.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/vs/workbench/contrib/browserView/electron-browser/browserEditor.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* 15%

* fix

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-29 09:52:38 -08:00

246 lines
8.2 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { session } from 'electron';
import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { IBrowserViewBounds, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewService, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions } from '../common/browserView.js';
import { joinPath } from '../../../base/common/resources.js';
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js';
import { BrowserView } from './browserView.js';
import { generateUuid } from '../../../base/common/uuid.js';
export const IBrowserViewMainService = createDecorator<IBrowserViewMainService>('browserViewMainService');
export interface IBrowserViewMainService extends IBrowserViewService {
tryGetBrowserView(id: string): BrowserView | undefined;
}
// Same as webviews
const allowedPermissions = new Set([
'pointerLock',
'notifications',
'clipboard-read',
'clipboard-sanitized-write'
]);
export class BrowserViewMainService extends Disposable implements IBrowserViewMainService {
declare readonly _serviceBrand: undefined;
/**
* Check if a webContents belongs to an integrated browser view
*/
private static readonly knownSessions = new WeakSet<Electron.Session>();
static isBrowserViewWebContents(contents: Electron.WebContents): boolean {
return BrowserViewMainService.knownSessions.has(contents.session);
}
private readonly browserViews = this._register(new DisposableMap<string, BrowserView>());
constructor(
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
}
/**
* Get the session for a browser view based on data storage setting and workspace
*/
private getSession(requestedScope: BrowserViewStorageScope, viewId?: string, workspaceId?: string): {
session: Electron.Session;
resolvedScope: BrowserViewStorageScope;
} {
switch (requestedScope) {
case 'global':
return { session: session.fromPartition('persist:vscode-browser'), resolvedScope: BrowserViewStorageScope.Global };
case 'workspace':
if (workspaceId) {
const storage = joinPath(this.environmentMainService.workspaceStorageHome, workspaceId, 'browserStorage');
return { session: session.fromPath(storage.fsPath), resolvedScope: BrowserViewStorageScope.Workspace };
}
// fallthrough
case 'ephemeral':
default:
return { session: session.fromPartition(`vscode-browser-${viewId ?? generateUuid()}`), resolvedScope: BrowserViewStorageScope.Ephemeral };
}
}
private configureSession(viewSession: Electron.Session): void {
viewSession.setPermissionRequestHandler((_webContents, permission, callback) => {
return callback(allowedPermissions.has(permission));
});
viewSession.setPermissionCheckHandler((_webContents, permission, _origin) => {
return allowedPermissions.has(permission);
});
}
async getOrCreateBrowserView(id: string, scope: BrowserViewStorageScope, workspaceId?: string): Promise<IBrowserViewState> {
if (this.browserViews.has(id)) {
// Note: scope will be ignored if the view already exists.
// Browser views cannot be moved between sessions after creation.
const view = this.browserViews.get(id)!;
return view.getState();
}
const { session, resolvedScope } = this.getSession(scope, id, workspaceId);
this.configureSession(session);
BrowserViewMainService.knownSessions.add(session);
const view = this.instantiationService.createInstance(BrowserView, session, resolvedScope);
this.browserViews.set(id, view);
return view.getState();
}
tryGetBrowserView(id: string): BrowserView | undefined {
return this.browserViews.get(id);
}
/**
* Get a browser view or throw if not found
*/
private _getBrowserView(id: string): BrowserView {
const view = this.browserViews.get(id);
if (!view) {
throw new Error(`Browser view ${id} not found`);
}
return view;
}
onDynamicDidNavigate(id: string) {
return this._getBrowserView(id).onDidNavigate;
}
onDynamicDidChangeLoadingState(id: string) {
return this._getBrowserView(id).onDidChangeLoadingState;
}
onDynamicDidChangeFocus(id: string) {
return this._getBrowserView(id).onDidChangeFocus;
}
onDynamicDidChangeVisibility(id: string) {
return this._getBrowserView(id).onDidChangeVisibility;
}
onDynamicDidChangeDevToolsState(id: string) {
return this._getBrowserView(id).onDidChangeDevToolsState;
}
onDynamicDidKeyCommand(id: string) {
return this._getBrowserView(id).onDidKeyCommand;
}
onDynamicDidChangeTitle(id: string) {
return this._getBrowserView(id).onDidChangeTitle;
}
onDynamicDidChangeFavicon(id: string) {
return this._getBrowserView(id).onDidChangeFavicon;
}
onDynamicDidRequestNewPage(id: string) {
return this._getBrowserView(id).onDidRequestNewPage;
}
onDynamicDidFindInPage(id: string) {
return this._getBrowserView(id).onDidFindInPage;
}
onDynamicDidClose(id: string) {
return this._getBrowserView(id).onDidClose;
}
async destroyBrowserView(id: string): Promise<void> {
this.browserViews.deleteAndDispose(id);
}
async layout(id: string, bounds: IBrowserViewBounds): Promise<void> {
return this._getBrowserView(id).layout(bounds);
}
async setVisible(id: string, visible: boolean): Promise<void> {
return this._getBrowserView(id).setVisible(visible);
}
async loadURL(id: string, url: string): Promise<void> {
return this._getBrowserView(id).loadURL(url);
}
async getURL(id: string): Promise<string> {
return this._getBrowserView(id).getURL();
}
async goBack(id: string): Promise<void> {
return this._getBrowserView(id).goBack();
}
async goForward(id: string): Promise<void> {
return this._getBrowserView(id).goForward();
}
async reload(id: string): Promise<void> {
return this._getBrowserView(id).reload();
}
async toggleDevTools(id: string): Promise<void> {
return this._getBrowserView(id).toggleDevTools();
}
async canGoBack(id: string): Promise<boolean> {
return this._getBrowserView(id).canGoBack();
}
async canGoForward(id: string): Promise<boolean> {
return this._getBrowserView(id).canGoForward();
}
async captureScreenshot(id: string, options?: IBrowserViewCaptureScreenshotOptions): Promise<VSBuffer> {
return this._getBrowserView(id).captureScreenshot(options);
}
async dispatchKeyEvent(id: string, keyEvent: IBrowserViewKeyDownEvent): Promise<void> {
return this._getBrowserView(id).dispatchKeyEvent(keyEvent);
}
async setZoomFactor(id: string, zoomFactor: number): Promise<void> {
return this._getBrowserView(id).setZoomFactor(zoomFactor);
}
async focus(id: string): Promise<void> {
return this._getBrowserView(id).focus();
}
async findInPage(id: string, text: string, options?: IBrowserViewFindInPageOptions): Promise<void> {
return this._getBrowserView(id).findInPage(text, options);
}
async stopFindInPage(id: string, keepSelection?: boolean): Promise<void> {
return this._getBrowserView(id).stopFindInPage(keepSelection);
}
async clearStorage(id: string): Promise<void> {
return this._getBrowserView(id).clearStorage();
}
async clearGlobalStorage(): Promise<void> {
const { session, resolvedScope } = this.getSession(BrowserViewStorageScope.Global);
if (resolvedScope !== BrowserViewStorageScope.Global) {
throw new Error('Failed to resolve global storage session');
}
await session.clearData();
}
async clearWorkspaceStorage(workspaceId: string): Promise<void> {
const { session, resolvedScope } = this.getSession(BrowserViewStorageScope.Workspace, undefined, workspaceId);
if (resolvedScope !== BrowserViewStorageScope.Workspace) {
throw new Error('Failed to resolve workspace storage session');
}
await session.clearData();
}
}