| |
| //== HEADLESS ==// |
| |
| var headlessPrint = function(x) { |
| //print(x); |
| } |
| |
| var window = { |
| // adjustable parameters |
| location: { |
| toString: function() { |
| return '%s'; |
| }, |
| search: '?%s', |
| pathname: '%s', |
| }, |
| onIdle: function(){ headlessPrint('triggering click'); document.querySelector('.fullscreen-button.low-res').callEventListeners('click'); window.onIdle = null; }, |
| dirsToDrop: 0, // go back to root dir if first_js is in a subdir |
| // |
| |
| headless: true, |
| |
| stopped: false, |
| fakeNow: 0, // we don't use Date.now() |
| rafs: [], |
| timeouts: [], |
| uid: 0, |
| requestAnimationFrame: function(func) { |
| func.uid = window.uid++; |
| headlessPrint('adding raf ' + func.uid); |
| window.rafs.push(func); |
| }, |
| setTimeout: function(func, ms) { |
| func.uid = window.uid++; |
| headlessPrint('adding timeout ' + func.uid); |
| window.timeouts.push({ |
| func: func, |
| when: window.fakeNow + (ms || 0) |
| }); |
| window.timeouts.sort(function(x, y) { return y.when - x.when }); |
| }, |
| runEventLoop: function() { |
| // run forever until an exception stops this replay |
| var iter = 0; |
| while (!this.stopped) { |
| var start = Date.realNow(); |
| headlessPrint('event loop: ' + (iter++)); |
| if (window.rafs.length == 0 && window.timeouts.length == 0) { |
| if (window.onIdle) { |
| window.onIdle(); |
| } else { |
| throw 'main loop is idle!'; |
| } |
| } |
| // rafs |
| var currRafs = window.rafs; |
| window.rafs = []; |
| for (var i = 0; i < currRafs.length; i++) { |
| var raf = currRafs[i]; |
| headlessPrint('calling raf: ' + raf.uid);// + ': ' + raf.toString().substring(0, 50)); |
| raf(); |
| } |
| // timeouts |
| var now = window.fakeNow; |
| var timeouts = window.timeouts; |
| window.timeouts = []; |
| while (timeouts.length && timeouts[timeouts.length-1].when <= now) { |
| var timeout = timeouts.pop(); |
| headlessPrint('calling timeout: ' + timeout.func.uid);// + ': ' + timeout.func.toString().substring(0, 50)); |
| timeout.func(); |
| } |
| // increment 'time' |
| window.fakeNow += 16.666; |
| headlessPrint('main event loop iteration took ' + (Date.realNow() - start) + ' ms'); |
| } |
| }, |
| eventListeners: {}, |
| addEventListener: function(id, func) { |
| var listeners = this.eventListeners[id]; |
| if (!listeners) { |
| listeners = this.eventListeners[id] = []; |
| } |
| listeners.push(func); |
| }, |
| removeEventListener: function(id, func) { |
| var listeners = this.eventListeners[id]; |
| if (!listeners) return; |
| for (var i = 0; i < listeners.length; i++) { |
| if (listeners[i] === func) { |
| listeners.splice(i, 1); |
| return; |
| } |
| } |
| }, |
| callEventListeners: function(id) { |
| var listeners = this.eventListeners[id]; |
| if (listeners) { |
| listeners.forEach(function(listener) { listener() }); |
| } |
| }, |
| URL: { |
| createObjectURL: function(x) { |
| return x; // the blob itself is returned |
| }, |
| revokeObjectURL: function(x) {}, |
| }, |
| encodeURIComponent: function(x) { return x }, |
| }; |
| var setTimeout = window.setTimeout; |
| var document = { |
| headless: true, |
| eventListeners: {}, |
| addEventListener: window.addEventListener, |
| removeEventListener: window.removeEventListener, |
| callEventListeners: window.callEventListeners, |
| getElementById: function(id) { |
| switch(id) { |
| case 'canvas': { |
| if (this.canvas) return this.canvas; |
| return this.canvas = headlessCanvas(); |
| } |
| case 'status-text': case 'progress': { |
| return {}; |
| } |
| default: throw 'getElementById: ' + id; |
| } |
| }, |
| createElement: function(what) { |
| switch (what) { |
| case 'canvas': return document.getElementById(what); |
| case 'script': { |
| var ret = {}; |
| window.setTimeout(function() { |
| headlessPrint('loading script: ' + ret.src); |
| load(ret.src); |
| headlessPrint(' script loaded.'); |
| if (ret.onload) { |
| window.setTimeout(function() { |
| ret.onload(); // yeah yeah this might vanish |
| }); |
| } |
| }); |
| return ret; |
| } |
| case 'div': { |
| return { |
| appendChild: function() {}, |
| requestFullScreen: function() { |
| return document.getElementById('canvas').requestFullScreen(); |
| }, |
| }; |
| } |
| default: throw 'createElement ' + what + new Error().stack; |
| } |
| }, |
| elements: {}, |
| querySelector: function(id) { |
| if (!document.elements[id]) { |
| document.elements[id] = { |
| classList: { |
| add: function(){}, |
| remove: function(){}, |
| }, |
| eventListeners: {}, |
| addEventListener: document.addEventListener, |
| removeEventListener: document.removeEventListener, |
| callEventListeners: document.callEventListeners, |
| }; |
| }; |
| return document.elements[id]; |
| }, |
| styleSheets: [{ |
| cssRules: [], |
| insertRule: function(){}, |
| }], |
| body: { |
| appendChild: function(){}, |
| }, |
| exitPointerLock: function(){}, |
| cancelFullScreen: function(){}, |
| }; |
| var alert = function(x) { |
| print(x); |
| }; |
| var performance = { |
| now: function() { |
| return Date.now(); |
| }, |
| }; |
| function fixPath(path) { |
| if (path[0] == '/') path = path.substring(1); |
| for (var i = 0; i < window.dirsToDrop; i++) { |
| path = '../' + path; |
| } |
| return path |
| } |
| var XMLHttpRequest = function() { |
| return { |
| open: function(mode, path, async) { |
| path = fixPath(path); |
| this.mode = mode; |
| this.path = path; |
| this.async = async; |
| }, |
| send: function() { |
| if (!this.async) { |
| this.doSend(); |
| } else { |
| var that = this; |
| window.setTimeout(function() { |
| that.doSend(); |
| if (that.onload) that.onload(); |
| }, 0); |
| } |
| }, |
| doSend: function() { |
| if (this.responseType == 'arraybuffer') { |
| this.response = read(this.path, 'binary'); |
| } else { |
| this.responseText = read(this.path); |
| } |
| }, |
| }; |
| }; |
| var Audio = function() { |
| return { |
| play: function(){}, |
| pause: function(){}, |
| cloneNode: function() { |
| return this; |
| }, |
| }; |
| }; |
| var Image = function() { |
| var that = this; |
| window.setTimeout(function() { |
| that.complete = true; |
| that.width = 64; |
| that.height = 64; |
| if (that.onload) that.onload(); |
| }); |
| }; |
| var Worker = function(workerPath) { |
| workerPath = fixPath(workerPath); |
| var workerCode = read(workerPath); |
| workerCode = workerCode.replace(/Module/g, 'zzModuleyy' + (Worker.id++)). // prevent collision with the global Module object. Note that this becomes global, so we need unique ids |
| replace(/\nonmessage = /, '\nvar onmessage = '); // workers commonly do "onmessage = ", we need to varify that to sandbox |
| headlessPrint('loading worker ' + workerPath + ' : ' + workerCode.substring(0, 50)); |
| eval(workerCode); // will implement onmessage() |
| |
| function duplicateJSON(json) { |
| function handleTypedArrays(key, value) { |
| if (value && value.toString && value.toString().substring(0, 8) == '[object ' && value.length && value.byteLength) { |
| return Array.prototype.slice.call(value); |
| } |
| return value; |
| } |
| return JSON.parse(JSON.stringify(json, handleTypedArrays)) |
| } |
| this.terminate = function(){}; |
| this.postMessage = function(msg) { |
| msg.messageId = Worker.messageId++; |
| headlessPrint('main thread sending message ' + msg.messageId + ' to worker ' + workerPath); |
| window.setTimeout(function() { |
| headlessPrint('worker ' + workerPath + ' receiving message ' + msg.messageId); |
| onmessage({ data: duplicateJSON(msg) }); |
| }); |
| }; |
| var thisWorker = this; |
| var postMessage = function(msg) { |
| msg.messageId = Worker.messageId++; |
| headlessPrint('worker ' + workerPath + ' sending message ' + msg.messageId); |
| window.setTimeout(function() { |
| headlessPrint('main thread receiving message ' + msg.messageId + ' from ' + workerPath); |
| thisWorker.onmessage({ data: duplicateJSON(msg) }); |
| }); |
| }; |
| }; |
| Worker.id = 0; |
| Worker.messageId = 0; |
| var screen = { // XXX these values may need to be adjusted |
| width: 2100, |
| height: 1313, |
| availWidth: 2100, |
| availHeight: 1283, |
| }; |
| var console = { |
| log: function(x) { |
| print(x); |
| }, |
| }; |
| var MozBlobBuilder = function() { |
| this.data = new Uint8Array(0); |
| this.append = function(buffer) { |
| var data = new Uint8Array(buffer); |
| var combined = new Uint8Array(this.data.length + data.length); |
| combined.set(this.data); |
| combined.set(data, this.data.length); |
| this.data = combined; |
| }; |
| this.getBlob = function() { |
| return this.data.buffer; // return the buffer as a "blob". XXX We might need to change this if it is not opaque |
| }; |
| }; |
| |
| // additional setup |
| if (!Module['canvas']) { |
| Module['canvas'] = document.getElementById('canvas'); |
| } |
| |
| //== HEADLESS ==// |
| |