Path: blob/main/src/vs/workbench/api/browser/mainThreadBrowsers.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 { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';6import { IEditorService } from '../../services/editor/common/editorService.js';7import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';8import { BrowserTabDto, ExtHostBrowsersShape, ExtHostContext, MainContext, MainThreadBrowsersShape } from '../common/extHost.protocol.js';9import { IBrowserViewCDPService, IBrowserViewWorkbenchService } from '../../contrib/browserView/common/browserView.js';10import { BrowserViewUri } from '../../../platform/browserView/common/browserViewUri.js';11import { generateUuid } from '../../../base/common/uuid.js';12import { EditorGroupColumn, columnToEditorGroup } from '../../services/editor/common/editorGroupColumn.js';13import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js';14import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';15import { IEditorOptions } from '../../../platform/editor/common/editor.js';16import { CDPRequest } from '../../../platform/browserView/common/cdp/types.js';17import { BrowserEditorInput } from '../../contrib/browserView/common/browserEditorInput.js';1819@extHostNamedCustomer(MainContext.MainThreadBrowsers)20export class MainThreadBrowsers extends Disposable implements MainThreadBrowsersShape {2122private readonly _proxy: ExtHostBrowsersShape;2324private readonly _cdpSessions = this._register(new DisposableMap<string, { groupId: string } & IDisposable>());25private readonly _knownBrowsers = this._register(new DisposableMap<string, { input: BrowserEditorInput } & IDisposable>());2627constructor(28extHostContext: IExtHostContext,29@IEditorService private readonly editorService: IEditorService,30@IBrowserViewCDPService private readonly cdpService: IBrowserViewCDPService,31@IBrowserViewWorkbenchService private readonly browserViewService: IBrowserViewWorkbenchService,32@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,33@IConfigurationService private readonly configurationService: IConfigurationService,34) {35super();36this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostBrowsers);3738// Track open browser editors via the workbench service39this._register(this.browserViewService.onDidChangeBrowserViews(() => {40for (const editor of this.browserViewService.getKnownBrowserViews().values()) {41this._track(editor);42}43}));44this._register(this.editorService.onDidActiveEditorChange(() => this._syncActiveBrowserTab()));4546// Initial sync47for (const editor of this.browserViewService.getKnownBrowserViews().values()) {48this._track(editor);49}50this._syncActiveBrowserTab();51}5253// #region Browser tab open5455async $openBrowserTab(url: string, viewColumn?: EditorGroupColumn, options?: IEditorOptions): Promise<BrowserTabDto> {56const id = generateUuid();57const browserUri = BrowserViewUri.forId(id);5859await this.editorService.openEditor(60{61resource: browserUri,62options: { ...options, viewState: { url } }63},64columnToEditorGroup(this.editorGroupsService, this.configurationService, viewColumn),65);66const known = this._knownBrowsers.get(id);67if (!known) {68throw new Error('Failed to open browser tab');69}7071return this._toDto(known.input);72}7374// #endregion7576// #region Browser tab tracking7778private _lastActiveBrowserId: string | undefined = undefined;79private async _syncActiveBrowserTab(): Promise<void> {80const active = this.editorService.activeEditorPane?.input;81let activeId: string | undefined;82if (active instanceof BrowserEditorInput) {83this._track(active);84activeId = active.id;85}86if (this._lastActiveBrowserId !== activeId) {87this._lastActiveBrowserId = activeId;88this._proxy.$onDidChangeActiveBrowserTab(activeId);89}90}9192private _track(input: BrowserEditorInput): void {93if (this._knownBrowsers.has(input.id)) {94return;95}96const disposables = new DisposableStore();9798// Track property changes. Currently all the tracked properties are covered under the `onDidChangeLabel` event.99disposables.add(input.onDidChangeLabel(() => {100this._proxy.$onDidChangeBrowserTabState(this._toDto(input));101}));102disposables.add(input.onWillDispose(() => {103this._knownBrowsers.deleteAndDispose(input.id);104}));105disposables.add(toDisposable(() => {106this._proxy.$onDidCloseBrowserTab(input.id);107}));108109this._knownBrowsers.set(input.id, { input, dispose: () => disposables.dispose() });110this._proxy.$onDidOpenBrowserTab(this._toDto(input));111}112113private _toDto(input: BrowserEditorInput): BrowserTabDto {114return {115id: input.id,116url: input.url || 'about:blank',117title: input.getTitle(),118favicon: input.favicon,119};120}121122// #endregion123124// #region CDP session management125126async $startCDPSession(sessionId: string, browserId: string): Promise<void> {127const known = this._knownBrowsers.get(browserId);128if (!known) {129throw new Error(`Unknown browser id: ${browserId}`);130}131132// Before starting a session, resolve the input to ensure the underlying web contents exist and can be attached.133await known.input.resolve();134135const groupId = await this.cdpService.createSessionGroup(browserId);136const disposables = new DisposableStore();137138// Wire CDP messages from main process back to ext host139disposables.add(this.cdpService.onCDPMessage(groupId)(message => {140this._proxy.$onCDPSessionMessage(sessionId, message);141}));142disposables.add(this.cdpService.onDidDestroy(groupId)(() => {143this._cdpSessions.deleteAndDispose(sessionId);144}));145disposables.add(toDisposable(() => {146this.cdpService.destroySessionGroup(groupId).catch(() => { });147this._proxy.$onCDPSessionClosed(sessionId);148}));149150this._cdpSessions.set(sessionId, { groupId, dispose: () => disposables.dispose() });151}152153async $closeCDPSession(sessionId: string): Promise<void> {154this._cdpSessions.deleteAndDispose(sessionId);155}156157async $sendCDPMessage(sessionId: string, message: CDPRequest): Promise<void> {158const session = this._cdpSessions.get(sessionId);159if (session) {160await this.cdpService.sendCDPMessage(session.groupId, message);161}162}163164async $closeBrowserTab(browserId: string): Promise<void> {165const known = this._knownBrowsers.get(browserId);166if (!known) {167throw new Error(`Unknown browser id: ${browserId}`);168}169known.input.dispose();170}171172// #endregion173}174175176