Comparing version 0.1.0 to 0.2.0
@@ -14,5 +14,6 @@ /** | ||
if (typeof window == 'undefined' && self.importScripts) { | ||
if (typeof window == 'undefined' && self.importScripts) { | ||
// I'm a worker! Run the boiler-script: | ||
// (Operative itself is called in IE10 as a worker, to avoid SecurityErrors) | ||
// (Operative itself is called in IE10 as a worker, | ||
// to avoid SecurityErrors) | ||
workerBoilerScript(); | ||
@@ -29,2 +30,11 @@ return; | ||
// Default base URL (to be prepended to relative dependency URLs) | ||
// is current page's parent dir: | ||
var baseURL = ( | ||
location.protocol + '//' + | ||
location.hostname + | ||
(location.port?':'+location.port:'') + | ||
location.pathname | ||
).replace(/[^\/]+$/, ''); | ||
var URL = window.URL || window.webkitURL; | ||
@@ -42,2 +52,11 @@ var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; | ||
/** | ||
* Provide Object.create shim | ||
*/ | ||
var objCreate = Object.create || function(o) { | ||
function F() {} | ||
F.prototype = o; | ||
return new F(); | ||
}; | ||
function makeBlobURI(script) { | ||
@@ -48,3 +67,3 @@ var blob; | ||
blob = new Blob([script], { type: 'text/javascript' }); | ||
} catch (e) { | ||
} catch (e) { | ||
blob = new BlobBuilder(); | ||
@@ -75,2 +94,10 @@ blob.append(script); | ||
operative.setBaseURL = function(base) { | ||
baseURL = base; | ||
}; | ||
operative.getBaseURL = function() { | ||
return baseURL; | ||
}; | ||
/** | ||
@@ -80,14 +107,6 @@ * Operative: Exposed Operative Constructor | ||
*/ | ||
function Operative(module) { | ||
function Operative(module, dependencies) { | ||
var _self = this; | ||
if (typeof module == 'function') { | ||
// Allow a single function to be passed. | ||
var o = new Operative({ main: module }); | ||
return function() { | ||
return o.main.apply(o, arguments); | ||
}; | ||
} | ||
module.get = module.get || function(prop) { | ||
@@ -101,11 +120,12 @@ return this[prop]; | ||
this._msgQueue = []; | ||
this._curToken = 0; | ||
this._queue = []; | ||
this.isDestroyed = false; | ||
this.workerIsReady = false; | ||
this.isContextReady = false; | ||
this.module = module; | ||
this.dependencies = dependencies || []; | ||
this.dataProperties = {}; | ||
this.api = {}; | ||
@@ -115,7 +135,4 @@ this.callbacks = {}; | ||
if (operative.hasWorkerSupport) { | ||
this._setupWorker(); | ||
} else { | ||
this._setupFallback(); | ||
} | ||
this._fixDependencyURLs(); | ||
this._setup(); | ||
@@ -135,3 +152,2 @@ for (var methodName in module) { | ||
return this.api; | ||
} | ||
@@ -141,4 +157,33 @@ | ||
_buildWorkerScript: function(doIncludeBoilerScript) { | ||
_marshal: function(v) { | ||
return v; | ||
}, | ||
_demarshal: function(v) { | ||
return v; | ||
}, | ||
_enqueue: function(fn) { | ||
this._queue.push(fn); | ||
}, | ||
_fixDependencyURLs: function() { | ||
var deps = this.dependencies; | ||
for (var i = 0, l = deps.length; i < l; ++i) { | ||
var dep = deps[i]; | ||
if (!/\/\//.test(dep)) { | ||
deps[i] = dep.replace(/^\/?/, baseURL); | ||
} | ||
} | ||
}, | ||
_dequeueAll: function() { | ||
for (var i = 0, l = this._queue.length; i < l; ++i) { | ||
this._queue[i].call(this); | ||
} | ||
this._queue = []; | ||
}, | ||
_buildContextScript: function(boilerScript) { | ||
var script = []; | ||
@@ -150,3 +195,3 @@ var module = this.module; | ||
for (var i in module) { | ||
var property = module[i]; | ||
property = module[i]; | ||
if (typeof property == 'function') { | ||
@@ -160,3 +205,3 @@ script.push(' self["' + i.replace(/"/g, '\\"') + '"] = ' + property.toString() + ';'); | ||
return script.join('\n') + ( | ||
doIncludeBoilerScript ? '\n(' + workerBoilerScript.toString() + '());' : '' | ||
boilerScript ? '\n(' + boilerScript.toString() + '());' : '' | ||
); | ||
@@ -166,205 +211,317 @@ | ||
_onWorkerMessage: function(e) { | ||
var data = e.data; | ||
_createExposedMethod: function(methodName) { | ||
if (typeof data === 'string' && data.indexOf('pingback') === 0) { | ||
if (data === 'pingback:objectTransferSupport=NO') { | ||
// No transferrableObj support (marshal JSON from now on): | ||
this._marshal = function(o) { return JSON.stringify(o); }; | ||
this._demarshal = function(o) { return JSON.parse(o); }; | ||
var self = this; | ||
this.api[methodName] = function() { | ||
if (self.isDestroyed) { | ||
throw new Error('Operative: Cannot run method. Operative has already been destroyed'); | ||
} | ||
this.workerIsReady = true; | ||
this._postQueudMessages(); | ||
return; | ||
var token = ++self._curToken; | ||
var args = slice.call(arguments); | ||
var cb = typeof args[args.length - 1] == 'function' && args.pop(); | ||
} | ||
if (!cb && !operative.Promise) { | ||
throw new Error( | ||
'Operative: No callback has been passed. Assumed that you want a promise. ' + | ||
'But `operative.Promise` is null. Please provide Promise polyfill/lib.' | ||
); | ||
} | ||
data = this._demarshal(data); | ||
if (cb) { | ||
switch (data.cmd) { | ||
case 'console': | ||
window.console && window.console[data.method].apply(window.console, data.args); | ||
break; | ||
case 'result': | ||
self.callbacks[token] = cb; | ||
var callback = this.callbacks[data.token]; | ||
var deferred = this.deferreds[data.token]; | ||
// Ensure either context runs the method async: | ||
setTimeout(function() { | ||
runMethod(); | ||
}, 1); | ||
delete this.callbacks[data.token]; | ||
delete this.deferreds[data.token]; | ||
} else if (operative.Promise) { | ||
var deferredAction = data.result && data.result.isDeferred && data.result.action; | ||
// No Callback -- Promise used: | ||
if (deferred && deferredAction) { | ||
deferred[deferredAction](data.result.arg); | ||
} else if (callback) { | ||
callback(data.result); | ||
return new operative.Promise(function(deferred) { | ||
deferred.fulfil = deferred.fulfill; | ||
self.deferreds[token] = deferred; | ||
runMethod(); | ||
}); | ||
} | ||
function runMethod() { | ||
if (self.isContextReady) { | ||
self._runMethod(methodName, token, args); | ||
} else { | ||
self._enqueue(runMethod); | ||
} | ||
} | ||
break; | ||
} | ||
}, | ||
}; | ||
_marshal: function(v) { | ||
return v; | ||
}, | ||
_demarshal: function(v) { | ||
return v; | ||
}, | ||
destroy: function() { | ||
this.isDestroyed = true; | ||
} | ||
}; | ||
_postWorkerMessage: function(msg) { | ||
if (!this.workerIsReady) { | ||
this._msgQueue.push(msg); | ||
return; | ||
} | ||
return this.worker.postMessage(this._marshal(msg)); | ||
}, | ||
_postQueudMessages: function() { | ||
var msgQueue = this._msgQueue; | ||
for (var i = 0, l = msgQueue.length; i < l; ++i) { | ||
this._postWorkerMessage( msgQueue[i] ); | ||
/** | ||
* Operative Worker | ||
*/ | ||
Operative.Worker = function Worker(module) { | ||
this._msgQueue = []; | ||
Operative.apply(this, arguments); | ||
}; | ||
var WorkerProto = Operative.Worker.prototype = objCreate(Operative.prototype); | ||
WorkerProto._onWorkerMessage = function(e) { | ||
var data = e.data; | ||
if (typeof data === 'string' && data.indexOf('pingback') === 0) { | ||
if (data === 'pingback:objectTransferSupport=NO') { | ||
// No transferrableObj support (marshal JSON from now on): | ||
this._marshal = function(o) { return JSON.stringify(o); }; | ||
this._demarshal = function(o) { return JSON.parse(o); }; | ||
} | ||
this._msgQueue = null; | ||
}, | ||
_setupWorker: function() { | ||
this.isContextReady = true; | ||
this._dequeueAll(); | ||
return; | ||
var _self = this; | ||
} | ||
var worker; | ||
data = this._demarshal(data); | ||
if (workerViaBlobSupport) { | ||
worker = this.worker = new Worker( makeBlobURI(this._buildWorkerScript(true)) ); | ||
} else { | ||
if (!opScriptURL) { | ||
throw new Error('Operaritve: No operative.js URL available. Please set via operative.setSelfURL(...)'); | ||
switch (data.cmd) { | ||
case 'console': | ||
window.console && window.console[data.method].apply(window.console, data.args); | ||
break; | ||
case 'result': | ||
var callback = this.callbacks[data.token]; | ||
var deferred = this.deferreds[data.token]; | ||
delete this.callbacks[data.token]; | ||
delete this.deferreds[data.token]; | ||
var deferredAction = data.result && data.result.isDeferred && data.result.action; | ||
if (deferred && deferredAction) { | ||
deferred[deferredAction](data.result.args[0]); | ||
} else if (callback) { | ||
callback.apply(this, data.result.args); | ||
} | ||
worker = this.worker = new Worker( opScriptURL ); | ||
// Marshal-agnostic initial message is boiler-code: | ||
// (We don't yet know if transferrableObjs are supported so we send a string) | ||
worker.postMessage('EVAL|' + this._buildWorkerScript(false)); | ||
} | ||
worker.postMessage(['PING']); // Initial PING | ||
break; | ||
} | ||
}; | ||
worker.addEventListener('message', function(e) { | ||
_self._onWorkerMessage(e); | ||
}); | ||
WorkerProto._setup = function() { | ||
var self = this; | ||
this._postWorkerMessage({ | ||
definitions: this.dataProperties | ||
}); | ||
var worker; | ||
var script = this._buildContextScript(workerBoilerScript); | ||
}, | ||
if (this.dependencies.length) { | ||
script = 'importScripts("' + this.dependencies.join('", "') + '");\n' + script; | ||
} | ||
_createExposedMethod: function(methodName) { | ||
if (workerViaBlobSupport) { | ||
worker = this.worker = new Worker( makeBlobURI(script) ); | ||
} else { | ||
if (!opScriptURL) { | ||
throw new Error('Operaritve: No operative.js URL available. Please set via operative.setSelfURL(...)'); | ||
} | ||
worker = this.worker = new Worker( opScriptURL ); | ||
// Marshal-agnostic initial message is boiler-code: | ||
// (We don't yet know if transferrableObjs are supported so we send a string) | ||
worker.postMessage('EVAL|' + this._buildWorkerScript(null)); | ||
} | ||
var _self = this; | ||
worker.postMessage(['PING']); // Initial PING | ||
this.api[methodName] = function() { | ||
worker.addEventListener('message', function(e) { | ||
self._onWorkerMessage(e); | ||
}); | ||
if (_self.isDestroyed) { | ||
throw new Error('Operative: Cannot run method. Operative has already been destroyed'); | ||
} | ||
this._postMessage({ | ||
definitions: this.dataProperties | ||
}); | ||
}; | ||
var token = ++_self._curToken; | ||
var args = slice.call(arguments); | ||
var cb = typeof args[args.length - 1] == 'function' && args.pop(); | ||
WorkerProto._postMessage = function(msg) { | ||
return this.worker.postMessage(this._marshal(msg)); | ||
}; | ||
if (!cb && !operative.Promise) { | ||
throw new Error( | ||
'Operative: No callback has been passed. Assumed that you want a promise. ' + | ||
'But `operative.Promise` is null. Please provide Promise polyfill/lib.' | ||
); | ||
} | ||
WorkerProto._runMethod = function(methodName, token, args) { | ||
this._postMessage({ | ||
method: methodName, | ||
args: args, | ||
token: token | ||
}); | ||
}; | ||
if (operative.hasWorkerSupport) { | ||
WorkerProto.destroy = function() { | ||
this.worker.terminate(); | ||
Operative.prototype.destroy.call(this); | ||
}; | ||
if (cb) { | ||
_self.callbacks[token] = cb; | ||
sendToWorker(); | ||
} else if (operative.Promise) { | ||
return new operative.Promise(function(deferred) { | ||
_self.deferreds[token] = deferred; | ||
sendToWorker(); | ||
}); | ||
} | ||
} else { | ||
if (cb) { | ||
setTimeout(function() { | ||
runInline(); | ||
}, 1); | ||
} else if (operative.Promise) { | ||
return new operative.Promise(function(deferred) { | ||
deferred.fulfil = deferred.fulfill; | ||
setTimeout(function() { | ||
runInline(deferred); | ||
}, 1); | ||
}); | ||
} | ||
} | ||
/** | ||
* Operative IFrame | ||
*/ | ||
Operative.Iframe = function Iframe(module) { | ||
Operative.apply(this, arguments); | ||
}; | ||
function runInline(deferred) { | ||
var IframeProto = Operative.Iframe.prototype = objCreate(Operative.prototype); | ||
var isAsync = false; | ||
IframeProto._setup = function() { | ||
_self.module.async = function() { | ||
isAsync = true; | ||
return cb; | ||
}; | ||
var self = this; | ||
_self.module.deferred = function() { | ||
return deferred; | ||
}; | ||
this.module.isWorker = false; | ||
var result = _self.module[methodName].apply(_self.module, args); | ||
var iframe = this.iframe = document.body.appendChild( | ||
document.createElement('iframe') | ||
); | ||
_self.module.async = function() { | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
iframe.style.display = 'none'; | ||
if (!isAsync && !deferred) { | ||
cb(result); | ||
} | ||
var iWin = this.iframeWindow = iframe.contentWindow; | ||
var iDoc = iWin.document; | ||
} | ||
iWin.__loaded__ = function() { | ||
function sendToWorker() { | ||
_self._postWorkerMessage({ | ||
method: methodName, | ||
args: args, | ||
token: token | ||
}); | ||
} | ||
}; | ||
var script = iDoc.createElement('script'); | ||
var js = self._buildContextScript(iframeBoilerScript); | ||
}, | ||
if (script.text !== void 0) { | ||
script.text = js; | ||
} else { | ||
script.innerHTML = js; | ||
} | ||
_setupFallback: function() { | ||
this.module.isWorker = false; | ||
this.module.setup && this.module.setup(); | ||
}, | ||
iDoc.documentElement.appendChild(script); | ||
destroy: function() { | ||
this.isDestroyed = true; | ||
if (this.worker) { | ||
this.worker.terminate(); | ||
for (var i in self.dataProperties) { | ||
iWin[i] = self.dataProperties[i]; | ||
} | ||
self.isContextReady = true; | ||
self._dequeueAll(); | ||
}; | ||
iDoc.open(); | ||
if (this.dependencies.length) { | ||
iDoc.write( | ||
'<script src="' + this.dependencies.join('"></script><script src="') + '"></script>' | ||
); | ||
} | ||
iDoc.write('<script>__loaded__()</script>'); | ||
iDoc.close(); | ||
}; | ||
IframeProto._runMethod = function(methodName, token, args) { | ||
var self = this; | ||
var callback = this.callbacks[token]; | ||
var deferred = this.deferreds[token]; | ||
delete this.callbacks[token]; | ||
delete this.deferreds[token]; | ||
this.iframeWindow.__run__(methodName, args, function() { | ||
var cb = callback; | ||
if (cb) { | ||
callback = null; | ||
cb.apply(self, arguments); | ||
} else { | ||
throw new Error('Operative: You have already returned.'); | ||
} | ||
}, deferred); | ||
}; | ||
IframeProto.destroy = function() { | ||
this.iframe.parentNode.removeChild(this.iframe); | ||
Operative.prototype.destroy.call(this); | ||
}; | ||
operative.Operative = Operative; | ||
function operative(methods) { | ||
return new Operative(methods); | ||
/** | ||
* Exposed operative factory | ||
*/ | ||
function operative(module, dependencies) { | ||
var OperativeContext = operative.hasWorkerSupport ? | ||
Operative.Worker : Operative.Iframe; | ||
if (typeof module == 'function') { | ||
// Allow a single function to be passed. | ||
var o = new OperativeContext({ main: module }, dependencies); | ||
return function() { | ||
return o.api.main.apply(o, arguments); | ||
}; | ||
} | ||
return new OperativeContext(module, dependencies).api; | ||
} | ||
/** | ||
* The boilerplae for the Iframe Context | ||
* NOTE: | ||
* this'll be executed within an iframe, not here. | ||
* Indented @ Zero to make nicer debug code within worker | ||
*/ | ||
function iframeBoilerScript() { | ||
// Called from parent-window: | ||
window.__run__ = function(methodName, args, cb, deferred) { | ||
var isAsync = false; | ||
var isDeferred = false; | ||
window.async = function() { | ||
isAsync = true; | ||
return cb; | ||
}; | ||
window.deferred = function() { | ||
isDeferred = true; | ||
return deferred; | ||
}; | ||
if (cb) { | ||
args.push(cb); | ||
} | ||
var result = window[methodName].apply(window, args); | ||
window.async = function() { | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
window.deferred = function() { | ||
throw new Error('Operative: deferred() called at odd time'); | ||
}; | ||
if (!isDeferred && !isAsync && result !== void 0) { | ||
// Deprecated direct-returning as of 0.2.0 | ||
cb(result); | ||
} | ||
}; | ||
} | ||
/** | ||
* The boilerplate for the Worker Blob | ||
* (Be warned: this'll be executed within a worker, not here.) | ||
* Note: Indented @ Zero to make nicer debug code within worker :) | ||
* NOTE: | ||
* this'll be executed within an iframe, not here. | ||
* Indented @ Zero to make nicer debug code within worker | ||
*/ | ||
@@ -428,2 +585,5 @@ function workerBoilerScript() { | ||
var defs = data.definitions; | ||
var isDeferred = false; | ||
var isAsync = false; | ||
var args = data.args; | ||
@@ -435,12 +595,15 @@ if (defs) { | ||
} | ||
self.setup && self.setup(); | ||
return; | ||
} | ||
var isAsync = false; | ||
var isDeferred = false; | ||
args.push(function() { | ||
// Callback function to be passed to operative method | ||
returnResult({ | ||
args: [].slice.call(arguments) | ||
}); | ||
}); | ||
self.async = function() { | ||
self.async = function() { // Async deprecated as of 0.2.0 | ||
isAsync = true; | ||
return function(r) { returnResult(r); }; | ||
return function() { returnResult({ args: [].slice.call(arguments) }); }; | ||
}; | ||
@@ -455,3 +618,3 @@ | ||
action: 'fulfill', | ||
arg: r | ||
args: [r] | ||
}); | ||
@@ -464,3 +627,3 @@ return def; | ||
action: 'reject', | ||
arg: r | ||
args: [r] | ||
}); | ||
@@ -473,8 +636,12 @@ } | ||
var result = self[data.method].apply(self, data.args); | ||
// Call actual operative method: | ||
var result = self[data.method].apply(self, args); | ||
// Clear async/deferred so they're not accidentally used by other code | ||
self.async = function() { | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
if (!isDeferred && !isAsync && result !== void 0) { | ||
// Deprecated direct-returning as of 0.2.0 | ||
returnResult({ | ||
args: [result] | ||
}); | ||
} | ||
self.deferred = function() { | ||
@@ -484,5 +651,5 @@ throw new Error('Operative: deferred() called at odd time'); | ||
if (!isAsync && !isDeferred) { | ||
returnResult(result); | ||
} | ||
self.async = function() { // Async deprecated as of 0.2.0 | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
@@ -495,2 +662,6 @@ function returnResult(res) { | ||
}); | ||
// Override with error-thrower if we've already returned: | ||
returnResult = function() { | ||
throw new Error('Operative: You have already returned.'); | ||
}; | ||
} | ||
@@ -497,0 +668,0 @@ }); |
@@ -1,2 +0,2 @@ | ||
/** Operative v0.1.0 (c) 2013 James padolsey, MIT-licensed, http://github.com/padolsey/operative **/ | ||
(function(){function makeBlobURI(e){var r;try{r=new Blob([e],{type:"text/javascript"})}catch(t){r=new BlobBuilder,r.append(e),r=r.getBlob()}return URL.createObjectURL(r)}function Operative(e){var r=this;if("function"==typeof e){var t=new Operative({main:e});return function(){return t.main.apply(t,arguments)}}e.get=e.get||function(e){return this[e]},e.set=e.set||function(e,r){return this[e]=r},this._msgQueue=[],this._curToken=0,this.isDestroyed=!1,this.workerIsReady=!1,this.module=e,this.dataProperties={},this.api={},this.callbacks={},this.deferreds={},operative.hasWorkerSupport?this._setupWorker():this._setupFallback();for(var s in e)hasOwn.call(e,s)&&this._createExposedMethod(s);return this.api.__operative__=this,this.api.destroy=function(){return r.destroy()},this.api}function operative(e){return new Operative(e)}function workerBoilerScript(){var postMessage=self.postMessage,objectTransferSupport=null;self.console={},self.isWorker=!0,["log","debug","error","info","warn","time","timeEnd"].forEach(function(e){self.console[e]=function(){postMessage({cmd:"console",method:e,args:[].slice.call(arguments)})}}),self.addEventListener("message",function(e){function returnResult(e){postMessage({cmd:"result",token:data.token,result:e})}var data=e.data;if("string"==typeof data&&0===data.indexOf("EVAL|"))return eval(data.substring(5)),void 0;if(null==objectTransferSupport)return objectTransferSupport="PING"===e.data[0],self.postMessage(objectTransferSupport?"pingback:objectTransferSupport=YES":"pingback:objectTransferSupport=NO"),objectTransferSupport||(postMessage=function(e){return self.postMessage(JSON.stringify(e))}),void 0;objectTransferSupport||(data=JSON.parse(data));var defs=data.definitions;if(defs){for(var i in defs)self[i]=defs[i];return self.setup&&self.setup(),void 0}var isAsync=!1,isDeferred=!1;self.async=function(){return isAsync=!0,function(e){returnResult(e)}},self.deferred=function(){function e(e){return returnResult({isDeferred:!0,action:"fulfill",arg:e}),t}function r(e){returnResult({isDeferred:!0,action:"reject",arg:e})}isDeferred=!0;var t={};return t.fulfil=t.fulfill=e,t.reject=r,t};var result=self[data.method].apply(self,data.args);self.async=function(){throw Error("Operative: async() called at odd time")},self.deferred=function(){throw Error("Operative: deferred() called at odd time")},isAsync||isDeferred||returnResult(result)})}if("undefined"==typeof window&&self.importScripts)return workerBoilerScript(),void 0;var slice=[].slice,hasOwn={}.hasOwnProperty,scripts=document.getElementsByTagName("script"),opScript=scripts[scripts.length-1],opScriptURL=/operative/.test(opScript.src)&&opScript.src,URL=window.URL||window.webkitURL,BlobBuilder=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder,workerViaBlobSupport=function(){try{new Worker(makeBlobURI(";"))}catch(e){return!1}return!0}();operative.hasWorkerSupport=!!window.Worker,operative.Promise=window.Promise,"undefined"!=typeof module&&module.exports?module.exports=operative:window.operative=operative,operative.setSelfURL=function(e){opScriptURL=e},Operative.prototype={_buildWorkerScript:function(e){var r,t=[],s=this.module,o=this.dataProperties;for(var i in s){var r=s[i];"function"==typeof r?t.push(' self["'+i.replace(/"/g,'\\"')+'"] = '+(""+r)+";"):o[i]=r}return t.join("\n")+(e?"\n("+(""+workerBoilerScript)+"());":"")},_onWorkerMessage:function(e){var r=e.data;if("string"==typeof r&&0===r.indexOf("pingback"))return"pingback:objectTransferSupport=NO"===r&&(this._marshal=function(e){return JSON.stringify(e)},this._demarshal=function(e){return JSON.parse(e)}),this.workerIsReady=!0,this._postQueudMessages(),void 0;switch(r=this._demarshal(r),r.cmd){case"console":window.console&&window.console[r.method].apply(window.console,r.args);break;case"result":var t=this.callbacks[r.token],s=this.deferreds[r.token];delete this.callbacks[r.token],delete this.deferreds[r.token];var o=r.result&&r.result.isDeferred&&r.result.action;s&&o?s[o](r.result.arg):t&&t(r.result)}},_marshal:function(e){return e},_demarshal:function(e){return e},_postWorkerMessage:function(e){return this.workerIsReady?this.worker.postMessage(this._marshal(e)):(this._msgQueue.push(e),void 0)},_postQueudMessages:function(){for(var e=this._msgQueue,r=0,t=e.length;t>r;++r)this._postWorkerMessage(e[r]);this._msgQueue=null},_setupWorker:function(){var e,r=this;if(workerViaBlobSupport)e=this.worker=new Worker(makeBlobURI(this._buildWorkerScript(!0)));else{if(!opScriptURL)throw Error("Operaritve: No operative.js URL available. Please set via operative.setSelfURL(...)");e=this.worker=new Worker(opScriptURL),e.postMessage("EVAL|"+this._buildWorkerScript(!1))}e.postMessage(["PING"]),e.addEventListener("message",function(e){r._onWorkerMessage(e)}),this._postWorkerMessage({definitions:this.dataProperties})},_createExposedMethod:function(e){var r=this;this.api[e]=function(){function t(t){var s=!1;r.module.async=function(){return s=!0,n},r.module.deferred=function(){return t};var o=r.module[e].apply(r.module,i);r.module.async=function(){throw Error("Operative: async() called at odd time")},s||t||n(o)}function s(){r._postWorkerMessage({method:e,args:i,token:o})}if(r.isDestroyed)throw Error("Operative: Cannot run method. Operative has already been destroyed");var o=++r._curToken,i=slice.call(arguments),n="function"==typeof i[i.length-1]&&i.pop();if(!n&&!operative.Promise)throw Error("Operative: No callback has been passed. Assumed that you want a promise. But `operative.Promise` is null. Please provide Promise polyfill/lib.");if(operative.hasWorkerSupport){if(n)r.callbacks[o]=n,s();else if(operative.Promise)return new operative.Promise(function(e){r.deferreds[o]=e,s()})}else if(n)setTimeout(function(){t()},1);else if(operative.Promise)return new operative.Promise(function(e){e.fulfil=e.fulfill,setTimeout(function(){t(e)},1)})}},_setupFallback:function(){this.module.isWorker=!1,this.module.setup&&this.module.setup()},destroy:function(){this.isDestroyed=!0,this.worker&&this.worker.terminate()}},operative.Operative=Operative})(); | ||
/** Operative v0.2.0 (c) 2013 James padolsey, MIT-licensed, http://github.com/padolsey/operative **/ | ||
(function(){function makeBlobURI(e){var t;try{t=new Blob([e],{type:"text/javascript"})}catch(r){t=new BlobBuilder,t.append(e),t=t.getBlob()}return URL.createObjectURL(t)}function Operative(e,t){var r=this;e.get=e.get||function(e){return this[e]},e.set=e.set||function(e,t){return this[e]=t},this._curToken=0,this._queue=[],this.isDestroyed=!1,this.isContextReady=!1,this.module=e,this.dependencies=t||[],this.dataProperties={},this.api={},this.callbacks={},this.deferreds={},this._fixDependencyURLs(),this._setup();for(var o in e)hasOwn.call(e,o)&&this._createExposedMethod(o);this.api.__operative__=this,this.api.destroy=function(){return r.destroy()}}function operative(e,t){var r=operative.hasWorkerSupport?Operative.Worker:Operative.Iframe;if("function"==typeof e){var o=new r({main:e},t);return function(){return o.api.main.apply(o,arguments)}}return new r(e,t).api}function iframeBoilerScript(){window.__run__=function(e,t,r,o){var i=!1,s=!1;window.async=function(){return i=!0,r},window.deferred=function(){return s=!0,o},r&&t.push(r);var n=window[e].apply(window,t);window.async=function(){throw Error("Operative: async() called at odd time")},window.deferred=function(){throw Error("Operative: deferred() called at odd time")},s||i||void 0===n||r(n)}}function workerBoilerScript(){var postMessage=self.postMessage,objectTransferSupport=null;self.console={},self.isWorker=!0,["log","debug","error","info","warn","time","timeEnd"].forEach(function(e){self.console[e]=function(){postMessage({cmd:"console",method:e,args:[].slice.call(arguments)})}}),self.addEventListener("message",function(e){function returnResult(e){postMessage({cmd:"result",token:data.token,result:e}),returnResult=function(){throw Error("Operative: You have already returned.")}}var data=e.data;if("string"==typeof data&&0===data.indexOf("EVAL|"))return eval(data.substring(5)),void 0;if(null==objectTransferSupport)return objectTransferSupport="PING"===e.data[0],self.postMessage(objectTransferSupport?"pingback:objectTransferSupport=YES":"pingback:objectTransferSupport=NO"),objectTransferSupport||(postMessage=function(e){return self.postMessage(JSON.stringify(e))}),void 0;objectTransferSupport||(data=JSON.parse(data));var defs=data.definitions,isDeferred=!1,isAsync=!1,args=data.args;if(defs)for(var i in defs)self[i]=defs[i];else{args.push(function(){returnResult({args:[].slice.call(arguments)})}),self.async=function(){return isAsync=!0,function(){returnResult({args:[].slice.call(arguments)})}},self.deferred=function(){function e(e){return returnResult({isDeferred:!0,action:"fulfill",args:[e]}),r}function t(e){returnResult({isDeferred:!0,action:"reject",args:[e]})}isDeferred=!0;var r={};return r.fulfil=r.fulfill=e,r.reject=t,r};var result=self[data.method].apply(self,args);isDeferred||isAsync||void 0===result||returnResult({args:[result]}),self.deferred=function(){throw Error("Operative: deferred() called at odd time")},self.async=function(){throw Error("Operative: async() called at odd time")}}})}if("undefined"==typeof window&&self.importScripts)return workerBoilerScript(),void 0;var slice=[].slice,hasOwn={}.hasOwnProperty,scripts=document.getElementsByTagName("script"),opScript=scripts[scripts.length-1],opScriptURL=/operative/.test(opScript.src)&&opScript.src,baseURL=(location.protocol+"//"+location.hostname+(location.port?":"+location.port:"")+location.pathname).replace(/[^\/]+$/,""),URL=window.URL||window.webkitURL,BlobBuilder=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder,workerViaBlobSupport=function(){try{new Worker(makeBlobURI(";"))}catch(e){return!1}return!0}(),objCreate=Object.create||function(e){function t(){}return t.prototype=e,new t};operative.hasWorkerSupport=!!window.Worker,operative.Promise=window.Promise,"undefined"!=typeof module&&module.exports?module.exports=operative:window.operative=operative,operative.setSelfURL=function(e){opScriptURL=e},operative.setBaseURL=function(e){baseURL=e},operative.getBaseURL=function(){return baseURL},Operative.prototype={_marshal:function(e){return e},_demarshal:function(e){return e},_enqueue:function(e){this._queue.push(e)},_fixDependencyURLs:function(){for(var e=this.dependencies,t=0,r=e.length;r>t;++t){var o=e[t];/\/\//.test(o)||(e[t]=o.replace(/^\/?/,baseURL))}},_dequeueAll:function(){for(var e=0,t=this._queue.length;t>e;++e)this._queue[e].call(this);this._queue=[]},_buildContextScript:function(e){var t,r=[],o=this.module,i=this.dataProperties;for(var s in o)t=o[s],"function"==typeof t?r.push(' self["'+s.replace(/"/g,'\\"')+'"] = '+(""+t)+";"):i[s]=t;return r.join("\n")+(e?"\n("+(""+e)+"());":"")},_createExposedMethod:function(e){var t=this;this.api[e]=function(){function r(){t.isContextReady?t._runMethod(e,o,i):t._enqueue(r)}if(t.isDestroyed)throw Error("Operative: Cannot run method. Operative has already been destroyed");var o=++t._curToken,i=slice.call(arguments),s="function"==typeof i[i.length-1]&&i.pop();if(!s&&!operative.Promise)throw Error("Operative: No callback has been passed. Assumed that you want a promise. But `operative.Promise` is null. Please provide Promise polyfill/lib.");if(s)t.callbacks[o]=s,setTimeout(function(){r()},1);else if(operative.Promise)return new operative.Promise(function(e){e.fulfil=e.fulfill,t.deferreds[o]=e,r()})}},destroy:function(){this.isDestroyed=!0}},Operative.Worker=function Worker(){this._msgQueue=[],Operative.apply(this,arguments)};var WorkerProto=Operative.Worker.prototype=objCreate(Operative.prototype);WorkerProto._onWorkerMessage=function(e){var t=e.data;if("string"==typeof t&&0===t.indexOf("pingback"))return"pingback:objectTransferSupport=NO"===t&&(this._marshal=function(e){return JSON.stringify(e)},this._demarshal=function(e){return JSON.parse(e)}),this.isContextReady=!0,this._dequeueAll(),void 0;switch(t=this._demarshal(t),t.cmd){case"console":window.console&&window.console[t.method].apply(window.console,t.args);break;case"result":var r=this.callbacks[t.token],o=this.deferreds[t.token];delete this.callbacks[t.token],delete this.deferreds[t.token];var i=t.result&&t.result.isDeferred&&t.result.action;o&&i?o[i](t.result.args[0]):r&&r.apply(this,t.result.args)}},WorkerProto._setup=function(){var e,t=this,r=this._buildContextScript(workerBoilerScript);if(this.dependencies.length&&(r='importScripts("'+this.dependencies.join('", "')+'");\n'+r),workerViaBlobSupport)e=this.worker=new Worker(makeBlobURI(r));else{if(!opScriptURL)throw Error("Operaritve: No operative.js URL available. Please set via operative.setSelfURL(...)");e=this.worker=new Worker(opScriptURL),e.postMessage("EVAL|"+this._buildWorkerScript(null))}e.postMessage(["PING"]),e.addEventListener("message",function(e){t._onWorkerMessage(e)}),this._postMessage({definitions:this.dataProperties})},WorkerProto._postMessage=function(e){return this.worker.postMessage(this._marshal(e))},WorkerProto._runMethod=function(e,t,r){this._postMessage({method:e,args:r,token:t})},WorkerProto.destroy=function(){this.worker.terminate(),Operative.prototype.destroy.call(this)},Operative.Iframe=function Iframe(){Operative.apply(this,arguments)};var IframeProto=Operative.Iframe.prototype=objCreate(Operative.prototype);IframeProto._setup=function(){var e=this;this.module.isWorker=!1;var t=this.iframe=document.body.appendChild(document.createElement("iframe"));t.style.display="none";var r=this.iframeWindow=t.contentWindow,o=r.document;r.__loaded__=function(){var t=o.createElement("script"),i=e._buildContextScript(iframeBoilerScript);void 0!==t.text?t.text=i:t.innerHTML=i,o.documentElement.appendChild(t);for(var s in e.dataProperties)r[s]=e.dataProperties[s];e.isContextReady=!0,e._dequeueAll()},o.open(),this.dependencies.length&&o.write('<script src="'+this.dependencies.join('"></script><script src="')+'"></script>'),o.write("<script>__loaded__()</script>"),o.close()},IframeProto._runMethod=function(e,t,r){var o=this,i=this.callbacks[t],s=this.deferreds[t];delete this.callbacks[t],delete this.deferreds[t],this.iframeWindow.__run__(e,r,function(){var e=i;if(!e)throw Error("Operative: You have already returned.");i=null,e.apply(o,arguments)},s)},IframeProto.destroy=function(){this.iframe.parentNode.removeChild(this.iframe),Operative.prototype.destroy.call(this)},operative.Operative=Operative})(); |
@@ -5,3 +5,3 @@ { | ||
"description": "Operative: Inline Web-Worker Helper", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"author": "James Padolsey (http://git.io/padolsey)", | ||
@@ -8,0 +8,0 @@ "main": "dist/operative.min.js", |
@@ -29,4 +29,4 @@ # Operative | ||
var calculator = operative({ | ||
add: function(a, b) { | ||
return a + b; | ||
add: function(a, b, cb) { | ||
cb( a + b ); | ||
} | ||
@@ -57,3 +57,3 @@ }); | ||
doStuff: function() { | ||
return something + 456; | ||
something += 456; | ||
} | ||
@@ -71,3 +71,3 @@ }); | ||
doStuff: function() { | ||
return this.something + 456; | ||
this.something += 456; | ||
} | ||
@@ -82,3 +82,3 @@ }); | ||
doCrazy: function() { | ||
doCrazy: function(cb) { | ||
@@ -89,3 +89,3 @@ console.time('Craziness'); | ||
return 'I am done!'; | ||
cb('I am done!'); | ||
} | ||
@@ -119,3 +119,3 @@ | ||
* Full Worker via Eval & JSON marshalling (Sf4) | ||
* No Worker: Regular JS called inline (*older browsers*) | ||
* No Worker: Regular JS called via iframe (*older browsers*) | ||
@@ -133,2 +133,4 @@ Operative will degrade in environments with no Worker or Blob support. In such a case the code would execute as regular in-place JavaScript. The calls will still be asynchronous though, not immediate. | ||
* *{Function}* *operative*: A global function which creates a new Operative module with the passed methods/properties. Note: Non-function properties must be basic objects that can be passed to `JSON.stringify`. | ||
* Pass an object of methods, e.g. `operative({ method: function() {...} ... });` | ||
* Or pass a single function, e.g. `operative(function() {})` (*in which case a single function is returned*) | ||
* *{Boolean}* *operative.hasWorkerSupport*: A boolean indicating whether both Blob and Worker support is detected. | ||
@@ -143,6 +145,6 @@ * *{Function}* *operative.setSelfURL*: Allows you to set the URL of the operative script. Use this if you want IE10 & Safari 4/5 support *and* you're not including operative by the conventional `<script src="operative.js"></script>`. | ||
var myOperative = operative({ | ||
doX: function(a, b, c) { | ||
doX: function(a, b, c, callback) { | ||
// ... | ||
}, | ||
doY: function(a, b, c) { | ||
doY: function(a, b, c, callback) { | ||
// ... | ||
@@ -156,4 +158,5 @@ } | ||
```js | ||
var myOperative = operative(function(a, b, c) { | ||
var myOperative = operative(function(a, b, c, callback) { | ||
// Send the result to the parent page: | ||
callback(...); | ||
}); | ||
@@ -167,7 +170,7 @@ | ||
The most simple way to use operative is to pass in a callback function when calling an operative function and within the operative method returning the result directly: | ||
The most simple way to use operative is to pass in a callback function when calling an operative function and within the operative method call the callback with your result: | ||
```js | ||
var combine = operative(function(foo, bar) { | ||
return foo + bar; | ||
var combine = operative(function(foo, bar, callback) { | ||
callback(foo + bar); | ||
}); | ||
@@ -182,17 +185,2 @@ | ||
To implement a basic asynchronous return, but with the same callback pattern, you can utilise `this.async()` within your operative: | ||
```js | ||
var combine = operative(function(foo, bar) { | ||
var finish = this.async(); | ||
setTimeout(function() { | ||
finish(foo + bar); | ||
}, 1000); // example async | ||
}); | ||
combine('foo', 'bar', function() { | ||
result; // => 'foobar' | ||
}); | ||
``` | ||
#### Return via Promises | ||
@@ -230,5 +218,47 @@ | ||
#### Delcaring dependencies | ||
Operative accepts a second argument, an array of JS files to load within the worker ( *or in its degraded state, an Iframe* ): | ||
```js | ||
// Create interface to call lodash methods inside a worker: | ||
var lodashWorker = operative(function(method, args, cb) { | ||
cb( | ||
_[method].apply(_, args) | ||
); | ||
}, [ | ||
'http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.3.1/lodash.min.js' | ||
]); | ||
lodashWorker('uniq', [[1, 2, 3, 3, 2, 1, 4, 3, 2]], function(output) { | ||
output; // => [1, 2, 3, 4] | ||
}); | ||
``` | ||
Declared dependencies will be loaded before any of your operative's methods are called. Even if you call one from the outside, that call will be queued until the context (Worker/iFrame) completes loading the dependencies. | ||
Note: Each dependency, if not an absolute URL, will be loaded relative to the calculated base URL, which operative determines like so: | ||
```js | ||
var baseURL = ( | ||
location.protocol + '//' + | ||
location.hostname + | ||
(location.port?':'+location.port:'') + | ||
location.pathname | ||
).replace(/[^\/]+$/, ''); | ||
``` | ||
To override at runtime use: | ||
```js | ||
operative.setBaseURL('http://some.com/new/absolute/base/'); | ||
// Ensure it ends in a '/' | ||
// To retrieve the current Base URL: | ||
operative.getBaseURL(); | ||
``` | ||
#### Destroying an operative | ||
To destroy the operative (and thus its worker): | ||
To destroy the operative (and thus its worker/iframe): | ||
@@ -257,3 +287,7 @@ ```js | ||
* 0.1.0 (25 Jul 2013) Support Promises (from [Issue #3](https://github.com/padolsey/operative/issues/3)) if they're provided by a [native Promise implementation](http://dom.spec.whatwg.org/#promises) or [compliant polyfill](https://github.com/slightlyoff/Promises) | ||
* 0.2.0 (29 Jul 2013) See #10 | ||
* Dependency Loading (initially suggested in #8) | ||
* Deprecate direct returning in favour of a callback passed to each operative invocation. | ||
* Fallback to IFrame (to provide safer sandbox for degraded state) | ||
* 0.1.0 (25 Jul 2013) Support Promises (from [Issue #3](https://github.com/padolsey/operative/issues/3)) if they're provided by a [native Promise implementation](http://dom.spec.whatwg.org/#promises) or [compliant polyfill](https://github.com/slightlyoff/Promises). Also added support for `operative(Function)` which returns a single function. | ||
* 0.0.3 (18 Jul 2013) Support for asynchronous returning from within operartive methods (via `this.async()`). | ||
@@ -263,3 +297,3 @@ * 0.0.2 (12 Jul 2013) Improved browser support: IE10 support via eval, degraded JSON-marshalling etc. | ||
### Contributors | ||
### Contributors | ||
@@ -266,0 +300,0 @@ * [James Padolsey](https://github.com/padolsey) |
@@ -14,5 +14,6 @@ /** | ||
if (typeof window == 'undefined' && self.importScripts) { | ||
if (typeof window == 'undefined' && self.importScripts) { | ||
// I'm a worker! Run the boiler-script: | ||
// (Operative itself is called in IE10 as a worker, to avoid SecurityErrors) | ||
// (Operative itself is called in IE10 as a worker, | ||
// to avoid SecurityErrors) | ||
workerBoilerScript(); | ||
@@ -29,2 +30,11 @@ return; | ||
// Default base URL (to be prepended to relative dependency URLs) | ||
// is current page's parent dir: | ||
var baseURL = ( | ||
location.protocol + '//' + | ||
location.hostname + | ||
(location.port?':'+location.port:'') + | ||
location.pathname | ||
).replace(/[^\/]+$/, ''); | ||
var URL = window.URL || window.webkitURL; | ||
@@ -42,2 +52,11 @@ var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; | ||
/** | ||
* Provide Object.create shim | ||
*/ | ||
var objCreate = Object.create || function(o) { | ||
function F() {} | ||
F.prototype = o; | ||
return new F(); | ||
}; | ||
function makeBlobURI(script) { | ||
@@ -48,3 +67,3 @@ var blob; | ||
blob = new Blob([script], { type: 'text/javascript' }); | ||
} catch (e) { | ||
} catch (e) { | ||
blob = new BlobBuilder(); | ||
@@ -75,2 +94,10 @@ blob.append(script); | ||
operative.setBaseURL = function(base) { | ||
baseURL = base; | ||
}; | ||
operative.getBaseURL = function() { | ||
return baseURL; | ||
}; | ||
/** | ||
@@ -80,14 +107,6 @@ * Operative: Exposed Operative Constructor | ||
*/ | ||
function Operative(module) { | ||
function Operative(module, dependencies) { | ||
var _self = this; | ||
if (typeof module == 'function') { | ||
// Allow a single function to be passed. | ||
var o = new Operative({ main: module }); | ||
return function() { | ||
return o.main.apply(o, arguments); | ||
}; | ||
} | ||
module.get = module.get || function(prop) { | ||
@@ -101,11 +120,12 @@ return this[prop]; | ||
this._msgQueue = []; | ||
this._curToken = 0; | ||
this._queue = []; | ||
this.isDestroyed = false; | ||
this.workerIsReady = false; | ||
this.isContextReady = false; | ||
this.module = module; | ||
this.dependencies = dependencies || []; | ||
this.dataProperties = {}; | ||
this.api = {}; | ||
@@ -115,7 +135,4 @@ this.callbacks = {}; | ||
if (operative.hasWorkerSupport) { | ||
this._setupWorker(); | ||
} else { | ||
this._setupFallback(); | ||
} | ||
this._fixDependencyURLs(); | ||
this._setup(); | ||
@@ -135,3 +152,2 @@ for (var methodName in module) { | ||
return this.api; | ||
} | ||
@@ -141,4 +157,33 @@ | ||
_buildWorkerScript: function(doIncludeBoilerScript) { | ||
_marshal: function(v) { | ||
return v; | ||
}, | ||
_demarshal: function(v) { | ||
return v; | ||
}, | ||
_enqueue: function(fn) { | ||
this._queue.push(fn); | ||
}, | ||
_fixDependencyURLs: function() { | ||
var deps = this.dependencies; | ||
for (var i = 0, l = deps.length; i < l; ++i) { | ||
var dep = deps[i]; | ||
if (!/\/\//.test(dep)) { | ||
deps[i] = dep.replace(/^\/?/, baseURL); | ||
} | ||
} | ||
}, | ||
_dequeueAll: function() { | ||
for (var i = 0, l = this._queue.length; i < l; ++i) { | ||
this._queue[i].call(this); | ||
} | ||
this._queue = []; | ||
}, | ||
_buildContextScript: function(boilerScript) { | ||
var script = []; | ||
@@ -150,3 +195,3 @@ var module = this.module; | ||
for (var i in module) { | ||
var property = module[i]; | ||
property = module[i]; | ||
if (typeof property == 'function') { | ||
@@ -160,3 +205,3 @@ script.push(' self["' + i.replace(/"/g, '\\"') + '"] = ' + property.toString() + ';'); | ||
return script.join('\n') + ( | ||
doIncludeBoilerScript ? '\n(' + workerBoilerScript.toString() + '());' : '' | ||
boilerScript ? '\n(' + boilerScript.toString() + '());' : '' | ||
); | ||
@@ -166,205 +211,317 @@ | ||
_onWorkerMessage: function(e) { | ||
var data = e.data; | ||
_createExposedMethod: function(methodName) { | ||
if (typeof data === 'string' && data.indexOf('pingback') === 0) { | ||
if (data === 'pingback:objectTransferSupport=NO') { | ||
// No transferrableObj support (marshal JSON from now on): | ||
this._marshal = function(o) { return JSON.stringify(o); }; | ||
this._demarshal = function(o) { return JSON.parse(o); }; | ||
var self = this; | ||
this.api[methodName] = function() { | ||
if (self.isDestroyed) { | ||
throw new Error('Operative: Cannot run method. Operative has already been destroyed'); | ||
} | ||
this.workerIsReady = true; | ||
this._postQueudMessages(); | ||
return; | ||
var token = ++self._curToken; | ||
var args = slice.call(arguments); | ||
var cb = typeof args[args.length - 1] == 'function' && args.pop(); | ||
} | ||
if (!cb && !operative.Promise) { | ||
throw new Error( | ||
'Operative: No callback has been passed. Assumed that you want a promise. ' + | ||
'But `operative.Promise` is null. Please provide Promise polyfill/lib.' | ||
); | ||
} | ||
data = this._demarshal(data); | ||
if (cb) { | ||
switch (data.cmd) { | ||
case 'console': | ||
window.console && window.console[data.method].apply(window.console, data.args); | ||
break; | ||
case 'result': | ||
self.callbacks[token] = cb; | ||
var callback = this.callbacks[data.token]; | ||
var deferred = this.deferreds[data.token]; | ||
// Ensure either context runs the method async: | ||
setTimeout(function() { | ||
runMethod(); | ||
}, 1); | ||
delete this.callbacks[data.token]; | ||
delete this.deferreds[data.token]; | ||
} else if (operative.Promise) { | ||
var deferredAction = data.result && data.result.isDeferred && data.result.action; | ||
// No Callback -- Promise used: | ||
if (deferred && deferredAction) { | ||
deferred[deferredAction](data.result.arg); | ||
} else if (callback) { | ||
callback(data.result); | ||
return new operative.Promise(function(deferred) { | ||
deferred.fulfil = deferred.fulfill; | ||
self.deferreds[token] = deferred; | ||
runMethod(); | ||
}); | ||
} | ||
function runMethod() { | ||
if (self.isContextReady) { | ||
self._runMethod(methodName, token, args); | ||
} else { | ||
self._enqueue(runMethod); | ||
} | ||
} | ||
break; | ||
} | ||
}, | ||
}; | ||
_marshal: function(v) { | ||
return v; | ||
}, | ||
_demarshal: function(v) { | ||
return v; | ||
}, | ||
destroy: function() { | ||
this.isDestroyed = true; | ||
} | ||
}; | ||
_postWorkerMessage: function(msg) { | ||
if (!this.workerIsReady) { | ||
this._msgQueue.push(msg); | ||
return; | ||
} | ||
return this.worker.postMessage(this._marshal(msg)); | ||
}, | ||
_postQueudMessages: function() { | ||
var msgQueue = this._msgQueue; | ||
for (var i = 0, l = msgQueue.length; i < l; ++i) { | ||
this._postWorkerMessage( msgQueue[i] ); | ||
/** | ||
* Operative Worker | ||
*/ | ||
Operative.Worker = function Worker(module) { | ||
this._msgQueue = []; | ||
Operative.apply(this, arguments); | ||
}; | ||
var WorkerProto = Operative.Worker.prototype = objCreate(Operative.prototype); | ||
WorkerProto._onWorkerMessage = function(e) { | ||
var data = e.data; | ||
if (typeof data === 'string' && data.indexOf('pingback') === 0) { | ||
if (data === 'pingback:objectTransferSupport=NO') { | ||
// No transferrableObj support (marshal JSON from now on): | ||
this._marshal = function(o) { return JSON.stringify(o); }; | ||
this._demarshal = function(o) { return JSON.parse(o); }; | ||
} | ||
this._msgQueue = null; | ||
}, | ||
_setupWorker: function() { | ||
this.isContextReady = true; | ||
this._dequeueAll(); | ||
return; | ||
var _self = this; | ||
} | ||
var worker; | ||
data = this._demarshal(data); | ||
if (workerViaBlobSupport) { | ||
worker = this.worker = new Worker( makeBlobURI(this._buildWorkerScript(true)) ); | ||
} else { | ||
if (!opScriptURL) { | ||
throw new Error('Operaritve: No operative.js URL available. Please set via operative.setSelfURL(...)'); | ||
switch (data.cmd) { | ||
case 'console': | ||
window.console && window.console[data.method].apply(window.console, data.args); | ||
break; | ||
case 'result': | ||
var callback = this.callbacks[data.token]; | ||
var deferred = this.deferreds[data.token]; | ||
delete this.callbacks[data.token]; | ||
delete this.deferreds[data.token]; | ||
var deferredAction = data.result && data.result.isDeferred && data.result.action; | ||
if (deferred && deferredAction) { | ||
deferred[deferredAction](data.result.args[0]); | ||
} else if (callback) { | ||
callback.apply(this, data.result.args); | ||
} | ||
worker = this.worker = new Worker( opScriptURL ); | ||
// Marshal-agnostic initial message is boiler-code: | ||
// (We don't yet know if transferrableObjs are supported so we send a string) | ||
worker.postMessage('EVAL|' + this._buildWorkerScript(false)); | ||
} | ||
worker.postMessage(['PING']); // Initial PING | ||
break; | ||
} | ||
}; | ||
worker.addEventListener('message', function(e) { | ||
_self._onWorkerMessage(e); | ||
}); | ||
WorkerProto._setup = function() { | ||
var self = this; | ||
this._postWorkerMessage({ | ||
definitions: this.dataProperties | ||
}); | ||
var worker; | ||
var script = this._buildContextScript(workerBoilerScript); | ||
}, | ||
if (this.dependencies.length) { | ||
script = 'importScripts("' + this.dependencies.join('", "') + '");\n' + script; | ||
} | ||
_createExposedMethod: function(methodName) { | ||
if (workerViaBlobSupport) { | ||
worker = this.worker = new Worker( makeBlobURI(script) ); | ||
} else { | ||
if (!opScriptURL) { | ||
throw new Error('Operaritve: No operative.js URL available. Please set via operative.setSelfURL(...)'); | ||
} | ||
worker = this.worker = new Worker( opScriptURL ); | ||
// Marshal-agnostic initial message is boiler-code: | ||
// (We don't yet know if transferrableObjs are supported so we send a string) | ||
worker.postMessage('EVAL|' + this._buildWorkerScript(null)); | ||
} | ||
var _self = this; | ||
worker.postMessage(['PING']); // Initial PING | ||
this.api[methodName] = function() { | ||
worker.addEventListener('message', function(e) { | ||
self._onWorkerMessage(e); | ||
}); | ||
if (_self.isDestroyed) { | ||
throw new Error('Operative: Cannot run method. Operative has already been destroyed'); | ||
} | ||
this._postMessage({ | ||
definitions: this.dataProperties | ||
}); | ||
}; | ||
var token = ++_self._curToken; | ||
var args = slice.call(arguments); | ||
var cb = typeof args[args.length - 1] == 'function' && args.pop(); | ||
WorkerProto._postMessage = function(msg) { | ||
return this.worker.postMessage(this._marshal(msg)); | ||
}; | ||
if (!cb && !operative.Promise) { | ||
throw new Error( | ||
'Operative: No callback has been passed. Assumed that you want a promise. ' + | ||
'But `operative.Promise` is null. Please provide Promise polyfill/lib.' | ||
); | ||
} | ||
WorkerProto._runMethod = function(methodName, token, args) { | ||
this._postMessage({ | ||
method: methodName, | ||
args: args, | ||
token: token | ||
}); | ||
}; | ||
if (operative.hasWorkerSupport) { | ||
WorkerProto.destroy = function() { | ||
this.worker.terminate(); | ||
Operative.prototype.destroy.call(this); | ||
}; | ||
if (cb) { | ||
_self.callbacks[token] = cb; | ||
sendToWorker(); | ||
} else if (operative.Promise) { | ||
return new operative.Promise(function(deferred) { | ||
_self.deferreds[token] = deferred; | ||
sendToWorker(); | ||
}); | ||
} | ||
} else { | ||
if (cb) { | ||
setTimeout(function() { | ||
runInline(); | ||
}, 1); | ||
} else if (operative.Promise) { | ||
return new operative.Promise(function(deferred) { | ||
deferred.fulfil = deferred.fulfill; | ||
setTimeout(function() { | ||
runInline(deferred); | ||
}, 1); | ||
}); | ||
} | ||
} | ||
/** | ||
* Operative IFrame | ||
*/ | ||
Operative.Iframe = function Iframe(module) { | ||
Operative.apply(this, arguments); | ||
}; | ||
function runInline(deferred) { | ||
var IframeProto = Operative.Iframe.prototype = objCreate(Operative.prototype); | ||
var isAsync = false; | ||
IframeProto._setup = function() { | ||
_self.module.async = function() { | ||
isAsync = true; | ||
return cb; | ||
}; | ||
var self = this; | ||
_self.module.deferred = function() { | ||
return deferred; | ||
}; | ||
this.module.isWorker = false; | ||
var result = _self.module[methodName].apply(_self.module, args); | ||
var iframe = this.iframe = document.body.appendChild( | ||
document.createElement('iframe') | ||
); | ||
_self.module.async = function() { | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
iframe.style.display = 'none'; | ||
if (!isAsync && !deferred) { | ||
cb(result); | ||
} | ||
var iWin = this.iframeWindow = iframe.contentWindow; | ||
var iDoc = iWin.document; | ||
} | ||
iWin.__loaded__ = function() { | ||
function sendToWorker() { | ||
_self._postWorkerMessage({ | ||
method: methodName, | ||
args: args, | ||
token: token | ||
}); | ||
} | ||
}; | ||
var script = iDoc.createElement('script'); | ||
var js = self._buildContextScript(iframeBoilerScript); | ||
}, | ||
if (script.text !== void 0) { | ||
script.text = js; | ||
} else { | ||
script.innerHTML = js; | ||
} | ||
_setupFallback: function() { | ||
this.module.isWorker = false; | ||
this.module.setup && this.module.setup(); | ||
}, | ||
iDoc.documentElement.appendChild(script); | ||
destroy: function() { | ||
this.isDestroyed = true; | ||
if (this.worker) { | ||
this.worker.terminate(); | ||
for (var i in self.dataProperties) { | ||
iWin[i] = self.dataProperties[i]; | ||
} | ||
self.isContextReady = true; | ||
self._dequeueAll(); | ||
}; | ||
iDoc.open(); | ||
if (this.dependencies.length) { | ||
iDoc.write( | ||
'<script src="' + this.dependencies.join('"></script><script src="') + '"></script>' | ||
); | ||
} | ||
iDoc.write('<script>__loaded__()</script>'); | ||
iDoc.close(); | ||
}; | ||
IframeProto._runMethod = function(methodName, token, args) { | ||
var self = this; | ||
var callback = this.callbacks[token]; | ||
var deferred = this.deferreds[token]; | ||
delete this.callbacks[token]; | ||
delete this.deferreds[token]; | ||
this.iframeWindow.__run__(methodName, args, function() { | ||
var cb = callback; | ||
if (cb) { | ||
callback = null; | ||
cb.apply(self, arguments); | ||
} else { | ||
throw new Error('Operative: You have already returned.'); | ||
} | ||
}, deferred); | ||
}; | ||
IframeProto.destroy = function() { | ||
this.iframe.parentNode.removeChild(this.iframe); | ||
Operative.prototype.destroy.call(this); | ||
}; | ||
operative.Operative = Operative; | ||
function operative(methods) { | ||
return new Operative(methods); | ||
/** | ||
* Exposed operative factory | ||
*/ | ||
function operative(module, dependencies) { | ||
var OperativeContext = operative.hasWorkerSupport ? | ||
Operative.Worker : Operative.Iframe; | ||
if (typeof module == 'function') { | ||
// Allow a single function to be passed. | ||
var o = new OperativeContext({ main: module }, dependencies); | ||
return function() { | ||
return o.api.main.apply(o, arguments); | ||
}; | ||
} | ||
return new OperativeContext(module, dependencies).api; | ||
} | ||
/** | ||
* The boilerplae for the Iframe Context | ||
* NOTE: | ||
* this'll be executed within an iframe, not here. | ||
* Indented @ Zero to make nicer debug code within worker | ||
*/ | ||
function iframeBoilerScript() { | ||
// Called from parent-window: | ||
window.__run__ = function(methodName, args, cb, deferred) { | ||
var isAsync = false; | ||
var isDeferred = false; | ||
window.async = function() { | ||
isAsync = true; | ||
return cb; | ||
}; | ||
window.deferred = function() { | ||
isDeferred = true; | ||
return deferred; | ||
}; | ||
if (cb) { | ||
args.push(cb); | ||
} | ||
var result = window[methodName].apply(window, args); | ||
window.async = function() { | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
window.deferred = function() { | ||
throw new Error('Operative: deferred() called at odd time'); | ||
}; | ||
if (!isDeferred && !isAsync && result !== void 0) { | ||
// Deprecated direct-returning as of 0.2.0 | ||
cb(result); | ||
} | ||
}; | ||
} | ||
/** | ||
* The boilerplate for the Worker Blob | ||
* (Be warned: this'll be executed within a worker, not here.) | ||
* Note: Indented @ Zero to make nicer debug code within worker :) | ||
* NOTE: | ||
* this'll be executed within an iframe, not here. | ||
* Indented @ Zero to make nicer debug code within worker | ||
*/ | ||
@@ -428,2 +585,5 @@ function workerBoilerScript() { | ||
var defs = data.definitions; | ||
var isDeferred = false; | ||
var isAsync = false; | ||
var args = data.args; | ||
@@ -435,12 +595,15 @@ if (defs) { | ||
} | ||
self.setup && self.setup(); | ||
return; | ||
} | ||
var isAsync = false; | ||
var isDeferred = false; | ||
args.push(function() { | ||
// Callback function to be passed to operative method | ||
returnResult({ | ||
args: [].slice.call(arguments) | ||
}); | ||
}); | ||
self.async = function() { | ||
self.async = function() { // Async deprecated as of 0.2.0 | ||
isAsync = true; | ||
return function(r) { returnResult(r); }; | ||
return function() { returnResult({ args: [].slice.call(arguments) }); }; | ||
}; | ||
@@ -455,3 +618,3 @@ | ||
action: 'fulfill', | ||
arg: r | ||
args: [r] | ||
}); | ||
@@ -464,3 +627,3 @@ return def; | ||
action: 'reject', | ||
arg: r | ||
args: [r] | ||
}); | ||
@@ -473,8 +636,12 @@ } | ||
var result = self[data.method].apply(self, data.args); | ||
// Call actual operative method: | ||
var result = self[data.method].apply(self, args); | ||
// Clear async/deferred so they're not accidentally used by other code | ||
self.async = function() { | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
if (!isDeferred && !isAsync && result !== void 0) { | ||
// Deprecated direct-returning as of 0.2.0 | ||
returnResult({ | ||
args: [result] | ||
}); | ||
} | ||
self.deferred = function() { | ||
@@ -484,5 +651,5 @@ throw new Error('Operative: deferred() called at odd time'); | ||
if (!isAsync && !isDeferred) { | ||
returnResult(result); | ||
} | ||
self.async = function() { // Async deprecated as of 0.2.0 | ||
throw new Error('Operative: async() called at odd time'); | ||
}; | ||
@@ -495,2 +662,6 @@ function returnResult(res) { | ||
}); | ||
// Override with error-thrower if we've already returned: | ||
returnResult = function() { | ||
throw new Error('Operative: You have already returned.'); | ||
}; | ||
} | ||
@@ -497,0 +668,0 @@ }); |
@@ -1,211 +0,162 @@ | ||
var async = function async(fn) { | ||
// Little wrapper for async tests | ||
jasmine.getEnv().currentSpec.queue.add({ | ||
execute: function(next) { | ||
fn(next); | ||
} | ||
}); | ||
}; | ||
describe('Operative (Worker Context)', function() { | ||
describe('Operative', function() { | ||
describe('With Worker Support', function() { | ||
it('Works with basic calculator', function() { | ||
describe('Callback', function() { | ||
it('Is called and removed correctly', function() { | ||
var o = operative({ | ||
add: function(a, b) { | ||
return a + b; | ||
}, | ||
subtract: function(a, b) { | ||
return a - b; | ||
}, | ||
isItAWorker: function() { | ||
return this.isWorker; | ||
longAction: function(cb) { | ||
for (var i = 0; i < 10000000; ++i); | ||
cb(); | ||
} | ||
}); | ||
var callback = jasmine.createSpy('callback'); | ||
async(function(nxt) { | ||
o.add(1, 2, function(n) { | ||
expect(n).toBe(3); | ||
nxt(); | ||
}); | ||
o.longAction(callback); | ||
runs(function() { | ||
expect(o.__operative__.callbacks[1]).toBe(callback); | ||
}); | ||
async(function(nxt) { | ||
o.isItAWorker(function(isWorker) { | ||
expect(isWorker).toBe(true); | ||
nxt(); | ||
}); | ||
waitsFor(function() { | ||
return callback.callCount === 1; | ||
}); | ||
async(function(nxt) { | ||
o.subtract(100, 2, function(n) { | ||
expect(n).toBe(98); | ||
nxt(); | ||
}); | ||
runs(function() { | ||
expect(o.__operative__.callbacks[1]).not.toBeDefined(); | ||
}); | ||
}); | ||
}); | ||
describe('Callback', function() { | ||
it('Is called and removed correctly', function() { | ||
var o = operative({ | ||
longAction: function() { | ||
for (var i = 0; i < 10000000; ++i); | ||
} | ||
}); | ||
var callback = jasmine.createSpy('callback'); | ||
describe('Async Operative', function() { | ||
it('Should be able to return [within the worker] asynchronously', function() { | ||
var o = operative({ | ||
doAsyncFoo: function() { | ||
var finish = this.async(); | ||
setTimeout(function() { | ||
finish(123); | ||
}, 150); | ||
}, | ||
doAsyncBar: function() { | ||
var finish = this.async(); | ||
setTimeout(function() { | ||
finish(456); | ||
}, 10); | ||
} | ||
}); | ||
o.longAction(callback); | ||
var result = []; | ||
runs(function() { | ||
expect(o.__operative__.callbacks[1]).toBe(callback); | ||
}); | ||
runs(function() { | ||
o.doAsyncFoo(function(v) { result.push(v); }); | ||
o.doAsyncBar(function(v) { result.push(v); }); | ||
}); | ||
waitsFor(function() { | ||
return callback.callCount === 1; | ||
}); | ||
waitsFor(function(nxt) { | ||
return result.length === 2; | ||
}); | ||
runs(function() { | ||
expect(o.__operative__.callbacks[1]).not.toBeDefined(); | ||
}); | ||
runs(function() { | ||
expect(result).toEqual([456, 123]); | ||
}); | ||
}); | ||
}); | ||
describe('Async Operative', function() { | ||
it('Should be able to return [within the worker] asynchronously', function() { | ||
var o = operative({ | ||
doAsyncFoo: function() { | ||
var finish = this.async(); | ||
setTimeout(function() { | ||
finish(123); | ||
}, 150); | ||
}, | ||
doAsyncBar: function() { | ||
var finish = this.async(); | ||
setTimeout(function() { | ||
finish(456); | ||
}, 10); | ||
} | ||
}); | ||
describe('Multiple Operatives', function() { | ||
it('Each complete asynchronously', function() { | ||
var s = []; | ||
var a = operative({ | ||
run: function(cb) { | ||
for (var i = 0; i < 1000000; ++i); | ||
cb('A'); | ||
} | ||
}); | ||
var b = operative({ | ||
run: function(cb) { | ||
for (var i = 0; i < 1000; ++i); | ||
cb('B'); | ||
} | ||
}); | ||
var c = operative({ | ||
run: function(cb) { | ||
for (var i = 0; i < 1; ++i); | ||
cb('C'); | ||
} | ||
}); | ||
function add(v) { s.push(v); } | ||
var result = []; | ||
a.run(add); | ||
b.run(add); | ||
c.run(add); | ||
runs(function() { | ||
o.doAsyncFoo(function(v) { result.push(v); }); | ||
o.doAsyncBar(function(v) { result.push(v); }); | ||
}); | ||
waitsFor(function(nxt) { | ||
return result.length === 2; | ||
}); | ||
runs(function() { | ||
expect(result).toEqual([456, 123]); | ||
}); | ||
expect(s.length).toBe(0); | ||
waitsFor(function() { | ||
return s.length === 3; | ||
}); | ||
runs(function() { | ||
expect(s.sort().join('')).toBe('ABC'); | ||
}); | ||
}); | ||
}); | ||
describe('Multiple Operatives', function() { | ||
it('Each complete asynchronously', function() { | ||
var s = []; | ||
var a = operative({ | ||
run: function() { | ||
for (var i = 0; i < 1000000; ++i); | ||
return 'A'; | ||
describe('Promise API', function() { | ||
describe('Operative', function() { | ||
var op; | ||
beforeEach(function() { | ||
op = operative(function(beSuccessful, cb) { | ||
var deferred = this.deferred(); | ||
if (beSuccessful) { | ||
deferred.fulfil(873); | ||
} else { | ||
deferred.reject(999); | ||
} | ||
}); | ||
var b = operative({ | ||
run: function() { | ||
for (var i = 0; i < 1000; ++i); | ||
return 'B'; | ||
} | ||
}); | ||
var c = operative({ | ||
run: function() { | ||
for (var i = 0; i < 1; ++i); | ||
return 'C'; | ||
} | ||
}); | ||
function add(v) { s.push(v); } | ||
a.run(add); | ||
b.run(add); | ||
c.run(add); | ||
expect(s.length).toBe(0); | ||
waitsFor(function() { | ||
return s.length === 3; | ||
}); | ||
runs(function() { | ||
expect(s.sort().join('')).toBe('ABC'); | ||
}); | ||
}) | ||
it('Should return a promise', function() { | ||
expect(op() instanceof operative.Promise).toBe(true); | ||
}); | ||
}); | ||
describe('Promise API', function() { | ||
describe('Operative', function() { | ||
var op; | ||
beforeEach(function() { | ||
op = operative(function(beSuccessful) { | ||
var deferred = this.deferred(); | ||
if (beSuccessful) { | ||
return deferred.fulfil(873); | ||
} else { | ||
return deferred.reject(999); | ||
} | ||
describe('fulfil()', function() { | ||
it('Should fulfil the exposed promise', function() { | ||
var fulfilled = false; | ||
runs(function() { | ||
op(true).then(function(a) { | ||
expect(a).toBe(873); | ||
fulfilled = true; | ||
}, function() {}); | ||
}); | ||
}) | ||
it('Should return a promise', function() { | ||
expect(op() instanceof operative.Promise).toBe(true); | ||
}); | ||
describe('fulfil()', function() { | ||
it('Should fulfil the exposed promise', function() { | ||
var fulfilled = false; | ||
runs(function() { | ||
op(true).then(function(a) { | ||
expect(a).toBe(873); | ||
fulfilled = true; | ||
}, function() {}); | ||
}); | ||
waitsFor(function() { | ||
return fulfilled === true; | ||
}); | ||
waitsFor(function() { | ||
return fulfilled === true; | ||
}); | ||
}); | ||
describe('reject()', function() { | ||
it('Should reject the exposed promise', function() { | ||
var rejected = false; | ||
var fulfilled = false; | ||
runs(function() { | ||
op(false).then(function() { | ||
fulfilled = true; | ||
}, function(err) { | ||
expect(err).toBe(999); | ||
rejected = true; | ||
}); | ||
}); | ||
describe('reject()', function() { | ||
it('Should reject the exposed promise', function() { | ||
var rejected = false; | ||
var fulfilled = false; | ||
runs(function() { | ||
op(false).then(function() { | ||
fulfilled = true; | ||
}, function(err) { | ||
expect(err).toBe(999); | ||
rejected = true; | ||
}); | ||
waitsFor(function() { | ||
return rejected === true; | ||
}); | ||
runs(function() { | ||
expect(fulfilled).toBe(false); | ||
}); | ||
}); | ||
waitsFor(function() { | ||
return rejected === true; | ||
}); | ||
runs(function() { | ||
expect(fulfilled).toBe(false); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('Without Worker Support', function() { | ||
describe('An example stream of operations', function() { | ||
beforeEach(function() { | ||
operative.hasWorkerSupport = false; | ||
}); | ||
it('Works with basic calculator', function() { | ||
var o = operative({ | ||
something: 3333, | ||
setup: function() { | ||
setup: function(cb) { | ||
this.somethingElse = 4444; | ||
cb(); | ||
}, | ||
@@ -233,2 +184,6 @@ add: function(a, b) { | ||
async(function(nxt) { | ||
o.setup(nxt); | ||
}); | ||
async(function(nxt) { | ||
o.add(1, 2, function(n) { | ||
@@ -242,3 +197,3 @@ expect(n).toBe(3); | ||
o.isItAWorker_Promisable().then(function(isWorker) { | ||
expect(isWorker).toBe(false); | ||
expect(isWorker).toBe(true); | ||
nxt(); | ||
@@ -265,4 +220,2 @@ }, function() {}); | ||
expect(n).toBe(98); | ||
// END: | ||
operative.hasWorkerSupport = true; | ||
nxt(); | ||
@@ -276,2 +229,46 @@ }); | ||
describe('Dependency loading', function() { | ||
it('Can load dependencies', function() { | ||
var o = operative({ | ||
typeofDependency1: function(cb) { | ||
cb( typeof dependency1 ); | ||
}, | ||
typeofDependency2: function(cb) { | ||
cb( typeof dependency2 ); | ||
} | ||
}, ['dependency1.js', 'dependency2.js']); | ||
async(function(nxt) { | ||
o.typeofDependency1(function(t) { | ||
expect(t).toBe('function'); | ||
nxt(); | ||
}); | ||
}); | ||
async(function(nxt) { | ||
o.typeofDependency2(function(t) { | ||
expect(t).toBe('function'); | ||
nxt(); | ||
}); | ||
}); | ||
}); | ||
it('Can load external dependencies', function() { | ||
var o = operative({ | ||
version_: function(cb) { | ||
cb( _.VERSION ); | ||
} | ||
}, ['http://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.3.1/lodash.min.js']); | ||
async(function(nxt) { | ||
o.version_(function(t) { | ||
expect(t).toBe('1.3.1'); | ||
nxt(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,1 +0,15 @@ | ||
// Helpers can go here... | ||
// Helpers can go here... | ||
var async = function async(fn) { | ||
// Little wrapper for async tests | ||
jasmine.getEnv().currentSpec.queue.add({ | ||
execute: function(next) { | ||
fn(next); | ||
} | ||
}); | ||
}; | ||
if (/_SpecRunner/.test(location.href)) { | ||
// Ensure correct base-url for grunt-run jasmine: | ||
operative.setBaseURL( operative.getBaseURL() + 'test/resources/' ); | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
451445
22
3675
289