From 54b0f19d9e8130de16053b0915d114c346c99f17 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Mar 2014 13:08:23 -0700 Subject: [PATCH 01/24] forward CLOSURE_COMPILER to settings, and use that to avoid a closure-specific workaround for Module detection; fixes #2209 --- emcc | 7 ++++--- src/settings.js | 1 + src/shell.js | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/emcc b/emcc index de782071f7f27..0454b3b8ac4ce 100755 --- a/emcc +++ b/emcc @@ -1034,9 +1034,6 @@ try: if DEBUG: start_time = time.time() # done after parsing arguments, which might affect debug state - if closure: - assert os.path.exists(shared.CLOSURE_COMPILER), logging.error('fatal: Closure compiler (%s) does not exist', shared.CLOSURE_COMPILER) - for i in range(len(newargs)): if newargs[i] == '-s': if is_minus_s_for_emcc(newargs, i): @@ -1252,6 +1249,10 @@ try: logging.warning('disabling closure because debug info was requested') closure = False + if closure: + shared.Settings.CLOSURE_COMPILER = 1 + assert os.path.exists(shared.CLOSURE_COMPILER), logging.error('fatal: Closure compiler (%s) does not exist', shared.CLOSURE_COMPILER) + assert shared.LLVM_TARGET in shared.COMPILER_OPTS if shared.LLVM_TARGET == 'i386-pc-linux-gnu': shared.Settings.TARGET_X86 = 1 diff --git a/src/settings.js b/src/settings.js index 02f6c8b591ae0..1c41676de0a51 100644 --- a/src/settings.js +++ b/src/settings.js @@ -133,6 +133,7 @@ var PRECISE_F32 = 0; // 0: Use JS numbers for floating-point values. These are 6 // therefore recommended. var SIMD = 0; // Whether to emit SIMD code ( https://github.com/johnmccutchan/ecmascript_simd ) +var CLOSURE_COMPILER = 0; // Whether closure compiling is being run on this output var CLOSURE_ANNOTATIONS = 0; // If set, the generated code will be annotated for the closure // compiler. This potentially lets closure optimize the code better. diff --git a/src/shell.js b/src/shell.js index 65f7b92321dd6..e1c0eb54fd0e6 100644 --- a/src/shell.js +++ b/src/shell.js @@ -14,7 +14,11 @@ // before the code. Then that object will be used in the code, and you // can continue to use Module afterwards as well. var Module; +#if CLOSURE_COMPILER if (!Module) Module = eval('(function() { try { return {{{ EXPORT_NAME }}} || {} } catch(e) { return {} } })()'); +#else +if (!Module) Module = (typeof {{{ EXPORT_NAME }}} !== 'undefined' ? {{{ EXPORT_NAME }}} : null) || {}; +#endif // Sometimes an existing Module object exists with properties // meant to overwrite the default module functionality. Here From fe4bffcd5171d47e7d4e21e65023bdaec233cdcc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Mar 2014 13:18:43 -0700 Subject: [PATCH 02/24] restore some closure testing --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 5c34d47e8aeff..a954e312ba8c3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1938,7 +1938,7 @@ def process(filename): def test_emscripten_get_now(self): if Settings.USE_TYPED_ARRAYS != 2: return self.skip('requires ta2') - if self.run_name == 'o2': + if self.run_name == 'slow2asm': self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage self.do_run(open(path_from_root('tests', 'emscripten_get_now.cpp')).read(), 'Timer resolution is good.') From 131713b71a110a34b64956c7678538e408772c85 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Mar 2014 14:16:11 -0700 Subject: [PATCH 03/24] avoid llvm-dis when using save-bc in fastcomp --- emcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emcc b/emcc index 0454b3b8ac4ce..538590ceddfa9 100755 --- a/emcc +++ b/emcc @@ -1498,7 +1498,7 @@ try: # Prepare .ll for Emscripten if not LEAVE_INPUTS_RAW: - if save_bc: + if save_bc and not fastcomp: final = shared.Building.llvm_dis(final, final + '.ll') else: assert len(input_files) == 1 From 1ec3afe6d8dfac35e9e66714eb87840f60aa1f31 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Mar 2014 15:50:29 -0700 Subject: [PATCH 04/24] add -w to spidermonkey to see odin warnings --- tests/fuzz/csmith_driver.py | 2 +- tools/shared.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/fuzz/csmith_driver.py b/tests/fuzz/csmith_driver.py index 76d646dda675f..09f1c249994a1 100755 --- a/tests/fuzz/csmith_driver.py +++ b/tests/fuzz/csmith_driver.py @@ -138,7 +138,7 @@ def try_js(args): # This is ok. Try in secondary JS engine too if opts != '-O0' and engine2 and normal: try: - js2 = shared.run_js(filename + '.js', stderr=PIPE, engine=engine2, full_output=True, check_timeout=True) + js2 = shared.run_js(filename + '.js', stderr=PIPE, engine=engine2 + ['-w'], full_output=True, check_timeout=True) except: print 'failed to run in secondary', js2 break diff --git a/tools/shared.py b/tools/shared.py index 8a27e96667121..1adbdfaa468bc 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -709,13 +709,14 @@ def get_llvm_target(): # Engine tweaks try: + SPIDERMONKEY_ENGINE = listify(SPIDERMONKEY_ENGINE) if 'gcparam' not in str(SPIDERMONKEY_ENGINE): new_spidermonkey = SPIDERMONKEY_ENGINE - if type(SPIDERMONKEY_ENGINE) is str: - new_spidermonkey = [SPIDERMONKEY_ENGINE] new_spidermonkey += ['-e', "gcparam('maxBytes', 1024*1024*1024);"] # Our very large files need lots of gc heap JS_ENGINES = map(lambda x: x if x != SPIDERMONKEY_ENGINE else new_spidermonkey, JS_ENGINES) SPIDERMONKEY_ENGINE = new_spidermonkey + if '-w' not in SPIDERMONKEY_ENGINE: + SPIDERMONKEY_ENGINE += ['-w'] except NameError: pass From 4d4fe2e9f093b07a4797e1e86eab4da9ecb50211 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Mar 2014 15:58:22 -0700 Subject: [PATCH 05/24] add test for odin validation --- tests/test_other.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_other.py b/tests/test_other.py index 5af2b22cc950e..3d5cef96b4794 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -2561,3 +2561,8 @@ def test_llvm_lit(self): p.communicate() assert p.returncode == 0, 'LLVM tests must pass with exit code 0' + def test_odin_validation(self): + Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-O1'], stdout=PIPE, stderr=PIPE).communicate() + output = run_js('a.out.js', stderr=PIPE, full_output=True, engine=SPIDERMONKEY_ENGINE) + assert 'asm.js' in output, 'spidermonkey should mention asm.js compilation: ' + output + From 7998d7bc93d638666510fb515b58fd3715ca52e4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 10 Mar 2014 17:02:27 -0700 Subject: [PATCH 06/24] fix mouse wheel deltas for closure compiler in html5 API, and add some closure testing; #2214 --- src/library_html5.js | 8 ++++---- tests/test_browser.py | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/library_html5.js b/src/library_html5.js index b17a0d4d75ace..0a938df618f1c 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -240,10 +240,10 @@ var LibraryJSEvents = { var handlerFunc = function(event) { var e = event || window.event; JSEvents.fillMouseEventData(JSEvents.wheelEvent, e); - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e.deltaX', 'double') }}}; - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'e.deltaY', 'double') }}}; - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, 'e.deltaZ', 'double') }}}; - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, 'e.deltaMode', 'i32') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["deltaX"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'e["deltaY"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, 'e["deltaZ"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, 'e["deltaMode"]', 'i32') }}}; var shouldCancel = Runtime.dynCall('iiii', callbackfunc, [eventTypeId, JSEvents.wheelEvent, userData]); if (shouldCancel) { e.preventDefault(); diff --git a/tests/test_browser.py b/tests/test_browser.py index d49244e2f4801..d594970964955 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1786,7 +1786,9 @@ def test_doublestart_bug(self): self.btest('doublestart.c', args=['--pre-js', 'pre.js', '-o', 'test.html'], expected='1') def test_html5(self): - self.btest(path_from_root('tests', 'test_html5.c'), expected='0') + for opts in [[], ['-O2', '-g1', '--closure', '1']]: + print opts + self.btest(path_from_root('tests', 'test_html5.c'), args=opts, expected='0') def test_codemods(self): for opt_level in [0, 2]: From 5c692fd82fcfafc0685f1c73328f66f905b52305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 11 Mar 2014 13:23:46 +0200 Subject: [PATCH 07/24] Clean up warnings in emscripten_log test. --- tests/emscripten_log/emscripten_log.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/emscripten_log/emscripten_log.cpp b/tests/emscripten_log/emscripten_log.cpp index 5973e94c2f4f5..f4cc41db88589 100644 --- a/tests/emscripten_log/emscripten_log.cpp +++ b/tests/emscripten_log/emscripten_log.cpp @@ -5,10 +5,6 @@ #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) -#ifndef REPORT_RESULT -#define REPORT_RESULT int dummy -#endif - int result = 1; // If 1, this test succeeded. // A custom assert macro to test varargs routing to emscripten_log(). @@ -134,11 +130,10 @@ void __attribute__((noinline)) Foo() // Arbitrary function signature to add some int main() { Foo(); -#ifndef RUN_FROM_JS_SHELL +#ifdef REPORT_RESULT REPORT_RESULT(); - return 0; -#else +#endif if (result) printf("Success!\n"); -#endif + return 0; } From d4ef9376223f3f7f861ec7f9086a0a5a3e190ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 11 Mar 2014 13:25:36 +0200 Subject: [PATCH 08/24] Implement a IE10+ specific path to emscripten_get_callstack to get the call stack information. In IE, callstacks are populated only when an exception object is thrown. Closes #2212. --- src/library.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/library.js b/src/library.js index 2c3d5e03adc5c..f7155e9d8ad59 100644 --- a/src/library.js +++ b/src/library.js @@ -8915,10 +8915,19 @@ LibraryManager.library = { emscripten_get_callstack_js: function(flags) { var err = new Error(); if (!err.stack) { - Runtime.warnOnce('emscripten_get_callstack_js is not supported on this browser!'); - return ''; + // IE10+ special cases: It does have callstack info, but it is only populated if an Error object is thrown, + // so try that as a special-case. + try { + throw new Error(0); + } catch(e) { + err = e; + } + if (!err.stack) { + Runtime.warnOnce('emscripten_get_callstack_js is not supported on this browser!'); + return ''; + } } - var callstack = new Error().stack.toString(); + var callstack = err.stack.toString(); // Find the symbols in the callstack that corresponds to the functions that report callstack information, and remove everyhing up to these from the output. var iThisFunc = callstack.lastIndexOf('_emscripten_log'); From 065c4cd1556d959d11a9d5ef50fdc92bbea05439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 11 Mar 2014 14:03:57 +0200 Subject: [PATCH 09/24] Updated ChangeLog to latest. --- ChangeLog | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index fea67ddd074d6..328e3cac7a1f0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,7 +10,28 @@ Not all changes are documented here. In particular, new features, user-oriented Current trunk code ------------------ - To see a list of commits in the active development branch 'incoming', which have not yet been packaged in a release, see - https://github.com/kripken/emscripten/compare/1.12.3...incoming + https://github.com/kripken/emscripten/compare/1.13.1...incoming + +v1.13.1: 3/10/2014 +------------------ + - Disallow C implicit function declarations by making it an error instead of a warning by default. These will not work with Emscripten, due to strict Emscripten signature requirements when calling function pointers (#2175). + - Allow transitioning to full screen from SDL as a response to mouse press events. + - Fixed a bug in previous 1.13.0 release that broke fullscreen transitioning from working. + - Fixed emscripten/html5.h to be used in C source files. + - Fix an issue where extraneous system libraries would get included in the generated output (#2191). + - Added a new function emscripten_async_wget2_data() that allows reading from an XMLHTTPRequest directly into memory while supporting advanced features. + - Fixed esc key code in GLFW. + - Full list of changes: https://github.com/kripken/emscripten/compare/1.13.0...1.13.1 + +v1.13.0: 3/3/2014 +------------------ + - Fixed the deprecated source mapping syntax warning. + - Fixed a buffer overflow issue in emscripten_get_callstack (#2171). + - Added support for -Os (optimize for size) and -Oz (aggressively optimize for size) arguments to emcc. + - Fixed a typo that broko the call signature of glCompressedTexSubImage2D() function (#2173). + - Added new browser fullscreen resize logic that always retains aspect ratio and adds support for IE11. + - Improve debug messaging with bad function pointer calls when -s ASSERTIONS=2 is set. + - Full list of changes: https://github.com/kripken/emscripten/compare/1.12.3...1.13.0 v1.12.3: 2/27/2014 ------------------ From f69f770f7c9c9e1fff85b2ba0b89b512a5ab56ed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 11 Mar 2014 10:07:48 -0700 Subject: [PATCH 10/24] improve CHECK_HEAP_ALIGN message --- emcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emcc b/emcc index 538590ceddfa9..e28ec767831d8 100755 --- a/emcc +++ b/emcc @@ -1187,7 +1187,7 @@ try: shared.Settings.ASM_JS = 1 if opt_level > 0 else 2 try: assert shared.Settings.UNALIGNED_MEMORY == 0, 'forced unaligned memory not supported in fastcomp' - assert shared.Settings.CHECK_HEAP_ALIGN == 0, 'check heap align not supported in fastcomp yet' + assert shared.Settings.CHECK_HEAP_ALIGN == 0, 'check heap align not supported in fastcomp - use SAFE_HEAP instead' assert shared.Settings.SAFE_DYNCALLS == 0, 'safe dyncalls not supported in fastcomp' assert shared.Settings.ASM_HEAP_LOG == 0, 'asm heap log not supported in fastcomp' assert shared.Settings.LABEL_DEBUG == 0, 'label debug not supported in fastcomp' From 77e8b7dc65244ec83a53340394fd57f3632b317a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 11 Mar 2014 11:14:53 -0700 Subject: [PATCH 11/24] improve demangler a little --- src/preamble.js | 9 ++++++++- tests/test_other.py | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/preamble.js b/src/preamble.js index 3f724562e437c..89ab5026ccf6f 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -805,7 +805,14 @@ function demangle(func) { } } if (!allowVoid && list.length === 1 && list[0] === 'void') list = []; // avoid (void) - return rawList ? list : ret + flushList(); + if (rawList) { + if (ret) { + list.push(ret + '?'); + } + return list; + } else { + return ret + flushList(); + } } try { // Special-case the entry point, since its name differs from other name mangling. diff --git a/tests/test_other.py b/tests/test_other.py index 3d5cef96b4794..fcf7e49d90e93 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -2104,6 +2104,7 @@ def test_demangle(self): EM_ASM(Module.print(demangle('__Z5multiwahtjmxyz'))); EM_ASM(Module.print(demangle('__Z1aA32_iPA5_c'))); EM_ASM(Module.print(demangle('__ZN21FWakaGLXFleeflsMarfooC2EjjjPKvbjj'))); + EM_ASM(Module.print(demangle('__ZN5wakaw2Cm10RasterBaseINS_6watwat9PolocatorEE8merbine1INS4_2OREEEvPKjj'))); // we get this wrong, but at least emit a '?' one(17); return 0; } @@ -2127,6 +2128,7 @@ def test_demangle(self): multi(wchar_t, signed char, unsigned char, unsigned short, unsigned int, unsigned long, long long, unsigned long long, ...) a(int [32], char [5]*) FWakaGLXFleeflsMarfoo::FWakaGLXFleeflsMarfoo(unsigned int, unsigned int, unsigned int, void*, bool, unsigned int, unsigned int) +void wakaw::Cm::RasterBase(unsigned int*, unsigned int) ''', output) # test for multiple functions in one stack trace assert 'one(int)' in output From 7dfd4a4b22668a652c2276d56ff69fee8d7fd20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 12 Mar 2014 10:34:58 +0200 Subject: [PATCH 12/24] Make tests/test_html5_fullscreen.c standalone-compilable outside test suite. --- tests/test_html5_fullscreen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_html5_fullscreen.c b/tests/test_html5_fullscreen.c index 9a284b093cb24..f1224dcea7fc7 100644 --- a/tests/test_html5_fullscreen.c +++ b/tests/test_html5_fullscreen.c @@ -3,7 +3,6 @@ #include #include -#ifdef REPORT_RESULT void report_result(int result) { if (result == 0) { @@ -11,9 +10,10 @@ void report_result(int result) } else { printf("Test failed!\n"); } +#ifdef REPORT_RESULT REPORT_RESULT(); -} #endif +} static inline const char *emscripten_event_type_to_string(int eventType) { const char *events[] = { "(invalid)", "(none)", "keypress", "keydown", "keyup", "click", "mousedown", "mouseup", "dblclick", "mousemove", "wheel", "resize", From 74a3d9f57867891282f037ed1ed4d6a7292aa473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 12 Mar 2014 11:13:48 +0200 Subject: [PATCH 13/24] Register mouse callbacks in test_html5_fullscreen.c sample so that it can be tested on IE as well. --- tests/test_html5_fullscreen.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_html5_fullscreen.c b/tests/test_html5_fullscreen.c index f1224dcea7fc7..54b834bdf81a7 100644 --- a/tests/test_html5_fullscreen.c +++ b/tests/test_html5_fullscreen.c @@ -90,6 +90,11 @@ EM_BOOL fullscreenchange_callback(int eventType, const EmscriptenFullscreenChang return 0; } +EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userData) +{ + return 0; +} + int main() { EMSCRIPTEN_RESULT ret = emscripten_set_keypress_callback(0, 0, 1, key_callback); @@ -98,7 +103,21 @@ int main() ret = emscripten_set_fullscreenchange_callback(0, 0, 1, fullscreenchange_callback); TEST_RESULT(emscripten_set_fullscreenchange_callback); + // For Internet Explorer, fullscreen and pointer lock requests cannot be run + // from inside keyboard event handlers. Therefore we must register a callback to + // mouse events (any other than mousedown) to activate deferred fullscreen/pointerlock + // requests to occur for IE. The callback itself can be a no-op. + ret = emscripten_set_click_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_click_callback); + ret = emscripten_set_mousedown_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mousedown_callback); + ret = emscripten_set_mouseup_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mouseup_callback); + ret = emscripten_set_dblclick_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_dblclick_callback); + printf("To finish this test, press f to enter fullscreen mode, and then exit it.\n"); + printf("On IE, press a mouse key over the canvas after pressing f to activate the fullscreen request event.\n"); /* For the events to function, one must either call emscripten_set_main_loop or enable Module.noExitRuntime by some other means. Otherwise the application will exit after leaving main(), and the atexit handlers will clean up all event hooks (by design). */ From 50ecad0fcae2514333bf2437c85a206d8f4c399a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Wed, 12 Mar 2014 11:14:35 +0200 Subject: [PATCH 14/24] Fix IE fullscreen requests to not run in mousedown events, since IE doesn't allow them. Related to #2219. --- src/library_html5.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/library_html5.js b/src/library_html5.js index 0a938df618f1c..5534781da2eba 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -118,6 +118,8 @@ var LibraryJSEvents = { // Stores objects representing each currently registered JS event handler. eventHandlers: [], + isInternetExplorer: function() { return navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0; }, + _removeHandler: function(i) { JSEvents.eventHandlers[i].target.removeEventListener(JSEvents.eventHandlers[i].eventTypeString, JSEvents.eventHandlers[i].handlerFunc, true); JSEvents.eventHandlers.splice(i, 1); @@ -177,11 +179,9 @@ var LibraryJSEvents = { } }; - var isInternetExplorer = (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0); - var eventHandler = { target: JSEvents.findEventTarget(target), - allowsDeferredCalls: isInternetExplorer ? false : true, // MSIE doesn't allow fullscreen and pointerlock requests from key handlers, others do. + allowsDeferredCalls: JSEvents.isInternetExplorer ? false : true, // MSIE doesn't allow fullscreen and pointerlock requests from key handlers, others do. eventTypeString: eventTypeString, callbackfunc: callbackfunc, handlerFunc: handlerFunc, @@ -230,6 +230,8 @@ var LibraryJSEvents = { handlerFunc: handlerFunc, useCapture: useCapture }; + // In IE, mousedown events don't either allow deferred calls to be run! + if (JSEvents.isInternetExplorer() && eventTypeString == 'mousedown') eventHandler.allowsDeferredCalls = false; JSEvents.registerOrRemoveHandler(eventHandler); }, From 0d1230df4fe5eb58aa166a21cd62b0717d1219bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 13 Mar 2014 15:27:42 +0200 Subject: [PATCH 15/24] Fix typo with missing parentheses from previous commit that broke keyevents from performing deferred fullscreen/pointerlock requests. --- src/library_html5.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_html5.js b/src/library_html5.js index 5534781da2eba..cb3ea13a7720a 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -181,7 +181,7 @@ var LibraryJSEvents = { var eventHandler = { target: JSEvents.findEventTarget(target), - allowsDeferredCalls: JSEvents.isInternetExplorer ? false : true, // MSIE doesn't allow fullscreen and pointerlock requests from key handlers, others do. + allowsDeferredCalls: JSEvents.isInternetExplorer() ? false : true, // MSIE doesn't allow fullscreen and pointerlock requests from key handlers, others do. eventTypeString: eventTypeString, callbackfunc: callbackfunc, handlerFunc: handlerFunc, From a124ef1a5416dff35d3fe651065d845f7be86e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 13 Mar 2014 15:51:41 +0200 Subject: [PATCH 16/24] Manually implement mouse movementXY in HTML5 mousemove events when the browser does not provide the data. Fixes part 2) from issue #2219. --- src/library_html5.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/library_html5.js b/src/library_html5.js index cb3ea13a7720a..b15270fe6805c 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -17,6 +17,11 @@ var LibraryJSEvents = { // so that we can report information about that element in the event message. previousFullscreenElement: null, + // Remember the current mouse coordinates in case we need to emulate movementXY generation for browsers that don't support it. + // Some browsers (e.g. Safari 6.0.5) only give movementXY when Pointerlock is active. + previousScreenX: null, + previousScreenY: null, + // When the C runtime exits via exit(), we unregister all event handlers added by this library to be nice and clean. // Track in this field whether we have yet registered that __ATEXIT__ handler. removeEventListenersRegistered: false, @@ -203,10 +208,12 @@ var LibraryJSEvents = { {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.metaKey, 'e.metaKey', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.button, 'e.button', 'i16') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.buttons, 'e.buttons', 'i16') }}}; - {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementX, 'e.movementX || e.mozMovementX || e.webkitMovementX', 'i32') }}}; - {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementY, 'e.movementY || e.mozMovementY || e.webkitMovementY', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementX, 'e.movementX || e.mozMovementX || e.webkitMovementX || (e.screenX-JSEvents.previousScreenX)', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementY, 'e.movementY || e.mozMovementY || e.webkitMovementY || (e.screenY-JSEvents.previousScreenY)', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.canvasX, 'e.clientX - rect.left', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.canvasY, 'e.clientY - rect.top', 'i32') }}}; + JSEvents.previousScreenX = e.screenX; + JSEvents.previousScreenY = e.screenY; }, registerMouseEventCallback: function(target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString) { From fb0b19e4c5dbf53f61ddfd02ce480ac97aee20d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 13 Mar 2014 16:34:46 +0200 Subject: [PATCH 17/24] Implement support for Webkit mouse wheel events. Add new interactive mouse test. Checked on OSX Safari 6.0.5. Fixes #2219. --- src/library_html5.js | 30 ++++++++-- tests/test_html5_mouse.c | 117 ++++++++++++++++++++++++++++++++++++++ tests/test_interactive.py | 3 + 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 tests/test_html5_mouse.c diff --git a/src/library_html5.js b/src/library_html5.js index b15270fe6805c..080bfe394025d 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -246,7 +246,8 @@ var LibraryJSEvents = { if (!JSEvents.wheelEvent) { JSEvents.wheelEvent = _malloc( {{{ C_STRUCTS.EmscriptenWheelEvent.__size__ }}} ); } - var handlerFunc = function(event) { + // The DOM Level 3 events spec event 'wheel' + var wheelHandlerFunc = function(event) { var e = event || window.event; JSEvents.fillMouseEventData(JSEvents.wheelEvent, e); {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["deltaX"]', 'double') }}}; @@ -258,13 +259,26 @@ var LibraryJSEvents = { e.preventDefault(); } }; + // The 'mousewheel' event as implemented in Safari 6.0.5 + var mouseWheelHandlerFunc = function(event) { + var e = event || window.event; + JSEvents.fillMouseEventData(JSEvents.wheelEvent, e); + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["wheelDeltaX"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, '-e["wheelDeltaY"] /* Invert to unify direction with the DOM Level 3 wheel event. */', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, '0 /* Not available */', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, '0 /* DOM_DELTA_PIXEL */', 'i32') }}}; + var shouldCancel = Runtime.dynCall('iiii', callbackfunc, [eventTypeId, JSEvents.wheelEvent, userData]); + if (shouldCancel) { + e.preventDefault(); + } + }; var eventHandler = { target: JSEvents.findEventTarget(target), allowsDeferredCalls: true, eventTypeString: eventTypeString, callbackfunc: callbackfunc, - handlerFunc: handlerFunc, + handlerFunc: (eventTypeString == 'wheel') ? wheelHandlerFunc : mouseWheelHandlerFunc, useCapture: useCapture }; JSEvents.registerOrRemoveHandler(eventHandler); @@ -927,8 +941,16 @@ var LibraryJSEvents = { }, emscripten_set_wheel_callback: function(target, userData, useCapture, callbackfunc) { - JSEvents.registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "wheel"); - return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; + target = JSEvents.findEventTarget(target); + if (typeof target.onwheel !== 'undefined') { + JSEvents.registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "wheel"); + return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; + } else if (typeof target.onmousewheel !== 'undefined') { + JSEvents.registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "mousewheel"); + return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; + } else { + return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}}; + } }, emscripten_set_resize_callback: function(target, userData, useCapture, callbackfunc) { diff --git a/tests/test_html5_mouse.c b/tests/test_html5_mouse.c new file mode 100644 index 0000000000000..f05967184be3c --- /dev/null +++ b/tests/test_html5_mouse.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include + +void report_result(int result) +{ + if (result == 0) { + printf("Test successful!\n"); + } else { + printf("Test failed!\n"); + } +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif +} + +static inline const char *emscripten_event_type_to_string(int eventType) { + const char *events[] = { "(invalid)", "(none)", "keypress", "keydown", "keyup", "click", "mousedown", "mouseup", "dblclick", "mousemove", "wheel", "resize", + "scroll", "blur", "focus", "focusin", "focusout", "deviceorientation", "devicemotion", "orientationchange", "fullscreenchange", "pointerlockchange", + "visibilitychange", "touchstart", "touchend", "touchmove", "touchcancel", "gamepadconnected", "gamepaddisconnected", "beforeunload", + "batterychargingchange", "batterylevelchange", "webglcontextlost", "webglcontextrestored", "(invalid)" }; + ++eventType; + if (eventType < 0) eventType = 0; + if (eventType >= sizeof(events)/sizeof(events[0])) eventType = sizeof(events)/sizeof(events[0])-1; + return events[eventType]; +} + +const char *emscripten_result_to_string(EMSCRIPTEN_RESULT result) { + if (result == EMSCRIPTEN_RESULT_SUCCESS) return "EMSCRIPTEN_RESULT_SUCCESS"; + if (result == EMSCRIPTEN_RESULT_DEFERRED) return "EMSCRIPTEN_RESULT_DEFERRED"; + if (result == EMSCRIPTEN_RESULT_NOT_SUPPORTED) return "EMSCRIPTEN_RESULT_NOT_SUPPORTED"; + if (result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED) return "EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED"; + if (result == EMSCRIPTEN_RESULT_INVALID_TARGET) return "EMSCRIPTEN_RESULT_INVALID_TARGET"; + if (result == EMSCRIPTEN_RESULT_UNKNOWN_TARGET) return "EMSCRIPTEN_RESULT_UNKNOWN_TARGET"; + if (result == EMSCRIPTEN_RESULT_INVALID_PARAM) return "EMSCRIPTEN_RESULT_INVALID_PARAM"; + if (result == EMSCRIPTEN_RESULT_FAILED) return "EMSCRIPTEN_RESULT_FAILED"; + if (result == EMSCRIPTEN_RESULT_NO_DATA) return "EMSCRIPTEN_RESULT_NO_DATA"; + return "Unknown EMSCRIPTEN_RESULT!"; +} + +#define TEST_RESULT(x) if (ret != EMSCRIPTEN_RESULT_SUCCESS) printf("%s returned %s.\n", #x, emscripten_result_to_string(ret)); + +int gotClick = 0; +int gotMouseDown = 0; +int gotMouseUp = 0; +int gotDblClick = 0; +int gotMouseMove = 0; +int gotWheel = 0; + +void instruction() +{ + if (!gotClick) { printf("Please click on the canvas.\n"); return; } + if (!gotMouseDown) { printf("Please click on the canvas.\n"); return; } + if (!gotMouseUp) { printf("Please click on the canvas.\n"); return; } + if (!gotDblClick) { printf("Please double-click on the canvas.\n"); return; } + if (!gotMouseMove) { printf("Please move the mouse on the canvas.\n"); return; } + if (!gotWheel) { printf("Please scroll the mouse wheel.\n"); return; } + + if (gotClick && gotMouseDown && gotMouseUp && gotDblClick && gotMouseMove && gotWheel) report_result(0); +} + +EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userData) +{ + /* + printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld)\n", + emscripten_event_type_to_string(eventType), e->screenX, e->screenY, e->clientX, e->clientY, + e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", + e->button, e->buttons, e->movementX, e->movementY, e->canvasX, e->canvasY); +*/ + if (eventType == EMSCRIPTEN_EVENT_CLICK) gotClick = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) gotMouseDown = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) gotMouseUp = 1; + if (eventType == EMSCRIPTEN_EVENT_DBLCLICK) gotDblClick = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && (e->movementX != 0 || e->movementY != 0)) gotMouseMove = 1; + + instruction(); + return 0; +} + +EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent *e, void *userData) +{ + /* + printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), delta:(%g,%g,%g), deltaMode:%lu\n", + emscripten_event_type_to_string(eventType), e->mouse.screenX, e->mouse.screenY, e->mouse.clientX, e->mouse.clientY, + e->mouse.ctrlKey ? " CTRL" : "", e->mouse.shiftKey ? " SHIFT" : "", e->mouse.altKey ? " ALT" : "", e->mouse.metaKey ? " META" : "", + e->mouse.button, e->mouse.buttons, e->mouse.canvasX, e->mouse.canvasY, + (float)e->deltaX, (float)e->deltaY, (float)e->deltaZ, e->deltaMode); +*/ + if (e->deltaY != 0.f) + gotWheel = 1; + + instruction(); + return 0; +} + +int main() +{ + EMSCRIPTEN_RESULT ret = emscripten_set_click_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_click_callback); + ret = emscripten_set_mousedown_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mousedown_callback); + ret = emscripten_set_mouseup_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mouseup_callback); + ret = emscripten_set_dblclick_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_dblclick_callback); + ret = emscripten_set_mousemove_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mousemove_callback); + + ret = emscripten_set_wheel_callback(0, 0, 1, wheel_callback); + TEST_RESULT(emscripten_set_wheel_callback); + + /* For the events to function, one must either call emscripten_set_main_loop or enable Module.noExitRuntime by some other means. + Otherwise the application will exit after leaving main(), and the atexit handlers will clean up all event hooks (by design). */ + EM_ASM(Module['noExitRuntime'] = true); + return 0; +} diff --git a/tests/test_interactive.py b/tests/test_interactive.py index 715e7d6b3ebab..4ac52f55a43ff 100644 --- a/tests/test_interactive.py +++ b/tests/test_interactive.py @@ -22,6 +22,9 @@ def setUpClass(self): def test_html5_fullscreen(self): self.btest(path_from_root('tests', 'test_html5_fullscreen.c'), expected='0') + def test_html5_mouse(self): + self.btest(path_from_root('tests', 'test_html5_mouse.c'), expected='0') + def test_sdl_wm_togglefullscreen(self): self.btest('sdl_wm_togglefullscreen.c', expected='1', args=['-s', 'NO_EXIT_RUNTIME=1']) From a564e870359a576ad42531bb39a279db12bd5b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 14 Mar 2014 11:26:19 +0200 Subject: [PATCH 18/24] Add an automated version of the html5 mouse test in browser test suite. --- tests/test_browser.py | 5 +++++ tests/test_html5_mouse.c | 42 ++++++++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/tests/test_browser.py b/tests/test_browser.py index d594970964955..f03436697dcbc 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1790,6 +1790,11 @@ def test_html5(self): print opts self.btest(path_from_root('tests', 'test_html5.c'), args=opts, expected='0') + def test_html5_mouse(self): + for opts in [[], ['-O2', '-g1', '--closure', '1']]: + print opts + self.btest(path_from_root('tests', 'test_html5_mouse.c'), args=opts + ['-DAUTOMATE_SUCCESS=1'], expected='0') + def test_codemods(self): for opt_level in [0, 2]: print 'opt level', opt_level diff --git a/tests/test_html5_mouse.c b/tests/test_html5_mouse.c index f05967184be3c..e73211c4f0a11 100644 --- a/tests/test_html5_mouse.c +++ b/tests/test_html5_mouse.c @@ -62,17 +62,22 @@ void instruction() EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userData) { - /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld)\n", emscripten_event_type_to_string(eventType), e->screenX, e->screenY, e->clientX, e->clientY, e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", e->button, e->buttons, e->movementX, e->movementY, e->canvasX, e->canvasY); -*/ - if (eventType == EMSCRIPTEN_EVENT_CLICK) gotClick = 1; - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) gotMouseDown = 1; - if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) gotMouseUp = 1; - if (eventType == EMSCRIPTEN_EVENT_DBLCLICK) gotDblClick = 1; - if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && (e->movementX != 0 || e->movementY != 0)) gotMouseMove = 1; + + if (e->screenX != 0 && e->screenY != 0 && e->clientX != 0 && e->clientY != 0 && e->canvasX != 0 && e->canvasY != 0) + { + if (e->buttons != 0) + { + if (eventType == EMSCRIPTEN_EVENT_CLICK) gotClick = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) gotMouseDown = 1; + if (eventType == EMSCRIPTEN_EVENT_DBLCLICK) gotDblClick = 1; + } + if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) gotMouseUp = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && (e->movementX != 0 || e->movementY != 0)) gotMouseMove = 1; + } instruction(); return 0; @@ -80,14 +85,13 @@ EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userD EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent *e, void *userData) { - /* printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), delta:(%g,%g,%g), deltaMode:%lu\n", emscripten_event_type_to_string(eventType), e->mouse.screenX, e->mouse.screenY, e->mouse.clientX, e->mouse.clientY, e->mouse.ctrlKey ? " CTRL" : "", e->mouse.shiftKey ? " SHIFT" : "", e->mouse.altKey ? " ALT" : "", e->mouse.metaKey ? " META" : "", e->mouse.button, e->mouse.buttons, e->mouse.canvasX, e->mouse.canvasY, (float)e->deltaX, (float)e->deltaY, (float)e->deltaZ, e->deltaMode); -*/ - if (e->deltaY != 0.f) + + if (e->deltaY > 0.f || e->deltaY < 0.f) gotWheel = 1; instruction(); @@ -110,6 +114,24 @@ int main() ret = emscripten_set_wheel_callback(0, 0, 1, wheel_callback); TEST_RESULT(emscripten_set_wheel_callback); +#ifdef AUTOMATE_SUCCESS + EM_ASM( + function sendEvent(type, data) { + var event = document.createEvent('Event'); + event.initEvent(type, true, true); + for(var d in data) event[d] = data[d]; + window.dispatchEvent(event); + } + sendEvent('click', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 1 }); + sendEvent('mousedown', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 1 }); + sendEvent('mouseup', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0 }); + sendEvent('dblclick', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 1 }); + sendEvent('mousemove', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0, movementX: 1, movementY: 1 }); + sendEvent('wheel', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0, deltaX: 1, deltaY: 1, deltaZ: 1, deltaMode: 1 }); + sendEvent('mousewheel', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0, wheelDeltaX: 1, wheelDeltaY: 1 }); + ); +#endif + /* For the events to function, one must either call emscripten_set_main_loop or enable Module.noExitRuntime by some other means. Otherwise the application will exit after leaving main(), and the atexit handlers will clean up all event hooks (by design). */ EM_ASM(Module['noExitRuntime'] = true); From b3b29bede566334d184cf87a3cc748be23445474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 14 Mar 2014 20:29:02 +0200 Subject: [PATCH 19/24] Fix SDL Web Audio to work on Safari 6.0.5. That browser does not implement the AudioBufferSourceNode.start() method, but only has the older .noteOn() method, and the audio ctors don't come out as functions, but strongly typed ctor objects. --- src/library_sdl.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/library_sdl.js b/src/library_sdl.js index b50073bedee45..364349b6a6f4c 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -1746,9 +1746,9 @@ var LibrarySDL = { // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. if (!SDL.audioContext) { - if (typeof(AudioContext) === 'function') { + if (typeof(AudioContext) !== 'undefined') { SDL.audioContext = new AudioContext(); - } else if (typeof(webkitAudioContext) === 'function') { + } else if (typeof(webkitAudioContext) !== 'undefined') { SDL.audioContext = new webkitAudioContext(); } else { throw 'Web Audio API is not available!'; @@ -1791,7 +1791,12 @@ var LibrarySDL = { } #endif var playtime = Math.max(curtime, SDL.audio.nextPlayTime); - SDL.audio.soundSource[SDL.audio.nextSoundSource]['start'](playtime); + var ss = SDL.audio.soundSource[SDL.audio.nextSoundSource]; + if (typeof ss['start'] !== 'undefined') { + ss['start'](playtime); + } else if (typeof ss['noteOn'] !== 'undefined') { + ss['noteOn'](playtime); + } var buffer_duration = sizeSamplesPerChannel / SDL.audio.freq; SDL.audio.nextPlayTime = playtime + buffer_duration; // Timer will be scheduled before the buffer completed playing. @@ -1864,7 +1869,7 @@ var LibrarySDL = { } else if (!SDL.audio.timer && !SDL.audio.scriptProcessorNode) { // If we are using the same sampling frequency as the native sampling rate of the Web Audio graph is using, we can feed our buffers via // Web Audio ScriptProcessorNode, which is a pull-mode API that calls back to our code to get audio data. - if (SDL.audioContext !== undefined && SDL.audio.freq == SDL.audioContext['sampleRate']) { + if (SDL.audioContext !== undefined && SDL.audio.freq == SDL.audioContext['sampleRate'] && typeof SDL.audioContext['createScriptProcessor'] !== 'undefined') { var sizeSamplesPerChannel = SDL.audio.bufferSize / SDL.audio.bytesPerSample / SDL.audio.channels; // How many samples per a single channel fit in the cb buffer? SDL.audio.scriptProcessorNode = SDL.audioContext['createScriptProcessor'](sizeSamplesPerChannel, 0, SDL.audio.channels); SDL.audio.scriptProcessorNode['onaudioprocess'] = function (e) { From 7cd9a3c64c4cbbede7fac8002b351f5f599eb799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 14 Mar 2014 20:52:37 +0200 Subject: [PATCH 20/24] Fix OpenAL library to work with old Web Audio API syntax that exists in Safari 6.0.5 at least. --- src/library_openal.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/library_openal.js b/src/library_openal.js index fd382aa1eb7d8..56ddf11a6f932 100644 --- a/src/library_openal.js +++ b/src/library_openal.js @@ -58,9 +58,21 @@ var LibraryOpenAL = { entry.src = AL.currentContext.ctx.createBufferSource(); entry.src.buffer = entry.buffer; entry.src.connect(src.gain); - entry.src.start(startTime, offset); - + if (typeof(entry.src.start) !== 'undefined') { + entry.src.start(startTime, offset); + } else if (typeof(entry.src.noteOn) !== 'undefined') { + entry.src.noteOn(startTime); #if OPENAL_DEBUG + if (offset > 0) { + Runtime.warnOnce('The current browser does not support AudioBufferSourceNode.start(when, offset); method, so cannot play back audio with an offset '+offset+' secs! Audio glitches will occur!'); + } +#endif + } +#if OPENAL_DEBUG + else { + Runtime.warnOnce('Unable to start AudioBufferSourceNode playback! Not supported by the browser?'); + } + console.log('updateSource queuing buffer ' + i + ' for source ' + idx + ' at ' + startTime + ' (offset by ' + offset + ')'); #endif } @@ -204,6 +216,9 @@ var LibraryOpenAL = { } if (ctx) { + // Old Web Audio API (e.g. Safari 6.0.5) had aninconsistently named createGainNode function. + if (typeof(ctx.createGain) === 'undefined') ctx.createGain = ctx.createGainNode; + var gain = ctx.createGain(); gain.connect(ctx.destination); var context = { @@ -1283,16 +1298,16 @@ var LibraryOpenAL = { ret = 'Out of Memory'; break; case 0x1004 /* ALC_DEFAULT_DEVICE_SPECIFIER */: - if (typeof(AudioContext) == "function" || - typeof(webkitAudioContext) == "function") { + if (typeof(AudioContext) !== "undefined" || + typeof(webkitAudioContext) !== "undefined") { ret = 'Device'; } else { return 0; } break; case 0x1005 /* ALC_DEVICE_SPECIFIER */: - if (typeof(AudioContext) == "function" || - typeof(webkitAudioContext) == "function") { + if (typeof(AudioContext) !== "undefined" || + typeof(webkitAudioContext) !== "undefined") { ret = 'Device\0'; } else { ret = '\0'; From 21fc381f6e0fd575da709bc461dd5251d30536fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 14 Mar 2014 20:52:56 +0200 Subject: [PATCH 21/24] Make tests/openal_playback.cpp compilable outside the test runner. --- tests/openal_playback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/openal_playback.cpp b/tests/openal_playback.cpp index 880b69062717e..46c4f8a365bbf 100644 --- a/tests/openal_playback.cpp +++ b/tests/openal_playback.cpp @@ -25,7 +25,7 @@ void playSource(void* arg) alGetSourcei(source, AL_SOURCE_STATE, &state); assert(state == AL_STOPPED); -#ifdef __EMSCRIPTEN__ +#ifdef REPORT_RESULT int result = 1; REPORT_RESULT(); #endif From dee56ea62fd1cd4a1d35024c9c5243c4952ffecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 14 Mar 2014 21:00:23 +0200 Subject: [PATCH 22/24] Fix typo in comment. --- src/library_openal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_openal.js b/src/library_openal.js index 56ddf11a6f932..58b11607fdd71 100644 --- a/src/library_openal.js +++ b/src/library_openal.js @@ -216,7 +216,7 @@ var LibraryOpenAL = { } if (ctx) { - // Old Web Audio API (e.g. Safari 6.0.5) had aninconsistently named createGainNode function. + // Old Web Audio API (e.g. Safari 6.0.5) had an inconsistently named createGainNode function. if (typeof(ctx.createGain) === 'undefined') ctx.createGain = ctx.createGainNode; var gain = ctx.createGain(); From cef72b48d5f288b8954c174b8b6d7be94c51988e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Fri, 14 Mar 2014 21:52:26 +0200 Subject: [PATCH 23/24] Add better documentation to emscripten/html5.h KeyEvent structure on the current implementation and deprecation status of the various fields. See also pr #2222. --- system/include/emscripten/html5.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index 4f74af0382be5..db81725a35cb3 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -123,6 +123,10 @@ extern "C" { /* * The event structure passed in keyboard events keypress, keydown and keyup. * https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#keys + * + * Note that since the DOM Level 3 Events spec is very recent at the time of writing (2014-03), uniform + * support for the different fields in the spec is still in flux. Be sure to check the results in multiple + * browsers. See the unmerged pull request #2222 for an example way on how to interpret the legacy key events. */ typedef struct EmscriptenKeyboardEvent { // The printed representation of the pressed key. @@ -144,14 +148,18 @@ typedef struct EmscriptenKeyboardEvent { EM_UTF8 locale[32]; // The following fields are values from previous versions of the DOM key events specifications. // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent?redirectlocale=en-US&redirectslug=DOM%2FKeyboardEvent - // The character representation of the key. + // The character representation of the key. This is the field 'char' from the docs, but renamed to charValue to avoid a C reserved word. + // Warning: This attribute has been dropped from DOM Level 3 events. EM_UTF8 charValue[32]; // The Unicode reference number of the key; this attribute is used only by the keypress event. For keys whose char attribute // contains multiple characters, this is the Unicode value of the first character in that attribute. + // Warning: This attribute is deprecated, you should use the field 'key' instead, if available. unsigned long charCode; // A system and implementation dependent numerical code identifying the unmodified value of the pressed key. + // Warning: This attribute is deprecated, you should use the field 'key' instead, if available. unsigned long keyCode; // A system and implementation dependent numeric code identifying the unmodified value of the pressed key; this is usually the same as keyCode. + // Warning: This attribute is deprecated, you should use the field 'key' instead, if available. unsigned long which; } EmscriptenKeyboardEvent; From 5c03805d684cb541ba5a40138457bb598642de20 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 14 Mar 2014 15:09:59 -0700 Subject: [PATCH 24/24] add a Nested branch type in relooper, to represent a path we must make sure is nested so that parallel paths do not get intertwined. this allows us to emit a more canonical form of nested ifs as a result of short-circuit operators; 1.13.2 --- emscripten-version.txt | 2 +- src/relooper/Relooper.cpp | 51 +++++++++++++++++++--- src/relooper/Relooper.h | 8 ++-- src/relooper/test.cpp | 90 +++++++++++++++++++++++++++++++++++++++ src/relooper/test.txt | 39 +++++++++++++++++ 5 files changed, 181 insertions(+), 9 deletions(-) diff --git a/emscripten-version.txt b/emscripten-version.txt index 87684ab8378d6..2e46fa6fa330a 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.13.1 +1.13.2 diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index c11de0999518a..c68301589121e 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -122,7 +122,7 @@ void Branch::Render(Block *Target, bool SetLabel) { if (Code) PrintIndented("%s\n", Code); if (SetLabel) PrintIndented("label = %d;\n", Target->Id); if (Ancestor) { - if (Type != Direct) { + if (Type == Break || Type == Continue) { if (Labeled) { PrintIndented("%s L%d;\n", Type == Break ? "break" : "continue", Ancestor->Id); } else { @@ -287,6 +287,11 @@ void Block::Render(bool InLoop) { Details->Render(Target, SetCurrLabel); if (HasFusedContent) { Fused->InnerMap.find(Target)->second->Render(InLoop); + } else if (Details->Type == Branch::Nested) { + // Nest the parent content here, and remove it from showing up afterwards as Next + assert(Parent->Next); + Parent->Next->Render(InLoop); + Parent->Next = NULL; } if (useSwitch && iter != ProcessedBranchesOut.end()) { PrintIndented("break;\n"); @@ -1077,12 +1082,48 @@ void Relooper::Calculate(Block *Entry) { SHAPE_SWITCH(Root, { if (Simple->Inner->BranchVar) LastLoop = NULL; // a switch clears out the loop (TODO: only for breaks, not continue) - // If there is a next block, we already know at Simple creation time to make direct branches, - // and we can do nothing more. If there is no next however, then Natural is where we will - // go to by doing nothing, so we can potentially optimize some branches to direct. if (Simple->Next) { + if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2) { + // If there is a next block, we already know at Simple creation time to make direct branches, + // and we can do nothing more in general. But, we try to optimize the case of a break and + // a direct: This would normally be if (break?) { break; } .. but if we + // make sure to nest the else, we can save the break, if (!break?) { .. } . This is also + // better because the more canonical nested form is easier to further optimize later. The + // downside is more nesting, which adds to size in builds with whitespace. + // Note that we avoid switches, as it complicates control flow and is not relevant + // for the common case we optimize here. + bool Found = false; + bool Abort = false; + for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { + Block *Target = iter->first; + Branch *Details = iter->second; + if (Details->Type == Branch::Break) { + Found = true; + if (!contains(NaturalBlocks, Target)) Abort = true; + } else if (Details->Type != Branch::Direct) { + Abort = true; + } + } + if (Found && !Abort) { + for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { + Block *Target = iter->first; + Branch *Details = iter->second; + if (Details->Type == Branch::Break) { + Details->Type = Branch::Direct; + if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { + Multiple->NeedLoop--; + } + } else { + assert(Details->Type == Branch::Direct); + Details->Type = Branch::Nested; + } + } + } + } Next = Simple->Next; } else { + // If there is no next then Natural is where we will + // go to by doing nothing, so we can potentially optimize some branches to direct. for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { Block *Target = iter->first; Branch *Details = iter->second; @@ -1140,7 +1181,7 @@ void Relooper::Calculate(Block *Entry) { for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { Block *Target = iter->first; Branch *Details = iter->second; - if (Details->Type != Branch::Direct) { + if (Details->Type == Branch::Break || Details->Type == Branch::Continue) { assert(LoopStack.size() > 0); if (Details->Ancestor != LoopStack.top() && Details->Labeled) { LabeledShape *Labeled = Shape::IsLabeled(Details->Ancestor); diff --git a/src/relooper/Relooper.h b/src/relooper/Relooper.h index 85adf359c2589..152bae0e6e709 100644 --- a/src/relooper/Relooper.h +++ b/src/relooper/Relooper.h @@ -24,9 +24,11 @@ struct Shape; // Info about a branching from one block to another struct Branch { enum FlowType { - Direct = 0, // We will directly reach the right location through other means, no need for continue or break + Direct = 0, // We will directly reach the right location through other means, no need for continue or break Break = 1, - Continue = 2 + Continue = 2, + Nested = 3 // This code is directly reached, but we must be careful to ensure it is nested in an if - it is not reached + // unconditionally, other code paths exist alongside it that we need to make sure do not intertwine }; Shape *Ancestor; // If not NULL, this shape is the relevant one for purposes of getting to the target block. We break or continue on it Branch::FlowType Type; // If Ancestor is not NULL, this says whether to break or continue @@ -59,7 +61,7 @@ struct Block { Shape *Parent; // The shape we are directly inside int Id; // A unique identifier, defined when added to relooper. Note that this uniquely identifies a *logical* block - if we split it, the two instances have the same content *and* the same Id const char *Code; // The string representation of the code in this block. Owning pointer (we copy the input) - const char *BranchVar; // If we have more than one branch out, the variable whose value determines where we go + const char *BranchVar; // A variable whose value determines where we go; if this is not NULL, emit a switch on that variable bool IsCheckedMultipleEntry; // If true, we are a multiple entry, so reaching us requires setting the label variable Block(const char *CodeInit, const char *BranchVarInit); diff --git a/src/relooper/test.cpp b/src/relooper/test.cpp index b4ce669c63454..2b25001b854cc 100644 --- a/src/relooper/test.cpp +++ b/src/relooper/test.cpp @@ -314,5 +314,95 @@ int main() { puts(buffer); } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_c, NULL, NULL); + + b_b->AddBranchTo(b_c, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); + } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + Block *b_d = new Block("// block D\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_d, NULL, NULL); + + b_b->AddBranchTo(b_c, "b == 10", NULL); + b_b->AddBranchTo(b_d, NULL, NULL); + + b_c->AddBranchTo(b_d, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + r.AddBlock(b_d); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); + } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized, long) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + Block *b_d = new Block("// block D\n", NULL); + Block *b_e = new Block("// block E\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_e, NULL, NULL); + + b_b->AddBranchTo(b_c, "b == 10", NULL); + b_b->AddBranchTo(b_e, NULL, NULL); + + b_c->AddBranchTo(b_d, "c == 10", NULL); + b_c->AddBranchTo(b_e, NULL, NULL); + + b_d->AddBranchTo(b_e, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + r.AddBlock(b_d); + r.AddBlock(b_e); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); + } } diff --git a/src/relooper/test.txt b/src/relooper/test.txt index 82b02ad7107c2..1fa205bade632 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -360,3 +360,42 @@ } } + + +-- If chain (optimized) -- + + // block A + if (a == 10) { + // block B + } + // block C + + + +-- If chain (optimized) -- + + // block A + if (a == 10) { + // block B + if (b == 10) { + // block C + } + } + // block D + + + +-- If chain (optimized, long) -- + + // block A + if (a == 10) { + // block B + if (b == 10) { + // block C + if (c == 10) { + // block D + } + } + } + // block E +