diff options
Diffstat (limited to 'src/library_browser.js')
-rw-r--r-- | src/library_browser.js | 269 |
1 files changed, 216 insertions, 53 deletions
diff --git a/src/library_browser.js b/src/library_browser.js index fd09f478..a690286b 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -3,10 +3,11 @@ // Utilities for browser environments mergeInto(LibraryManager.library, { - $Browser__postset: 'Module["requestFullScreen"] = function() { Browser.requestFullScreen() };\n' + // exports + $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', + 'Module["resumeMainLoop"] = function() { Browser.mainLoop.resume() };\n' + + 'Module["getUserMedia"] = function() { Browser.getUserMedia() }', $Browser: { mainLoop: { scheduler: null, @@ -40,13 +41,14 @@ mergeInto(LibraryManager.library, { } } }, + isFullScreen: false, pointerLock: false, moduleContextCreatedCallbacks: [], workers: [], - ensureObjects: function() { - if (Browser.ensured) return; - Browser.ensured = true; + init: function() { + if (Browser.initted) return; + Browser.initted = true; try { new Blob(); Browser.hasBlobConstructor = true; @@ -68,20 +70,20 @@ mergeInto(LibraryManager.library, { 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(-3)]; - return ret; + }[name.substr(name.lastIndexOf('.')+1)]; } if (!Module["preloadPlugins"]) Module["preloadPlugins"] = []; var imagePlugin = {}; imagePlugin['canHandle'] = function(name) { - return name.substr(-4) in { '.jpg': 1, '.png': 1, '.bmp': 1 }; + return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/.exec(name); }; imagePlugin['handle'] = function(byteArray, name, onload, onerror) { var b = null; @@ -98,7 +100,9 @@ mergeInto(LibraryManager.library, { b = bb.getBlob(); } var url = Browser.URLObject.createObjectURL(b); +#if ASSERTIONS assert(typeof url == 'string', 'createObjectURL must return a url as a string'); +#endif var img = new Image(); img.onload = function() { assert(img.complete, 'Image ' + name + ' could not be decoded'); @@ -121,7 +125,7 @@ mergeInto(LibraryManager.library, { var audioPlugin = {}; audioPlugin['canHandle'] = function(name) { - return name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; + return !Module.noAudioDecoding && name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; }; audioPlugin['handle'] = function(byteArray, name, onload, onerror) { var done = false; @@ -144,7 +148,9 @@ mergeInto(LibraryManager.library, { return fail(); } var url = Browser.URLObject.createObjectURL(b); // XXX we never revoke this! +#if ASSERTIONS assert(typeof url == 'string', 'createObjectURL must return a url as a string'); +#endif var audio = new Audio(); audio.addEventListener('canplaythrough', function() { finish(audio) }, false); // use addEventListener due to chromium bug 124926 audio.onerror = function(event) { @@ -179,7 +185,7 @@ mergeInto(LibraryManager.library, { }; audio.src = url; // workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror - setTimeout(function() { + Browser.safeSetTimeout(function() { finish(audio); // try to use it even though it is not necessarily ready to play }, 10000); } else { @@ -187,6 +193,37 @@ mergeInto(LibraryManager.library, { } }; Module['preloadPlugins'].push(audioPlugin); + + // Canvas event setup + + var canvas = Module['canvas']; + canvas.requestPointerLock = canvas['requestPointerLock'] || + canvas['mozRequestPointerLock'] || + canvas['webkitRequestPointerLock']; + canvas.exitPointerLock = document['exitPointerLock'] || + document['mozExitPointerLock'] || + document['webkitExitPointerLock'] || + function(){}; // no-op if function does not exist + canvas.exitPointerLock = canvas.exitPointerLock.bind(document); + + function pointerLockChange() { + Browser.pointerLock = document['pointerLockElement'] === canvas || + document['mozPointerLockElement'] === canvas || + document['webkitPointerLockElement'] === canvas; + } + + document.addEventListener('pointerlockchange', pointerLockChange, false); + document.addEventListener('mozpointerlockchange', pointerLockChange, false); + document.addEventListener('webkitpointerlockchange', pointerLockChange, false); + + if (Module['elementPointerLock']) { + canvas.addEventListener("click", function(ev) { + if (!Browser.pointerLock && canvas.requestPointerLock) { + canvas.requestPointerLock(); + ev.preventDefault(); + } + }, false); + } }, createContext: function(canvas, useWebGL, setInModule) { @@ -196,8 +233,18 @@ mergeInto(LibraryManager.library, { return null; } #endif + var ctx; try { - var ctx = canvas.getContext(useWebGL ? 'experimental-webgl' : '2d'); + if (useWebGL) { + ctx = canvas.getContext('experimental-webgl', { +#if GL_TESTING + preserveDrawingBuffer: true, +#endif + alpha: false + }); + } else { + ctx = canvas.getContext('2d'); + } if (!ctx) throw ':('; } catch (e) { Module.print('Could not create canvas - ' + e); @@ -255,44 +302,52 @@ mergeInto(LibraryManager.library, { Module.ctx = ctx; Module.useWebGL = useWebGL; Browser.moduleContextCreatedCallbacks.forEach(function(callback) { callback() }); + Browser.init(); } return ctx; }, - requestFullScreen: function() { + destroyContext: function(canvas, useWebGL, setInModule) {}, + + fullScreenHandlersInstalled: false, + lockPointer: undefined, + resizeCanvas: undefined, + requestFullScreen: function(lockPointer, resizeCanvas) { + Browser.lockPointer = lockPointer; + Browser.resizeCanvas = resizeCanvas; + if (typeof Browser.lockPointer === 'undefined') Browser.lockPointer = true; + if (typeof Browser.resizeCanvas === 'undefined') Browser.resizeCanvas = false; + var canvas = Module['canvas']; function fullScreenChange() { - var isFullScreen = false; + Browser.isFullScreen = false; if ((document['webkitFullScreenElement'] || document['webkitFullscreenElement'] || document['mozFullScreenElement'] || document['mozFullscreenElement'] || document['fullScreenElement'] || document['fullscreenElement']) === canvas) { - canvas.requestPointerLock = canvas['requestPointerLock'] || - canvas['mozRequestPointerLock'] || - canvas['webkitRequestPointerLock']; - canvas.requestPointerLock(); - isFullScreen = true; + canvas.cancelFullScreen = document['cancelFullScreen'] || + document['mozCancelFullScreen'] || + document['webkitCancelFullScreen']; + canvas.cancelFullScreen = canvas.cancelFullScreen.bind(document); + if (Browser.lockPointer) canvas.requestPointerLock(); + Browser.isFullScreen = true; + if (Browser.resizeCanvas) Browser.setFullScreenCanvasSize(); + } else if (Browser.resizeCanvas){ + Browser.setWindowedCanvasSize(); } - if (Module['onFullScreen']) Module['onFullScreen'](isFullScreen); + if (Module['onFullScreen']) Module['onFullScreen'](Browser.isFullScreen); } - document.addEventListener('fullscreenchange', fullScreenChange, false); - document.addEventListener('mozfullscreenchange', fullScreenChange, false); - document.addEventListener('webkitfullscreenchange', fullScreenChange, false); - - function pointerLockChange() { - Browser.pointerLock = document['pointerLockElement'] === canvas || - document['mozPointerLockElement'] === canvas || - document['webkitPointerLockElement'] === canvas; + if (!Browser.fullScreenHandlersInstalled) { + Browser.fullScreenHandlersInstalled = true; + document.addEventListener('fullscreenchange', fullScreenChange, false); + document.addEventListener('mozfullscreenchange', fullScreenChange, false); + document.addEventListener('webkitfullscreenchange', fullScreenChange, false); } - document.addEventListener('pointerlockchange', pointerLockChange, false); - document.addEventListener('mozpointerlockchange', pointerLockChange, false); - document.addEventListener('webkitpointerlockchange', pointerLockChange, false); - canvas.requestFullScreen = canvas['requestFullScreen'] || canvas['mozRequestFullScreen'] || (canvas['webkitRequestFullScreen'] ? function() { canvas['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) } : null); - canvas.requestFullScreen(); + canvas.requestFullScreen(); }, requestAnimationFrame: function(func) { @@ -307,6 +362,38 @@ mergeInto(LibraryManager.library, { window.requestAnimationFrame(func); }, + // generic abort-aware wrapper for an async callback + safeCallback: function(func) { + return function() { + if (!ABORT) return func.apply(null, arguments); + }; + }, + + // abort-aware versions + safeRequestAnimationFrame: function(func) { + Browser.requestAnimationFrame(function() { + if (!ABORT) func(); + }); + }, + safeSetTimeout: function(func, timeout) { + setTimeout(function() { + if (!ABORT) func(); + }, timeout); + }, + safeSetInterval: function(func, timeout) { + setInterval(function() { + if (!ABORT) func(); + }, timeout); + }, + + getUserMedia: function(func) { + if(!window.getUserMedia) { + window.getUserMedia = navigator['getUserMedia'] || + navigator['mozGetUserMedia']; + } + window.getUserMedia(func); + }, + getMovementX: function(event) { return event['movementX'] || event['mozMovementX'] || @@ -321,12 +408,53 @@ mergeInto(LibraryManager.library, { 0; }, + mouseX: 0, + mouseY: 0, + mouseMovementX: 0, + mouseMovementY: 0, + + calculateMouseEvent: function(event) { // event should be mousemove, mousedown or mouseup + if (Browser.pointerLock) { + // When the pointer is locked, calculate the coordinates + // based on the movement of the mouse. + // Workaround for Firefox bug 764498 + if (event.type != 'mousemove' && + ('mozMovementX' in event)) { + Browser.mouseMovementX = Browser.mouseMovementY = 0; + } else { + Browser.mouseMovementX = Browser.getMovementX(event); + Browser.mouseMovementY = Browser.getMovementY(event); + } + Browser.mouseX = SDL.mouseX + Browser.mouseMovementX; + Browser.mouseY = SDL.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); + + // the canvas might be CSS-scaled compared to its backbuffer; + // SDL-using content will want mouse coordinates in terms + // of backbuffer units. + var cw = Module["canvas"].width; + var ch = Module["canvas"].height; + x = x * (cw / rect.width); + y = y * (ch / rect.height); + + Browser.mouseMovementX = x - Browser.mouseX; + Browser.mouseMovementY = y - Browser.mouseY; + Browser.mouseX = x; + Browser.mouseY = y; + } + }, + xhrLoad: function(url, onload, onerror) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; xhr.onload = function() { - if (xhr.status == 200) { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 onload(xhr.response); } else { onerror(); @@ -365,7 +493,32 @@ mergeInto(LibraryManager.library, { canvas.width = width; canvas.height = height; if (!noUpdates) Browser.updateResizeListeners(); + }, + + windowedWidth: 0, + windowedHeight: 0, + setFullScreenCanvasSize: function() { + var canvas = Module['canvas']; + this.windowedWidth = canvas.width; + 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') }}} + 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') }}} + Browser.updateResizeListeners(); } + }, emscripten_async_wget: function(url, file, onload, onerror) { @@ -389,10 +542,10 @@ mergeInto(LibraryManager.library, { Browser.asyncLoad(Pointer_stringify(url), function(byteArray) { var buffer = _malloc(byteArray.length); HEAPU8.set(byteArray, buffer); - FUNCTION_TABLE[onload](arg, buffer, byteArray.length); + Runtime.dynCall('viii', onload, [arg, buffer, byteArray.length]); _free(buffer); }, function() { - if (onerror) FUNCTION_TABLE[onerror](arg); + if (onerror) Runtime.dynCall('vi', onerror, [arg]); }, true /* no need for run dependency, this is async but will not do any prepare etc. step */ ); }, @@ -402,34 +555,34 @@ 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) { if (http.status == 200) { FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true); - if (onload) FUNCTION_TABLE[onload](arg, file); + if (onload) Runtime.dynCall('vii', onload, [arg, file]); } else { - if (onerror) FUNCTION_TABLE[onerror](arg, http.status); + if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]); } }; - + // ERROR http.onerror = function(e) { - if (onerror) FUNCTION_TABLE[onerror](arg, http.status); + if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]); }; - + // PROGRESS http.onprogress = function(e) { var percentComplete = (e.position / e.totalSize)*100; - if (onprogress) FUNCTION_TABLE[onprogress](arg, percentComplete); + 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 */ } @@ -444,7 +597,7 @@ mergeInto(LibraryManager.library, { http.send(null); } }, - + emscripten_async_prepare: function(file, onload, onerror) { var _file = Pointer_stringify(file); var data = FS.analyzePath(_file); @@ -492,7 +645,7 @@ mergeInto(LibraryManager.library, { Module['noExitRuntime'] = true; // TODO: cache these to avoid generating garbage - setTimeout(function() { + Browser.safeSetTimeout(function() { _emscripten_run_script(script); }, millis); }, @@ -501,6 +654,7 @@ mergeInto(LibraryManager.library, { Module['noExitRuntime'] = true; Browser.mainLoop.runner = function() { + if (ABORT) return; if (Browser.mainLoop.queue.length > 0) { var start = Date.now(); var blocker = Browser.mainLoop.queue.shift(); @@ -558,7 +712,7 @@ mergeInto(LibraryManager.library, { Browser.mainLoop.scheduler(); if (simulateInfiniteLoop) { - throw 'emscripten_set_main_loop simulating infinite loop by throwing so we get right into the JS event loop'; + throw 'SimulateInfiniteLoop'; } }, @@ -603,12 +757,17 @@ mergeInto(LibraryManager.library, { } if (millis >= 0) { - setTimeout(wrapper, millis); + Browser.safeSetTimeout(wrapper, millis); } else { - Browser.requestAnimationFrame(wrapper); + Browser.safeRequestAnimationFrame(wrapper); } }, + emscripten_exit_with_live_runtime: function() { + Module['noExitRuntime'] = true; + throw 'SimulateInfiniteLoop'; + }, + emscripten_hide_mouse: function() { var styleSheet = document.styleSheets[0]; var rules = styleSheet.cssRules; @@ -626,7 +785,11 @@ mergeInto(LibraryManager.library, { }, emscripten_get_now: function() { - if (window['performance'] && window['performance']['now']) { + 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(); @@ -691,7 +854,7 @@ mergeInto(LibraryManager.library, { info.worker.postMessage({ 'funcName': funcName, 'callbackId': callbackId, - 'data': data ? {{{ makeHEAPView('U8', 'data', 'data + size') }}} : 0 + 'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0 // XXX copy to a new typed array as a workaround for chrome bug 169705 }); }, @@ -701,7 +864,7 @@ mergeInto(LibraryManager.library, { workerResponded = true; postMessage({ 'callbackId': workerCallbackId, - 'data': data ? {{{ makeHEAPView('U8', 'data', 'data + size') }}} : 0 + 'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0 // XXX copy to a new typed array as a workaround for chrome bug 169705 }); }, |