Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/webWorkerFactory.ts
3292 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 { createTrustedTypesPolicy } from './trustedTypes.js';
7
import { onUnexpectedError } from '../common/errors.js';
8
import { COI } from '../common/network.js';
9
import { URI } from '../common/uri.js';
10
import { IWebWorker, IWebWorkerClient, Message, WebWorkerClient } from '../common/worker/webWorker.js';
11
import { Disposable, toDisposable } from '../common/lifecycle.js';
12
import { coalesce } from '../common/arrays.js';
13
import { getNLSLanguage, getNLSMessages } from '../../nls.js';
14
import { Emitter } from '../common/event.js';
15
16
// Reuse the trusted types policy defined from worker bootstrap
17
// when available.
18
// Refs https://github.com/microsoft/vscode/issues/222193
19
let ttPolicy: ReturnType<typeof createTrustedTypesPolicy>;
20
if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && (globalThis as any).workerttPolicy !== undefined) {
21
ttPolicy = (globalThis as any).workerttPolicy;
22
} else {
23
ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value });
24
}
25
26
export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker {
27
if (!blobUrl.startsWith('blob:')) {
28
throw new URIError('Not a blob-url: ' + blobUrl);
29
}
30
return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: 'module' });
31
}
32
33
function getWorker(descriptor: IWebWorkerDescriptor, id: number): Worker | Promise<Worker> {
34
const label = descriptor.label || 'anonymous' + id;
35
36
// Option for hosts to overwrite the worker script (used in the standalone editor)
37
interface IMonacoEnvironment {
38
getWorker?(moduleId: string, label: string): Worker | Promise<Worker>;
39
getWorkerUrl?(moduleId: string, label: string): string;
40
}
41
const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment;
42
if (monacoEnvironment) {
43
if (typeof monacoEnvironment.getWorker === 'function') {
44
return monacoEnvironment.getWorker('workerMain.js', label);
45
}
46
if (typeof monacoEnvironment.getWorkerUrl === 'function') {
47
const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label);
48
return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' });
49
}
50
}
51
52
const esmWorkerLocation = descriptor.esmModuleLocation;
53
if (esmWorkerLocation) {
54
const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation.toString(true));
55
const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' });
56
return whenESMWorkerReady(worker);
57
}
58
59
throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`);
60
}
61
62
function getWorkerBootstrapUrl(label: string, workerScriptUrl: string): string {
63
if (/^((http:)|(https:)|(file:))/.test(workerScriptUrl) && workerScriptUrl.substring(0, globalThis.origin.length) !== globalThis.origin) {
64
// this is the cross-origin case
65
// i.e. the webpage is running at a different origin than where the scripts are loaded from
66
} else {
67
const start = workerScriptUrl.lastIndexOf('?');
68
const end = workerScriptUrl.lastIndexOf('#', start);
69
const params = start > 0
70
? new URLSearchParams(workerScriptUrl.substring(start + 1, ~end ? end : undefined))
71
: new URLSearchParams();
72
73
COI.addSearchParam(params, true, true);
74
const search = params.toString();
75
if (!search) {
76
workerScriptUrl = `${workerScriptUrl}#${label}`;
77
} else {
78
workerScriptUrl = `${workerScriptUrl}?${params.toString()}#${label}`;
79
}
80
}
81
82
// In below blob code, we are using JSON.stringify to ensure the passed
83
// in values are not breaking our script. The values may contain string
84
// terminating characters (such as ' or ").
85
const blob = new Blob([coalesce([
86
`/*${label}*/`,
87
`globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(getNLSMessages())};`,
88
`globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(getNLSLanguage())};`,
89
`globalThis._VSCODE_FILE_ROOT = ${JSON.stringify(globalThis._VSCODE_FILE_ROOT)};`,
90
`const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`,
91
`globalThis.workerttPolicy = ttPolicy;`,
92
`await import(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});`,
93
`globalThis.postMessage({ type: 'vscode-worker-ready' });`,
94
`/*${label}*/`
95
]).join('')], { type: 'application/javascript' });
96
return URL.createObjectURL(blob);
97
}
98
99
function whenESMWorkerReady(worker: Worker): Promise<Worker> {
100
return new Promise<Worker>((resolve, reject) => {
101
worker.onmessage = function (e) {
102
if (e.data.type === 'vscode-worker-ready') {
103
worker.onmessage = null;
104
resolve(worker);
105
}
106
};
107
worker.onerror = reject;
108
});
109
}
110
111
function isPromiseLike<T>(obj: unknown): obj is PromiseLike<T> {
112
return !!obj && typeof (obj as PromiseLike<T>).then === 'function';
113
}
114
115
/**
116
* A worker that uses HTML5 web workers so that is has
117
* its own global scope and its own thread.
118
*/
119
class WebWorker extends Disposable implements IWebWorker {
120
121
private static LAST_WORKER_ID = 0;
122
123
private readonly id: number;
124
private worker: Promise<Worker> | null;
125
126
private readonly _onMessage = this._register(new Emitter<Message>());
127
public readonly onMessage = this._onMessage.event;
128
129
private readonly _onError = this._register(new Emitter<any>());
130
public readonly onError = this._onError.event;
131
132
constructor(descriptorOrWorker: IWebWorkerDescriptor | Worker | Promise<Worker>) {
133
super();
134
this.id = ++WebWorker.LAST_WORKER_ID;
135
const workerOrPromise = (
136
descriptorOrWorker instanceof Worker
137
? descriptorOrWorker :
138
'then' in descriptorOrWorker ? descriptorOrWorker
139
: getWorker(descriptorOrWorker, this.id)
140
);
141
if (isPromiseLike(workerOrPromise)) {
142
this.worker = workerOrPromise;
143
} else {
144
this.worker = Promise.resolve(workerOrPromise);
145
}
146
this.postMessage('-please-ignore-', []); // TODO: Eliminate this extra message
147
const errorHandler = (ev: ErrorEvent) => {
148
this._onError.fire(ev);
149
};
150
this.worker.then((w) => {
151
w.onmessage = (ev) => {
152
this._onMessage.fire(ev.data);
153
};
154
w.onmessageerror = (ev) => {
155
this._onError.fire(ev);
156
};
157
if (typeof w.addEventListener === 'function') {
158
w.addEventListener('error', errorHandler);
159
}
160
});
161
this._register(toDisposable(() => {
162
this.worker?.then(w => {
163
w.onmessage = null;
164
w.onmessageerror = null;
165
w.removeEventListener('error', errorHandler);
166
w.terminate();
167
});
168
this.worker = null;
169
}));
170
}
171
172
public getId(): number {
173
return this.id;
174
}
175
176
public postMessage(message: unknown, transfer: Transferable[]): void {
177
this.worker?.then(w => {
178
try {
179
w.postMessage(message, transfer);
180
} catch (err) {
181
onUnexpectedError(err);
182
onUnexpectedError(new Error(`FAILED to post message to worker`, { cause: err }));
183
}
184
});
185
}
186
}
187
188
export interface IWebWorkerDescriptor {
189
readonly esmModuleLocation: URI | undefined;
190
readonly label: string | undefined;
191
}
192
193
export class WebWorkerDescriptor implements IWebWorkerDescriptor {
194
constructor(
195
public readonly esmModuleLocation: URI,
196
public readonly label: string | undefined,
197
) { }
198
}
199
200
export function createWebWorker<T extends object>(esmModuleLocation: URI, label: string | undefined): IWebWorkerClient<T>;
201
export function createWebWorker<T extends object>(workerDescriptor: IWebWorkerDescriptor | Worker | Promise<Worker>): IWebWorkerClient<T>;
202
export function createWebWorker<T extends object>(arg0: URI | IWebWorkerDescriptor | Worker | Promise<Worker>, arg1?: string | undefined): IWebWorkerClient<T> {
203
const workerDescriptorOrWorker = (URI.isUri(arg0) ? new WebWorkerDescriptor(arg0, arg1) : arg0);
204
return new WebWorkerClient<T>(new WebWorker(workerDescriptorOrWorker));
205
}
206
207