Reputation: 1389
Is it possible to spawn a web worker, and somehow inject parent-generated JavaScript into it from the parent thread? ie. without having to make the worker include a file, rather I want the parent to inject it somehow.
An example could be where the parent creates a bunch of math functions at run time, then creates a Web worker to run them.
Upvotes: 1
Views: 1766
Reputation:
This question is almost 10yrs old so things have changed since it was asked. It was also asked without context on what it means to "inject functions". Do you want to inject single functions into an existing environment or do you want to run whole scripts or do you want to inject functions into scripts.
In any case, you can run a script via in a worker via a blob
const script = `
function doIt() {
console.log('hello world');
}
doIt();
`;
const blob = new Blob([script], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
If you run the script above and check the JavaScript console you'll see it ran
Another solution is to run a service worker. A service worker effectively intercepts network requests from the page and lets you apply JavaScript to them before they are sent to the network itself.
So, effectively you can return your own strings from the service-worker in response to requests from the webpage or workers. You can put functions in those strings and you can provide the strings by sending them to the service-worker
In pseudo-code
// service-worker.js
let injectString = '';
self.addEventListener('fetch', (event) => {
event.respondWith(async function() {
const url = new URL(event.request.url);
if (url.pathname === 'inject-fn.js')
const response = new Response(injectString, {
headers: {'Content-Type': 'application/javascript'},
});
return response;
}
return await fetch(event.request);
}());
}
self.addEventListener('message', (event) => {
injectString = event.data;
});
This makes it so if the worker requests 'inject-fn.js'
then it will receive the string stored in injectString
, otherwise it will go the network as normal
To set the string you post a message to the service-worker and the load the script
const worker = navigator.serviceWorker?.controller || navigator.serviceWorker?.active;
worker.postMessage(`
function doIt() {
console.log('hello world');
}
`);
new Worker('inject-fn.js');
you might need to append a query or something to make sure the browser doesn't load 'inject-fn.js' from the cache. As in
new Worker(`inject-fn.js?${Math.random()});
The advantage to using a service-worker is from the POV of the page, the scripts come from the same URL where as via a blob they do not and so can not reference resources without absolute paths. Another problem with blobs is they are hard to debug because every time they're run them they'll get a random blob URL so any break points you set on the code in the blob will not match the next time.
The disadvantage to using a service-worker is they are finicky, at least in my experience.
Note: the code above is pseudo-code. It takes more code to properly start a service-worker and communicate with it. This site, for which the code is on github, and which is effectively a static web page, uses both techniques. The service worker when it can. The blob when it can not.
Upvotes: 1
Reputation: 74036
One option is to send the functions code through the usual channel and use the constructor new Function()
(or eval()
) to recreate the function.
In both cases you should check, what is actually transmitted to prevent security risks.
main script
// your function given as arguments and code
var funktion = {
args: ['a', 'b' ],
source: 'return a + b;'
};
// send it to your worker
worker.postMessage( funktion );
worker
self.addEventListener( 'message', function( msg ){
// build array for constructor arguments
var args = [ null ].concat( fk.a, fk.c );
// create function
var yourFunc = new (Function.prototype.bind.apply(Function, args));
// use yourFunc
});
This uses the dynamic use of the Function
constructor as described in this answer.
Using eval()
may be simpler depending on how you have the function's code:
main script
// your function given as arguments and code
var funktion = "function add(a,b){ return a + b; }";
// send it to your worker
worker.postMessage( funktion );
worker
self.addEventListener( 'message', function( msg ){
// create function
var yourFunc = eval( "(function(){ return " + funktion + "; })()" );
// use yourFunc
});
Upvotes: 2