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