diff options
Diffstat (limited to 'src/library_browser.js')
-rw-r--r-- | src/library_browser.js | 333 |
1 files changed, 233 insertions, 100 deletions
diff --git a/src/library_browser.js b/src/library_browser.js index 1a79a49b..50a8fc6f 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -3,13 +3,17 @@ // Utilities for browser environments mergeInto(LibraryManager.library, { - $Browser__postset: 'Module["requestFullScreen"] = function(lockPointer, resizeCanvas) { Browser.requestFullScreen(lockPointer, resizeCanvas) };\n' + // exports - 'Module["requestAnimationFrame"] = function(func) { Browser.requestAnimationFrame(func) };\n' + - 'Module["pauseMainLoop"] = function() { Browser.mainLoop.pause() };\n' + - 'Module["resumeMainLoop"] = function() { Browser.mainLoop.resume() };\n', + $Browser__deps: ['$PATH'], + $Browser__postset: 'Module["requestFullScreen"] = function Module_requestFullScreen(lockPointer, resizeCanvas) { Browser.requestFullScreen(lockPointer, resizeCanvas) };\n' + // exports + 'Module["requestAnimationFrame"] = function Module_requestAnimationFrame(func) { Browser.requestAnimationFrame(func) };\n' + + 'Module["setCanvasSize"] = function Module_setCanvasSize(width, height, noUpdates) { Browser.setCanvasSize(width, height, noUpdates) };\n' + + 'Module["pauseMainLoop"] = function Module_pauseMainLoop() { Browser.mainLoop.pause() };\n' + + 'Module["resumeMainLoop"] = function Module_resumeMainLoop() { Browser.mainLoop.resume() };\n' + + 'Module["getUserMedia"] = function Module_getUserMedia() { Browser.getUserMedia() }', $Browser: { mainLoop: { scheduler: null, + method: '', shouldPause: false, paused: false, queue: [], @@ -46,8 +50,11 @@ mergeInto(LibraryManager.library, { workers: [], init: function() { - if (Browser.initted) return; + if (!Module["preloadPlugins"]) Module["preloadPlugins"] = []; // needs to exist even in workers + + if (Browser.initted || ENVIRONMENT_IS_WORKER) return; Browser.initted = true; + try { new Blob(); Browser.hasBlobConstructor = true; @@ -56,7 +63,11 @@ mergeInto(LibraryManager.library, { console.log("warning: no blob constructor, cannot create blobs with mimetypes"); } Browser.BlobBuilder = typeof MozBlobBuilder != "undefined" ? MozBlobBuilder : (typeof WebKitBlobBuilder != "undefined" ? WebKitBlobBuilder : (!Browser.hasBlobConstructor ? console.log("warning: no BlobBuilder") : null)); - Browser.URLObject = typeof window != "undefined" ? (window.URL ? window.URL : window.webkitURL) : console.log("warning: cannot create object URLs"); + Browser.URLObject = typeof window != "undefined" ? (window.URL ? window.URL : window.webkitURL) : undefined; + if (!Module.noImageDecoding && typeof Browser.URLObject === 'undefined') { + console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available."); + Module.noImageDecoding = true; + } // Support for plugins that can process preloaded files. You can add more of these to // your app by creating and appending to Module.preloadPlugins. @@ -66,29 +77,19 @@ mergeInto(LibraryManager.library, { // (possibly modified) data. For example, a plugin might decompress a file, or it // might create some side data structure for use later (like an Image element, etc.). - function getMimetype(name) { - return { - 'jpg': 'image/jpeg', - 'jpeg': 'image/jpeg', - 'png': 'image/png', - 'bmp': 'image/bmp', - 'ogg': 'audio/ogg', - 'wav': 'audio/wav', - 'mp3': 'audio/mpeg' - }[name.substr(name.lastIndexOf('.')+1)]; - } - - if (!Module["preloadPlugins"]) Module["preloadPlugins"] = []; - var imagePlugin = {}; - imagePlugin['canHandle'] = function(name) { - return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/.exec(name); + imagePlugin['canHandle'] = function imagePlugin_canHandle(name) { + return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name); }; - imagePlugin['handle'] = function(byteArray, name, onload, onerror) { + imagePlugin['handle'] = function imagePlugin_handle(byteArray, name, onload, onerror) { var b = null; if (Browser.hasBlobConstructor) { try { - b = new Blob([byteArray], { type: getMimetype(name) }); + b = new Blob([byteArray], { type: Browser.getMimetype(name) }); + if (b.size !== byteArray.length) { // Safari bug #118630 + // Safari's Blob can only take an ArrayBuffer + b = new Blob([(new Uint8Array(byteArray)).buffer], { type: Browser.getMimetype(name) }); + } } catch(e) { Runtime.warnOnce('Blob constructor present but fails: ' + e + '; falling back to blob builder'); } @@ -103,7 +104,7 @@ mergeInto(LibraryManager.library, { assert(typeof url == 'string', 'createObjectURL must return a url as a string'); #endif var img = new Image(); - img.onload = function() { + img.onload = function img_onload() { assert(img.complete, 'Image ' + name + ' could not be decoded'); var canvas = document.createElement('canvas'); canvas.width = img.width; @@ -114,7 +115,7 @@ mergeInto(LibraryManager.library, { Browser.URLObject.revokeObjectURL(url); if (onload) onload(byteArray); }; - img.onerror = function(event) { + img.onerror = function img_onerror(event) { console.log('Image ' + url + ' could not be decoded'); if (onerror) onerror(); }; @@ -123,10 +124,10 @@ mergeInto(LibraryManager.library, { Module['preloadPlugins'].push(imagePlugin); var audioPlugin = {}; - audioPlugin['canHandle'] = function(name) { + audioPlugin['canHandle'] = function audioPlugin_canHandle(name) { return !Module.noAudioDecoding && name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; }; - audioPlugin['handle'] = function(byteArray, name, onload, onerror) { + audioPlugin['handle'] = function audioPlugin_handle(byteArray, name, onload, onerror) { var done = false; function finish(audio) { if (done) return; @@ -142,7 +143,7 @@ mergeInto(LibraryManager.library, { } if (Browser.hasBlobConstructor) { try { - var b = new Blob([byteArray], { type: getMimetype(name) }); + var b = new Blob([byteArray], { type: Browser.getMimetype(name) }); } catch(e) { return fail(); } @@ -152,7 +153,7 @@ mergeInto(LibraryManager.library, { #endif var audio = new Audio(); audio.addEventListener('canplaythrough', function() { finish(audio) }, false); // use addEventListener due to chromium bug 124926 - audio.onerror = function(event) { + audio.onerror = function audio_onerror(event) { if (done) return; console.log('warning: browser could not fully decode audio ' + name + ', trying slower base64 approach'); function encode64(data) { @@ -225,7 +226,7 @@ mergeInto(LibraryManager.library, { } }, - createContext: function(canvas, useWebGL, setInModule) { + createContext: function(canvas, useWebGL, setInModule, webGLContextAttributes) { #if !USE_TYPED_ARRAYS if (useWebGL) { Module.print('(USE_TYPED_ARRAYS needs to be enabled for WebGL)'); @@ -235,18 +236,39 @@ mergeInto(LibraryManager.library, { var ctx; try { if (useWebGL) { - ctx = canvas.getContext('experimental-webgl', { + var contextAttributes = { + antialias: false, + alpha: false + }; + + if (webGLContextAttributes) { + for (var attribute in webGLContextAttributes) { + contextAttributes[attribute] = webGLContextAttributes[attribute]; + } + } + #if GL_TESTING - preserveDrawingBuffer: true, + contextAttributes.preserveDrawingBuffer = true; #endif - alpha: false - }); + + var errorInfo = '?'; + function onContextCreationError(event) { + errorInfo = event.statusMessage || errorInfo; + } + canvas.addEventListener('webglcontextcreationerror', onContextCreationError, false); + try { + ['experimental-webgl', 'webgl'].some(function(webglId) { + return ctx = canvas.getContext(webglId, contextAttributes); + }); + } finally { + canvas.removeEventListener('webglcontextcreationerror', onContextCreationError, false); + } } else { ctx = canvas.getContext('2d'); } if (!ctx) throw ':('; } catch (e) { - Module.print('Could not create canvas - ' + e); + Module.print('Could not create canvas: ' + [errorInfo, e]); return null; } if (useWebGL) { @@ -258,7 +280,7 @@ mergeInto(LibraryManager.library, { (function(prop) { switch (typeof tempCtx[prop]) { case 'function': { - wrapper[prop] = function() { + wrapper[prop] = function gl_wrapper() { if (GL.debug) { var printArgs = Array.prototype.slice.call(arguments).map(Runtime.prettyPrint); Module.printErr('[gl_f:' + prop + ':' + printArgs + ']'); @@ -298,7 +320,7 @@ mergeInto(LibraryManager.library, { }, false); } if (setInModule) { - Module.ctx = ctx; + GLctx = Module.ctx = ctx; Module.useWebGL = useWebGL; Browser.moduleContextCreatedCallbacks.forEach(function(callback) { callback() }); Browser.init(); @@ -346,19 +368,23 @@ mergeInto(LibraryManager.library, { canvas.requestFullScreen = canvas['requestFullScreen'] || canvas['mozRequestFullScreen'] || (canvas['webkitRequestFullScreen'] ? function() { canvas['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) } : null); - canvas.requestFullScreen(); + canvas.requestFullScreen(); }, - requestAnimationFrame: function(func) { - if (!window.requestAnimationFrame) { - window.requestAnimationFrame = window['requestAnimationFrame'] || - window['mozRequestAnimationFrame'] || - window['webkitRequestAnimationFrame'] || - window['msRequestAnimationFrame'] || - window['oRequestAnimationFrame'] || - window['setTimeout']; + requestAnimationFrame: function requestAnimationFrame(func) { + if (typeof window === 'undefined') { // Provide fallback to setTimeout if window is undefined (e.g. in Node.js) + setTimeout(func, 1000/60); + } else { + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = window['requestAnimationFrame'] || + window['mozRequestAnimationFrame'] || + window['webkitRequestAnimationFrame'] || + window['msRequestAnimationFrame'] || + window['oRequestAnimationFrame'] || + window['setTimeout']; + } + window.requestAnimationFrame(func); } - window.requestAnimationFrame(func); }, // generic abort-aware wrapper for an async callback @@ -370,21 +396,42 @@ mergeInto(LibraryManager.library, { // abort-aware versions safeRequestAnimationFrame: function(func) { - Browser.requestAnimationFrame(function() { + return Browser.requestAnimationFrame(function() { if (!ABORT) func(); }); }, safeSetTimeout: function(func, timeout) { - setTimeout(function() { + return setTimeout(function() { if (!ABORT) func(); }, timeout); }, safeSetInterval: function(func, timeout) { - setInterval(function() { + return setInterval(function() { if (!ABORT) func(); }, timeout); }, + getMimetype: function(name) { + return { + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'png': 'image/png', + 'bmp': 'image/bmp', + 'ogg': 'audio/ogg', + 'wav': 'audio/wav', + 'mp3': 'audio/mpeg' + }[name.substr(name.lastIndexOf('.')+1)]; + }, + + getUserMedia: function(func) { + if(!window.getUserMedia) { + window.getUserMedia = navigator['getUserMedia'] || + navigator['mozGetUserMedia']; + } + window.getUserMedia(func); + }, + + getMovementX: function(event) { return event['movementX'] || event['mozMovementX'] || @@ -399,6 +446,10 @@ mergeInto(LibraryManager.library, { 0; }, + getMouseWheelDelta: function(event) { + return Math.max(-1, Math.min(1, event.type === 'DOMMouseScroll' ? event.detail : -event.wheelDelta)); + }, + mouseX: 0, mouseY: 0, mouseMovementX: 0, @@ -416,14 +467,47 @@ mergeInto(LibraryManager.library, { Browser.mouseMovementX = Browser.getMovementX(event); Browser.mouseMovementY = Browser.getMovementY(event); } - Browser.mouseX = SDL.mouseX + Browser.mouseMovementX; - Browser.mouseY = SDL.mouseY + Browser.mouseMovementY; + + // check if SDL is available + if (typeof SDL != "undefined") { + Browser.mouseX = SDL.mouseX + Browser.mouseMovementX; + Browser.mouseY = SDL.mouseY + Browser.mouseMovementY; + } else { + // just add the mouse delta to the current absolut mouse position + // FIXME: ideally this should be clamped against the canvas size and zero + Browser.mouseX += Browser.mouseMovementX; + Browser.mouseY += Browser.mouseMovementY; + } } else { // Otherwise, calculate the movement based on the changes // in the coordinates. var rect = Module["canvas"].getBoundingClientRect(); - var x = event.pageX - (window.scrollX + rect.left); - var y = event.pageY - (window.scrollY + rect.top); + var x, y; + + // Neither .scrollX or .pageXOffset are defined in a spec, but + // we prefer .scrollX because it is currently in a spec draft. + // (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/) + var scrollX = ((typeof window.scrollX !== 'undefined') ? window.scrollX : window.pageXOffset); + var scrollY = ((typeof window.scrollY !== 'undefined') ? window.scrollY : window.pageYOffset); +#if ASSERTIONS + // If this assert lands, it's likely because the browser doesn't support scrollX or pageXOffset + // and we have no viable fallback. + assert((typeof scrollX !== 'undefined') && (typeof scrollY !== 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.'); +#endif + if (event.type == 'touchstart' || + event.type == 'touchend' || + event.type == 'touchmove') { + var t = event.touches.item(0); + if (t) { + x = t.pageX - (scrollX + rect.left); + y = t.pageY - (scrollY + rect.top); + } else { + return; + } + } else { + x = event.pageX - (scrollX + rect.left); + y = event.pageY - (scrollY + rect.top); + } // the canvas might be CSS-scaled compared to its backbuffer; // SDL-using content will want mouse coordinates in terms @@ -444,7 +528,7 @@ mergeInto(LibraryManager.library, { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; - xhr.onload = function() { + xhr.onload = function xhr_onload() { if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 onload(xhr.response); } else { @@ -494,31 +578,36 @@ mergeInto(LibraryManager.library, { this.windowedHeight = canvas.height; canvas.width = screen.width; canvas.height = screen.height; - var flags = {{{ makeGetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'i32', 0, 1) }}}; - flags = flags | 0x00800000; // set SDL_FULLSCREEN flag - {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} + // check if SDL is available + if (typeof SDL != "undefined") { + var flags = {{{ makeGetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'i32', 0, 1) }}}; + flags = flags | 0x00800000; // set SDL_FULLSCREEN flag + {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} + } Browser.updateResizeListeners(); }, - + setWindowedCanvasSize: function() { var canvas = Module['canvas']; canvas.width = this.windowedWidth; canvas.height = this.windowedHeight; - var flags = {{{ makeGetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'i32', 0, 1) }}}; - flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag - {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} + // check if SDL is available + if (typeof SDL != "undefined") { + var flags = {{{ makeGetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'i32', 0, 1) }}}; + flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag + {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} + } Browser.updateResizeListeners(); } - + }, emscripten_async_wget: function(url, file, onload, onerror) { var _url = Pointer_stringify(url); var _file = Pointer_stringify(file); - var index = _file.lastIndexOf('/'); FS.createPreloadedFile( - _file.substr(0, index), - _file.substr(index +1), + PATH.dirname(_file), + PATH.basename(_file), _url, true, true, function() { if (onload) Runtime.dynCall('vi', onload, [file]); @@ -546,13 +635,13 @@ mergeInto(LibraryManager.library, { var _request = Pointer_stringify(request); var _param = Pointer_stringify(param); var index = _file.lastIndexOf('/'); - + var http = new XMLHttpRequest(); http.open(_request, _url, true); http.responseType = 'arraybuffer'; - + // LOAD - http.onload = function(e) { + http.onload = function http_onload(e) { if (http.status == 200) { FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true); if (onload) Runtime.dynCall('vii', onload, [arg, file]); @@ -560,20 +649,20 @@ mergeInto(LibraryManager.library, { if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]); } }; - + // ERROR - http.onerror = function(e) { + http.onerror = function http_onerror(e) { if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]); }; - + // PROGRESS - http.onprogress = function(e) { + http.onprogress = function http_onprogress(e) { var percentComplete = (e.position / e.totalSize)*100; if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]); }; - + // Useful because the browser can limit the number of redirection - try { + try { if (http.channel instanceof Ci.nsIHttpChannel) http.channel.redirectionLimit = 0; } catch (ex) { /* whatever */ } @@ -588,15 +677,16 @@ mergeInto(LibraryManager.library, { http.send(null); } }, - + emscripten_async_prepare: function(file, onload, onerror) { + Module['noExitRuntime'] = true; + var _file = Pointer_stringify(file); var data = FS.analyzePath(_file); if (!data.exists) return -1; - var index = _file.lastIndexOf('/'); FS.createPreloadedFile( - _file.substr(0, index), - _file.substr(index +1), + PATH.dirname(_file), + PATH.basename(_file), new Uint8Array(data.object.contents), true, true, function() { if (onload) Runtime.dynCall('vi', onload, [file]); @@ -610,13 +700,15 @@ mergeInto(LibraryManager.library, { }, emscripten_async_prepare_data: function(data, size, suffix, arg, onload, onerror) { + Module['noExitRuntime'] = true; + var _suffix = Pointer_stringify(suffix); if (!Browser.asyncPrepareDataCounter) Browser.asyncPrepareDataCounter = 0; var name = 'prepare_data_' + (Browser.asyncPrepareDataCounter++) + '.' + _suffix; var cname = _malloc(name.length+1); writeStringToMemory(name, cname); FS.createPreloadedFile( - '', + '/', name, {{{ makeHEAPView('U8', 'data', 'data + size') }}}, true, true, @@ -641,10 +733,29 @@ mergeInto(LibraryManager.library, { }, millis); }, + emscripten_async_load_script: function(url, onload, onerror) { + Module['noExitRuntime'] = true; + + onload = Runtime.getFuncWrapper(onload, 'v'); + + assert(runDependencies === 0, 'async_load_script must be run when no other dependencies are active'); + var script = document.createElement('script'); + script.onload = function script_onload() { + if (runDependencies > 0) { + dependenciesFulfilled = onload; + } else { + onload(); + } + }; + script.onerror = onerror; + script.src = Pointer_stringify(url); + document.body.appendChild(script); + }, + emscripten_set_main_loop: function(func, fps, simulateInfiniteLoop) { Module['noExitRuntime'] = true; - Browser.mainLoop.runner = function() { + Browser.mainLoop.runner = function Browser_mainLoop_runner() { if (ABORT) return; if (Browser.mainLoop.queue.length > 0) { var start = Date.now(); @@ -673,11 +784,34 @@ mergeInto(LibraryManager.library, { return; } + // Signal GL rendering layer that processing of a new frame is about to start. This helps it optimize + // VBO double-buffering and reduce GPU stalls. +#if FULL_ES2 + GL.newRenderingFrameStarted(); +#endif +#if LEGACY_GL_EMULATION + GL.newRenderingFrameStarted(); +#endif + + if (Browser.mainLoop.method === 'timeout' && Module.ctx) { + Module.printErr('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!'); + Browser.mainLoop.method = ''; // just warn once per call to set main loop + } + if (Module['preMainLoop']) { Module['preMainLoop'](); } - Runtime.dynCall('v', func); + try { + Runtime.dynCall('v', func); + } catch (e) { + if (e instanceof ExitStatus) { + return; + } else { + if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); + throw e; + } + } if (Module['postMainLoop']) { Module['postMainLoop'](); @@ -692,13 +826,15 @@ mergeInto(LibraryManager.library, { Browser.mainLoop.scheduler(); } if (fps && fps > 0) { - Browser.mainLoop.scheduler = function() { + Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() { setTimeout(Browser.mainLoop.runner, 1000/fps); // doing this each time means that on exception, we stop - } + }; + Browser.mainLoop.method = 'timeout'; } else { - Browser.mainLoop.scheduler = function() { + Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler() { Browser.requestAnimationFrame(Browser.mainLoop.runner); - } + }; + Browser.mainLoop.method = 'rAF'; } Browser.mainLoop.scheduler(); @@ -763,7 +899,7 @@ mergeInto(LibraryManager.library, { var styleSheet = document.styleSheets[0]; var rules = styleSheet.cssRules; for (var i = 0; i < rules.length; i++) { - if (rules[i].cssText.substr(0, 5) == 'canvas') { + if (rules[i].cssText.substr(0, 6) == 'canvas') { styleSheet.deleteRule(i); i--; } @@ -774,17 +910,12 @@ mergeInto(LibraryManager.library, { emscripten_set_canvas_size: function(width, height) { Browser.setCanvasSize(width, height); }, - - emscripten_get_now: function() { - if (ENVIRONMENT_IS_NODE) { - var t = process['hrtime'](); - return t[0] * 1e3 + t[1] / 1e6; - } - else if (window['performance'] && window['performance']['now']) { - return window['performance']['now'](); - } else { - return Date.now(); - } + + emscripten_get_canvas_size: function(width, height, isFullscreen) { + var canvas = Module['canvas']; + {{{ makeSetValue('width', '0', 'canvas.width', 'i32') }}}; + {{{ makeSetValue('height', '0', 'canvas.height', 'i32') }}}; + {{{ makeSetValue('isFullscreen', '0', 'Browser.isFullScreen ? 1 : 0', 'i32') }}}; }, emscripten_create_worker: function(url) { @@ -797,7 +928,7 @@ mergeInto(LibraryManager.library, { buffer: 0, bufferSize: 0 }; - info.worker.onmessage = function(msg) { + info.worker.onmessage = function info_worker_onmessage(msg) { var info = Browser.workers[id]; if (!info) return; // worker was destroyed meanwhile var callbackId = msg.data['callbackId']; @@ -831,6 +962,8 @@ mergeInto(LibraryManager.library, { }, emscripten_call_worker: function(id, funcName, data, size, callback, arg) { + Module['noExitRuntime'] = true; // should we only do this if there is a callback? + funcName = Pointer_stringify(funcName); var info = Browser.workers[id]; var callbackId = -1; |