As said in the question, the only way of I thought of doing this was with web workers. Jeremy J Starcher mentioned in the comments about using an iframe, which I didn't even think of till the mention. So an example of that is provided as well.
Web Worker Example
- All data needed would need to be passed as arguments.
- Browser would need support for
- Web Workers (of course)
- Structured cloning, used in this example
- Transferable objects, not used in this example
- If neither above are supported would require the (de)serialization of arguments to strings
- Requires use of a callback as worker is async (use promises instead?)
- Might be modifiable to allow binding the function to some context, but that would require the context to be able to be passed as an argument, have not tried this
Main javascript
(function(window){
'use strict';
function respond(oEvent){
this.cb&&this.cb(oEvent.data);
this.worker.terminate();
}
window.sandbox = function(fn){
return function(){
var worker = new Worker("sandboxWorker.js");
worker.onmessage = respond.bind({
worker:worker,
cb:[].pop.call(arguments)
});
worker.postMessage({
"fn":fn.toString(),
"args":[].slice.call(arguments,0)
});
};
};
})(window);
sandboxWorker.js
onmessage = function (oEvent) {
var fn = eval("("+oEvent.data.fn+")");
var res = fn.apply(null,oEvent.data.args);
postMessage(res);
};
Test
//Assume jQuery has been included
function maliciousAdd(a,b){
jQuery = function(){ alert("No it doesn't fix everything"); }
return a+b;
}
var sandBoxed = window.sandbox(maliciousAdd);
sandBoxed(1,2,function(result){
jQuery(document.body).css("background","#3F3");
console.log(result);
});
JSFiddle Demo
IFrame Example
- If same-origin source, function could still access parent window etc.
- If using different origin, use of
postMessage
would have to be used - Do not need to (de)serialize/clone arguments, in the case of same-origin iframe
- Synchronous, but could be made to be async as well.
Javascript: modified to use Function
to generate inner sandbox function as suggested by Felix Kling
window.onload = function(){
var frame = document.createElement("iframe");
frame.src = "";
frame.style.display = "none";
window.document.body.appendChild(frame);
var win = (frame.contentWindow) ? frame.contentWindow : (frame.contentDocument.document) ? frame.contentDocument.document : frame.contentDocument;
window.sandbox = function(fn){
return function(){
var args = [].slice.call(arguments);
win.location.reload();
win.sandbox = win.Function(
'fn','args',
'fn = eval("("+fn+")"); return fn.apply(this,args);'
);
win.document.head.appendChild(script);
return win.sandbox(fn.toString(),args);
};
};
};