Path: blob/main/src/vs/base/browser/webWorkerFactory.ts
3292 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { createTrustedTypesPolicy } from './trustedTypes.js';6import { onUnexpectedError } from '../common/errors.js';7import { COI } from '../common/network.js';8import { URI } from '../common/uri.js';9import { IWebWorker, IWebWorkerClient, Message, WebWorkerClient } from '../common/worker/webWorker.js';10import { Disposable, toDisposable } from '../common/lifecycle.js';11import { coalesce } from '../common/arrays.js';12import { getNLSLanguage, getNLSMessages } from '../../nls.js';13import { Emitter } from '../common/event.js';1415// Reuse the trusted types policy defined from worker bootstrap16// when available.17// Refs https://github.com/microsoft/vscode/issues/22219318let ttPolicy: ReturnType<typeof createTrustedTypesPolicy>;19if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && (globalThis as any).workerttPolicy !== undefined) {20ttPolicy = (globalThis as any).workerttPolicy;21} else {22ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value });23}2425export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker {26if (!blobUrl.startsWith('blob:')) {27throw new URIError('Not a blob-url: ' + blobUrl);28}29return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: 'module' });30}3132function getWorker(descriptor: IWebWorkerDescriptor, id: number): Worker | Promise<Worker> {33const label = descriptor.label || 'anonymous' + id;3435// Option for hosts to overwrite the worker script (used in the standalone editor)36interface IMonacoEnvironment {37getWorker?(moduleId: string, label: string): Worker | Promise<Worker>;38getWorkerUrl?(moduleId: string, label: string): string;39}40const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment;41if (monacoEnvironment) {42if (typeof monacoEnvironment.getWorker === 'function') {43return monacoEnvironment.getWorker('workerMain.js', label);44}45if (typeof monacoEnvironment.getWorkerUrl === 'function') {46const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label);47return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' });48}49}5051const esmWorkerLocation = descriptor.esmModuleLocation;52if (esmWorkerLocation) {53const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation.toString(true));54const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' });55return whenESMWorkerReady(worker);56}5758throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`);59}6061function getWorkerBootstrapUrl(label: string, workerScriptUrl: string): string {62if (/^((http:)|(https:)|(file:))/.test(workerScriptUrl) && workerScriptUrl.substring(0, globalThis.origin.length) !== globalThis.origin) {63// this is the cross-origin case64// i.e. the webpage is running at a different origin than where the scripts are loaded from65} else {66const start = workerScriptUrl.lastIndexOf('?');67const end = workerScriptUrl.lastIndexOf('#', start);68const params = start > 069? new URLSearchParams(workerScriptUrl.substring(start + 1, ~end ? end : undefined))70: new URLSearchParams();7172COI.addSearchParam(params, true, true);73const search = params.toString();74if (!search) {75workerScriptUrl = `${workerScriptUrl}#${label}`;76} else {77workerScriptUrl = `${workerScriptUrl}?${params.toString()}#${label}`;78}79}8081// In below blob code, we are using JSON.stringify to ensure the passed82// in values are not breaking our script. The values may contain string83// terminating characters (such as ' or ").84const blob = new Blob([coalesce([85`/*${label}*/`,86`globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(getNLSMessages())};`,87`globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(getNLSLanguage())};`,88`globalThis._VSCODE_FILE_ROOT = ${JSON.stringify(globalThis._VSCODE_FILE_ROOT)};`,89`const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`,90`globalThis.workerttPolicy = ttPolicy;`,91`await import(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});`,92`globalThis.postMessage({ type: 'vscode-worker-ready' });`,93`/*${label}*/`94]).join('')], { type: 'application/javascript' });95return URL.createObjectURL(blob);96}9798function whenESMWorkerReady(worker: Worker): Promise<Worker> {99return new Promise<Worker>((resolve, reject) => {100worker.onmessage = function (e) {101if (e.data.type === 'vscode-worker-ready') {102worker.onmessage = null;103resolve(worker);104}105};106worker.onerror = reject;107});108}109110function isPromiseLike<T>(obj: unknown): obj is PromiseLike<T> {111return !!obj && typeof (obj as PromiseLike<T>).then === 'function';112}113114/**115* A worker that uses HTML5 web workers so that is has116* its own global scope and its own thread.117*/118class WebWorker extends Disposable implements IWebWorker {119120private static LAST_WORKER_ID = 0;121122private readonly id: number;123private worker: Promise<Worker> | null;124125private readonly _onMessage = this._register(new Emitter<Message>());126public readonly onMessage = this._onMessage.event;127128private readonly _onError = this._register(new Emitter<any>());129public readonly onError = this._onError.event;130131constructor(descriptorOrWorker: IWebWorkerDescriptor | Worker | Promise<Worker>) {132super();133this.id = ++WebWorker.LAST_WORKER_ID;134const workerOrPromise = (135descriptorOrWorker instanceof Worker136? descriptorOrWorker :137'then' in descriptorOrWorker ? descriptorOrWorker138: getWorker(descriptorOrWorker, this.id)139);140if (isPromiseLike(workerOrPromise)) {141this.worker = workerOrPromise;142} else {143this.worker = Promise.resolve(workerOrPromise);144}145this.postMessage('-please-ignore-', []); // TODO: Eliminate this extra message146const errorHandler = (ev: ErrorEvent) => {147this._onError.fire(ev);148};149this.worker.then((w) => {150w.onmessage = (ev) => {151this._onMessage.fire(ev.data);152};153w.onmessageerror = (ev) => {154this._onError.fire(ev);155};156if (typeof w.addEventListener === 'function') {157w.addEventListener('error', errorHandler);158}159});160this._register(toDisposable(() => {161this.worker?.then(w => {162w.onmessage = null;163w.onmessageerror = null;164w.removeEventListener('error', errorHandler);165w.terminate();166});167this.worker = null;168}));169}170171public getId(): number {172return this.id;173}174175public postMessage(message: unknown, transfer: Transferable[]): void {176this.worker?.then(w => {177try {178w.postMessage(message, transfer);179} catch (err) {180onUnexpectedError(err);181onUnexpectedError(new Error(`FAILED to post message to worker`, { cause: err }));182}183});184}185}186187export interface IWebWorkerDescriptor {188readonly esmModuleLocation: URI | undefined;189readonly label: string | undefined;190}191192export class WebWorkerDescriptor implements IWebWorkerDescriptor {193constructor(194public readonly esmModuleLocation: URI,195public readonly label: string | undefined,196) { }197}198199export function createWebWorker<T extends object>(esmModuleLocation: URI, label: string | undefined): IWebWorkerClient<T>;200export function createWebWorker<T extends object>(workerDescriptor: IWebWorkerDescriptor | Worker | Promise<Worker>): IWebWorkerClient<T>;201export function createWebWorker<T extends object>(arg0: URI | IWebWorkerDescriptor | Worker | Promise<Worker>, arg1?: string | undefined): IWebWorkerClient<T> {202const workerDescriptorOrWorker = (URI.isUri(arg0) ? new WebWorkerDescriptor(arg0, arg1) : arg0);203return new WebWorkerClient<T>(new WebWorker(workerDescriptorOrWorker));204}205206207