26848da0ac4a505b4b5a89614da9749f563118cfefc3ae4ddb8a018afe9c73a29f2504c26b3b10c594ae52fa068a8c598ab99eaa2339a973e9ff999010b71e 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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. import { transformErrorForSerialization } from '../errors.js';
  6. import { Emitter } from '../event.js';
  7. import { Disposable } from '../lifecycle.js';
  8. import { globals, isWeb } from '../platform.js';
  9. import * as types from '../types.js';
  10. import * as strings from '../strings.js';
  11. const INITIALIZE = '$initialize';
  12. let webWorkerWarningLogged = false;
  13. export function logOnceWebWorkerWarning(err) {
  14. if (!isWeb) {
  15. // running tests
  16. return;
  17. }
  18. if (!webWorkerWarningLogged) {
  19. webWorkerWarningLogged = true;
  20. 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');
  21. }
  22. console.warn(err.message);
  23. }
  24. class RequestMessage {
  25. constructor(vsWorker, req, method, args) {
  26. this.vsWorker = vsWorker;
  27. this.req = req;
  28. this.method = method;
  29. this.args = args;
  30. this.type = 0 /* MessageType.Request */;
  31. }
  32. }
  33. class ReplyMessage {
  34. constructor(vsWorker, seq, res, err) {
  35. this.vsWorker = vsWorker;
  36. this.seq = seq;
  37. this.res = res;
  38. this.err = err;
  39. this.type = 1 /* MessageType.Reply */;
  40. }
  41. }
  42. class SubscribeEventMessage {
  43. constructor(vsWorker, req, eventName, arg) {
  44. this.vsWorker = vsWorker;
  45. this.req = req;
  46. this.eventName = eventName;
  47. this.arg = arg;
  48. this.type = 2 /* MessageType.SubscribeEvent */;
  49. }
  50. }
  51. class EventMessage {
  52. constructor(vsWorker, req, event) {
  53. this.vsWorker = vsWorker;
  54. this.req = req;
  55. this.event = event;
  56. this.type = 3 /* MessageType.Event */;
  57. }
  58. }
  59. class UnsubscribeEventMessage {
  60. constructor(vsWorker, req) {
  61. this.vsWorker = vsWorker;
  62. this.req = req;
  63. this.type = 4 /* MessageType.UnsubscribeEvent */;
  64. }
  65. }
  66. class SimpleWorkerProtocol {
  67. constructor(handler) {
  68. this._workerId = -1;
  69. this._handler = handler;
  70. this._lastSentReq = 0;
  71. this._pendingReplies = Object.create(null);
  72. this._pendingEmitters = new Map();
  73. this._pendingEvents = new Map();
  74. }
  75. setWorkerId(workerId) {
  76. this._workerId = workerId;
  77. }
  78. sendMessage(method, args) {
  79. const req = String(++this._lastSentReq);
  80. return new Promise((resolve, reject) => {
  81. this._pendingReplies[req] = {
  82. resolve: resolve,
  83. reject: reject
  84. };
  85. this._send(new RequestMessage(this._workerId, req, method, args));
  86. });
  87. }
  88. listen(eventName, arg) {
  89. let req = null;
  90. const emitter = new Emitter({
  91. onFirstListenerAdd: () => {
  92. req = String(++this._lastSentReq);
  93. this._pendingEmitters.set(req, emitter);
  94. this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg));
  95. },
  96. onLastListenerRemove: () => {
  97. this._pendingEmitters.delete(req);
  98. this._send(new UnsubscribeEventMessage(this._workerId, req));
  99. req = null;
  100. }
  101. });
  102. return emitter.event;
  103. }
  104. handleMessage(message) {
  105. if (!message || !message.vsWorker) {
  106. return;
  107. }
  108. if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
  109. return;
  110. }
  111. this._handleMessage(message);
  112. }
  113. _handleMessage(msg) {
  114. switch (msg.type) {
  115. case 1 /* MessageType.Reply */:
  116. return this._handleReplyMessage(msg);
  117. case 0 /* MessageType.Request */:
  118. return this._handleRequestMessage(msg);
  119. case 2 /* MessageType.SubscribeEvent */:
  120. return this._handleSubscribeEventMessage(msg);
  121. case 3 /* MessageType.Event */:
  122. return this._handleEventMessage(msg);
  123. case 4 /* MessageType.UnsubscribeEvent */:
  124. return this._handleUnsubscribeEventMessage(msg);
  125. }
  126. }
  127. _handleReplyMessage(replyMessage) {
  128. if (!this._pendingReplies[replyMessage.seq]) {
  129. console.warn('Got reply to unknown seq');
  130. return;
  131. }
  132. const reply = this._pendingReplies[replyMessage.seq];
  133. delete this._pendingReplies[replyMessage.seq];
  134. if (replyMessage.err) {
  135. let err = replyMessage.err;
  136. if (replyMessage.err.$isError) {
  137. err = new Error();
  138. err.name = replyMessage.err.name;
  139. err.message = replyMessage.err.message;
  140. err.stack = replyMessage.err.stack;
  141. }
  142. reply.reject(err);
  143. return;
  144. }
  145. reply.resolve(replyMessage.res);
  146. }
  147. _handleRequestMessage(requestMessage) {
  148. const req = requestMessage.req;
  149. const result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
  150. result.then((r) => {
  151. this._send(new ReplyMessage(this._workerId, req, r, undefined));
  152. }, (e) => {
  153. if (e.detail instanceof Error) {
  154. // Loading errors have a detail property that points to the actual error
  155. e.detail = transformErrorForSerialization(e.detail);
  156. }
  157. this._send(new ReplyMessage(this._workerId, req, undefined, transformErrorForSerialization(e)));
  158. });
  159. }
  160. _handleSubscribeEventMessage(msg) {
  161. const req = msg.req;
  162. const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => {
  163. this._send(new EventMessage(this._workerId, req, event));
  164. });
  165. this._pendingEvents.set(req, disposable);
  166. }
  167. _handleEventMessage(msg) {
  168. if (!this._pendingEmitters.has(msg.req)) {
  169. console.warn('Got event for unknown req');
  170. return;
  171. }
  172. this._pendingEmitters.get(msg.req).fire(msg.event);
  173. }
  174. _handleUnsubscribeEventMessage(msg) {
  175. if (!this._pendingEvents.has(msg.req)) {
  176. console.warn('Got unsubscribe for unknown req');
  177. return;
  178. }
  179. this._pendingEvents.get(msg.req).dispose();
  180. this._pendingEvents.delete(msg.req);
  181. }
  182. _send(msg) {
  183. const transfer = [];
  184. if (msg.type === 0 /* MessageType.Request */) {
  185. for (let i = 0; i < msg.args.length; i++) {
  186. if (msg.args[i] instanceof ArrayBuffer) {
  187. transfer.push(msg.args[i]);
  188. }
  189. }
  190. }
  191. else if (msg.type === 1 /* MessageType.Reply */) {
  192. if (msg.res instanceof ArrayBuffer) {
  193. transfer.push(msg.res);
  194. }
  195. }
  196. this._handler.sendMessage(msg, transfer);
  197. }
  198. }
  199. /**
  200. * Main thread side
  201. */
  202. export class SimpleWorkerClient extends Disposable {
  203. constructor(workerFactory, moduleId, host) {
  204. super();
  205. let lazyProxyReject = null;
  206. this._worker = this._register(workerFactory.create('vs/base/common/worker/simpleWorker', (msg) => {
  207. this._protocol.handleMessage(msg);
  208. }, (err) => {
  209. // in Firefox, web workers fail lazily :(
  210. // we will reject the proxy
  211. lazyProxyReject === null || lazyProxyReject === void 0 ? void 0 : lazyProxyReject(err);
  212. }));
  213. this._protocol = new SimpleWorkerProtocol({
  214. sendMessage: (msg, transfer) => {
  215. this._worker.postMessage(msg, transfer);
  216. },
  217. handleMessage: (method, args) => {
  218. if (typeof host[method] !== 'function') {
  219. return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
  220. }
  221. try {
  222. return Promise.resolve(host[method].apply(host, args));
  223. }
  224. catch (e) {
  225. return Promise.reject(e);
  226. }
  227. },
  228. handleEvent: (eventName, arg) => {
  229. if (propertyIsDynamicEvent(eventName)) {
  230. const event = host[eventName].call(host, arg);
  231. if (typeof event !== 'function') {
  232. throw new Error(`Missing dynamic event ${eventName} on main thread host.`);
  233. }
  234. return event;
  235. }
  236. if (propertyIsEvent(eventName)) {
  237. const event = host[eventName];
  238. if (typeof event !== 'function') {
  239. throw new Error(`Missing event ${eventName} on main thread host.`);
  240. }
  241. return event;
  242. }
  243. throw new Error(`Malformed event name ${eventName}`);
  244. }
  245. });
  246. this._protocol.setWorkerId(this._worker.getId());
  247. // Gather loader configuration
  248. let loaderConfiguration = null;
  249. if (typeof globals.require !== 'undefined' && typeof globals.require.getConfig === 'function') {
  250. // Get the configuration from the Monaco AMD Loader
  251. loaderConfiguration = globals.require.getConfig();
  252. }
  253. else if (typeof globals.requirejs !== 'undefined') {
  254. // Get the configuration from requirejs
  255. loaderConfiguration = globals.requirejs.s.contexts._.config;
  256. }
  257. const hostMethods = types.getAllMethodNames(host);
  258. // Send initialize message
  259. this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
  260. this._worker.getId(),
  261. JSON.parse(JSON.stringify(loaderConfiguration)),
  262. moduleId,
  263. hostMethods,
  264. ]);
  265. // Create proxy to loaded code
  266. const proxyMethodRequest = (method, args) => {
  267. return this._request(method, args);
  268. };
  269. const proxyListen = (eventName, arg) => {
  270. return this._protocol.listen(eventName, arg);
  271. };
  272. this._lazyProxy = new Promise((resolve, reject) => {
  273. lazyProxyReject = reject;
  274. this._onModuleLoaded.then((availableMethods) => {
  275. resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen));
  276. }, (e) => {
  277. reject(e);
  278. this._onError('Worker failed to load ' + moduleId, e);
  279. });
  280. });
  281. }
  282. getProxyObject() {
  283. return this._lazyProxy;
  284. }
  285. _request(method, args) {
  286. return new Promise((resolve, reject) => {
  287. this._onModuleLoaded.then(() => {
  288. this._protocol.sendMessage(method, args).then(resolve, reject);
  289. }, reject);
  290. });
  291. }
  292. _onError(message, error) {
  293. console.error(message);
  294. console.info(error);
  295. }
  296. }
  297. function propertyIsEvent(name) {
  298. // Assume a property is an event if it has a form of "onSomething"
  299. return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
  300. }
  301. function propertyIsDynamicEvent(name) {
  302. // Assume a property is a dynamic event (a method that returns an event) if it has a form of "onDynamicSomething"
  303. return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9));
  304. }
  305. function createProxyObject(methodNames, invoke, proxyListen) {
  306. const createProxyMethod = (method) => {
  307. return function () {
  308. const args = Array.prototype.slice.call(arguments, 0);
  309. return invoke(method, args);
  310. };
  311. };
  312. const createProxyDynamicEvent = (eventName) => {
  313. return function (arg) {
  314. return proxyListen(eventName, arg);
  315. };
  316. };
  317. const result = {};
  318. for (const methodName of methodNames) {
  319. if (propertyIsDynamicEvent(methodName)) {
  320. result[methodName] = createProxyDynamicEvent(methodName);
  321. continue;
  322. }
  323. if (propertyIsEvent(methodName)) {
  324. result[methodName] = proxyListen(methodName, undefined);
  325. continue;
  326. }
  327. result[methodName] = createProxyMethod(methodName);
  328. }
  329. return result;
  330. }
  331. /**
  332. * Worker side
  333. */
  334. export class SimpleWorkerServer {
  335. constructor(postMessage, requestHandlerFactory) {
  336. this._requestHandlerFactory = requestHandlerFactory;
  337. this._requestHandler = null;
  338. this._protocol = new SimpleWorkerProtocol({
  339. sendMessage: (msg, transfer) => {
  340. postMessage(msg, transfer);
  341. },
  342. handleMessage: (method, args) => this._handleMessage(method, args),
  343. handleEvent: (eventName, arg) => this._handleEvent(eventName, arg)
  344. });
  345. }
  346. onmessage(msg) {
  347. this._protocol.handleMessage(msg);
  348. }
  349. _handleMessage(method, args) {
  350. if (method === INITIALIZE) {
  351. return this.initialize(args[0], args[1], args[2], args[3]);
  352. }
  353. if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
  354. return Promise.reject(new Error('Missing requestHandler or method: ' + method));
  355. }
  356. try {
  357. return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args));
  358. }
  359. catch (e) {
  360. return Promise.reject(e);
  361. }
  362. }
  363. _handleEvent(eventName, arg) {
  364. if (!this._requestHandler) {
  365. throw new Error(`Missing requestHandler`);
  366. }
  367. if (propertyIsDynamicEvent(eventName)) {
  368. const event = this._requestHandler[eventName].call(this._requestHandler, arg);
  369. if (typeof event !== 'function') {
  370. throw new Error(`Missing dynamic event ${eventName} on request handler.`);
  371. }
  372. return event;
  373. }
  374. if (propertyIsEvent(eventName)) {
  375. const event = this._requestHandler[eventName];
  376. if (typeof event !== 'function') {
  377. throw new Error(`Missing event ${eventName} on request handler.`);
  378. }
  379. return event;
  380. }
  381. throw new Error(`Malformed event name ${eventName}`);
  382. }
  383. initialize(workerId, loaderConfig, moduleId, hostMethods) {
  384. this._protocol.setWorkerId(workerId);
  385. const proxyMethodRequest = (method, args) => {
  386. return this._protocol.sendMessage(method, args);
  387. };
  388. const proxyListen = (eventName, arg) => {
  389. return this._protocol.listen(eventName, arg);
  390. };
  391. const hostProxy = createProxyObject(hostMethods, proxyMethodRequest, proxyListen);
  392. if (this._requestHandlerFactory) {
  393. // static request handler
  394. this._requestHandler = this._requestHandlerFactory(hostProxy);
  395. return Promise.resolve(types.getAllMethodNames(this._requestHandler));
  396. }
  397. if (loaderConfig) {
  398. // Remove 'baseUrl', handling it is beyond scope for now
  399. if (typeof loaderConfig.baseUrl !== 'undefined') {
  400. delete loaderConfig['baseUrl'];
  401. }
  402. if (typeof loaderConfig.paths !== 'undefined') {
  403. if (typeof loaderConfig.paths.vs !== 'undefined') {
  404. delete loaderConfig.paths['vs'];
  405. }
  406. }
  407. if (typeof loaderConfig.trustedTypesPolicy !== undefined) {
  408. // don't use, it has been destroyed during serialize
  409. delete loaderConfig['trustedTypesPolicy'];
  410. }
  411. // Since this is in a web worker, enable catching errors
  412. loaderConfig.catchError = true;
  413. globals.require.config(loaderConfig);
  414. }
  415. return new Promise((resolve, reject) => {
  416. // Use the global require to be sure to get the global config
  417. // ESM-comment-begin
  418. // const req = (globals.require || require);
  419. // ESM-comment-end
  420. // ESM-uncomment-begin
  421. const req = globals.require;
  422. // ESM-uncomment-end
  423. req([moduleId], (module) => {
  424. this._requestHandler = module.create(hostProxy);
  425. if (!this._requestHandler) {
  426. reject(new Error(`No RequestHandler!`));
  427. return;
  428. }
  429. resolve(types.getAllMethodNames(this._requestHandler));
  430. }, reject);
  431. });
  432. }
  433. }
  434. /**
  435. * Called on the worker side
  436. */
  437. export function create(postMessage) {
  438. return new SimpleWorkerServer(postMessage, null);
  439. }