Comparing version
module.exports = ` | ||
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> | ||
<meta charset="UTF-8"><script type="text/javascript"> | ||
(function (win) { | ||
(function (self) { | ||
var init = function (e) { | ||
win.removeEventListener('message', init); | ||
self.removeEventListener('message', init); | ||
// eslint-disable-next-line no-eval | ||
(e && e.data && (typeof e.data.__init_uvm === 'string')) && eval(e.data.__init_uvm); | ||
}; | ||
win.addEventListener('message', init); | ||
}(window)); | ||
</script> | ||
</head></html>`; | ||
self.addEventListener('message', init); | ||
}(self)); | ||
`; |
/* | ||
* @note | ||
* options.bootTimeout is not implemented in browser sandbox. Reason is that as long as we use iFrame, there is no way | ||
* to interrupt an infinite loop. | ||
* options.bootTimeout is not implemented in browser sandbox because there is no way to interrupt an infinite loop. | ||
*/ | ||
var _ = require('lodash'), | ||
uuid = require('uuid'), | ||
var uuid = require('uuid'), | ||
Flatted = require('flatted'), | ||
MESSAGE = 'message', | ||
LOAD = 'load', | ||
ERROR = 'error', | ||
TARGET_ALL = '*', | ||
SANDBOX_ELEMENT_TAG = 'iframe', | ||
@@ -19,27 +14,14 @@ // code for bridge | ||
/** | ||
* Default DOM attributes of sandbox | ||
* @type {Object} | ||
*/ | ||
sandboxAttributes = { | ||
'class': 'postman-uvm-context', | ||
'style': 'display:none;', | ||
'sandbox': 'allow-scripts' | ||
}, | ||
/** | ||
* Returns the HTML to be executed inside iFrame. | ||
* Returns the firmware code to be executed inside Web Worker. | ||
* | ||
* @param {String} code | ||
* @param {String} id | ||
* @param {Boolean} firmwareOnly | ||
* @return {String} | ||
*/ | ||
sandboxCode = function (code, id, firmwareOnly) { | ||
var firmware = ` | ||
__uvm_emit = function (args) {window.parent.postMessage({__id_uvm: "${id}",__emit_uvm: args}, "*");}; | ||
sandboxFirmware = function (code, id) { | ||
return ` | ||
__uvm_emit = function (args) {self.postMessage({__id_uvm: "${id}",__emit_uvm: args});}; | ||
try {${code}} catch (e) { setTimeout(function () { throw e; }, 0); } | ||
(function (emit, id) { | ||
window.addEventListener("message", function (e) { | ||
self.addEventListener("message", function (e) { | ||
(e && e.data && (typeof e.data.__emit_uvm === 'string') && (e.data.__id_uvm === id)) && | ||
@@ -49,9 +31,4 @@ emit(e.data.__emit_uvm); | ||
}(__uvm_dispatch, "${id}")); | ||
__uvm_emit('${Flatted.stringify(['load.' + id])}'); | ||
__uvm_dispatch = null; __uvm_emit = null; | ||
`; | ||
return firmwareOnly ? firmware : `<!DOCTYPE html><html><head> | ||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"><meta charset="UTF-8"> | ||
<script type="text/javascript">${firmware}</script></head></html>`; | ||
}; | ||
@@ -61,12 +38,7 @@ | ||
var id = uuid(), | ||
code = bridgeClientCode(options.bootCode), | ||
firmwareCode = sandboxFirmware(bridgeClientCode(options.bootCode), id), | ||
// make sure we escape the code only once | ||
serializedFirmware = unescape(encodeURIComponent(sandboxCode(code, id))), | ||
iframe = options._sandbox || document.createElement(SANDBOX_ELEMENT_TAG), | ||
// function to forward messages emitted | ||
forwardEmits = function (e) { | ||
if (!(e && e.data && _.isString(e.data.__emit_uvm) && (e.data.__id_uvm === id))) { return; } | ||
if (!(e && e.data && (typeof e.data.__emit_uvm === 'string') && (e.data.__id_uvm === id))) { return; } | ||
@@ -79,68 +51,67 @@ var args; | ||
processCallback = function () { | ||
!options._sandbox && iframe.removeEventListener(LOAD, processCallback); | ||
// function to forward errors emitted | ||
forwardErrors = function (e) { | ||
bridge.emit(ERROR, e); | ||
}, | ||
bridge._dispatch = function () { | ||
if (!iframe) { | ||
return bridge.emit(ERROR, | ||
new Error('uvm: unable to dispatch "' + arguments[0] + '" post disconnection.')); | ||
} | ||
firmwareObjectURL, | ||
worker; | ||
iframe.contentWindow.postMessage({ | ||
__emit_uvm: Flatted.stringify(Array.prototype.slice.call(arguments)), | ||
__id_uvm: id | ||
}, TARGET_ALL); | ||
}; | ||
// if sandbox worker is provided, we simply need to init with firmware code | ||
// @todo validate sandbox type or APIs | ||
if (options._sandbox) { | ||
worker = options._sandbox; | ||
worker.postMessage({ __init_uvm: firmwareCode }); | ||
} | ||
// else, spawn a new worker | ||
else { | ||
// convert the firmware code into a blob URL | ||
firmwareObjectURL = window.URL.createObjectURL( | ||
new Blob([firmwareCode], { type: 'text/javascript' }) | ||
); | ||
callback(null, bridge); | ||
}; | ||
worker = new Worker(firmwareObjectURL); | ||
} | ||
// add event listener for receiving events from iframe (is removed on disconnect) | ||
window.addEventListener(MESSAGE, forwardEmits); | ||
// add event listener for receiving events from worker (is removed on disconnect) | ||
// don't set `onmessage` and `onerror` as it might override external sandbox | ||
worker.addEventListener(MESSAGE, forwardEmits); | ||
worker.addEventListener(ERROR, forwardErrors); | ||
// equip bridge to disconnect (i.e. delete the iframe) | ||
bridge._disconnect = function () { | ||
if (!iframe) { return; } | ||
bridge._dispatch = function () { | ||
if (!worker) { | ||
return bridge.emit(ERROR, | ||
new Error('uvm: unable to dispatch "' + arguments[0] + '" post disconnection.')); | ||
} | ||
// remove the message handler for this sandbox | ||
window.removeEventListener(MESSAGE, forwardEmits); | ||
// do not delete sandbox element if not created for the bridge | ||
!options._sandbox && iframe.parentNode && iframe.parentNode.removeChild(iframe); | ||
iframe = null; | ||
worker.postMessage({ | ||
__emit_uvm: Flatted.stringify(Array.prototype.slice.call(arguments)), | ||
__id_uvm: id | ||
}); | ||
}; | ||
// if sandbox element is provided, we simply need to init with firmware code and wait for load event to be fired | ||
if (options._sandbox) { | ||
bridge.once('load.' + id, processCallback); // on load attach the dispatcher | ||
// equip bridge to disconnect (i.e. terminate the worker) | ||
bridge._disconnect = function () { | ||
if (!worker) { return; } | ||
iframe.contentWindow.postMessage({ | ||
__init_uvm: sandboxCode(code, id, true) // the last `true` param indicates firmwareOnly | ||
}, TARGET_ALL); | ||
// remove event listeners for this sandbox | ||
worker.removeEventListener(MESSAGE, forwardEmits); | ||
worker.removeEventListener(ERROR, forwardErrors); | ||
return; | ||
} | ||
// do not terminate sandbox worker if not spawned for the bridge | ||
if (!options._sandbox) { | ||
worker.terminate(); | ||
// this point onwards, it is our own iframe, so we do all the appending and other stuff to DOM | ||
iframe.addEventListener(LOAD, processCallback); // removed right when executed | ||
// revoke after termination. otherwise, blob reference is retained until GC | ||
// refer: "chrome://blob-internals" | ||
window.URL.revokeObjectURL(firmwareObjectURL); | ||
} | ||
// prepare an iframe as context | ||
_.forEach(sandboxAttributes, function (val, prop) { | ||
iframe.setAttribute(prop, val); | ||
}); | ||
worker = null; | ||
}; | ||
// add HTML and bootstrap code to the iframe | ||
iframe.setAttribute('src', 'data:text/html;base64, ' + btoa(serializedFirmware)); | ||
// help GC collect large variables | ||
firmwareCode = null; | ||
// data uri has size limits depending on the browsers | ||
// in browsers that don't support srcdoc attribute the src attribute is accepted | ||
// https://www.w3.org/TR/html5/semantics-embedded-content.html#an-iframe-srcdoc-document | ||
iframe.setAttribute('srcdoc', serializedFirmware); | ||
// now append the iframe to start processing stuff | ||
document.body.appendChild(iframe); | ||
// help GC collect large variables | ||
code = null; | ||
serializedFirmware = null; | ||
callback(null, bridge); | ||
}; |
{ | ||
"name": "uvm", | ||
"version": "1.7.9", | ||
"version": "1.8.0-beta.1", | ||
"description": "Universal Virtual Machine for Node and Browser", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# uvm [](https://travis-ci.org/postmanlabs/uvm) [](https://codecov.io/gh/postmanlabs/uvm) | ||
Module that exposes an event emitter to send data across contexts (vm in node and iframe in browser) | ||
Module that exposes an event emitter to send data across contexts ([VM](https://nodejs.org/api/vm.html) in Node.js and [Web Workers](https://www.w3.org/TR/workers/) in browser) | ||
@@ -5,0 +5,0 @@ ## Usage |
@@ -1,19 +0,23 @@ | ||
(typeof window !== 'undefined' ? describe : describe.skip)('custom iframe in browser', function () { | ||
(typeof window !== 'undefined' ? describe : describe.skip)('custom sandbox in browser', function () { | ||
var uvm = require('../../lib'), | ||
firmware = require('../../firmware/sandbox-base'), | ||
iframe; | ||
firmwareUrl, | ||
worker; | ||
beforeEach(function (done) { | ||
iframe = document.createElement('iframe'); | ||
iframe.setAttribute('src', 'data:text/html;base64, ' + | ||
btoa(unescape(encodeURIComponent(firmware)))); | ||
iframe.addEventListener('load', function () { | ||
done(); | ||
}); | ||
document.body.appendChild(iframe); | ||
beforeEach(function () { | ||
firmwareUrl = window.URL.createObjectURL( | ||
new Blob([firmware], { type: 'text/javascript' }) | ||
); | ||
worker = new Worker(firmwareUrl); | ||
}); | ||
afterEach(function () { | ||
worker.terminate(); | ||
window.URL.revokeObjectURL(firmwareUrl); | ||
worker = firmwareUrl = null; | ||
}); | ||
it('should load and dispatch messages', function (done) { | ||
uvm.spawn({ | ||
_sandbox: iframe, | ||
_sandbox: worker, | ||
bootCode: ` | ||
@@ -35,7 +39,2 @@ bridge.on('loopback', function (data) { | ||
}); | ||
afterEach(function () { | ||
iframe.parentNode.removeChild(iframe); | ||
iframe = null; | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
(typeof window !== 'undefined' ? describe : describe.skip)('custom iframe in browser', function () { | ||
(typeof window !== 'undefined' ? describe : describe.skip)('custom sandbox in browser', function () { | ||
var _ = require('lodash'), | ||
@@ -17,38 +17,36 @@ uvm = require('../../lib'), | ||
return ` | ||
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> | ||
<meta charset="UTF-8"> | ||
<script type="text/javascript"> | ||
${code} | ||
</script> | ||
<script type="text/javascript"> | ||
(function (win) { | ||
var init = function (e) { | ||
win.removeEventListener('message', init); | ||
// eslint-disable-next-line no-eval | ||
(e && e.data && (typeof e.data.__init_uvm === 'string')) && eval(e.data.__init_uvm); | ||
}; | ||
win.addEventListener('message', init); | ||
}(window)); | ||
</script> | ||
</head></html>`; | ||
${code} | ||
(function (self) { | ||
var init = function (e) { | ||
self.removeEventListener('message', init); | ||
// eslint-disable-next-line no-eval | ||
(e && e.data && (typeof e.data.__init_uvm === 'string')) && eval(e.data.__init_uvm); | ||
}; | ||
self.addEventListener('message', init); | ||
}(self)); | ||
`; | ||
}, | ||
iframe; | ||
firmwareUrl, | ||
worker; | ||
beforeEach(function (done) { | ||
var fakeBundleSize = 5 * 1024 * 1024, // 10mb (5 million characters with 2 bytes each) | ||
beforeEach(function () { | ||
var fakeBundleSize = 5 * 1024 * 1024, // 10MB (5 million characters with 2 bytes each) | ||
largeJSStatement = `var x = '${getLargeString(fakeBundleSize)}';`; | ||
iframe = document.createElement('iframe'); | ||
iframe.setAttribute('src', 'data:text/html;base64, ' + | ||
btoa(unescape(encodeURIComponent(getFirmware(largeJSStatement))))); | ||
iframe.setAttribute('srcdoc', unescape(encodeURIComponent(getFirmware(largeJSStatement)))); | ||
iframe.addEventListener('load', function () { | ||
done(); | ||
}); | ||
document.body.appendChild(iframe); | ||
firmwareUrl = window.URL.createObjectURL( | ||
new Blob([getFirmware(largeJSStatement)], { type: 'text/javascript' }) | ||
); | ||
worker = new Worker(firmwareUrl); | ||
}); | ||
afterEach(function () { | ||
worker.terminate(); | ||
window.URL.revokeObjectURL(firmwareUrl); | ||
worker = firmwareUrl = null; | ||
}); | ||
it('should load and dispatch messages', function (done) { | ||
uvm.spawn({ | ||
_sandbox: iframe, | ||
_sandbox: worker, | ||
bootCode: ` | ||
@@ -70,7 +68,2 @@ bridge.on('loopback', function (data) { | ||
}); | ||
afterEach(function () { | ||
iframe.parentNode.removeChild(iframe); | ||
iframe = null; | ||
}); | ||
}); |
((typeof window === 'undefined') ? describe : describe.skip)('uvm timeout option', function () { | ||
var uvm = require('../../lib'); | ||
// options.bootTimeout is not implemented in browser sandbox. Reason is that as long as we use iFrame, there is no | ||
// way to interrupt an infinite loop. | ||
// options.bootTimeout is not implemented in browser sandbox because there | ||
// is no way to interrupt an infinite loop. | ||
it('must exit if bootCode has infinite loop', function (done) { | ||
@@ -23,4 +23,4 @@ uvm.spawn({ | ||
// options.dispatchTimeout is not implemented in browser sandbox. Reason is that as long as we use iFrame, there is | ||
// no way to interrupt an infinite loop. | ||
// options.dispatchTimeout is not implemented in browser sandbox because | ||
// there is no way to interrupt an infinite loop. | ||
it('must exit if dispatch is has infinite loop', function (done) { | ||
@@ -27,0 +27,0 @@ uvm.spawn({ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
123147
-1.56%2010
-1.57%1
Infinity%