Path: blob/main/src/vs/platform/browserView/electron-main/browserViewMainService.ts
4776 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 { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';7import { VSBuffer } from '../../../base/common/buffer.js';8import { IBrowserViewBounds, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewService, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions } from '../common/browserView.js';9import { joinPath } from '../../../base/common/resources.js';10import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';11import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js';12import { BrowserView } from './browserView.js';13import { generateUuid } from '../../../base/common/uuid.js';1415export const IBrowserViewMainService = createDecorator<IBrowserViewMainService>('browserViewMainService');1617export interface IBrowserViewMainService extends IBrowserViewService {18// Additional electron-specific methods can be added here if needed in the future19}2021// Same as webviews22const allowedPermissions = new Set([23'pointerLock',24'notifications',25'clipboard-read',26'clipboard-sanitized-write'27]);2829export class BrowserViewMainService extends Disposable implements IBrowserViewMainService {30declare readonly _serviceBrand: undefined;3132/**33* Check if a webContents belongs to an integrated browser view34*/35private static readonly knownSessions = new WeakSet<Electron.Session>();36static isBrowserViewWebContents(contents: Electron.WebContents): boolean {37return BrowserViewMainService.knownSessions.has(contents.session);38}3940private readonly browserViews = this._register(new DisposableMap<string, BrowserView>());4142constructor(43@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,44@IInstantiationService private readonly instantiationService: IInstantiationService45) {46super();47}4849/**50* Get the session for a browser view based on data storage setting and workspace51*/52private getSession(requestedScope: BrowserViewStorageScope, viewId?: string, workspaceId?: string): {53session: Electron.Session;54resolvedScope: BrowserViewStorageScope;55} {56switch (requestedScope) {57case 'global':58return { session: session.fromPartition('persist:vscode-browser'), resolvedScope: BrowserViewStorageScope.Global };59case 'workspace':60if (workspaceId) {61const storage = joinPath(this.environmentMainService.workspaceStorageHome, workspaceId, 'browserStorage');62return { session: session.fromPath(storage.fsPath), resolvedScope: BrowserViewStorageScope.Workspace };63}64// fallthrough65case 'ephemeral':66default:67return { session: session.fromPartition(`vscode-browser-${viewId ?? generateUuid()}`), resolvedScope: BrowserViewStorageScope.Ephemeral };68}69}7071private configureSession(viewSession: Electron.Session): void {72viewSession.setPermissionRequestHandler((_webContents, permission, callback) => {73return callback(allowedPermissions.has(permission));74});75viewSession.setPermissionCheckHandler((_webContents, permission, _origin) => {76return allowedPermissions.has(permission);77});78}7980async getOrCreateBrowserView(id: string, scope: BrowserViewStorageScope, workspaceId?: string): Promise<IBrowserViewState> {81if (this.browserViews.has(id)) {82// Note: scope will be ignored if the view already exists.83// Browser views cannot be moved between sessions after creation.84const view = this.browserViews.get(id)!;85return view.getState();86}8788const { session, resolvedScope } = this.getSession(scope, id, workspaceId);89this.configureSession(session);90BrowserViewMainService.knownSessions.add(session);9192const view = this.instantiationService.createInstance(BrowserView, session, resolvedScope);93this.browserViews.set(id, view);9495return view.getState();96}9798/**99* Get a browser view or throw if not found100*/101private _getBrowserView(id: string): BrowserView {102const view = this.browserViews.get(id);103if (!view) {104throw new Error(`Browser view ${id} not found`);105}106return view;107}108109onDynamicDidNavigate(id: string) {110return this._getBrowserView(id).onDidNavigate;111}112113onDynamicDidChangeLoadingState(id: string) {114return this._getBrowserView(id).onDidChangeLoadingState;115}116117onDynamicDidChangeFocus(id: string) {118return this._getBrowserView(id).onDidChangeFocus;119}120121onDynamicDidChangeDevToolsState(id: string) {122return this._getBrowserView(id).onDidChangeDevToolsState;123}124125onDynamicDidKeyCommand(id: string) {126return this._getBrowserView(id).onDidKeyCommand;127}128129onDynamicDidChangeTitle(id: string) {130return this._getBrowserView(id).onDidChangeTitle;131}132133onDynamicDidChangeFavicon(id: string) {134return this._getBrowserView(id).onDidChangeFavicon;135}136137onDynamicDidRequestNewPage(id: string) {138return this._getBrowserView(id).onDidRequestNewPage;139}140141onDynamicDidClose(id: string) {142return this._getBrowserView(id).onDidClose;143}144145async destroyBrowserView(id: string): Promise<void> {146this.browserViews.deleteAndDispose(id);147}148149async layout(id: string, bounds: IBrowserViewBounds): Promise<void> {150return this._getBrowserView(id).layout(bounds);151}152153async setVisible(id: string, visible: boolean): Promise<void> {154return this._getBrowserView(id).setVisible(visible);155}156157async loadURL(id: string, url: string): Promise<void> {158return this._getBrowserView(id).loadURL(url);159}160161async getURL(id: string): Promise<string> {162return this._getBrowserView(id).getURL();163}164165async goBack(id: string): Promise<void> {166return this._getBrowserView(id).goBack();167}168169async goForward(id: string): Promise<void> {170return this._getBrowserView(id).goForward();171}172173async reload(id: string): Promise<void> {174return this._getBrowserView(id).reload();175}176177async toggleDevTools(id: string): Promise<void> {178return this._getBrowserView(id).toggleDevTools();179}180181async canGoBack(id: string): Promise<boolean> {182return this._getBrowserView(id).canGoBack();183}184185async canGoForward(id: string): Promise<boolean> {186return this._getBrowserView(id).canGoForward();187}188189async captureScreenshot(id: string, options?: IBrowserViewCaptureScreenshotOptions): Promise<VSBuffer> {190return this._getBrowserView(id).captureScreenshot(options);191}192193async dispatchKeyEvent(id: string, keyEvent: IBrowserViewKeyDownEvent): Promise<void> {194return this._getBrowserView(id).dispatchKeyEvent(keyEvent);195}196197async setZoomFactor(id: string, zoomFactor: number): Promise<void> {198return this._getBrowserView(id).setZoomFactor(zoomFactor);199}200201async focus(id: string): Promise<void> {202return this._getBrowserView(id).focus();203}204205async clearGlobalStorage(): Promise<void> {206const { session, resolvedScope } = this.getSession(BrowserViewStorageScope.Global);207if (resolvedScope !== BrowserViewStorageScope.Global) {208throw new Error('Failed to resolve global storage session');209}210await session.clearData();211}212213async clearWorkspaceStorage(workspaceId: string): Promise<void> {214const { session, resolvedScope } = this.getSession(BrowserViewStorageScope.Workspace, undefined, workspaceId);215if (resolvedScope !== BrowserViewStorageScope.Workspace) {216throw new Error('Failed to resolve workspace storage session');217}218await session.clearData();219}220}221222223