Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/worker/extensionHostWorker.ts
5252 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 { IMessagePassingProtocol } from '../../../base/parts/ipc/common/ipc.js';
7
import { VSBuffer } from '../../../base/common/buffer.js';
8
import { Emitter } from '../../../base/common/event.js';
9
import { isMessageOfType, MessageType, createMessageOfType, IExtensionHostInitData } from '../../services/extensions/common/extensionHostProtocol.js';
10
import { ExtensionHostMain } from '../common/extensionHostMain.js';
11
import { IHostUtils } from '../common/extHostExtensionService.js';
12
import { NestedWorker } from '../../services/extensions/worker/polyfillNestedWorker.js';
13
import * as path from '../../../base/common/path.js';
14
import * as performance from '../../../base/common/performance.js';
15
16
import '../common/extHost.common.services.js';
17
import './extHost.worker.services.js';
18
import { FileAccess } from '../../../base/common/network.js';
19
import { URI } from '../../../base/common/uri.js';
20
21
//#region --- Define, capture, and override some globals
22
23
declare function postMessage(data: any, transferables?: Transferable[]): void;
24
declare const name: string; // https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/name
25
declare type _Fetch = typeof fetch;
26
27
declare namespace self {
28
let close: any;
29
let postMessage: any;
30
let addEventListener: any;
31
let removeEventListener: any;
32
let dispatchEvent: any;
33
let indexedDB: { open: any;[k: string]: any };
34
let caches: { open: any;[k: string]: any };
35
let importScripts: any;
36
let fetch: _Fetch;
37
let XMLHttpRequest: any;
38
}
39
40
const nativeClose = self.close.bind(self);
41
self.close = () => console.trace(`'close' has been blocked`);
42
43
const nativePostMessage = postMessage.bind(self);
44
self.postMessage = () => console.trace(`'postMessage' has been blocked`);
45
46
function shouldTransformUri(uri: string): boolean {
47
// In principle, we could convert any URI, but we have concerns
48
// that parsing https URIs might end up decoding escape characters
49
// and result in an unintended transformation
50
return /^(file|vscode-remote):/i.test(uri);
51
}
52
53
const nativeFetch = fetch.bind(self);
54
function patchFetching(asBrowserUri: (uri: URI) => Promise<URI>) {
55
self.fetch = async function (input, init) {
56
if (input instanceof Request) {
57
// Request object - massage not supported
58
return nativeFetch(input, init);
59
}
60
if (shouldTransformUri(String(input))) {
61
input = (await asBrowserUri(URI.parse(String(input)))).toString(true);
62
}
63
return nativeFetch(input, init);
64
};
65
66
self.XMLHttpRequest = class extends XMLHttpRequest {
67
override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void {
68
(async () => {
69
if (shouldTransformUri(url.toString())) {
70
url = (await asBrowserUri(URI.parse(url.toString()))).toString(true);
71
}
72
super.open(method, url, async ?? true, username, password);
73
})();
74
}
75
};
76
}
77
78
self.importScripts = () => { throw new Error(`'importScripts' has been blocked`); };
79
80
// const nativeAddEventListener = addEventListener.bind(self);
81
self.addEventListener = () => console.trace(`'addEventListener' has been blocked`);
82
83
// eslint-disable-next-line local/code-no-any-casts
84
(<any>self)['AMDLoader'] = undefined;
85
// eslint-disable-next-line local/code-no-any-casts
86
(<any>self)['NLSLoaderPlugin'] = undefined;
87
// eslint-disable-next-line local/code-no-any-casts
88
(<any>self)['define'] = undefined;
89
// eslint-disable-next-line local/code-no-any-casts
90
(<any>self)['require'] = undefined;
91
// eslint-disable-next-line local/code-no-any-casts
92
(<any>self)['webkitRequestFileSystem'] = undefined;
93
// eslint-disable-next-line local/code-no-any-casts
94
(<any>self)['webkitRequestFileSystemSync'] = undefined;
95
// eslint-disable-next-line local/code-no-any-casts
96
(<any>self)['webkitResolveLocalFileSystemSyncURL'] = undefined;
97
// eslint-disable-next-line local/code-no-any-casts
98
(<any>self)['webkitResolveLocalFileSystemURL'] = undefined;
99
100
// eslint-disable-next-line local/code-no-any-casts
101
if ((<any>self).Worker) {
102
103
// make sure new Worker(...) always uses blob: (to maintain current origin)
104
// eslint-disable-next-line local/code-no-any-casts
105
const _Worker = (<any>self).Worker;
106
// eslint-disable-next-line local/code-no-any-casts
107
Worker = <any>function (stringUrl: string | URL, options?: WorkerOptions) {
108
if (/^file:/i.test(stringUrl.toString())) {
109
stringUrl = FileAccess.uriToBrowserUri(URI.parse(stringUrl.toString())).toString(true);
110
} else if (/^vscode-remote:/i.test(stringUrl.toString())) {
111
// Supporting transformation of vscode-remote URIs requires an async call to the main thread,
112
// but we cannot do this call from within the embedded Worker, and the only way out would be
113
// to use templating instead of a function in the web api (`resourceUriProvider`)
114
throw new Error(`Creating workers from remote extensions is currently not supported.`);
115
}
116
117
// IMPORTANT: bootstrapFn is stringified and injected as worker blob-url. Because of that it CANNOT
118
// have dependencies on other functions or variables. Only constant values are supported. Due to
119
// that logic of FileAccess.asBrowserUri had to be copied, see `asWorkerBrowserUrl` (below).
120
const bootstrapFnSource = (function bootstrapFn(workerUrl: string) {
121
function asWorkerBrowserUrl(url: string | URL | TrustedScriptURL): any {
122
if (typeof url === 'string' || url instanceof URL) {
123
return String(url).replace(/^file:\/\//i, 'vscode-file://vscode-app');
124
}
125
return url;
126
}
127
128
const nativeFetch = fetch.bind(self);
129
self.fetch = function (input, init) {
130
if (input instanceof Request) {
131
// Request object - massage not supported
132
return nativeFetch(input, init);
133
}
134
return nativeFetch(asWorkerBrowserUrl(input), init);
135
};
136
self.XMLHttpRequest = class extends XMLHttpRequest {
137
override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void {
138
return super.open(method, asWorkerBrowserUrl(url), async ?? true, username, password);
139
}
140
};
141
const nativeImportScripts = importScripts.bind(self);
142
self.importScripts = (...urls: string[]) => {
143
nativeImportScripts(...urls.map(asWorkerBrowserUrl));
144
};
145
146
nativeImportScripts(workerUrl);
147
}).toString();
148
149
const js = `(${bootstrapFnSource}('${stringUrl}'))`;
150
options = options || {};
151
options.name = `${name} -> ${options.name || path.basename(stringUrl.toString())}`;
152
const blob = new Blob([js], { type: 'application/javascript' });
153
const blobUrl = URL.createObjectURL(blob);
154
return new _Worker(blobUrl, options);
155
};
156
157
} else {
158
// eslint-disable-next-line local/code-no-any-casts
159
(<any>self).Worker = class extends NestedWorker {
160
constructor(stringOrUrl: string | URL, options?: WorkerOptions) {
161
super(nativePostMessage, stringOrUrl, { name: path.basename(stringOrUrl.toString()), ...options });
162
}
163
};
164
}
165
166
//#endregion ---
167
168
const hostUtil = new class implements IHostUtils {
169
declare readonly _serviceBrand: undefined;
170
public readonly pid = undefined;
171
exit(_code?: number | undefined): void {
172
nativeClose();
173
}
174
};
175
176
177
class ExtensionWorker {
178
179
// protocol
180
readonly protocol: IMessagePassingProtocol;
181
182
constructor() {
183
184
const channel = new MessageChannel();
185
const emitter = new Emitter<VSBuffer>();
186
let terminating = false;
187
188
// send over port2, keep port1
189
nativePostMessage(channel.port2, [channel.port2]);
190
191
channel.port1.onmessage = event => {
192
const { data } = event;
193
if (!(data instanceof ArrayBuffer)) {
194
console.warn('UNKNOWN data received', data);
195
return;
196
}
197
198
const msg = VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength));
199
if (isMessageOfType(msg, MessageType.Terminate)) {
200
// handle terminate-message right here
201
terminating = true;
202
onTerminate('received terminate message from renderer');
203
return;
204
}
205
206
// emit non-terminate messages to the outside
207
emitter.fire(msg);
208
};
209
210
this.protocol = {
211
onMessage: emitter.event,
212
send: vsbuf => {
213
if (!terminating) {
214
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
215
channel.port1.postMessage(data, [data]);
216
}
217
}
218
};
219
}
220
}
221
222
interface IRendererConnection {
223
protocol: IMessagePassingProtocol;
224
initData: IExtensionHostInitData;
225
}
226
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
227
return new Promise<IRendererConnection>(resolve => {
228
const once = protocol.onMessage(raw => {
229
once.dispose();
230
const initData = <IExtensionHostInitData>JSON.parse(raw.toString());
231
protocol.send(createMessageOfType(MessageType.Initialized));
232
resolve({ protocol, initData });
233
});
234
protocol.send(createMessageOfType(MessageType.Ready));
235
});
236
}
237
238
let onTerminate = (reason: string) => nativeClose();
239
240
interface IInitMessage {
241
readonly type: 'vscode.init';
242
readonly data: ReadonlyMap<string, MessagePort>;
243
}
244
245
function isInitMessage(a: any): a is IInitMessage {
246
return !!a && typeof a === 'object' && a.type === 'vscode.init' && a.data instanceof Map;
247
}
248
249
export function create(): { onmessage: (message: any) => void } {
250
performance.mark(`code/extHost/willConnectToRenderer`);
251
const res = new ExtensionWorker();
252
253
return {
254
onmessage(message: any) {
255
if (!isInitMessage(message)) {
256
return; // silently ignore foreign messages
257
}
258
259
connectToRenderer(res.protocol).then(data => {
260
performance.mark(`code/extHost/didWaitForInitData`);
261
const extHostMain = new ExtensionHostMain(
262
data.protocol,
263
data.initData,
264
hostUtil,
265
null,
266
message.data
267
);
268
269
patchFetching(uri => extHostMain.asBrowserUri(uri));
270
271
onTerminate = (reason: string) => extHostMain.terminate(reason);
272
});
273
}
274
};
275
}
276
277