You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
14 KiB
516 lines
14 KiB
5 months ago
|
/**
|
||
|
* @licstart The following is the entire license notice for the
|
||
|
* Javascript code in this page
|
||
|
*
|
||
|
* Copyright 2020 Mozilla Foundation
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
* @licend The above is the entire license notice for the
|
||
|
* Javascript code in this page
|
||
|
*/
|
||
|
"use strict";
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.MessageHandler = void 0;
|
||
|
|
||
|
var _util = require("./util.js");
|
||
|
|
||
|
const CallbackKind = {
|
||
|
UNKNOWN: 0,
|
||
|
DATA: 1,
|
||
|
ERROR: 2
|
||
|
};
|
||
|
const StreamKind = {
|
||
|
UNKNOWN: 0,
|
||
|
CANCEL: 1,
|
||
|
CANCEL_COMPLETE: 2,
|
||
|
CLOSE: 3,
|
||
|
ENQUEUE: 4,
|
||
|
ERROR: 5,
|
||
|
PULL: 6,
|
||
|
PULL_COMPLETE: 7,
|
||
|
START_COMPLETE: 8
|
||
|
};
|
||
|
|
||
|
function wrapReason(reason) {
|
||
|
if (typeof reason !== "object" || reason === null) {
|
||
|
return reason;
|
||
|
}
|
||
|
|
||
|
switch (reason.name) {
|
||
|
case "AbortException":
|
||
|
return new _util.AbortException(reason.message);
|
||
|
|
||
|
case "MissingPDFException":
|
||
|
return new _util.MissingPDFException(reason.message);
|
||
|
|
||
|
case "UnexpectedResponseException":
|
||
|
return new _util.UnexpectedResponseException(reason.message, reason.status);
|
||
|
|
||
|
case "UnknownErrorException":
|
||
|
return new _util.UnknownErrorException(reason.message, reason.details);
|
||
|
|
||
|
default:
|
||
|
return new _util.UnknownErrorException(reason.message, reason.toString());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class MessageHandler {
|
||
|
constructor(sourceName, targetName, comObj) {
|
||
|
this.sourceName = sourceName;
|
||
|
this.targetName = targetName;
|
||
|
this.comObj = comObj;
|
||
|
this.callbackId = 1;
|
||
|
this.streamId = 1;
|
||
|
this.postMessageTransfers = true;
|
||
|
this.streamSinks = Object.create(null);
|
||
|
this.streamControllers = Object.create(null);
|
||
|
this.callbackCapabilities = Object.create(null);
|
||
|
this.actionHandler = Object.create(null);
|
||
|
|
||
|
this._onComObjOnMessage = event => {
|
||
|
const data = event.data;
|
||
|
|
||
|
if (data.targetName !== this.sourceName) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (data.stream) {
|
||
|
this._processStreamMessage(data);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (data.callback) {
|
||
|
const callbackId = data.callbackId;
|
||
|
const capability = this.callbackCapabilities[callbackId];
|
||
|
|
||
|
if (!capability) {
|
||
|
throw new Error(`Cannot resolve callback ${callbackId}`);
|
||
|
}
|
||
|
|
||
|
delete this.callbackCapabilities[callbackId];
|
||
|
|
||
|
if (data.callback === CallbackKind.DATA) {
|
||
|
capability.resolve(data.data);
|
||
|
} else if (data.callback === CallbackKind.ERROR) {
|
||
|
capability.reject(wrapReason(data.reason));
|
||
|
} else {
|
||
|
throw new Error("Unexpected callback case");
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const action = this.actionHandler[data.action];
|
||
|
|
||
|
if (!action) {
|
||
|
throw new Error(`Unknown action from worker: ${data.action}`);
|
||
|
}
|
||
|
|
||
|
if (data.callbackId) {
|
||
|
const cbSourceName = this.sourceName;
|
||
|
const cbTargetName = data.sourceName;
|
||
|
new Promise(function (resolve) {
|
||
|
resolve(action(data.data));
|
||
|
}).then(function (result) {
|
||
|
comObj.postMessage({
|
||
|
sourceName: cbSourceName,
|
||
|
targetName: cbTargetName,
|
||
|
callback: CallbackKind.DATA,
|
||
|
callbackId: data.callbackId,
|
||
|
data: result
|
||
|
});
|
||
|
}, function (reason) {
|
||
|
comObj.postMessage({
|
||
|
sourceName: cbSourceName,
|
||
|
targetName: cbTargetName,
|
||
|
callback: CallbackKind.ERROR,
|
||
|
callbackId: data.callbackId,
|
||
|
reason: wrapReason(reason)
|
||
|
});
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (data.streamId) {
|
||
|
this._createStreamSink(data);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
action(data.data);
|
||
|
};
|
||
|
|
||
|
comObj.addEventListener("message", this._onComObjOnMessage);
|
||
|
}
|
||
|
|
||
|
on(actionName, handler) {
|
||
|
const ah = this.actionHandler;
|
||
|
|
||
|
if (ah[actionName]) {
|
||
|
throw new Error(`There is already an actionName called "${actionName}"`);
|
||
|
}
|
||
|
|
||
|
ah[actionName] = handler;
|
||
|
}
|
||
|
|
||
|
send(actionName, data, transfers) {
|
||
|
this._postMessage({
|
||
|
sourceName: this.sourceName,
|
||
|
targetName: this.targetName,
|
||
|
action: actionName,
|
||
|
data
|
||
|
}, transfers);
|
||
|
}
|
||
|
|
||
|
sendWithPromise(actionName, data, transfers) {
|
||
|
const callbackId = this.callbackId++;
|
||
|
const capability = (0, _util.createPromiseCapability)();
|
||
|
this.callbackCapabilities[callbackId] = capability;
|
||
|
|
||
|
try {
|
||
|
this._postMessage({
|
||
|
sourceName: this.sourceName,
|
||
|
targetName: this.targetName,
|
||
|
action: actionName,
|
||
|
callbackId,
|
||
|
data
|
||
|
}, transfers);
|
||
|
} catch (ex) {
|
||
|
capability.reject(ex);
|
||
|
}
|
||
|
|
||
|
return capability.promise;
|
||
|
}
|
||
|
|
||
|
sendWithStream(actionName, data, queueingStrategy, transfers) {
|
||
|
const streamId = this.streamId++;
|
||
|
const sourceName = this.sourceName;
|
||
|
const targetName = this.targetName;
|
||
|
const comObj = this.comObj;
|
||
|
return new ReadableStream({
|
||
|
start: controller => {
|
||
|
const startCapability = (0, _util.createPromiseCapability)();
|
||
|
this.streamControllers[streamId] = {
|
||
|
controller,
|
||
|
startCall: startCapability,
|
||
|
pullCall: null,
|
||
|
cancelCall: null,
|
||
|
isClosed: false
|
||
|
};
|
||
|
|
||
|
this._postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
action: actionName,
|
||
|
streamId,
|
||
|
data,
|
||
|
desiredSize: controller.desiredSize
|
||
|
}, transfers);
|
||
|
|
||
|
return startCapability.promise;
|
||
|
},
|
||
|
pull: controller => {
|
||
|
const pullCapability = (0, _util.createPromiseCapability)();
|
||
|
this.streamControllers[streamId].pullCall = pullCapability;
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.PULL,
|
||
|
streamId,
|
||
|
desiredSize: controller.desiredSize
|
||
|
});
|
||
|
return pullCapability.promise;
|
||
|
},
|
||
|
cancel: reason => {
|
||
|
(0, _util.assert)(reason instanceof Error, "cancel must have a valid reason");
|
||
|
const cancelCapability = (0, _util.createPromiseCapability)();
|
||
|
this.streamControllers[streamId].cancelCall = cancelCapability;
|
||
|
this.streamControllers[streamId].isClosed = true;
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.CANCEL,
|
||
|
streamId,
|
||
|
reason: wrapReason(reason)
|
||
|
});
|
||
|
return cancelCapability.promise;
|
||
|
}
|
||
|
}, queueingStrategy);
|
||
|
}
|
||
|
|
||
|
_createStreamSink(data) {
|
||
|
const self = this;
|
||
|
const action = this.actionHandler[data.action];
|
||
|
const streamId = data.streamId;
|
||
|
const sourceName = this.sourceName;
|
||
|
const targetName = data.sourceName;
|
||
|
const comObj = this.comObj;
|
||
|
const streamSink = {
|
||
|
enqueue(chunk, size = 1, transfers) {
|
||
|
if (this.isCancelled) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const lastDesiredSize = this.desiredSize;
|
||
|
this.desiredSize -= size;
|
||
|
|
||
|
if (lastDesiredSize > 0 && this.desiredSize <= 0) {
|
||
|
this.sinkCapability = (0, _util.createPromiseCapability)();
|
||
|
this.ready = this.sinkCapability.promise;
|
||
|
}
|
||
|
|
||
|
self._postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.ENQUEUE,
|
||
|
streamId,
|
||
|
chunk
|
||
|
}, transfers);
|
||
|
},
|
||
|
|
||
|
close() {
|
||
|
if (this.isCancelled) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.isCancelled = true;
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.CLOSE,
|
||
|
streamId
|
||
|
});
|
||
|
delete self.streamSinks[streamId];
|
||
|
},
|
||
|
|
||
|
error(reason) {
|
||
|
(0, _util.assert)(reason instanceof Error, "error must have a valid reason");
|
||
|
|
||
|
if (this.isCancelled) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.isCancelled = true;
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.ERROR,
|
||
|
streamId,
|
||
|
reason: wrapReason(reason)
|
||
|
});
|
||
|
},
|
||
|
|
||
|
sinkCapability: (0, _util.createPromiseCapability)(),
|
||
|
onPull: null,
|
||
|
onCancel: null,
|
||
|
isCancelled: false,
|
||
|
desiredSize: data.desiredSize,
|
||
|
ready: null
|
||
|
};
|
||
|
streamSink.sinkCapability.resolve();
|
||
|
streamSink.ready = streamSink.sinkCapability.promise;
|
||
|
this.streamSinks[streamId] = streamSink;
|
||
|
new Promise(function (resolve) {
|
||
|
resolve(action(data.data, streamSink));
|
||
|
}).then(function () {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.START_COMPLETE,
|
||
|
streamId,
|
||
|
success: true
|
||
|
});
|
||
|
}, function (reason) {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.START_COMPLETE,
|
||
|
streamId,
|
||
|
reason: wrapReason(reason)
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_processStreamMessage(data) {
|
||
|
const streamId = data.streamId;
|
||
|
const sourceName = this.sourceName;
|
||
|
const targetName = data.sourceName;
|
||
|
const comObj = this.comObj;
|
||
|
|
||
|
switch (data.stream) {
|
||
|
case StreamKind.START_COMPLETE:
|
||
|
if (data.success) {
|
||
|
this.streamControllers[streamId].startCall.resolve();
|
||
|
} else {
|
||
|
this.streamControllers[streamId].startCall.reject(wrapReason(data.reason));
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case StreamKind.PULL_COMPLETE:
|
||
|
if (data.success) {
|
||
|
this.streamControllers[streamId].pullCall.resolve();
|
||
|
} else {
|
||
|
this.streamControllers[streamId].pullCall.reject(wrapReason(data.reason));
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case StreamKind.PULL:
|
||
|
if (!this.streamSinks[streamId]) {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.PULL_COMPLETE,
|
||
|
streamId,
|
||
|
success: true
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (this.streamSinks[streamId].desiredSize <= 0 && data.desiredSize > 0) {
|
||
|
this.streamSinks[streamId].sinkCapability.resolve();
|
||
|
}
|
||
|
|
||
|
this.streamSinks[streamId].desiredSize = data.desiredSize;
|
||
|
const {
|
||
|
onPull
|
||
|
} = this.streamSinks[data.streamId];
|
||
|
new Promise(function (resolve) {
|
||
|
resolve(onPull && onPull());
|
||
|
}).then(function () {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.PULL_COMPLETE,
|
||
|
streamId,
|
||
|
success: true
|
||
|
});
|
||
|
}, function (reason) {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.PULL_COMPLETE,
|
||
|
streamId,
|
||
|
reason: wrapReason(reason)
|
||
|
});
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case StreamKind.ENQUEUE:
|
||
|
(0, _util.assert)(this.streamControllers[streamId], "enqueue should have stream controller");
|
||
|
|
||
|
if (this.streamControllers[streamId].isClosed) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
this.streamControllers[streamId].controller.enqueue(data.chunk);
|
||
|
break;
|
||
|
|
||
|
case StreamKind.CLOSE:
|
||
|
(0, _util.assert)(this.streamControllers[streamId], "close should have stream controller");
|
||
|
|
||
|
if (this.streamControllers[streamId].isClosed) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
this.streamControllers[streamId].isClosed = true;
|
||
|
this.streamControllers[streamId].controller.close();
|
||
|
|
||
|
this._deleteStreamController(streamId);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case StreamKind.ERROR:
|
||
|
(0, _util.assert)(this.streamControllers[streamId], "error should have stream controller");
|
||
|
this.streamControllers[streamId].controller.error(wrapReason(data.reason));
|
||
|
|
||
|
this._deleteStreamController(streamId);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case StreamKind.CANCEL_COMPLETE:
|
||
|
if (data.success) {
|
||
|
this.streamControllers[streamId].cancelCall.resolve();
|
||
|
} else {
|
||
|
this.streamControllers[streamId].cancelCall.reject(wrapReason(data.reason));
|
||
|
}
|
||
|
|
||
|
this._deleteStreamController(streamId);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case StreamKind.CANCEL:
|
||
|
if (!this.streamSinks[streamId]) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const {
|
||
|
onCancel
|
||
|
} = this.streamSinks[data.streamId];
|
||
|
new Promise(function (resolve) {
|
||
|
resolve(onCancel && onCancel(wrapReason(data.reason)));
|
||
|
}).then(function () {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.CANCEL_COMPLETE,
|
||
|
streamId,
|
||
|
success: true
|
||
|
});
|
||
|
}, function (reason) {
|
||
|
comObj.postMessage({
|
||
|
sourceName,
|
||
|
targetName,
|
||
|
stream: StreamKind.CANCEL_COMPLETE,
|
||
|
streamId,
|
||
|
reason: wrapReason(reason)
|
||
|
});
|
||
|
});
|
||
|
this.streamSinks[streamId].sinkCapability.reject(wrapReason(data.reason));
|
||
|
this.streamSinks[streamId].isCancelled = true;
|
||
|
delete this.streamSinks[streamId];
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new Error("Unexpected stream case");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async _deleteStreamController(streamId) {
|
||
|
await Promise.allSettled([this.streamControllers[streamId].startCall, this.streamControllers[streamId].pullCall, this.streamControllers[streamId].cancelCall].map(function (capability) {
|
||
|
return capability && capability.promise;
|
||
|
}));
|
||
|
delete this.streamControllers[streamId];
|
||
|
}
|
||
|
|
||
|
_postMessage(message, transfers) {
|
||
|
if (transfers && this.postMessageTransfers) {
|
||
|
this.comObj.postMessage(message, transfers);
|
||
|
} else {
|
||
|
this.comObj.postMessage(message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
destroy() {
|
||
|
this.comObj.removeEventListener("message", this._onComObjOnMessage);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
exports.MessageHandler = MessageHandler;
|