message_handler.js 13 KB

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