Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/worker/extensionHostWorker.ts
3296 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
(<any>self)['AMDLoader'] = undefined;
84
(<any>self)['NLSLoaderPlugin'] = undefined;
85
(<any>self)['define'] = undefined;
86
(<any>self)['require'] = undefined;
87
(<any>self)['webkitRequestFileSystem'] = undefined;
88
(<any>self)['webkitRequestFileSystemSync'] = undefined;
89
(<any>self)['webkitResolveLocalFileSystemSyncURL'] = undefined;
90
(<any>self)['webkitResolveLocalFileSystemURL'] = undefined;
91
92
if ((<any>self).Worker) {
93
94
// make sure new Worker(...) always uses blob: (to maintain current origin)
95
const _Worker = (<any>self).Worker;
96
Worker = <any>function (stringUrl: string | URL, options?: WorkerOptions) {
97
if (/^file:/i.test(stringUrl.toString())) {
98
stringUrl = FileAccess.uriToBrowserUri(URI.parse(stringUrl.toString())).toString(true);
99
} else if (/^vscode-remote:/i.test(stringUrl.toString())) {
100
// Supporting transformation of vscode-remote URIs requires an async call to the main thread,
101
// but we cannot do this call from within the embedded Worker, and the only way out would be
102
// to use templating instead of a function in the web api (`resourceUriProvider`)
103
throw new Error(`Creating workers from remote extensions is currently not supported.`);
104
}
105
106
// IMPORTANT: bootstrapFn is stringified and injected as worker blob-url. Because of that it CANNOT
107
// have dependencies on other functions or variables. Only constant values are supported. Due to
108
// that logic of FileAccess.asBrowserUri had to be copied, see `asWorkerBrowserUrl` (below).
109
const bootstrapFnSource = (function bootstrapFn(workerUrl: string) {
110
function asWorkerBrowserUrl(url: string | URL | TrustedScriptURL): any {
111
if (typeof url === 'string' || url instanceof URL) {
112
return String(url).replace(/^file:\/\//i, 'vscode-file://vscode-app');
113
}
114
return url;
115
}
116
117
const nativeFetch = fetch.bind(self);
118
self.fetch = function (input, init) {
119
if (input instanceof Request) {
120
// Request object - massage not supported
121
return nativeFetch(input, init);
122
}
123
return nativeFetch(asWorkerBrowserUrl(input), init);
124
};
125
self.XMLHttpRequest = class extends XMLHttpRequest {
126
override open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void {
127
return super.open(method, asWorkerBrowserUrl(url), async ?? true, username, password);
128
}
129
};
130
const nativeImportScripts = importScripts.bind(self);
131
self.importScripts = (...urls: string[]) => {
132
nativeImportScripts(...urls.map(asWorkerBrowserUrl));
133
};
134
135
nativeImportScripts(workerUrl);
136
}).toString();
137
138
const js = `(${bootstrapFnSource}('${stringUrl}'))`;
139
options = options || {};
140
options.name = `${name} -> ${options.name || path.basename(stringUrl.toString())}`;
141
const blob = new Blob([js], { type: 'application/javascript' });
142
const blobUrl = URL.createObjectURL(blob);
143
return new _Worker(blobUrl, options);
144
};
145
146
} else {
147
(<any>self).Worker = class extends NestedWorker {
148
constructor(stringOrUrl: string | URL, options?: WorkerOptions) {
149
super(nativePostMessage, stringOrUrl, { name: path.basename(stringOrUrl.toString()), ...options });
150
}
151
};
152
}
153
154
//#endregion ---
155
156
const hostUtil = new class implements IHostUtils {
157
declare readonly _serviceBrand: undefined;
158
public readonly pid = undefined;
159
exit(_code?: number | undefined): void {
160
nativeClose();
161
}
162
};
163
164
165
class ExtensionWorker {
166
167
// protocol
168
readonly protocol: IMessagePassingProtocol;
169
170
constructor() {
171
172
const channel = new MessageChannel();
173
const emitter = new Emitter<VSBuffer>();
174
let terminating = false;
175
176
// send over port2, keep port1
177
nativePostMessage(channel.port2, [channel.port2]);
178
179
channel.port1.onmessage = event => {
180
const { data } = event;
181
if (!(data instanceof ArrayBuffer)) {
182
console.warn('UNKNOWN data received', data);
183
return;
184
}
185
186
const msg = VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength));
187
if (isMessageOfType(msg, MessageType.Terminate)) {
188
// handle terminate-message right here
189
terminating = true;
190
onTerminate('received terminate message from renderer');
191
return;
192
}
193
194
// emit non-terminate messages to the outside
195
emitter.fire(msg);
196
};
197
198
this.protocol = {
199
onMessage: emitter.event,
200
send: vsbuf => {
201
if (!terminating) {
202
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
203
channel.port1.postMessage(data, [data]);
204
}
205
}
206
};
207
}
208
}
209
210
interface IRendererConnection {
211
protocol: IMessagePassingProtocol;
212
initData: IExtensionHostInitData;
213
}
214
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
215
return new Promise<IRendererConnection>(resolve => {
216
const once = protocol.onMessage(raw => {
217
once.dispose();
218
const initData = <IExtensionHostInitData>JSON.parse(raw.toString());
219
protocol.send(createMessageOfType(MessageType.Initialized));
220
resolve({ protocol, initData });
221
});
222
protocol.send(createMessageOfType(MessageType.Ready));
223
});
224
}
225
226
let onTerminate = (reason: string) => nativeClose();
227
228
interface IInitMessage {
229
readonly type: 'vscode.init';
230
readonly data: ReadonlyMap<string, MessagePort>;
231
}
232
233
function isInitMessage(a: any): a is IInitMessage {
234
return !!a && typeof a === 'object' && a.type === 'vscode.init' && a.data instanceof Map;
235
}
236
237
export function create(): { onmessage: (message: any) => void } {
238
performance.mark(`code/extHost/willConnectToRenderer`);
239
const res = new ExtensionWorker();
240
241
return {
242
onmessage(message: any) {
243
if (!isInitMessage(message)) {
244
return; // silently ignore foreign messages
245
}
246
247
connectToRenderer(res.protocol).then(data => {
248
performance.mark(`code/extHost/didWaitForInitData`);
249
const extHostMain = new ExtensionHostMain(
250
data.protocol,
251
data.initData,
252
hostUtil,
253
null,
254
message.data
255
);
256
257
patchFetching(uri => extHostMain.asBrowserUri(uri));
258
259
onTerminate = (reason: string) => extHostMain.terminate(reason);
260
});
261
}
262
};
263
}
264
265