Move base64 conversions off of the main thread

This commit is contained in:
Scott Nonnenberg 2018-08-16 10:45:09 -07:00
parent 911bc63c67
commit 02fbea96c0
7 changed files with 167 additions and 5 deletions

View file

@ -12,6 +12,7 @@ test/views/*.js
# Generated files
js/components.js
js/libtextsecure.js
js/util_worker.js
js/libsignal-protocol-worker.js
libtextsecure/components.js
libtextsecure/test/test.js

1
.gitignore vendored
View file

@ -17,6 +17,7 @@ sql/
# generated files
js/components.js
js/util_worker.js
js/libtextsecure.js
libtextsecure/components.js
libtextsecure/test/test.js

View file

@ -6,6 +6,7 @@ config/local-*.json
config/local.json
dist/**
js/components.js
js/util_worker.js
js/libtextsecure.js
libtextsecure/components.js
libtextsecure/test/test.js

View file

@ -33,6 +33,14 @@ module.exports = grunt => {
src: components,
dest: 'js/components.js',
},
util_worker: {
src: [
'components/bytebuffer/dist/ByteBufferAB.js',
'components/long/dist/Long.js',
'js/util_worker_tasks.js',
],
dest: 'js/util_worker.js',
},
libtextsecurecomponents: {
src: libtextsecurecomponents,
dest: 'libtextsecure/components.js',

View file

@ -166,7 +166,7 @@ ipcRenderer.on(
const job = _getJob(jobId);
if (!job) {
throw new Error(
`Received job reply to job ${jobId}, but did not have it in our registry!`
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
);
}
@ -174,7 +174,9 @@ ipcRenderer.on(
if (errorForDisplay) {
return reject(
new Error(`Error calling channel ${fnName}: ${errorForDisplay}`)
new Error(
`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`
)
);
}
@ -196,7 +198,8 @@ function makeChannel(fnName) {
});
setTimeout(
() => reject(new Error(`Request to ${fnName} timed out`)),
() =>
reject(new Error(`SQL channel job ${jobId} (${fnName}) timed out`)),
DATABASE_UPDATE_TIMEOUT
);
});

44
js/util_worker_tasks.js Normal file
View file

@ -0,0 +1,44 @@
/* global dcodeIO */
/* eslint-disable strict */
'use strict';
const functions = {
stringToArrayBufferBase64,
arrayBufferToStringBase64,
};
onmessage = async e => {
const [jobId, fnName, ...args] = e.data;
try {
const fn = functions[fnName];
if (!fn) {
throw new Error(`Worker: job ${jobId} did not find function ${fnName}`);
}
const result = await fn(...args);
postMessage([jobId, null, result]);
} catch (error) {
const errorForDisplay = prepareErrorForPostMessage(error);
postMessage([jobId, errorForDisplay]);
}
};
function prepareErrorForPostMessage(error) {
if (!error) {
return null;
}
if (error.stack) {
return error.stack;
}
return error.message;
}
function stringToArrayBufferBase64(string) {
return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();
}
function arrayBufferToStringBase64(arrayBuffer) {
return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
}

View file

@ -9,9 +9,112 @@
/* global _: false */
/* global ContactBuffer: false */
/* global GroupBuffer: false */
/* global Worker: false */
/* eslint-disable more/no-then */
const WORKER_TIMEOUT = 60 * 1000; // one minute
const _utilWorker = new Worker('js/util_worker.js');
const _jobs = Object.create(null);
const _DEBUG = false;
let _jobCounter = 0;
function _makeJob(fnName) {
_jobCounter += 1;
const id = _jobCounter;
if (_DEBUG) {
window.log.info(`Worker job ${id} (${fnName}) started`);
}
_jobs[id] = {
fnName,
start: Date.now(),
};
return id;
}
function _updateJob(id, data) {
const { resolve, reject } = data;
const { fnName, start } = _jobs[id];
_jobs[id] = {
..._jobs[id],
...data,
resolve: value => {
_removeJob(id);
const end = Date.now();
window.log.info(
`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`
);
return resolve(value);
},
reject: error => {
_removeJob(id);
const end = Date.now();
window.log.info(
`Worker job ${id} (${fnName}) failed in ${end - start}ms`
);
return reject(error);
},
};
}
function _removeJob(id) {
if (_DEBUG) {
_jobs[id].complete = true;
} else {
delete _jobs[id];
}
}
function _getJob(id) {
return _jobs[id];
}
async function callWorker(fnName, ...args) {
const jobId = _makeJob(fnName);
return new Promise((resolve, reject) => {
_utilWorker.postMessage([jobId, fnName, ...args]);
_updateJob(jobId, {
resolve,
reject,
args: _DEBUG ? args : null,
});
setTimeout(
() => reject(new Error(`Worker job ${jobId} (${fnName}) timed out`)),
WORKER_TIMEOUT
);
});
}
_utilWorker.onmessage = e => {
const [jobId, errorForDisplay, result] = e.data;
const job = _getJob(jobId);
if (!job) {
throw new Error(
`Received worker reply to job ${jobId}, but did not have it in our registry!`
);
}
const { resolve, reject, fnName } = job;
if (errorForDisplay) {
return reject(
new Error(
`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`
)
);
}
return resolve(result);
};
function MessageReceiver(username, password, signalingKey, options = {}) {
this.count = 0;
@ -35,10 +138,11 @@ MessageReceiver.stringToArrayBuffer = string =>
Promise.resolve(dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer());
MessageReceiver.arrayBufferToString = arrayBuffer =>
Promise.resolve(dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary'));
MessageReceiver.stringToArrayBufferBase64 = string =>
Promise.resolve(dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer());
callWorker('stringToArrayBufferBase64', string);
MessageReceiver.arrayBufferToStringBase64 = arrayBuffer =>
Promise.resolve(dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'));
callWorker('arrayBufferToStringBase64', arrayBuffer);
MessageReceiver.prototype = new textsecure.EventTarget();
MessageReceiver.prototype.extend({