Skip to content

Commit bea120e

Browse files
Merge pull request #126 from martinRenou/improve_worker
Improve worker implementation
2 parents 3360626 + 8aeb8b2 commit bea120e

File tree

3 files changed

+128
-84
lines changed

3 files changed

+128
-84
lines changed

js/src/renderer.ts

+127-20
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,140 @@
11
import * as widgets from '@jupyter-widgets/base';
22
import * as Handsontable from 'handsontable';
3-
import {extend} from 'lodash';
3+
import {extend, forEach} from 'lodash';
44
import {semver_range} from './version';
5-
import {safeEval} from './worker_eval';
65

7-
let RendererModel = widgets.WidgetModel.extend({
8-
defaults: function() {
9-
return extend(RendererModel.__super__.defaults.call(this), {
6+
7+
export class ExecuteRequest {
8+
constructor(code: string) {
9+
this.id = widgets.uuid();
10+
this.code = code;
11+
12+
this.execute_promise = new Promise((resolve, reject) => {
13+
this.resolve = resolve;
14+
this.reject = reject;
15+
});
16+
}
17+
18+
id: string;
19+
code: string;
20+
execute_promise: Promise<any>;
21+
resolve: Function;
22+
reject: Function;
23+
};
24+
25+
26+
export class SafeJSKernel {
27+
constructor() {
28+
this.initialize();
29+
}
30+
31+
execute(code: string) {
32+
const request = new ExecuteRequest(code);
33+
34+
this.requests[request.id] = request;
35+
36+
this.worker.postMessage({ id: request.id, code: request.code });
37+
38+
return request.execute_promise;
39+
}
40+
41+
initialize() {
42+
const blobURL = URL.createObjectURL(new Blob([
43+
'(',
44+
function () {
45+
const _postMessage = postMessage;
46+
const _addEventListener = addEventListener;
47+
48+
((obj) => {
49+
'use strict';
50+
51+
let current = obj;
52+
const keepProperties = [
53+
// required
54+
'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT',
55+
// optional, but trivial to get back
56+
'Array', 'Boolean', 'Number', 'String', 'Symbol',
57+
// optional
58+
'Map', 'Math', 'Set',
59+
];
60+
61+
do {
62+
Object.getOwnPropertyNames(current).forEach((name) => {
63+
if (keepProperties.indexOf(name) === -1) {
64+
delete current[name];
65+
}
66+
});
67+
current = Object.getPrototypeOf(current);
68+
}
69+
while (current !== Object.prototype);
70+
})(this);
71+
72+
_addEventListener('message', ({ data }) => {
73+
const f = new Function('', `return (${data.code}\n);`);
74+
_postMessage({ id: data.id, result: f() }, undefined);
75+
});
76+
}.toString(),
77+
')()'
78+
], {
79+
type: 'application/javascript'
80+
}));
81+
82+
this.worker = new Worker(blobURL);
83+
84+
this.worker.onmessage = ({ data }) => {
85+
// Resolve the right Promise with the return value
86+
this.requests[data.id].resolve(data.result);
87+
delete this.requests[data.id];
88+
};
89+
90+
this.worker.onerror = ({ message }) => {
91+
// Reject all the pending promises, terminate the worker and start again
92+
forEach(this.requests, (request) => {
93+
request.reject(message);
94+
});
95+
this.requests = {};
96+
97+
this.worker.terminate();
98+
99+
this.initialize();
100+
};
101+
102+
URL.revokeObjectURL(blobURL);
103+
}
104+
105+
worker: Worker;
106+
requests: { [key:string] : ExecuteRequest; } = {};
107+
}
108+
109+
110+
export class RendererModel extends widgets.WidgetModel {
111+
defaults() {
112+
return {...widgets.WidgetModel.prototype.defaults(),
10113
_model_name : 'RendererModel',
11114
_model_module : 'ipysheet',
12115
_model_module_version : semver_range,
13116
name: '',
14117
code: ''
15-
});
16-
},
17-
initialize: function() {
18-
RendererModel.__super__.initialize.apply(this, arguments);
19-
// We add Handsontable manually as extra argument to put it in the scope
20-
var that = this;
21-
this.fn = function (instance, td, row, col, prop, value, cellProperties) {
22-
Handsontable.renderers.TextRenderer.apply(this, arguments);
23-
safeEval(`(${that.get('code')})(${value})`).then(function(style) {
24-
(Object as any).assign(td.style, style);
25-
});
26118
};
27-
(Handsontable.renderers as any).registerRenderer(this.get('name'), this.fn);
28119
}
29-
});
30120

31-
export {
32-
RendererModel
121+
initialize(attributes: any, options: any) {
122+
super.initialize(attributes, options);
123+
124+
this.kernel = new SafeJSKernel();
125+
126+
const that = this;
127+
this.rendering_function = function (instance, td, row, col, prop, value, cellProperties) {
128+
Handsontable.renderers.TextRenderer.apply(this, arguments);
129+
130+
that.kernel.execute(`(${that.get('code')})(${value})`).then((style) => {
131+
(Object as any).assign(td.style, style);
132+
});
133+
};
134+
135+
(Handsontable.renderers as any).registerRenderer(this.get('name'), this.rendering_function);
136+
}
137+
138+
kernel: SafeJSKernel;
139+
rendering_function: Function;
33140
};

js/src/test/test_renderer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('custom', function() {
4545
});
4646

4747
it('register', function() {
48-
expect(this.renderer.fn).to.not.equal(undefined);
48+
expect(this.renderer.rendering_function).to.not.equal(undefined);
4949
expect((Handsontable.renderers as any).getRenderer('test_renderer')).to.not.equal(undefined);
5050
});
5151
});

js/src/worker_eval.ts

-63
This file was deleted.

0 commit comments

Comments
 (0)