Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/browser/mainThreadBrowsers.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
7
import { IEditorService } from '../../services/editor/common/editorService.js';
8
import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';
9
import { BrowserTabDto, ExtHostBrowsersShape, ExtHostContext, MainContext, MainThreadBrowsersShape } from '../common/extHost.protocol.js';
10
import { IBrowserViewCDPService, IBrowserViewWorkbenchService } from '../../contrib/browserView/common/browserView.js';
11
import { BrowserViewUri } from '../../../platform/browserView/common/browserViewUri.js';
12
import { generateUuid } from '../../../base/common/uuid.js';
13
import { EditorGroupColumn, columnToEditorGroup } from '../../services/editor/common/editorGroupColumn.js';
14
import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js';
15
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
16
import { IEditorOptions } from '../../../platform/editor/common/editor.js';
17
import { CDPRequest } from '../../../platform/browserView/common/cdp/types.js';
18
import { BrowserEditorInput } from '../../contrib/browserView/common/browserEditorInput.js';
19
20
@extHostNamedCustomer(MainContext.MainThreadBrowsers)
21
export class MainThreadBrowsers extends Disposable implements MainThreadBrowsersShape {
22
23
private readonly _proxy: ExtHostBrowsersShape;
24
25
private readonly _cdpSessions = this._register(new DisposableMap<string, { groupId: string } & IDisposable>());
26
private readonly _knownBrowsers = this._register(new DisposableMap<string, { input: BrowserEditorInput } & IDisposable>());
27
28
constructor(
29
extHostContext: IExtHostContext,
30
@IEditorService private readonly editorService: IEditorService,
31
@IBrowserViewCDPService private readonly cdpService: IBrowserViewCDPService,
32
@IBrowserViewWorkbenchService private readonly browserViewService: IBrowserViewWorkbenchService,
33
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
34
@IConfigurationService private readonly configurationService: IConfigurationService,
35
) {
36
super();
37
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostBrowsers);
38
39
// Track open browser editors via the workbench service
40
this._register(this.browserViewService.onDidChangeBrowserViews(() => {
41
for (const editor of this.browserViewService.getKnownBrowserViews().values()) {
42
this._track(editor);
43
}
44
}));
45
this._register(this.editorService.onDidActiveEditorChange(() => this._syncActiveBrowserTab()));
46
47
// Initial sync
48
for (const editor of this.browserViewService.getKnownBrowserViews().values()) {
49
this._track(editor);
50
}
51
this._syncActiveBrowserTab();
52
}
53
54
// #region Browser tab open
55
56
async $openBrowserTab(url: string, viewColumn?: EditorGroupColumn, options?: IEditorOptions): Promise<BrowserTabDto> {
57
const id = generateUuid();
58
const browserUri = BrowserViewUri.forId(id);
59
60
await this.editorService.openEditor(
61
{
62
resource: browserUri,
63
options: { ...options, viewState: { url } }
64
},
65
columnToEditorGroup(this.editorGroupsService, this.configurationService, viewColumn),
66
);
67
const known = this._knownBrowsers.get(id);
68
if (!known) {
69
throw new Error('Failed to open browser tab');
70
}
71
72
return this._toDto(known.input);
73
}
74
75
// #endregion
76
77
// #region Browser tab tracking
78
79
private _lastActiveBrowserId: string | undefined = undefined;
80
private async _syncActiveBrowserTab(): Promise<void> {
81
const active = this.editorService.activeEditorPane?.input;
82
let activeId: string | undefined;
83
if (active instanceof BrowserEditorInput) {
84
this._track(active);
85
activeId = active.id;
86
}
87
if (this._lastActiveBrowserId !== activeId) {
88
this._lastActiveBrowserId = activeId;
89
this._proxy.$onDidChangeActiveBrowserTab(activeId);
90
}
91
}
92
93
private _track(input: BrowserEditorInput): void {
94
if (this._knownBrowsers.has(input.id)) {
95
return;
96
}
97
const disposables = new DisposableStore();
98
99
// Track property changes. Currently all the tracked properties are covered under the `onDidChangeLabel` event.
100
disposables.add(input.onDidChangeLabel(() => {
101
this._proxy.$onDidChangeBrowserTabState(this._toDto(input));
102
}));
103
disposables.add(input.onWillDispose(() => {
104
this._knownBrowsers.deleteAndDispose(input.id);
105
}));
106
disposables.add(toDisposable(() => {
107
this._proxy.$onDidCloseBrowserTab(input.id);
108
}));
109
110
this._knownBrowsers.set(input.id, { input, dispose: () => disposables.dispose() });
111
this._proxy.$onDidOpenBrowserTab(this._toDto(input));
112
}
113
114
private _toDto(input: BrowserEditorInput): BrowserTabDto {
115
return {
116
id: input.id,
117
url: input.url || 'about:blank',
118
title: input.getTitle(),
119
favicon: input.favicon,
120
};
121
}
122
123
// #endregion
124
125
// #region CDP session management
126
127
async $startCDPSession(sessionId: string, browserId: string): Promise<void> {
128
const known = this._knownBrowsers.get(browserId);
129
if (!known) {
130
throw new Error(`Unknown browser id: ${browserId}`);
131
}
132
133
// Before starting a session, resolve the input to ensure the underlying web contents exist and can be attached.
134
await known.input.resolve();
135
136
const groupId = await this.cdpService.createSessionGroup(browserId);
137
const disposables = new DisposableStore();
138
139
// Wire CDP messages from main process back to ext host
140
disposables.add(this.cdpService.onCDPMessage(groupId)(message => {
141
this._proxy.$onCDPSessionMessage(sessionId, message);
142
}));
143
disposables.add(this.cdpService.onDidDestroy(groupId)(() => {
144
this._cdpSessions.deleteAndDispose(sessionId);
145
}));
146
disposables.add(toDisposable(() => {
147
this.cdpService.destroySessionGroup(groupId).catch(() => { });
148
this._proxy.$onCDPSessionClosed(sessionId);
149
}));
150
151
this._cdpSessions.set(sessionId, { groupId, dispose: () => disposables.dispose() });
152
}
153
154
async $closeCDPSession(sessionId: string): Promise<void> {
155
this._cdpSessions.deleteAndDispose(sessionId);
156
}
157
158
async $sendCDPMessage(sessionId: string, message: CDPRequest): Promise<void> {
159
const session = this._cdpSessions.get(sessionId);
160
if (session) {
161
await this.cdpService.sendCDPMessage(session.groupId, message);
162
}
163
}
164
165
async $closeBrowserTab(browserId: string): Promise<void> {
166
const known = this._knownBrowsers.get(browserId);
167
if (!known) {
168
throw new Error(`Unknown browser id: ${browserId}`);
169
}
170
known.input.dispose();
171
}
172
173
// #endregion
174
}
175
176