Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/common/extHostBrowsers.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 { Emitter, Event } from '../../../base/common/event.js';
7
import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
8
import { URI } from '../../../base/common/uri.js';
9
import { Codicon } from '../../../base/common/codicons.js';
10
import type * as vscode from 'vscode';
11
import { BrowserTabDto, ExtHostBrowsersShape, IMainContext, MainContext, MainThreadBrowsersShape } from './extHost.protocol.js';
12
import { generateUuid } from '../../../base/common/uuid.js';
13
import * as extHostTypes from './extHostTypes.js';
14
import * as typeConverters from './extHostTypeConverters.js';
15
import { CDPEvent, CDPRequest, CDPResponse } from '../../../platform/browserView/common/cdp/types.js';
16
17
// #region Internal browser tab object
18
19
class ExtHostBrowserTab {
20
private _url: string;
21
private _title: string;
22
private _favicon: string | undefined;
23
24
readonly value: vscode.BrowserTab;
25
26
constructor(
27
readonly id: string,
28
private readonly _proxy: MainThreadBrowsersShape,
29
private readonly _sessions: DisposableMap<string, ExtHostBrowserCDPSession>,
30
data: BrowserTabDto,
31
) {
32
this._url = data.url;
33
this._title = data.title;
34
this._favicon = data.favicon;
35
36
const that = this;
37
this.value = {
38
get url(): string { return that._url; },
39
get title(): string { return that._title; },
40
get icon(): vscode.IconPath {
41
return that._favicon
42
? URI.parse(that._favicon)
43
: new extHostTypes.ThemeIcon(Codicon.globe.id) as vscode.ThemeIcon;
44
},
45
startCDPSession(): Promise<vscode.BrowserCDPSession> {
46
return that._startCDPSession();
47
},
48
close(): Promise<void> {
49
return that._close();
50
}
51
};
52
}
53
54
update(data: BrowserTabDto): boolean {
55
let changed = false;
56
if (data.url !== this._url) {
57
this._url = data.url;
58
changed = true;
59
}
60
if (data.title !== this._title) {
61
this._title = data.title;
62
changed = true;
63
}
64
if (data.favicon !== this._favicon) {
65
this._favicon = data.favicon;
66
changed = true;
67
}
68
return changed;
69
}
70
71
private async _startCDPSession(): Promise<vscode.BrowserCDPSession> {
72
const sessionId = generateUuid();
73
await this._proxy.$startCDPSession(sessionId, this.id);
74
const session = new ExtHostBrowserCDPSession(sessionId, this._proxy);
75
this._sessions.set(sessionId, session);
76
return session.value;
77
}
78
79
private async _close(): Promise<void> {
80
await this._proxy.$closeBrowserTab(this.id);
81
}
82
}
83
84
// #endregion
85
86
// #region CDP Session
87
88
class ExtHostBrowserCDPSession {
89
private readonly _onDidReceiveMessage = new Emitter<unknown>();
90
private readonly _onDidClose = new Emitter<void>();
91
92
private _closed = false;
93
94
readonly value: vscode.BrowserCDPSession;
95
96
constructor(
97
readonly id: string,
98
private readonly _proxy: MainThreadBrowsersShape,
99
) {
100
const that = this;
101
this.value = {
102
get onDidReceiveMessage(): Event<unknown> { return that._onDidReceiveMessage.event; },
103
get onDidClose(): Event<void> { return that._onDidClose.event; },
104
sendMessage(message: unknown): Promise<void> {
105
return that._sendMessage(message as CDPRequest);
106
},
107
close(): Promise<void> {
108
return that._close();
109
}
110
};
111
}
112
113
dispose(): void {
114
this._onDidReceiveMessage.dispose();
115
this._onDidClose.dispose();
116
}
117
118
private async _sendMessage(message: CDPRequest): Promise<void> {
119
if (this._closed) {
120
throw new Error('Session is closed');
121
}
122
if (!message || typeof message !== 'object') {
123
throw new Error('Message must be an object');
124
}
125
if (typeof message.id !== 'number') {
126
throw new Error('Message must have a numeric id');
127
}
128
if (typeof message.method !== 'string') {
129
throw new Error('Message must have a method string');
130
}
131
if (message.params !== undefined && typeof message.params !== 'object') {
132
throw new Error('Message params must be an object');
133
}
134
if (message.sessionId !== undefined && typeof message.sessionId !== 'string') {
135
throw new Error('Message sessionId must be a string');
136
}
137
await this._proxy.$sendCDPMessage(this.id, { id: message.id, method: message.method, params: message.params, sessionId: message.sessionId });
138
}
139
140
private async _close(): Promise<void> {
141
this._closed = true;
142
await this._proxy.$closeCDPSession(this.id);
143
}
144
145
// Called from main thread
146
_acceptMessage(message: unknown): void {
147
this._onDidReceiveMessage.fire(message);
148
}
149
150
_acceptClosed(): void {
151
this._closed = true;
152
this._onDidClose.fire();
153
}
154
}
155
156
// #endregion
157
158
export class ExtHostBrowsers extends Disposable implements ExtHostBrowsersShape {
159
private readonly _proxy: MainThreadBrowsersShape;
160
private readonly _browserTabs = new Map<string, ExtHostBrowserTab>();
161
private readonly _sessions = this._register(new DisposableMap<string, ExtHostBrowserCDPSession>());
162
163
private _activeBrowserTabId: string | undefined;
164
165
private readonly _onDidOpenBrowserTab = this._register(new Emitter<vscode.BrowserTab>());
166
readonly onDidOpenBrowserTab: Event<vscode.BrowserTab> = this._onDidOpenBrowserTab.event;
167
168
private readonly _onDidCloseBrowserTab = this._register(new Emitter<vscode.BrowserTab>());
169
readonly onDidCloseBrowserTab: Event<vscode.BrowserTab> = this._onDidCloseBrowserTab.event;
170
171
private readonly _onDidChangeActiveBrowserTab = this._register(new Emitter<vscode.BrowserTab | undefined>());
172
readonly onDidChangeActiveBrowserTab: Event<vscode.BrowserTab | undefined> = this._onDidChangeActiveBrowserTab.event;
173
174
private readonly _onDidChangeBrowserTabState = this._register(new Emitter<vscode.BrowserTab>());
175
readonly onDidChangeBrowserTabState: Event<vscode.BrowserTab> = this._onDidChangeBrowserTabState.event;
176
177
constructor(mainContext: IMainContext) {
178
super();
179
this._proxy = mainContext.getProxy(MainContext.MainThreadBrowsers);
180
}
181
182
// #region Public API (called from extension code)
183
184
get browserTabs(): readonly vscode.BrowserTab[] {
185
return [...this._browserTabs.values()].map(t => t.value);
186
}
187
188
get activeBrowserTab(): vscode.BrowserTab | undefined {
189
if (this._activeBrowserTabId) {
190
return this._browserTabs.get(this._activeBrowserTabId)?.value;
191
}
192
return undefined;
193
}
194
195
async openBrowserTab(url: string, options?: vscode.BrowserTabShowOptions): Promise<vscode.BrowserTab> {
196
const viewColumn = typeConverters.ViewColumn.from(options?.viewColumn);
197
const dto = await this._proxy.$openBrowserTab(url, viewColumn, {
198
preserveFocus: options?.preserveFocus,
199
inactive: options?.background,
200
});
201
202
return this._getOrCreateTab(dto).value;
203
}
204
205
// #endregion
206
207
// #region Internal helpers
208
209
private _getOrCreateTab(dto: BrowserTabDto): ExtHostBrowserTab {
210
let tab = this._browserTabs.get(dto.id);
211
if (!tab) {
212
tab = new ExtHostBrowserTab(dto.id, this._proxy, this._sessions, dto);
213
this._browserTabs.set(dto.id, tab);
214
this._onDidOpenBrowserTab.fire(tab.value);
215
} else {
216
tab.update(dto);
217
}
218
return tab;
219
}
220
221
// #endregion
222
223
// #region Main thread callbacks
224
225
$onDidOpenBrowserTab(dto: BrowserTabDto): void {
226
this._getOrCreateTab(dto);
227
}
228
229
$onDidCloseBrowserTab(browserId: string): void {
230
const tab = this._browserTabs.get(browserId);
231
if (tab) {
232
this._browserTabs.delete(browserId);
233
if (this._activeBrowserTabId === browserId) {
234
this._activeBrowserTabId = undefined;
235
}
236
this._onDidCloseBrowserTab.fire(tab.value);
237
}
238
}
239
240
$onDidChangeActiveBrowserTab(browserId: string | undefined): void {
241
this._activeBrowserTabId = browserId;
242
this._onDidChangeActiveBrowserTab.fire(this.activeBrowserTab);
243
}
244
245
$onDidChangeBrowserTabState(data: BrowserTabDto): void {
246
const tab = this._browserTabs.get(data.id);
247
if (tab && tab.update(data)) {
248
this._onDidChangeBrowserTabState.fire(tab.value);
249
}
250
}
251
252
$onCDPSessionMessage(sessionId: string, message: CDPResponse | CDPEvent): void {
253
const session = this._sessions.get(sessionId);
254
if (session) {
255
session._acceptMessage(message);
256
}
257
}
258
259
$onCDPSessionClosed(sessionId: string): void {
260
const session = this._sessions.get(sessionId);
261
if (session) {
262
session._acceptClosed();
263
this._sessions.deleteAndDispose(sessionId);
264
}
265
}
266
267
// #endregion
268
}
269
270