Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/browserView/common/browserView.ts
4780 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 { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
7
import { Emitter, Event } from '../../../../base/common/event.js';
8
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
9
import { VSBuffer } from '../../../../base/common/buffer.js';
10
import {
11
IBrowserViewBounds,
12
IBrowserViewNavigationEvent,
13
IBrowserViewLoadingEvent,
14
IBrowserViewLoadError,
15
IBrowserViewFocusEvent,
16
IBrowserViewKeyDownEvent,
17
IBrowserViewTitleChangeEvent,
18
IBrowserViewFaviconChangeEvent,
19
IBrowserViewNewPageRequest,
20
IBrowserViewDevToolsStateEvent,
21
IBrowserViewService,
22
BrowserViewStorageScope,
23
IBrowserViewCaptureScreenshotOptions
24
} from '../../../../platform/browserView/common/browserView.js';
25
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
26
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
27
import { isLocalhost } from '../../../../platform/tunnel/common/tunnel.js';
28
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
29
import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';
30
31
type IntegratedBrowserNavigationEvent = {
32
navigationType: 'urlInput' | 'goBack' | 'goForward' | 'reload';
33
isLocalhost: boolean;
34
};
35
36
type IntegratedBrowserNavigationClassification = {
37
navigationType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the navigation was triggered' };
38
isLocalhost: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the URL is a localhost address' };
39
owner: 'kycutler';
40
comment: 'Tracks navigation patterns in integrated browser';
41
};
42
43
export const IBrowserViewWorkbenchService = createDecorator<IBrowserViewWorkbenchService>('browserViewWorkbenchService');
44
45
/**
46
* Workbench-level service for browser views that provides model-based access to browser views.
47
* This service manages browser view models that proxy to the main process browser view service.
48
*/
49
export interface IBrowserViewWorkbenchService {
50
readonly _serviceBrand: undefined;
51
52
/**
53
* Get or create a browser view model for the given ID
54
* @param id The browser view identifier
55
* @returns A browser view model that proxies to the main process
56
*/
57
getOrCreateBrowserViewModel(id: string): Promise<IBrowserViewModel>;
58
59
/**
60
* Clear all storage data for the global browser session
61
*/
62
clearGlobalStorage(): Promise<void>;
63
64
/**
65
* Clear all storage data for the current workspace browser session
66
*/
67
clearWorkspaceStorage(): Promise<void>;
68
}
69
70
71
/**
72
* A browser view model that represents a single browser view instance in the workbench.
73
* This model proxies calls to the main process browser view service using its unique ID.
74
*/
75
export interface IBrowserViewModel extends IDisposable {
76
readonly id: string;
77
readonly url: string;
78
readonly title: string;
79
readonly favicon: string | undefined;
80
readonly screenshot: VSBuffer | undefined;
81
readonly loading: boolean;
82
readonly canGoBack: boolean;
83
readonly isDevToolsOpen: boolean;
84
readonly canGoForward: boolean;
85
readonly error: IBrowserViewLoadError | undefined;
86
87
readonly storageScope: BrowserViewStorageScope;
88
89
readonly onDidNavigate: Event<IBrowserViewNavigationEvent>;
90
readonly onDidChangeLoadingState: Event<IBrowserViewLoadingEvent>;
91
readonly onDidChangeFocus: Event<IBrowserViewFocusEvent>;
92
readonly onDidChangeDevToolsState: Event<IBrowserViewDevToolsStateEvent>;
93
readonly onDidKeyCommand: Event<IBrowserViewKeyDownEvent>;
94
readonly onDidChangeTitle: Event<IBrowserViewTitleChangeEvent>;
95
readonly onDidChangeFavicon: Event<IBrowserViewFaviconChangeEvent>;
96
readonly onDidRequestNewPage: Event<IBrowserViewNewPageRequest>;
97
readonly onDidClose: Event<void>;
98
readonly onWillDispose: Event<void>;
99
100
initialize(): Promise<void>;
101
102
layout(bounds: IBrowserViewBounds): Promise<void>;
103
setVisible(visible: boolean): Promise<void>;
104
loadURL(url: string): Promise<void>;
105
goBack(): Promise<void>;
106
goForward(): Promise<void>;
107
reload(): Promise<void>;
108
toggleDevTools(): Promise<void>;
109
captureScreenshot(options?: IBrowserViewCaptureScreenshotOptions): Promise<VSBuffer>;
110
dispatchKeyEvent(keyEvent: IBrowserViewKeyDownEvent): Promise<void>;
111
focus(): Promise<void>;
112
}
113
114
export class BrowserViewModel extends Disposable implements IBrowserViewModel {
115
private _url: string = '';
116
private _title: string = '';
117
private _favicon: string | undefined = undefined;
118
private _screenshot: VSBuffer | undefined = undefined;
119
private _loading: boolean = false;
120
private _isDevToolsOpen: boolean = false;
121
private _canGoBack: boolean = false;
122
private _canGoForward: boolean = false;
123
private _error: IBrowserViewLoadError | undefined = undefined;
124
private _storageScope: BrowserViewStorageScope = BrowserViewStorageScope.Ephemeral;
125
126
private readonly _onWillDispose = this._register(new Emitter<void>());
127
readonly onWillDispose: Event<void> = this._onWillDispose.event;
128
129
constructor(
130
readonly id: string,
131
private readonly browserViewService: IBrowserViewService,
132
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
133
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
134
@ITelemetryService private readonly telemetryService: ITelemetryService,
135
@IConfigurationService private readonly configurationService: IConfigurationService
136
) {
137
super();
138
}
139
140
get url(): string { return this._url; }
141
get title(): string { return this._title; }
142
get favicon(): string | undefined { return this._favicon; }
143
get loading(): boolean { return this._loading; }
144
get isDevToolsOpen(): boolean { return this._isDevToolsOpen; }
145
get canGoBack(): boolean { return this._canGoBack; }
146
get canGoForward(): boolean { return this._canGoForward; }
147
get screenshot(): VSBuffer | undefined { return this._screenshot; }
148
get error(): IBrowserViewLoadError | undefined { return this._error; }
149
get storageScope(): BrowserViewStorageScope { return this._storageScope; }
150
151
get onDidNavigate(): Event<IBrowserViewNavigationEvent> {
152
return this.browserViewService.onDynamicDidNavigate(this.id);
153
}
154
155
get onDidChangeLoadingState(): Event<IBrowserViewLoadingEvent> {
156
return this.browserViewService.onDynamicDidChangeLoadingState(this.id);
157
}
158
159
get onDidChangeFocus(): Event<IBrowserViewFocusEvent> {
160
return this.browserViewService.onDynamicDidChangeFocus(this.id);
161
}
162
163
get onDidChangeDevToolsState(): Event<IBrowserViewDevToolsStateEvent> {
164
return this.browserViewService.onDynamicDidChangeDevToolsState(this.id);
165
}
166
167
get onDidKeyCommand(): Event<IBrowserViewKeyDownEvent> {
168
return this.browserViewService.onDynamicDidKeyCommand(this.id);
169
}
170
171
get onDidChangeTitle(): Event<IBrowserViewTitleChangeEvent> {
172
return this.browserViewService.onDynamicDidChangeTitle(this.id);
173
}
174
175
get onDidChangeFavicon(): Event<IBrowserViewFaviconChangeEvent> {
176
return this.browserViewService.onDynamicDidChangeFavicon(this.id);
177
}
178
179
get onDidRequestNewPage(): Event<IBrowserViewNewPageRequest> {
180
return this.browserViewService.onDynamicDidRequestNewPage(this.id);
181
}
182
183
get onDidClose(): Event<void> {
184
return this.browserViewService.onDynamicDidClose(this.id);
185
}
186
187
/**
188
* Initialize the model with the current state from the main process
189
*/
190
async initialize(): Promise<void> {
191
const dataStorageSetting = this.configurationService.getValue<BrowserViewStorageScope>(
192
'workbench.browser.dataStorage'
193
) ?? BrowserViewStorageScope.Global;
194
195
// Wait for trust initialization before determining storage scope
196
await this.workspaceTrustManagementService.workspaceTrustInitialized;
197
const isWorkspaceUntrusted =
198
this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY &&
199
!this.workspaceTrustManagementService.isWorkspaceTrusted();
200
201
// Always use ephemeral sessions for untrusted workspaces
202
const dataStorage = isWorkspaceUntrusted ? BrowserViewStorageScope.Ephemeral : dataStorageSetting;
203
204
const workspaceId = this.workspaceContextService.getWorkspace().id;
205
const state = await this.browserViewService.getOrCreateBrowserView(this.id, dataStorage, workspaceId);
206
207
this._url = state.url;
208
this._title = state.title;
209
this._loading = state.loading;
210
this._isDevToolsOpen = state.isDevToolsOpen;
211
this._canGoBack = state.canGoBack;
212
this._canGoForward = state.canGoForward;
213
this._screenshot = state.lastScreenshot;
214
this._favicon = state.lastFavicon;
215
this._error = state.lastError;
216
this._storageScope = state.storageScope;
217
218
// Set up state synchronization
219
220
this._register(this.onDidNavigate(e => {
221
// Clear favicon on navigation to a different host
222
if (URL.parse(e.url)?.host !== URL.parse(this._url)?.host) {
223
this._favicon = undefined;
224
}
225
226
this._url = e.url;
227
this._canGoBack = e.canGoBack;
228
this._canGoForward = e.canGoForward;
229
}));
230
231
this._register(this.onDidChangeLoadingState(e => {
232
this._loading = e.loading;
233
this._error = e.error;
234
}));
235
236
this._register(this.onDidChangeDevToolsState(e => {
237
this._isDevToolsOpen = e.isDevToolsOpen;
238
}));
239
240
this._register(this.onDidChangeTitle(e => {
241
this._title = e.title;
242
}));
243
244
this._register(this.onDidChangeFavicon(e => {
245
this._favicon = e.favicon;
246
}));
247
}
248
249
async layout(bounds: IBrowserViewBounds): Promise<void> {
250
return this.browserViewService.layout(this.id, bounds);
251
}
252
253
async setVisible(visible: boolean): Promise<void> {
254
return this.browserViewService.setVisible(this.id, visible);
255
}
256
257
async loadURL(url: string): Promise<void> {
258
this.logNavigationTelemetry('urlInput', url);
259
return this.browserViewService.loadURL(this.id, url);
260
}
261
262
async goBack(): Promise<void> {
263
this.logNavigationTelemetry('goBack', this._url);
264
return this.browserViewService.goBack(this.id);
265
}
266
267
async goForward(): Promise<void> {
268
this.logNavigationTelemetry('goForward', this._url);
269
return this.browserViewService.goForward(this.id);
270
}
271
272
async reload(): Promise<void> {
273
this.logNavigationTelemetry('reload', this._url);
274
return this.browserViewService.reload(this.id);
275
}
276
277
async toggleDevTools(): Promise<void> {
278
return this.browserViewService.toggleDevTools(this.id);
279
}
280
281
async captureScreenshot(options?: IBrowserViewCaptureScreenshotOptions): Promise<VSBuffer> {
282
const result = await this.browserViewService.captureScreenshot(this.id, options);
283
// Store full-page screenshots for display in UI as placeholders
284
if (!options?.rect) {
285
this._screenshot = result;
286
}
287
return result;
288
}
289
290
async dispatchKeyEvent(keyEvent: IBrowserViewKeyDownEvent): Promise<void> {
291
return this.browserViewService.dispatchKeyEvent(this.id, keyEvent);
292
}
293
294
async focus(): Promise<void> {
295
return this.browserViewService.focus(this.id);
296
}
297
298
/**
299
* Log navigation telemetry event
300
*/
301
private logNavigationTelemetry(navigationType: IntegratedBrowserNavigationEvent['navigationType'], url: string): void {
302
let localhost: boolean;
303
try {
304
localhost = isLocalhost(new URL(url).hostname);
305
} catch {
306
localhost = false;
307
}
308
309
this.telemetryService.publicLog2<IntegratedBrowserNavigationEvent, IntegratedBrowserNavigationClassification>(
310
'integratedBrowser.navigation',
311
{
312
navigationType,
313
isLocalhost: localhost
314
}
315
);
316
}
317
318
override dispose(): void {
319
this._onWillDispose.fire();
320
321
// Clean up the browser view when the model is disposed
322
void this.browserViewService.destroyBrowserView(this.id);
323
324
super.dispose();
325
}
326
}
327
328