| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { transformErrorForSerialization } from '../errors.js';
- import { Emitter } from '../event.js';
- import { Disposable } from '../lifecycle.js';
- import { globals, isWeb } from '../platform.js';
- import * as types from '../types.js';
- import * as strings from '../strings.js';
- const INITIALIZE = '$initialize';
- let webWorkerWarningLogged = false;
- export function logOnceWebWorkerWarning(err) {
- if (!isWeb) {
- // running tests
- return;
- }
- if (!webWorkerWarningLogged) {
- webWorkerWarningLogged = true;
- console.warn('Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq');
- }
- console.warn(err.message);
- }
- class RequestMessage {
- constructor(vsWorker, req, method, args) {
- this.vsWorker = vsWorker;
- this.req = req;
- this.method = method;
- this.args = args;
- this.type = 0 /* MessageType.Request */;
- }
- }
- class ReplyMessage {
- constructor(vsWorker, seq, res, err) {
- this.vsWorker = vsWorker;
- this.seq = seq;
- this.res = res;
- this.err = err;
- this.type = 1 /* MessageType.Reply */;
- }
- }
- class SubscribeEventMessage {
- constructor(vsWorker, req, eventName, arg) {
- this.vsWorker = vsWorker;
- this.req = req;
- this.eventName = eventName;
- this.arg = arg;
- this.type = 2 /* MessageType.SubscribeEvent */;
- }
- }
- class EventMessage {
- constructor(vsWorker, req, event) {
- this.vsWorker = vsWorker;
- this.req = req;
- this.event = event;
- this.type = 3 /* MessageType.Event */;
- }
- }
- class UnsubscribeEventMessage {
- constructor(vsWorker, req) {
- this.vsWorker = vsWorker;
- this.req = req;
- this.type = 4 /* MessageType.UnsubscribeEvent */;
- }
- }
- class SimpleWorkerProtocol {
- constructor(handler) {
- this._workerId = -1;
- this._handler = handler;
- this._lastSentReq = 0;
- this._pendingReplies = Object.create(null);
- this._pendingEmitters = new Map();
- this._pendingEvents = new Map();
- }
- setWorkerId(workerId) {
- this._workerId = workerId;
- }
- sendMessage(method, args) {
- const req = String(++this._lastSentReq);
- return new Promise((resolve, reject) => {
- this._pendingReplies[req] = {
- resolve: resolve,
- reject: reject
- };
- this._send(new RequestMessage(this._workerId, req, method, args));
- });
- }
- listen(eventName, arg) {
- let req = null;
- const emitter = new Emitter({
- onFirstListenerAdd: () => {
- req = String(++this._lastSentReq);
- this._pendingEmitters.set(req, emitter);
- this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg));
- },
- onLastListenerRemove: () => {
- this._pendingEmitters.delete(req);
- this._send(new UnsubscribeEventMessage(this._workerId, req));
- req = null;
- }
- });
- return emitter.event;
- }
- handleMessage(message) {
- if (!message || !message.vsWorker) {
- return;
- }
- if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
- return;
- }
- this._handleMessage(message);
- }
- _handleMessage(msg) {
- switch (msg.type) {
- case 1 /* MessageType.Reply */:
- return this._handleReplyMessage(msg);
- case 0 /* MessageType.Request */:
- return this._handleRequestMessage(msg);
- case 2 /* MessageType.SubscribeEvent */:
- return this._handleSubscribeEventMessage(msg);
- case 3 /* MessageType.Event */:
- return this._handleEventMessage(msg);
- case 4 /* MessageType.UnsubscribeEvent */:
- return this._handleUnsubscribeEventMessage(msg);
- }
- }
- _handleReplyMessage(replyMessage) {
- if (!this._pendingReplies[replyMessage.seq]) {
- console.warn('Got reply to unknown seq');
- return;
- }
- const reply = this._pendingReplies[replyMessage.seq];
- delete this._pendingReplies[replyMessage.seq];
- if (replyMessage.err) {
- let err = replyMessage.err;
- if (replyMessage.err.$isError) {
- err = new Error();
- err.name = replyMessage.err.name;
- err.message = replyMessage.err.message;
- err.stack = replyMessage.err.stack;
- }
- reply.reject(err);
- return;
- }
- reply.resolve(replyMessage.res);
- }
- _handleRequestMessage(requestMessage) {
- const req = requestMessage.req;
- const result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
- result.then((r) => {
- this._send(new ReplyMessage(this._workerId, req, r, undefined));
- }, (e) => {
- if (e.detail instanceof Error) {
- // Loading errors have a detail property that points to the actual error
- e.detail = transformErrorForSerialization(e.detail);
- }
- this._send(new ReplyMessage(this._workerId, req, undefined, transformErrorForSerialization(e)));
- });
- }
- _handleSubscribeEventMessage(msg) {
- const req = msg.req;
- const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => {
- this._send(new EventMessage(this._workerId, req, event));
- });
- this._pendingEvents.set(req, disposable);
- }
- _handleEventMessage(msg) {
- if (!this._pendingEmitters.has(msg.req)) {
- console.warn('Got event for unknown req');
- return;
- }
- this._pendingEmitters.get(msg.req).fire(msg.event);
- }
- _handleUnsubscribeEventMessage(msg) {
- if (!this._pendingEvents.has(msg.req)) {
- console.warn('Got unsubscribe for unknown req');
- return;
- }
- this._pendingEvents.get(msg.req).dispose();
- this._pendingEvents.delete(msg.req);
- }
- _send(msg) {
- const transfer = [];
- if (msg.type === 0 /* MessageType.Request */) {
- for (let i = 0; i < msg.args.length; i++) {
- if (msg.args[i] instanceof ArrayBuffer) {
- transfer.push(msg.args[i]);
- }
- }
- }
- else if (msg.type === 1 /* MessageType.Reply */) {
- if (msg.res instanceof ArrayBuffer) {
- transfer.push(msg.res);
- }
- }
- this._handler.sendMessage(msg, transfer);
- }
- }
- /**
- * Main thread side
- */
- export class SimpleWorkerClient extends Disposable {
- constructor(workerFactory, moduleId, host) {
- super();
- let lazyProxyReject = null;
- this._worker = this._register(workerFactory.create('vs/base/common/worker/simpleWorker', (msg) => {
- this._protocol.handleMessage(msg);
- }, (err) => {
- // in Firefox, web workers fail lazily :(
- // we will reject the proxy
- lazyProxyReject === null || lazyProxyReject === void 0 ? void 0 : lazyProxyReject(err);
- }));
- this._protocol = new SimpleWorkerProtocol({
- sendMessage: (msg, transfer) => {
- this._worker.postMessage(msg, transfer);
- },
- handleMessage: (method, args) => {
- if (typeof host[method] !== 'function') {
- return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
- }
- try {
- return Promise.resolve(host[method].apply(host, args));
- }
- catch (e) {
- return Promise.reject(e);
- }
- },
- handleEvent: (eventName, arg) => {
- if (propertyIsDynamicEvent(eventName)) {
- const event = host[eventName].call(host, arg);
- if (typeof event !== 'function') {
- throw new Error(`Missing dynamic event ${eventName} on main thread host.`);
- }
- return event;
- }
- if (propertyIsEvent(eventName)) {
- const event = host[eventName];
- if (typeof event !== 'function') {
- throw new Error(`Missing event ${eventName} on main thread host.`);
- }
- return event;
- }
- throw new Error(`Malformed event name ${eventName}`);
- }
- });
- this._protocol.setWorkerId(this._worker.getId());
- // Gather loader configuration
- let loaderConfiguration = null;
- if (typeof globals.require !== 'undefined' && typeof globals.require.getConfig === 'function') {
- // Get the configuration from the Monaco AMD Loader
- loaderConfiguration = globals.require.getConfig();
- }
- else if (typeof globals.requirejs !== 'undefined') {
- // Get the configuration from requirejs
- loaderConfiguration = globals.requirejs.s.contexts._.config;
- }
- const hostMethods = types.getAllMethodNames(host);
- // Send initialize message
- this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
- this._worker.getId(),
- JSON.parse(JSON.stringify(loaderConfiguration)),
- moduleId,
- hostMethods,
- ]);
- // Create proxy to loaded code
- const proxyMethodRequest = (method, args) => {
- return this._request(method, args);
- };
- const proxyListen = (eventName, arg) => {
- return this._protocol.listen(eventName, arg);
- };
- this._lazyProxy = new Promise((resolve, reject) => {
- lazyProxyReject = reject;
- this._onModuleLoaded.then((availableMethods) => {
- resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen));
- }, (e) => {
- reject(e);
- this._onError('Worker failed to load ' + moduleId, e);
- });
- });
- }
- getProxyObject() {
- return this._lazyProxy;
- }
- _request(method, args) {
- return new Promise((resolve, reject) => {
- this._onModuleLoaded.then(() => {
- this._protocol.sendMessage(method, args).then(resolve, reject);
- }, reject);
- });
- }
- _onError(message, error) {
- console.error(message);
- console.info(error);
- }
- }
- function propertyIsEvent(name) {
- // Assume a property is an event if it has a form of "onSomething"
- return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
- }
- function propertyIsDynamicEvent(name) {
- // Assume a property is a dynamic event (a method that returns an event) if it has a form of "onDynamicSomething"
- return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9));
- }
- function createProxyObject(methodNames, invoke, proxyListen) {
- const createProxyMethod = (method) => {
- return function () {
- const args = Array.prototype.slice.call(arguments, 0);
- return invoke(method, args);
- };
- };
- const createProxyDynamicEvent = (eventName) => {
- return function (arg) {
- return proxyListen(eventName, arg);
- };
- };
- const result = {};
- for (const methodName of methodNames) {
- if (propertyIsDynamicEvent(methodName)) {
- result[methodName] = createProxyDynamicEvent(methodName);
- continue;
- }
- if (propertyIsEvent(methodName)) {
- result[methodName] = proxyListen(methodName, undefined);
- continue;
- }
- result[methodName] = createProxyMethod(methodName);
- }
- return result;
- }
- /**
- * Worker side
- */
- export class SimpleWorkerServer {
- constructor(postMessage, requestHandlerFactory) {
- this._requestHandlerFactory = requestHandlerFactory;
- this._requestHandler = null;
- this._protocol = new SimpleWorkerProtocol({
- sendMessage: (msg, transfer) => {
- postMessage(msg, transfer);
- },
- handleMessage: (method, args) => this._handleMessage(method, args),
- handleEvent: (eventName, arg) => this._handleEvent(eventName, arg)
- });
- }
- onmessage(msg) {
- this._protocol.handleMessage(msg);
- }
- _handleMessage(method, args) {
- if (method === INITIALIZE) {
- return this.initialize(args[0], args[1], args[2], args[3]);
- }
- if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
- return Promise.reject(new Error('Missing requestHandler or method: ' + method));
- }
- try {
- return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args));
- }
- catch (e) {
- return Promise.reject(e);
- }
- }
- _handleEvent(eventName, arg) {
- if (!this._requestHandler) {
- throw new Error(`Missing requestHandler`);
- }
- if (propertyIsDynamicEvent(eventName)) {
- const event = this._requestHandler[eventName].call(this._requestHandler, arg);
- if (typeof event !== 'function') {
- throw new Error(`Missing dynamic event ${eventName} on request handler.`);
- }
- return event;
- }
- if (propertyIsEvent(eventName)) {
- const event = this._requestHandler[eventName];
- if (typeof event !== 'function') {
- throw new Error(`Missing event ${eventName} on request handler.`);
- }
- return event;
- }
- throw new Error(`Malformed event name ${eventName}`);
- }
- initialize(workerId, loaderConfig, moduleId, hostMethods) {
- this._protocol.setWorkerId(workerId);
- const proxyMethodRequest = (method, args) => {
- return this._protocol.sendMessage(method, args);
- };
- const proxyListen = (eventName, arg) => {
- return this._protocol.listen(eventName, arg);
- };
- const hostProxy = createProxyObject(hostMethods, proxyMethodRequest, proxyListen);
- if (this._requestHandlerFactory) {
- // static request handler
- this._requestHandler = this._requestHandlerFactory(hostProxy);
- return Promise.resolve(types.getAllMethodNames(this._requestHandler));
- }
- if (loaderConfig) {
- // Remove 'baseUrl', handling it is beyond scope for now
- if (typeof loaderConfig.baseUrl !== 'undefined') {
- delete loaderConfig['baseUrl'];
- }
- if (typeof loaderConfig.paths !== 'undefined') {
- if (typeof loaderConfig.paths.vs !== 'undefined') {
- delete loaderConfig.paths['vs'];
- }
- }
- if (typeof loaderConfig.trustedTypesPolicy !== undefined) {
- // don't use, it has been destroyed during serialize
- delete loaderConfig['trustedTypesPolicy'];
- }
- // Since this is in a web worker, enable catching errors
- loaderConfig.catchError = true;
- globals.require.config(loaderConfig);
- }
- return new Promise((resolve, reject) => {
- // Use the global require to be sure to get the global config
- // ESM-comment-begin
- // const req = (globals.require || require);
- // ESM-comment-end
- // ESM-uncomment-begin
- const req = globals.require;
- // ESM-uncomment-end
- req([moduleId], (module) => {
- this._requestHandler = module.create(hostProxy);
- if (!this._requestHandler) {
- reject(new Error(`No RequestHandler!`));
- return;
- }
- resolve(types.getAllMethodNames(this._requestHandler));
- }, reject);
- });
- }
- }
- /**
- * Called on the worker side
- */
- export function create(postMessage) {
- return new SimpleWorkerServer(postMessage, null);
- }
|