blob: d329eb2ac98c81a521b11db0db7a970763567889 [file] [log] [blame]
//== 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 ==//