message_handler.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2021 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * Javascript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.MessageHandler = void 0;
  27. var _util = require("./util.js");
  28. const CallbackKind = {
  29. UNKNOWN: 0,
  30. DATA: 1,
  31. ERROR: 2
  32. };
  33. const StreamKind = {
  34. UNKNOWN: 0,
  35. CANCEL: 1,
  36. CANCEL_COMPLETE: 2,
  37. CLOSE: 3,
  38. ENQUEUE: 4,
  39. ERROR: 5,
  40. PULL: 6,
  41. PULL_COMPLETE: 7,
  42. START_COMPLETE: 8
  43. };
  44. function wrapReason(reason) {
  45. if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) {
  46. (0, _util.warn)('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
  47. return reason;
  48. }
  49. switch (reason.name) {
  50. case "AbortException":
  51. return new _util.AbortException(reason.message);
  52. case "MissingPDFException":
  53. return new _util.MissingPDFException(reason.message);
  54. case "PasswordException":
  55. return new _util.PasswordException(reason.message, reason.code);
  56. case "UnexpectedResponseException":
  57. return new _util.UnexpectedResponseException(reason.message, reason.status);
  58. case "UnknownErrorException":
  59. return new _util.UnknownErrorException(reason.message, reason.details);
  60. default:
  61. return new _util.UnknownErrorException(reason.message, reason.toString());
  62. }
  63. }
  64. class MessageHandler {
  65. constructor(sourceName, targetName, comObj) {
  66. this.sourceName = sourceName;
  67. this.targetName = targetName;
  68. this.comObj = comObj;
  69. this.callbackId = 1;
  70. this.streamId = 1;
  71. this.postMessageTransfers = true;
  72. this.streamSinks = Object.create(null);
  73. this.streamControllers = Object.create(null);
  74. this.callbackCapabilities = Object.create(null);
  75. this.actionHandler = Object.create(null);
  76. this._onComObjOnMessage = event => {
  77. const data = event.data;
  78. if (data.targetName !== this.sourceName) {
  79. return;
  80. }
  81. if (data.stream) {
  82. this._processStreamMessage(data);
  83. return;
  84. }
  85. if (data.callback) {
  86. const callbackId = data.callbackId;
  87. const capability = this.callbackCapabilities[callbackId];
  88. if (!capability) {
  89. throw new Error(`Cannot resolve callback ${callbackId}`);
  90. }
  91. delete this.callbackCapabilities[callbackId];
  92. if (data.callback === CallbackKind.DATA) {
  93. capability.resolve(data.data);
  94. } else if (data.callback === CallbackKind.ERROR) {
  95. capability.reject(wrapReason(data.reason));
  96. } else {
  97. throw new Error("Unexpected callback case");
  98. }
  99. return;
  100. }
  101. const action = this.actionHandler[data.action];
  102. if (!action) {
  103. throw new Error(`Unknown action from worker: ${data.action}`);
  104. }
  105. if (data.callbackId) {
  106. const cbSourceName = this.sourceName;
  107. const cbTargetName = data.sourceName;
  108. new Promise(function (resolve) {
  109. resolve(action(data.data));
  110. }).then(function (result) {
  111. comObj.postMessage({
  112. sourceName: cbSourceName,
  113. targetName: cbTargetName,
  114. callback: CallbackKind.DATA,
  115. callbackId: data.callbackId,
  116. data: result
  117. });
  118. }, function (reason) {
  119. comObj.postMessage({
  120. sourceName: cbSourceName,
  121. targetName: cbTargetName,
  122. callback: CallbackKind.ERROR,
  123. callbackId: data.callbackId,
  124. reason: wrapReason(reason)
  125. });
  126. });
  127. return;
  128. }
  129. if (data.streamId) {
  130. this._createStreamSink(data);
  131. return;
  132. }
  133. action(data.data);
  134. };
  135. comObj.addEventListener("message", this._onComObjOnMessage);
  136. }
  137. on(actionName, handler) {
  138. const ah = this.actionHandler;
  139. if (ah[actionName]) {
  140. throw new Error(`There is already an actionName called "${actionName}"`);
  141. }
  142. ah[actionName] = handler;
  143. }
  144. send(actionName, data, transfers) {
  145. this._postMessage({
  146. sourceName: this.sourceName,
  147. targetName: this.targetName,
  148. action: actionName,
  149. data
  150. }, transfers);
  151. }
  152. sendWithPromise(actionName, data, transfers) {
  153. const callbackId = this.callbackId++;
  154. const capability = (0, _util.createPromiseCapability)();
  155. this.callbackCapabilities[callbackId] = capability;
  156. try {
  157. this._postMessage({
  158. sourceName: this.sourceName,
  159. targetName: this.targetName,
  160. action: actionName,
  161. callbackId,
  162. data
  163. }, transfers);
  164. } catch (ex) {
  165. capability.reject(ex);
  166. }
  167. return capability.promise;
  168. }
  169. sendWithStream(actionName, data, queueingStrategy, transfers) {
  170. const streamId = this.streamId++,
  171. sourceName = this.sourceName,
  172. targetName = this.targetName,
  173. comObj = this.comObj;
  174. return new ReadableStream({
  175. start: controller => {
  176. const startCapability = (0, _util.createPromiseCapability)();
  177. this.streamControllers[streamId] = {
  178. controller,
  179. startCall: startCapability,
  180. pullCall: null,
  181. cancelCall: null,
  182. isClosed: false
  183. };
  184. this._postMessage({
  185. sourceName,
  186. targetName,
  187. action: actionName,
  188. streamId,
  189. data,
  190. desiredSize: controller.desiredSize
  191. }, transfers);
  192. return startCapability.promise;
  193. },
  194. pull: controller => {
  195. const pullCapability = (0, _util.createPromiseCapability)();
  196. this.streamControllers[streamId].pullCall = pullCapability;
  197. comObj.postMessage({
  198. sourceName,
  199. targetName,
  200. stream: StreamKind.PULL,
  201. streamId,
  202. desiredSize: controller.desiredSize
  203. });
  204. return pullCapability.promise;
  205. },
  206. cancel: reason => {
  207. (0, _util.assert)(reason instanceof Error, "cancel must have a valid reason");
  208. const cancelCapability = (0, _util.createPromiseCapability)();
  209. this.streamControllers[streamId].cancelCall = cancelCapability;
  210. this.streamControllers[streamId].isClosed = true;
  211. comObj.postMessage({
  212. sourceName,
  213. targetName,
  214. stream: StreamKind.CANCEL,
  215. streamId,
  216. reason: wrapReason(reason)
  217. });
  218. return cancelCapability.promise;
  219. }
  220. }, queueingStrategy);
  221. }
  222. _createStreamSink(data) {
  223. const streamId = data.streamId,
  224. sourceName = this.sourceName,
  225. targetName = data.sourceName,
  226. comObj = this.comObj;
  227. const self = this,
  228. action = this.actionHandler[data.action];
  229. const streamSink = {
  230. enqueue(chunk, size = 1, transfers) {
  231. if (this.isCancelled) {
  232. return;
  233. }
  234. const lastDesiredSize = this.desiredSize;
  235. this.desiredSize -= size;
  236. if (lastDesiredSize > 0 && this.desiredSize <= 0) {
  237. this.sinkCapability = (0, _util.createPromiseCapability)();
  238. this.ready = this.sinkCapability.promise;
  239. }
  240. self._postMessage({
  241. sourceName,
  242. targetName,
  243. stream: StreamKind.ENQUEUE,
  244. streamId,
  245. chunk
  246. }, transfers);
  247. },
  248. close() {
  249. if (this.isCancelled) {
  250. return;
  251. }
  252. this.isCancelled = true;
  253. comObj.postMessage({
  254. sourceName,
  255. targetName,
  256. stream: StreamKind.CLOSE,
  257. streamId
  258. });
  259. delete self.streamSinks[streamId];
  260. },
  261. error(reason) {
  262. (0, _util.assert)(reason instanceof Error, "error must have a valid reason");
  263. if (this.isCancelled) {
  264. return;
  265. }
  266. this.isCancelled = true;
  267. comObj.postMessage({
  268. sourceName,
  269. targetName,
  270. stream: StreamKind.ERROR,
  271. streamId,
  272. reason: wrapReason(reason)
  273. });
  274. },
  275. sinkCapability: (0, _util.createPromiseCapability)(),
  276. onPull: null,
  277. onCancel: null,
  278. isCancelled: false,
  279. desiredSize: data.desiredSize,
  280. ready: null
  281. };
  282. streamSink.sinkCapability.resolve();
  283. streamSink.ready = streamSink.sinkCapability.promise;
  284. this.streamSinks[streamId] = streamSink;
  285. new Promise(function (resolve) {
  286. resolve(action(data.data, streamSink));
  287. }).then(function () {
  288. comObj.postMessage({
  289. sourceName,
  290. targetName,
  291. stream: StreamKind.START_COMPLETE,
  292. streamId,
  293. success: true
  294. });
  295. }, function (reason) {
  296. comObj.postMessage({
  297. sourceName,
  298. targetName,
  299. stream: StreamKind.START_COMPLETE,
  300. streamId,
  301. reason: wrapReason(reason)
  302. });
  303. });
  304. }
  305. _processStreamMessage(data) {
  306. const streamId = data.streamId,
  307. sourceName = this.sourceName,
  308. targetName = data.sourceName,
  309. comObj = this.comObj;
  310. const streamController = this.streamControllers[streamId],
  311. streamSink = this.streamSinks[streamId];
  312. switch (data.stream) {
  313. case StreamKind.START_COMPLETE:
  314. if (data.success) {
  315. streamController.startCall.resolve();
  316. } else {
  317. streamController.startCall.reject(wrapReason(data.reason));
  318. }
  319. break;
  320. case StreamKind.PULL_COMPLETE:
  321. if (data.success) {
  322. streamController.pullCall.resolve();
  323. } else {
  324. streamController.pullCall.reject(wrapReason(data.reason));
  325. }
  326. break;
  327. case StreamKind.PULL:
  328. if (!streamSink) {
  329. comObj.postMessage({
  330. sourceName,
  331. targetName,
  332. stream: StreamKind.PULL_COMPLETE,
  333. streamId,
  334. success: true
  335. });
  336. break;
  337. }
  338. if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
  339. streamSink.sinkCapability.resolve();
  340. }
  341. streamSink.desiredSize = data.desiredSize;
  342. new Promise(function (resolve) {
  343. resolve(streamSink.onPull && streamSink.onPull());
  344. }).then(function () {
  345. comObj.postMessage({
  346. sourceName,
  347. targetName,
  348. stream: StreamKind.PULL_COMPLETE,
  349. streamId,
  350. success: true
  351. });
  352. }, function (reason) {
  353. comObj.postMessage({
  354. sourceName,
  355. targetName,
  356. stream: StreamKind.PULL_COMPLETE,
  357. streamId,
  358. reason: wrapReason(reason)
  359. });
  360. });
  361. break;
  362. case StreamKind.ENQUEUE:
  363. (0, _util.assert)(streamController, "enqueue should have stream controller");
  364. if (streamController.isClosed) {
  365. break;
  366. }
  367. streamController.controller.enqueue(data.chunk);
  368. break;
  369. case StreamKind.CLOSE:
  370. (0, _util.assert)(streamController, "close should have stream controller");
  371. if (streamController.isClosed) {
  372. break;
  373. }
  374. streamController.isClosed = true;
  375. streamController.controller.close();
  376. this._deleteStreamController(streamController, streamId);
  377. break;
  378. case StreamKind.ERROR:
  379. (0, _util.assert)(streamController, "error should have stream controller");
  380. streamController.controller.error(wrapReason(data.reason));
  381. this._deleteStreamController(streamController, streamId);
  382. break;
  383. case StreamKind.CANCEL_COMPLETE:
  384. if (data.success) {
  385. streamController.cancelCall.resolve();
  386. } else {
  387. streamController.cancelCall.reject(wrapReason(data.reason));
  388. }
  389. this._deleteStreamController(streamController, streamId);
  390. break;
  391. case StreamKind.CANCEL:
  392. if (!streamSink) {
  393. break;
  394. }
  395. new Promise(function (resolve) {
  396. resolve(streamSink.onCancel && streamSink.onCancel(wrapReason(data.reason)));
  397. }).then(function () {
  398. comObj.postMessage({
  399. sourceName,
  400. targetName,
  401. stream: StreamKind.CANCEL_COMPLETE,
  402. streamId,
  403. success: true
  404. });
  405. }, function (reason) {
  406. comObj.postMessage({
  407. sourceName,
  408. targetName,
  409. stream: StreamKind.CANCEL_COMPLETE,
  410. streamId,
  411. reason: wrapReason(reason)
  412. });
  413. });
  414. streamSink.sinkCapability.reject(wrapReason(data.reason));
  415. streamSink.isCancelled = true;
  416. delete this.streamSinks[streamId];
  417. break;
  418. default:
  419. throw new Error("Unexpected stream case");
  420. }
  421. }
  422. async _deleteStreamController(streamController, streamId) {
  423. await Promise.allSettled([streamController.startCall && streamController.startCall.promise, streamController.pullCall && streamController.pullCall.promise, streamController.cancelCall && streamController.cancelCall.promise]);
  424. delete this.streamControllers[streamId];
  425. }
  426. _postMessage(message, transfers) {
  427. if (transfers && this.postMessageTransfers) {
  428. this.comObj.postMessage(message, transfers);
  429. } else {
  430. this.comObj.postMessage(message);
  431. }
  432. }
  433. destroy() {
  434. this.comObj.removeEventListener("message", this._onComObjOnMessage);
  435. }
  436. }
  437. exports.MessageHandler = MessageHandler;