From 32b7a94b600974f95828ed11f70b5bd2b77d0c0c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 12 Nov 2025 00:41:33 +0100 Subject: [PATCH] Introduces IWebWorkerService to allow the monaco editor to customize web worker handling via service injection --- .../index-workbench.ts | 7 +- eslint.config.js | 1 - src/tsec.exemptions.json | 2 +- src/vs/base/browser/webWorkerFactory.ts | 236 ------------------ .../browser/services/editorWorkerService.ts | 18 +- src/vs/editor/editor.api.ts | 3 - .../services/standaloneWebWorkerService.ts | 44 ++++ .../standalone/browser/standaloneEditor.ts | 3 +- .../standalone/browser/standaloneServices.ts | 3 + .../standalone/browser/standaloneWebWorker.ts | 9 +- .../profileAnalysisWorkerService.ts | 6 +- .../webWorker/browser/webWorkerDescriptor.ts | 24 ++ .../webWorker/browser/webWorkerService.ts | 16 ++ .../webWorker/browser/webWorkerServiceImpl.ts | 181 ++++++++++++++ .../services/notebookWorkerServiceImpl.ts | 17 +- .../output/browser/outputLinkProvider.ts | 9 +- .../languageDetectionWorkerServiceImpl.ts | 10 +- .../services/search/browser/searchService.ts | 6 +- .../threadedBackgroundTokenizerFactory.ts | 6 +- .../services/timer/browser/timerService.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 3 + 21 files changed, 335 insertions(+), 271 deletions(-) delete mode 100644 src/vs/base/browser/webWorkerFactory.ts create mode 100644 src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts create mode 100644 src/vs/platform/webWorker/browser/webWorkerDescriptor.ts create mode 100644 src/vs/platform/webWorker/browser/webWorkerService.ts create mode 100644 src/vs/platform/webWorker/browser/webWorkerServiceImpl.ts diff --git a/build/monaco-editor-playground/index-workbench.ts b/build/monaco-editor-playground/index-workbench.ts index 49bbf4c59e1..3a22438108a 100644 --- a/build/monaco-editor-playground/index-workbench.ts +++ b/build/monaco-editor-playground/index-workbench.ts @@ -3,8 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -WebWorkerDescriptor.useBundlerLocationRef(); +registerSingleton(IWebWorkerService, StandaloneWebWorkerService, InstantiationType.Eager); -import { WebWorkerDescriptor } from '../../src/vs/base/browser/webWorkerFactory.js'; import '../../src/vs/code/browser/workbench/workbench.ts'; +import { InstantiationType, registerSingleton } from '../../src/vs/platform/instantiation/common/extensions.ts'; +import { IWebWorkerService } from '../../src/vs/platform/webWorker/browser/webWorkerService.ts'; +// eslint-disable-next-line local/code-no-standalone-editor +import { StandaloneWebWorkerService } from '../../src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts'; diff --git a/eslint.config.js b/eslint.config.js index 946426a939c..24e3bf503ca 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -206,7 +206,6 @@ export default tseslint.config( 'src/vs/base/browser/dom.ts', 'src/vs/base/browser/markdownRenderer.ts', 'src/vs/base/browser/touch.ts', - 'src/vs/base/browser/webWorkerFactory.ts', 'src/vs/base/common/async.ts', 'src/vs/base/common/desktopEnvironmentInfo.ts', 'src/vs/base/common/objects.ts', diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index f913df5e7da..83691e2de5a 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -18,7 +18,7 @@ "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts" ], "ban-worker-calls": [ - "vs/base/browser/webWorkerFactory.ts", + "vs/platform/webWorker/browser/webWorkerServiceImpl.ts", "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts" ], "ban-worker-importscripts": [ diff --git a/src/vs/base/browser/webWorkerFactory.ts b/src/vs/base/browser/webWorkerFactory.ts deleted file mode 100644 index 77a7779d636..00000000000 --- a/src/vs/base/browser/webWorkerFactory.ts +++ /dev/null @@ -1,236 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createTrustedTypesPolicy } from './trustedTypes.js'; -import { onUnexpectedError } from '../common/errors.js'; -import { COI } from '../common/network.js'; -import { URI } from '../common/uri.js'; -import { IWebWorker, IWebWorkerClient, Message, WebWorkerClient } from '../common/worker/webWorker.js'; -import { Disposable, toDisposable } from '../common/lifecycle.js'; -import { coalesce } from '../common/arrays.js'; -import { getNLSLanguage, getNLSMessages } from '../../nls.js'; -import { Emitter } from '../common/event.js'; -import { getMonacoEnvironment } from './browser.js'; - -type WorkerGlobalWithPolicy = typeof globalThis & { - workerttPolicy?: ReturnType; -}; - -// Reuse the trusted types policy defined from worker bootstrap -// when available. -// Refs https://github.com/microsoft/vscode/issues/222193 -let ttPolicy: ReturnType; -const workerGlobalThis = globalThis as WorkerGlobalWithPolicy; -if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && workerGlobalThis.workerttPolicy !== undefined) { - ttPolicy = workerGlobalThis.workerttPolicy; -} else { - ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); -} - -export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker { - if (!blobUrl.startsWith('blob:')) { - throw new URIError('Not a blob-url: ' + blobUrl); - } - return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: 'module' }); -} - -function getWorker(descriptor: WebWorkerDescriptor, id: number): Worker | Promise { - const label = descriptor.label || 'anonymous' + id; - - // Option for hosts to overwrite the worker script (used in the standalone editor) - const monacoEnvironment = getMonacoEnvironment(); - if (monacoEnvironment) { - if (typeof monacoEnvironment.getWorker === 'function') { - const w = monacoEnvironment.getWorker('workerMain.js', label); - if (w !== undefined) { - return w; - } - } - if (typeof monacoEnvironment.getWorkerUrl === 'function') { - const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label); - if (workerUrl !== undefined) { - return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' }); - } - } - } - - const esmWorkerLocation = descriptor.getUrl(); - if (esmWorkerLocation) { - const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation); - const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: 'module' }); - return whenESMWorkerReady(worker); - } - - throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); -} - -function getWorkerBootstrapUrl(label: string, workerScriptUrl: string): string { - if (/^((http:)|(https:)|(file:))/.test(workerScriptUrl) && workerScriptUrl.substring(0, globalThis.origin.length) !== globalThis.origin) { - // this is the cross-origin case - // i.e. the webpage is running at a different origin than where the scripts are loaded from - } else { - const start = workerScriptUrl.lastIndexOf('?'); - const end = workerScriptUrl.lastIndexOf('#', start); - const params = start > 0 - ? new URLSearchParams(workerScriptUrl.substring(start + 1, ~end ? end : undefined)) - : new URLSearchParams(); - - COI.addSearchParam(params, true, true); - const search = params.toString(); - if (!search) { - workerScriptUrl = `${workerScriptUrl}#${label}`; - } else { - workerScriptUrl = `${workerScriptUrl}?${params.toString()}#${label}`; - } - } - - // In below blob code, we are using JSON.stringify to ensure the passed - // in values are not breaking our script. The values may contain string - // terminating characters (such as ' or "). - const blob = new Blob([coalesce([ - `/*${label}*/`, - `globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(getNLSMessages())};`, - `globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(getNLSLanguage())};`, - `globalThis._VSCODE_FILE_ROOT = ${JSON.stringify(globalThis._VSCODE_FILE_ROOT)};`, - `const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`, - `globalThis.workerttPolicy = ttPolicy;`, - `await import(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});`, - `globalThis.postMessage({ type: 'vscode-worker-ready' });`, - `/*${label}*/` - ]).join('')], { type: 'application/javascript' }); - return URL.createObjectURL(blob); -} - -function whenESMWorkerReady(worker: Worker): Promise { - return new Promise((resolve, reject) => { - worker.onmessage = function (e) { - if (e.data.type === 'vscode-worker-ready') { - worker.onmessage = null; - resolve(worker); - } - }; - worker.onerror = reject; - }); -} - -function isPromiseLike(obj: unknown): obj is PromiseLike { - return !!obj && typeof (obj as PromiseLike).then === 'function'; -} - -/** - * A worker that uses HTML5 web workers so that is has - * its own global scope and its own thread. - */ -class WebWorker extends Disposable implements IWebWorker { - - private static LAST_WORKER_ID = 0; - - private readonly id: number; - private worker: Promise | null; - - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage = this._onMessage.event; - - private readonly _onError = this._register(new Emitter()); - public readonly onError = this._onError.event; - - constructor(descriptorOrWorker: WebWorkerDescriptor | Worker | Promise) { - super(); - this.id = ++WebWorker.LAST_WORKER_ID; - const workerOrPromise = ( - descriptorOrWorker instanceof Worker - ? descriptorOrWorker : - 'then' in descriptorOrWorker ? descriptorOrWorker - : getWorker(descriptorOrWorker, this.id) - ); - if (isPromiseLike(workerOrPromise)) { - this.worker = workerOrPromise; - } else { - this.worker = Promise.resolve(workerOrPromise); - } - this.postMessage('-please-ignore-', []); // TODO: Eliminate this extra message - const errorHandler = (ev: ErrorEvent) => { - this._onError.fire(ev); - }; - this.worker.then((w) => { - w.onmessage = (ev) => { - this._onMessage.fire(ev.data); - }; - w.onmessageerror = (ev) => { - this._onError.fire(ev); - }; - if (typeof w.addEventListener === 'function') { - w.addEventListener('error', errorHandler); - } - }); - this._register(toDisposable(() => { - this.worker?.then(w => { - w.onmessage = null; - w.onmessageerror = null; - w.removeEventListener('error', errorHandler); - w.terminate(); - }); - this.worker = null; - })); - } - - public getId(): number { - return this.id; - } - - public postMessage(message: unknown, transfer: Transferable[]): void { - this.worker?.then(w => { - try { - w.postMessage(message, transfer); - } catch (err) { - onUnexpectedError(err); - onUnexpectedError(new Error(`FAILED to post message to worker`, { cause: err })); - } - }); - } -} - -export class WebWorkerDescriptor { - private static _useBundlerLocationRef = false; - - /** TODO @hediet: Use web worker service! */ - public static useBundlerLocationRef() { - WebWorkerDescriptor._useBundlerLocationRef = true; - } - - public readonly esmModuleLocation: URI | (() => URI) | undefined; - public readonly esmModuleLocationBundler: URL | (() => URL) | undefined; - public readonly label: string | undefined; - - constructor(args: { - /** The location of the esm module after transpilation */ - esmModuleLocation?: URI | (() => URI); - /** The location of the esm module when used in a bundler environment. Refer to the typescript file in the src folder and use `?worker`. */ - esmModuleLocationBundler?: URL | (() => URL); - label?: string; - }) { - this.esmModuleLocation = args.esmModuleLocation; - this.esmModuleLocationBundler = args.esmModuleLocationBundler; - this.label = args.label; - } - - getUrl(): string | undefined { - if (WebWorkerDescriptor._useBundlerLocationRef) { - if (this.esmModuleLocationBundler) { - const esmWorkerLocation = typeof this.esmModuleLocationBundler === 'function' ? this.esmModuleLocationBundler() : this.esmModuleLocationBundler; - return esmWorkerLocation.toString(); - } - } else if (this.esmModuleLocation) { - const esmWorkerLocation = typeof this.esmModuleLocation === 'function' ? this.esmModuleLocation() : this.esmModuleLocation; - return esmWorkerLocation.toString(true); - } - - return undefined; - } -} - -export function createWebWorker(workerDescriptor: WebWorkerDescriptor | Worker | Promise): IWebWorkerClient { - return new WebWorkerClient(new WebWorker(workerDescriptor)); -} diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index cf7a530444e..0a221ebefaf 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -7,7 +7,8 @@ import { timeout } from '../../../base/common/async.js'; import { Disposable, IDisposable } from '../../../base/common/lifecycle.js'; import { URI } from '../../../base/common/uri.js'; import { logOnceWebWorkerWarning, IWebWorkerClient, Proxied } from '../../../base/common/worker/webWorker.js'; -import { createWebWorker, WebWorkerDescriptor } from '../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js'; import { Position } from '../../common/core/position.js'; import { IRange, Range } from '../../common/core/range.js'; import { ITextModel } from '../../common/model.js'; @@ -67,6 +68,7 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ @ILogService logService: ILogService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IWebWorkerService private readonly _webWorkerService: IWebWorkerService, ) { super(); this._modelService = modelService; @@ -77,7 +79,7 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ label: 'editorWorkerService' }); - this._workerManager = this._register(new WorkerManager(workerDescriptor, this._modelService)); + this._workerManager = this._register(new WorkerManager(workerDescriptor, this._modelService, this._webWorkerService)); this._logService = logService; // register default link-provider and default completions-provider @@ -333,15 +335,18 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide class WorkerManager extends Disposable { private readonly _modelService: IModelService; + private readonly _webWorkerService: IWebWorkerService; private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; constructor( private readonly _workerDescriptor: WebWorkerDescriptor, - @IModelService modelService: IModelService + @IModelService modelService: IModelService, + @IWebWorkerService webWorkerService: IWebWorkerService ) { super(); this._modelService = modelService; + this._webWorkerService = webWorkerService; this._editorWorkerClient = null; this._lastWorkerUsedTime = (new Date()).getTime(); @@ -393,7 +398,7 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._workerDescriptor, false, this._modelService); + this._editorWorkerClient = new EditorWorkerClient(this._workerDescriptor, false, this._modelService, this._webWorkerService); } return Promise.resolve(this._editorWorkerClient); } @@ -428,6 +433,7 @@ export interface IEditorWorkerClient { export class EditorWorkerClient extends Disposable implements IEditorWorkerClient { private readonly _modelService: IModelService; + private readonly _webWorkerService: IWebWorkerService; private readonly _keepIdleModels: boolean; private _worker: IWebWorkerClient | null; private _modelManager: WorkerTextModelSyncClient | null; @@ -437,9 +443,11 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien private readonly _workerDescriptorOrWorker: WebWorkerDescriptor | Worker | Promise, keepIdleModels: boolean, @IModelService modelService: IModelService, + @IWebWorkerService webWorkerService: IWebWorkerService ) { super(); this._modelService = modelService; + this._webWorkerService = webWorkerService; this._keepIdleModels = keepIdleModels; this._worker = null; this._modelManager = null; @@ -453,7 +461,7 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien private _getOrCreateWorker(): IWebWorkerClient { if (!this._worker) { try { - this._worker = this._register(createWebWorker(this._workerDescriptorOrWorker)); + this._worker = this._register(this._webWorkerService.createWorkerClient(this._workerDescriptorOrWorker)); EditorWorkerHost.setChannel(this._worker, this._createEditorWorkerHost()); } catch (err) { logOnceWebWorkerWarning(err); diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index 28990070d39..8e414c22ed8 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -9,9 +9,6 @@ import { createMonacoEditorAPI } from './standalone/browser/standaloneEditor.js' import { createMonacoLanguagesAPI } from './standalone/browser/standaloneLanguages.js'; import { FormattingConflicts } from './contrib/format/browser/format.js'; import { getMonacoEnvironment } from '../base/browser/browser.js'; -import { WebWorkerDescriptor } from '../base/browser/webWorkerFactory.js'; - -WebWorkerDescriptor.useBundlerLocationRef(); // Set defaults for standalone editor EditorOptions.wrappingIndent.defaultValue = WrappingIndent.None; diff --git a/src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts b/src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts new file mode 100644 index 00000000000..e14c8175082 --- /dev/null +++ b/src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getMonacoEnvironment } from '../../../../base/browser/browser.js'; +import { WebWorkerDescriptor } from '../../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { WebWorkerService } from '../../../../platform/webWorker/browser/webWorkerServiceImpl.js'; + +export class StandaloneWebWorkerService extends WebWorkerService { + protected override _createWorker(descriptor: WebWorkerDescriptor): Promise { + const monacoEnvironment = getMonacoEnvironment(); + if (monacoEnvironment) { + if (typeof monacoEnvironment.getWorker === 'function') { + const worker = monacoEnvironment.getWorker('workerMain.js', descriptor.label); + if (worker !== undefined) { + return Promise.resolve(worker); + } + } + } + + return super._createWorker(descriptor); + } + + protected override _getUrl(descriptor: WebWorkerDescriptor): string { + const monacoEnvironment = getMonacoEnvironment(); + if (monacoEnvironment) { + if (typeof monacoEnvironment.getWorkerUrl === 'function') { + const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', descriptor.label); + if (workerUrl !== undefined) { + return workerUrl; + } + } + } + + if (!descriptor.esmModuleLocationBundler) { + throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); + } + + const url = typeof descriptor.esmModuleLocationBundler === 'function' ? descriptor.esmModuleLocationBundler() : descriptor.esmModuleLocationBundler; + const urlStr = url.toString(); + return urlStr; + } +} diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index f635ace5f1b..cdf1f4081f0 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -39,6 +39,7 @@ import { IKeybindingService } from '../../../platform/keybinding/common/keybindi import { IMarker, IMarkerData, IMarkerService } from '../../../platform/markers/common/markers.js'; import { IOpenerService } from '../../../platform/opener/common/opener.js'; import { MultiDiffEditorWidget } from '../../browser/widget/multiDiffEditor/multiDiffEditorWidget.js'; +import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js'; /** * Create a new editor under `domElement`. @@ -332,7 +333,7 @@ export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextMo * Specify an AMD module to load that will `create` an object that will be proxied. */ export function createWebWorker(opts: IInternalWebWorkerOptions): MonacoWebWorker { - return actualCreateWebWorker(StandaloneServices.get(IModelService), opts); + return actualCreateWebWorker(StandaloneServices.get(IModelService), StandaloneServices.get(IWebWorkerService), opts); } /** diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 5aabf5c42c8..7660b49ad76 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -96,6 +96,8 @@ import { ResourceMap } from '../../../base/common/map.js'; import { ITreeSitterLibraryService } from '../../common/services/treeSitter/treeSitterLibraryService.js'; import { StandaloneTreeSitterLibraryService } from './standaloneTreeSitterLibraryService.js'; import { IDataChannelService, NullDataChannelService } from '../../../platform/dataChannel/common/dataChannel.js'; +import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js'; +import { StandaloneWebWorkerService } from './services/standaloneWebWorkerService.js'; class SimpleModel implements IResolvedTextEditorModel { @@ -1110,6 +1112,7 @@ export interface IEditorOverrideServices { } +registerSingleton(IWebWorkerService, StandaloneWebWorkerService, InstantiationType.Eager); registerSingleton(ILogService, StandaloneLogService, InstantiationType.Eager); registerSingleton(IConfigurationService, StandaloneConfigurationService, InstantiationType.Eager); registerSingleton(ITextResourceConfigurationService, StandaloneResourceConfigurationService, InstantiationType.Eager); diff --git a/src/vs/editor/standalone/browser/standaloneWebWorker.ts b/src/vs/editor/standalone/browser/standaloneWebWorker.ts index a34425aa444..cf1f15d4255 100644 --- a/src/vs/editor/standalone/browser/standaloneWebWorker.ts +++ b/src/vs/editor/standalone/browser/standaloneWebWorker.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from '../../../base/common/uri.js'; +import { IWebWorkerService } from '../../../platform/webWorker/browser/webWorkerService.js'; import { EditorWorkerClient } from '../../browser/services/editorWorkerService.js'; import { IModelService } from '../../common/services/model.js'; @@ -11,8 +12,8 @@ import { IModelService } from '../../common/services/model.js'; * Create a new web worker that has model syncing capabilities built in. * Specify an AMD module to load that will `create` an object that will be proxied. */ -export function createWebWorker(modelService: IModelService, opts: IInternalWebWorkerOptions): MonacoWebWorker { - return new MonacoWebWorkerImpl(modelService, opts); +export function createWebWorker(modelService: IModelService, webWorkerService: IWebWorkerService, opts: IInternalWebWorkerOptions): MonacoWebWorker { + return new MonacoWebWorkerImpl(modelService, webWorkerService, opts); } /** @@ -55,8 +56,8 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement private readonly _foreignModuleHost: { [method: string]: Function } | null; private _foreignProxy: Promise; - constructor(modelService: IModelService, opts: IInternalWebWorkerOptions) { - super(opts.worker, opts.keepIdleModels || false, modelService); + constructor(modelService: IModelService, webWorkerService: IWebWorkerService, opts: IInternalWebWorkerOptions) { + super(opts.worker, opts.keepIdleModels || false, modelService, webWorkerService); this._foreignModuleHost = opts.host || null; this._foreignProxy = this._getProxy().then(proxy => { return new Proxy({}, { diff --git a/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts b/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts index c837b9b937d..de0aa66efc2 100644 --- a/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts +++ b/src/vs/platform/profiling/electron-browser/profileAnalysisWorkerService.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ -import { createWebWorker, WebWorkerDescriptor } from '../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../webWorker/browser/webWorkerDescriptor.js'; import { URI } from '../../../base/common/uri.js'; import { Proxied } from '../../../base/common/worker/webWorker.js'; import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; +import { IWebWorkerService } from '../../webWorker/browser/webWorkerService.js'; import { ILogService } from '../../log/common/log.js'; import { IV8Profile } from '../common/profiling.js'; import { BottomUpSample } from '../common/profilingModel.js'; @@ -44,11 +45,12 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, + @IWebWorkerService private readonly _webWorkerService: IWebWorkerService, ) { } private async _withWorker(callback: (worker: Proxied) => Promise): Promise { - const worker = createWebWorker( + const worker = this._webWorkerService.createWorkerClient( new WebWorkerDescriptor({ esmModuleLocation: FileAccess.asBrowserUri('vs/platform/profiling/electron-browser/profileAnalysisWorkerMain.js'), label: 'CpuProfileAnalysisWorker' diff --git a/src/vs/platform/webWorker/browser/webWorkerDescriptor.ts b/src/vs/platform/webWorker/browser/webWorkerDescriptor.ts new file mode 100644 index 00000000000..5deeaeba084 --- /dev/null +++ b/src/vs/platform/webWorker/browser/webWorkerDescriptor.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../base/common/uri.js'; + +export class WebWorkerDescriptor { + public readonly esmModuleLocation: URI | (() => URI) | undefined; + public readonly esmModuleLocationBundler: URL | (() => URL) | undefined; + public readonly label: string; + + constructor(args: { + /** The location of the esm module after transpilation */ + esmModuleLocation?: URI | (() => URI); + /** The location of the esm module when used in a bundler environment. Refer to the typescript file in the src folder and use `?worker`. */ + esmModuleLocationBundler?: URL | (() => URL); + label: string; + }) { + this.esmModuleLocation = args.esmModuleLocation; + this.esmModuleLocationBundler = args.esmModuleLocationBundler; + this.label = args.label; + } +} diff --git a/src/vs/platform/webWorker/browser/webWorkerService.ts b/src/vs/platform/webWorker/browser/webWorkerService.ts new file mode 100644 index 00000000000..1f9bce868ad --- /dev/null +++ b/src/vs/platform/webWorker/browser/webWorkerService.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from '../../instantiation/common/instantiation.js'; +import { IWebWorkerClient } from '../../../base/common/worker/webWorker.js'; +import { WebWorkerDescriptor } from './webWorkerDescriptor.js'; + +export const IWebWorkerService = createDecorator('IWebWorkerService'); + +export interface IWebWorkerService { + readonly _serviceBrand: undefined; + + createWorkerClient(workerDescriptor: WebWorkerDescriptor | Worker | Promise): IWebWorkerClient; +} diff --git a/src/vs/platform/webWorker/browser/webWorkerServiceImpl.ts b/src/vs/platform/webWorker/browser/webWorkerServiceImpl.ts new file mode 100644 index 00000000000..3a17a793ee7 --- /dev/null +++ b/src/vs/platform/webWorker/browser/webWorkerServiceImpl.ts @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createTrustedTypesPolicy } from '../../../base/browser/trustedTypes.js'; +import { coalesce } from '../../../base/common/arrays.js'; +import { onUnexpectedError } from '../../../base/common/errors.js'; +import { Emitter } from '../../../base/common/event.js'; +import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { COI } from '../../../base/common/network.js'; +import { IWebWorker, IWebWorkerClient, Message, WebWorkerClient } from '../../../base/common/worker/webWorker.js'; +import { getNLSLanguage, getNLSMessages } from '../../../nls.js'; +import { WebWorkerDescriptor } from './webWorkerDescriptor.js'; +import { IWebWorkerService } from './webWorkerService.js'; + +export class WebWorkerService implements IWebWorkerService { + private static _workerIdPool: number = 0; + declare readonly _serviceBrand: undefined; + + createWorkerClient(workerDescriptor: WebWorkerDescriptor | Worker | Promise): IWebWorkerClient { + let worker: Worker | Promise; + const id = ++WebWorkerService._workerIdPool; + if (workerDescriptor instanceof Worker || isPromiseLike(workerDescriptor)) { + worker = Promise.resolve(workerDescriptor); + } else { + worker = this._createWorker(workerDescriptor); + } + + return new WebWorkerClient(new WebWorker(worker, id)); + } + + protected _createWorker(descriptor: WebWorkerDescriptor): Promise { + const workerRunnerUrl = this._getUrl(descriptor); + + const workerUrl = getWorkerBootstrapUrl(descriptor.label, workerRunnerUrl); + const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: descriptor.label, type: 'module' }); + return whenESMWorkerReady(worker); + } + + protected _getUrl(descriptor: WebWorkerDescriptor): string { + if (!descriptor.esmModuleLocation) { + throw new Error('Missing esmModuleLocation in WebWorkerDescriptor'); + } + const uri = typeof descriptor.esmModuleLocation === 'function' ? descriptor.esmModuleLocation() : descriptor.esmModuleLocation; + const urlStr = uri.toString(true); + return urlStr; + } +} + +const ttPolicy = ((): ReturnType => { + type WorkerGlobalWithPolicy = typeof globalThis & { + workerttPolicy?: ReturnType; + }; + + // Reuse the trusted types policy defined from worker bootstrap + // when available. + // Refs https://github.com/microsoft/vscode/issues/222193 + const workerGlobalThis = globalThis as WorkerGlobalWithPolicy; + if (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope' && workerGlobalThis.workerttPolicy !== undefined) { + return workerGlobalThis.workerttPolicy; + } else { + return createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); + } +})(); + +export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker { + if (!blobUrl.startsWith('blob:')) { + throw new URIError('Not a blob-url: ' + blobUrl); + } + return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: 'module' }); +} + +function getWorkerBootstrapUrl(label: string, workerScriptUrl: string): string { + if (/^((http:)|(https:)|(file:))/.test(workerScriptUrl) && workerScriptUrl.substring(0, globalThis.origin.length) !== globalThis.origin) { + // this is the cross-origin case + // i.e. the webpage is running at a different origin than where the scripts are loaded from + } else { + const start = workerScriptUrl.lastIndexOf('?'); + const end = workerScriptUrl.lastIndexOf('#', start); + const params = start > 0 + ? new URLSearchParams(workerScriptUrl.substring(start + 1, ~end ? end : undefined)) + : new URLSearchParams(); + + COI.addSearchParam(params, true, true); + const search = params.toString(); + if (!search) { + workerScriptUrl = `${workerScriptUrl}#${label}`; + } else { + workerScriptUrl = `${workerScriptUrl}?${params.toString()}#${label}`; + } + } + + // In below blob code, we are using JSON.stringify to ensure the passed + // in values are not breaking our script. The values may contain string + // terminating characters (such as ' or "). + const blob = new Blob([coalesce([ + `/*${label}*/`, + `globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(getNLSMessages())};`, + `globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(getNLSLanguage())};`, + `globalThis._VSCODE_FILE_ROOT = ${JSON.stringify(globalThis._VSCODE_FILE_ROOT)};`, + `const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`, + `globalThis.workerttPolicy = ttPolicy;`, + `await import(ttPolicy?.createScriptURL(${JSON.stringify(workerScriptUrl)}) ?? ${JSON.stringify(workerScriptUrl)});`, + `globalThis.postMessage({ type: 'vscode-worker-ready' });`, + `/*${label}*/` + ]).join('')], { type: 'application/javascript' }); + return URL.createObjectURL(blob); +} + +function whenESMWorkerReady(worker: Worker): Promise { + return new Promise((resolve, reject) => { + worker.onmessage = function (e) { + if (e.data.type === 'vscode-worker-ready') { + worker.onmessage = null; + resolve(worker); + } + }; + worker.onerror = reject; + }); +} + +function isPromiseLike(obj: unknown): obj is PromiseLike { + return !!obj && typeof (obj as PromiseLike).then === 'function'; +} + +export class WebWorker extends Disposable implements IWebWorker { + private readonly id: number; + private worker: Promise | null; + + private readonly _onMessage = this._register(new Emitter()); + public readonly onMessage = this._onMessage.event; + + private readonly _onError = this._register(new Emitter()); + public readonly onError = this._onError.event; + + constructor(worker: Promise, id: number) { + super(); + this.id = id; + this.worker = worker; + this.postMessage('-please-ignore-', []); // TODO: Eliminate this extra message + const errorHandler = (ev: ErrorEvent) => { + this._onError.fire(ev); + }; + this.worker.then((w) => { + w.onmessage = (ev) => { + this._onMessage.fire(ev.data); + }; + w.onmessageerror = (ev) => { + this._onError.fire(ev); + }; + if (typeof w.addEventListener === 'function') { + w.addEventListener('error', errorHandler); + } + }); + this._register(toDisposable(() => { + this.worker?.then(w => { + w.onmessage = null; + w.onmessageerror = null; + w.removeEventListener('error', errorHandler); + w.terminate(); + }); + this.worker = null; + })); + } + + public getId(): number { + return this.id; + } + + public postMessage(message: unknown, transfer: Transferable[]): void { + this.worker?.then(w => { + try { + w.postMessage(message, transfer); + } catch (err) { + onUnexpectedError(err); + onUnexpectedError(new Error(`FAILED to post message to worker`, { cause: err })); + } + }); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts index 622692d670a..9ef0ba1dc82 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts @@ -6,7 +6,8 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { URI } from '../../../../../base/common/uri.js'; import { IWebWorkerClient, Proxied } from '../../../../../base/common/worker/webWorker.js'; -import { createWebWorker, WebWorkerDescriptor } from '../../../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../../../platform/webWorker/browser/webWorkerService.js'; import { NotebookCellTextModel } from '../../common/model/notebookCellTextModel.js'; import { CellUri, IMainCellDto, INotebookDiffResult, NotebookCellsChangeType, NotebookRawContentEventDto } from '../../common/notebookCommon.js'; import { INotebookService } from '../../common/notebookService.js'; @@ -26,10 +27,11 @@ export class NotebookEditorWorkerServiceImpl extends Disposable implements INote constructor( @INotebookService notebookService: INotebookService, @IModelService modelService: IModelService, + @IWebWorkerService webWorkerService: IWebWorkerService, ) { super(); - this._workerManager = this._register(new WorkerManager(notebookService, modelService)); + this._workerManager = this._register(new WorkerManager(notebookService, modelService, webWorkerService)); } canComputeDiff(original: URI, modified: URI): boolean { throw new Error('Method not implemented.'); @@ -55,6 +57,7 @@ class WorkerManager extends Disposable { constructor( private readonly _notebookService: INotebookService, private readonly _modelService: IModelService, + private readonly _webWorkerService: IWebWorkerService, ) { super(); this._editorWorkerClient = null; @@ -64,7 +67,7 @@ class WorkerManager extends Disposable { withWorker(): Promise { // this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, this._modelService); + this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, this._modelService, this._webWorkerService); this._register(this._editorWorkerClient); } return Promise.resolve(this._editorWorkerClient); @@ -240,7 +243,11 @@ class NotebookWorkerClient extends Disposable { private _modelManager: NotebookEditorModelManager | null; - constructor(private readonly _notebookService: INotebookService, private readonly _modelService: IModelService) { + constructor( + private readonly _notebookService: INotebookService, + private readonly _modelService: IModelService, + private readonly _webWorkerService: IWebWorkerService, + ) { super(); this._worker = null; this._modelManager = null; @@ -273,7 +280,7 @@ class NotebookWorkerClient extends Disposable { private _getOrCreateWorker(): IWebWorkerClient { if (!this._worker) { try { - this._worker = this._register(createWebWorker( + this._worker = this._register(this._webWorkerService.createWorkerClient( new WebWorkerDescriptor({ esmModuleLocation: FileAccess.asBrowserUri('vs/workbench/contrib/notebook/common/services/notebookWebWorkerMain.js'), label: 'NotebookEditorWorker' diff --git a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts index 6a291ecb3ff..89591f8ed93 100644 --- a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts +++ b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts @@ -12,7 +12,8 @@ import { OUTPUT_MODE_ID, LOG_MODE_ID } from '../../../services/output/common/out import { OutputLinkComputer } from '../common/outputLinkComputer.js'; import { IDisposable, dispose, Disposable } from '../../../../base/common/lifecycle.js'; import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; -import { createWebWorker, WebWorkerDescriptor } from '../../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../../platform/webWorker/browser/webWorkerService.js'; import { IWebWorkerClient } from '../../../../base/common/worker/webWorker.js'; import { WorkerTextModelSyncClient } from '../../../../editor/common/services/textModelSync/textModelSync.impl.js'; import { FileAccess } from '../../../../base/common/network.js'; @@ -29,6 +30,7 @@ export class OutputLinkProvider extends Disposable { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IModelService private readonly modelService: IModelService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, + @IWebWorkerService private readonly webWorkerService: IWebWorkerService, ) { super(); @@ -70,7 +72,7 @@ export class OutputLinkProvider extends Disposable { this.disposeWorkerScheduler.schedule(); if (!this.worker) { - this.worker = new OutputLinkWorkerClient(this.contextService, this.modelService); + this.worker = new OutputLinkWorkerClient(this.contextService, this.modelService, this.webWorkerService); } return this.worker; @@ -96,9 +98,10 @@ class OutputLinkWorkerClient extends Disposable { constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IModelService modelService: IModelService, + @IWebWorkerService webWorkerService: IWebWorkerService, ) { super(); - this._workerClient = this._register(createWebWorker( + this._workerClient = this._register(webWorkerService.createWorkerClient( new WebWorkerDescriptor({ esmModuleLocation: FileAccess.asBrowserUri('vs/workbench/contrib/output/common/outputLinkComputerMain.js'), label: 'OutputLinkDetectionWorker' diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 9fc668b5cb0..bfad4bc79ca 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -22,7 +22,8 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { LRUCache } from '../../../../base/common/map.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { canASAR } from '../../../../amdX.js'; -import { createWebWorker, WebWorkerDescriptor } from '../../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../../platform/webWorker/browser/webWorkerService.js'; import { WorkerTextModelSyncClient } from '../../../../editor/common/services/textModelSync/textModelSync.impl.js'; import { ILanguageDetectionWorker, LanguageDetectionWorkerHost } from './languageDetectionWorker.protocol.js'; @@ -62,7 +63,8 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet @IEditorService private readonly _editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @IWebWorkerService webWorkerService: IWebWorkerService, ) { super(); @@ -71,6 +73,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet modelService, languageService, telemetryService, + webWorkerService, // TODO See if it's possible to bundle vscode-languagedetection useAsar ? FileAccess.asBrowserUri(`${moduleLocationAsar}/dist/lib/index.js`).toString(true) @@ -187,6 +190,7 @@ export class LanguageDetectionWorkerClient extends Disposable { private readonly _modelService: IModelService, private readonly _languageService: ILanguageService, private readonly _telemetryService: ITelemetryService, + private readonly _webWorkerService: IWebWorkerService, private readonly _indexJsUri: string, private readonly _modelJsonUri: string, private readonly _weightsUri: string, @@ -200,7 +204,7 @@ export class LanguageDetectionWorkerClient extends Disposable { workerTextModelSyncClient: WorkerTextModelSyncClient; } { if (!this.worker) { - const workerClient = this._register(createWebWorker( + const workerClient = this._register(this._webWorkerService.createWorkerClient( new WebWorkerDescriptor({ esmModuleLocation: FileAccess.asBrowserUri('vs/workbench/services/languageDetection/browser/languageDetectionWebWorkerMain.js'), label: 'LanguageDetectionWorker' diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index 5c1ad04633e..c9011956dbb 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -16,7 +16,8 @@ import { SearchService } from '../common/searchService.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IWebWorkerClient, logOnceWebWorkerWarning } from '../../../../base/common/worker/webWorker.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { createWebWorker, WebWorkerDescriptor } from '../../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../../platform/webWorker/browser/webWorkerService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { ILocalFileSearchWorker, LocalFileSearchWorkerHost } from '../common/localFileSearchWorkerTypes.js'; import { memoize } from '../../../../base/common/decorators.js'; @@ -60,6 +61,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe constructor( @IFileService private fileService: IFileService, @IUriIdentityService private uriIdentityService: IUriIdentityService, + @IWebWorkerService private webWorkerService: IWebWorkerService, ) { super(); this._worker = null; @@ -187,7 +189,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe private _getOrCreateWorker(): IWebWorkerClient { if (!this._worker) { try { - this._worker = this._register(createWebWorker( + this._worker = this._register(this.webWorkerService.createWorkerClient( new WebWorkerDescriptor({ esmModuleLocation: FileAccess.asBrowserUri('vs/workbench/services/search/worker/localFileSearchMain.js'), label: 'LocalFileSearchWorker' diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts index cefef222089..3662b13b377 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts @@ -22,7 +22,8 @@ import { TextMateWorkerHost } from './worker/textMateWorkerHost.js'; import { TextMateWorkerTokenizerController } from './textMateWorkerTokenizerController.js'; import { IValidGrammarDefinition } from '../../common/TMScopeRegistry.js'; import type { IRawTheme } from 'vscode-textmate'; -import { createWebWorker, WebWorkerDescriptor } from '../../../../../base/browser/webWorkerFactory.js'; +import { WebWorkerDescriptor } from '../../../../../platform/webWorker/browser/webWorkerDescriptor.js'; +import { IWebWorkerService } from '../../../../../platform/webWorker/browser/webWorkerService.js'; import { IWebWorkerClient, Proxied } from '../../../../../base/common/worker/webWorker.js'; export class ThreadedBackgroundTokenizerFactory implements IDisposable { @@ -46,6 +47,7 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { @IEnvironmentService private readonly _environmentService: IEnvironmentService, @INotificationService private readonly _notificationService: INotificationService, @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IWebWorkerService private readonly _webWorkerService: IWebWorkerService, ) { } @@ -137,7 +139,7 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { grammarDefinitions: this._grammarDefinitions, onigurumaWASMUri: FileAccess.asBrowserUri(onigurumaWASM).toString(true), }; - const worker = this._worker = createWebWorker( + const worker = this._worker = this._webWorkerService.createWorkerClient( new WebWorkerDescriptor({ esmModuleLocation: FileAccess.asBrowserUri('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.workerMain.js'), label: 'TextMateWorker' diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index df3a9b6dd3b..9a0db4c5de9 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -18,7 +18,7 @@ import { IPaneCompositePartService } from '../../panecomposite/browser/panecompo import { ViewContainerLocation } from '../../../common/views.js'; import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js'; import { isWeb } from '../../../../base/common/platform.js'; -import { createBlobWorker } from '../../../../base/browser/webWorkerFactory.js'; +import { createBlobWorker } from '../../../../platform/webWorker/browser/webWorkerServiceImpl.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ITerminalBackendRegistry, TerminalExtensions } from '../../../../platform/terminal/common/terminal.js'; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 8734ede8cb8..8983b54eee7 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -159,6 +159,8 @@ import { AllowedExtensionsService } from '../platform/extensionManagement/common import { IAllowedMcpServersService, IMcpGalleryService } from '../platform/mcp/common/mcpManagement.js'; import { McpGalleryService } from '../platform/mcp/common/mcpGalleryService.js'; import { AllowedMcpServersService } from '../platform/mcp/common/allowedMcpServersService.js'; +import { IWebWorkerService } from '../platform/webWorker/browser/webWorkerService.js'; +import { WebWorkerService } from '../platform/webWorker/browser/webWorkerServiceImpl.js'; registerSingleton(IUserDataSyncLogService, UserDataSyncLogService, InstantiationType.Delayed); registerSingleton(IAllowedExtensionsService, AllowedExtensionsService, InstantiationType.Delayed); @@ -173,6 +175,7 @@ registerSingleton(IContextKeyService, ContextKeyService, InstantiationType.Delay registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService, InstantiationType.Delayed); registerSingleton(IDownloadService, DownloadService, InstantiationType.Delayed); registerSingleton(IOpenerService, OpenerService, InstantiationType.Delayed); +registerSingleton(IWebWorkerService, WebWorkerService, InstantiationType.Delayed); registerSingleton(IMcpGalleryService, McpGalleryService, InstantiationType.Delayed); registerSingleton(IAllowedMcpServersService, AllowedMcpServersService, InstantiationType.Delayed);