From adeb50bb7931d738456eb9151bee3bdda9df5c0f Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Thu, 9 Oct 2025 09:58:57 -0700 Subject: [PATCH 1/6] First pass at implementing #20983 - added emscripten_remove_callback - iterate over all handlers and only remove the one matching the arguments provided - changed all event setters to pass userData and eventTypeId which are required by this API --- src/lib/libhtml5.js | 63 ++++++++++++++ src/lib/libhtml5_webgl.js | 2 + system/include/emscripten/html5.h | 2 + test/test_browser.py | 12 +++ test/test_html5_remove_callback.c | 138 ++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 test/test_html5_remove_callback.c diff --git a/src/lib/libhtml5.js b/src/lib/libhtml5.js index b7363179d097b..119a9ed0e70b3 100644 --- a/src/lib/libhtml5.js +++ b/src/lib/libhtml5.js @@ -205,6 +205,25 @@ var LibraryHTML5 = { return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; }, + removeSingleHandler(eventHandler) { + if (!eventHandler.target) { +#if ASSERTIONS + err('removeSingleHandler: the target element for event handler registration does not exist, when processing the following event handler registration:'); + console.dir(eventHandler); +#endif + return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}}; + } + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if (JSEvents.eventHandlers[i].target === eventHandler.target + && JSEvents.eventHandlers[i].eventTypeId === eventHandler.eventTypeId + && JSEvents.eventHandlers[i].callbackfunc === eventHandler.callbackfunc + && JSEvents.eventHandlers[i].userData === eventHandler.userData) { + JSEvents._removeHandler(i--); + } + } + return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}}; + }, + #if PTHREADS getTargetThreadForEventCallback(targetThread) { switch (targetThread) { @@ -298,6 +317,8 @@ var LibraryHTML5 = { var eventHandler = { target: findEventTarget(target), eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: keyEventHandlerFunc, useCapture @@ -412,6 +433,18 @@ var LibraryHTML5 = { }, #endif + emscripten_remove_callback__proxy: 'sync', + emscripten_remove_callback__deps: ['$JSEvents', '$findEventTarget'], + emscripten_remove_callback: (target, userData, eventTypeId, callback) => { + var eventHandler = { + target: findEventTarget(target), + userData, + eventTypeId, + callbackfunc: callback, + }; + return JSEvents.removeSingleHandler(eventHandler); + }, + emscripten_set_keypress_callback_on_thread__proxy: 'sync', emscripten_set_keypress_callback_on_thread__deps: ['$registerKeyEventCallback'], emscripten_set_keypress_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) => @@ -503,6 +536,8 @@ var LibraryHTML5 = { allowsDeferredCalls: eventTypeString != 'mousemove' && eventTypeString != 'mouseenter' && eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them! #endif eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: mouseEventHandlerFunc, useCapture @@ -599,6 +634,8 @@ var LibraryHTML5 = { allowsDeferredCalls: true, #endif eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: wheelHandlerFunc, useCapture @@ -674,6 +711,8 @@ var LibraryHTML5 = { var eventHandler = { target, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: uiEventHandlerFunc, useCapture @@ -721,6 +760,8 @@ var LibraryHTML5 = { var eventHandler = { target: findEventTarget(target), eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: focusEventHandlerFunc, useCapture @@ -779,6 +820,8 @@ var LibraryHTML5 = { var eventHandler = { target: findEventTarget(target), eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: deviceOrientationEventHandlerFunc, useCapture @@ -849,6 +892,8 @@ var LibraryHTML5 = { var eventHandler = { target: findEventTarget(target), eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: deviceMotionEventHandlerFunc, useCapture @@ -935,6 +980,8 @@ var LibraryHTML5 = { var eventHandler = { target, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: orientationChangeEventHandlerFunc, useCapture @@ -1046,6 +1093,8 @@ var LibraryHTML5 = { var eventHandler = { target, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: fullscreenChangeEventhandlerFunc, useCapture @@ -1547,6 +1596,8 @@ var LibraryHTML5 = { var eventHandler = { target, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: pointerlockChangeEventHandlerFunc, useCapture @@ -1591,6 +1642,8 @@ var LibraryHTML5 = { var eventHandler = { target, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: pointerlockErrorEventHandlerFunc, useCapture @@ -1745,6 +1798,8 @@ var LibraryHTML5 = { var eventHandler = { target, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: visibilityChangeEventHandlerFunc, useCapture @@ -1863,6 +1918,8 @@ var LibraryHTML5 = { allowsDeferredCalls: eventTypeString == 'touchstart' || eventTypeString == 'touchend', #endif eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: touchEventHandlerFunc, useCapture @@ -1949,6 +2006,8 @@ var LibraryHTML5 = { allowsDeferredCalls: true, #endif eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: gamepadEventHandlerFunc, useCapture @@ -2035,6 +2094,8 @@ var LibraryHTML5 = { var eventHandler = { target: findEventTarget(target), eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: beforeUnloadEventHandlerFunc, useCapture @@ -2088,6 +2149,8 @@ var LibraryHTML5 = { var eventHandler = { target: battery, eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: batteryEventHandlerFunc, useCapture diff --git a/src/lib/libhtml5_webgl.js b/src/lib/libhtml5_webgl.js index 63f8d2ad39624..ed8b4bf27577c 100644 --- a/src/lib/libhtml5_webgl.js +++ b/src/lib/libhtml5_webgl.js @@ -441,6 +441,8 @@ var LibraryHtml5WebGL = { var eventHandler = { target: findEventTarget(target), eventTypeString, + eventTypeId, + userData, callbackfunc, handlerFunc: webGlEventHandlerFunc, useCapture diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index 6fb863e2f77b9..667a0a9c6d1d4 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -420,6 +420,8 @@ EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target __attribute void emscripten_html5_remove_all_event_listeners(void); +EMSCRIPTEN_RESULT emscripten_remove_callback(const char *target __attribute__((nonnull)), void *userData, int eventTypeId, void *callback __attribute__((nonnull))); + #define EM_CALLBACK_THREAD_CONTEXT_MAIN_RUNTIME_THREAD ((pthread_t)0x1) #define EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD ((pthread_t)0x2) diff --git a/test/test_browser.py b/test/test_browser.py index a9f9b71d86be2..3462e607a5679 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -2634,6 +2634,18 @@ def test_html5_core(self, opts): self.cflags.append('--pre-js=pre.js') self.btest_exit('test_html5_core.c', cflags=opts) + @parameterized({ + '': ([],), + 'closure': (['-O2', '-g1', '--closure=1'],), + 'pthread': (['-pthread'],), + 'proxy_to_pthread': (['-pthread', '-sPROXY_TO_PTHREAD'],), + 'legacy': (['-sMIN_FIREFOX_VERSION=0', '-sMIN_SAFARI_VERSION=0', '-sMIN_CHROME_VERSION=0', '-Wno-transpile'],), + }) + def test_html5_remove_callback(self, opts): + if self.is_wasm64() and '-sMIN_CHROME_VERSION=0' in opts: + self.skipTest('wasm64 does not support older browsers') + self.btest_exit('test_html5_remove_callback.c', cflags=opts) + @parameterized({ '': ([],), 'closure': (['-O2', '-g1', '--closure=1'],), diff --git a/test/test_html5_remove_callback.c b/test/test_html5_remove_callback.c new file mode 100644 index 0000000000000..23932f1fe4dfa --- /dev/null +++ b/test/test_html5_remove_callback.c @@ -0,0 +1,138 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include + +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!"; +} + +// Report API failure +#define TEST_RESULT(x) if (ret != EMSCRIPTEN_RESULT_SUCCESS) printf("%s returned %s.\n", #x, emscripten_result_to_string(ret)); + +// Like above above but also assert API success +#define ASSERT_RESULT(x) TEST_RESULT(x); assert(ret == EMSCRIPTEN_RESULT_SUCCESS); + +char const *userDataToString(void *userData) { + return userData ? (char const *) userData : "nullptr"; +} + +bool key_callback_1(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { + printf("key_callback_1: eventType=%d, userData=%s\n", eventType, userDataToString(userData)); + return 0; +} + +bool key_callback_2(int eventType, const EmscriptenKeyboardEvent *e, void *userData) { + printf("key_callback_2: eventType=%d, userData=%s\n", eventType, userDataToString(userData)); + return 0; +} + +bool mouse_callback_1(int eventType, const EmscriptenMouseEvent *e, void *userData) { + printf("mouse_callback_1: eventType=%d, userData=%s\n", eventType, userDataToString(userData)); + return 0; +} + +void checkCount(int count) +{ + int eventHandlersCount = EM_ASM_INT({ return JSEvents.eventHandlers.length; }); + printf("Detected [%d] handlers\n", eventHandlersCount); + assert(count == eventHandlersCount); +} + +void test_done() {} + +int main() { + bool useCapture = true; + void *userData3 = "3"; + + // first we check for invalid parameters + assert(emscripten_remove_callback("this_dom_element_does_not_exist", NULL, 0, key_callback_1) == EMSCRIPTEN_RESULT_UNKNOWN_TARGET); + + checkCount(0); + + EMSCRIPTEN_RESULT ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_1); + ASSERT_RESULT(emscripten_set_keypress_callback); + ret = emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_1); + ASSERT_RESULT(emscripten_set_keydown_callback); + ret = emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_1); + ASSERT_RESULT(emscripten_set_keyup_callback); + + checkCount(3); + + // removing keydown event + ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYDOWN, key_callback_1); + ASSERT_RESULT(emscripten_remove_callback); + + checkCount(2); + + // adding another keypress callback on the same target + ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_2); + ASSERT_RESULT(emscripten_set_keypress_callback); + checkCount(3); + + // adding another keypress callback on the same target with different user data + ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, userData3, useCapture, key_callback_2); + ASSERT_RESULT(emscripten_set_keypress_callback); + + checkCount(4); + + // removing a combination that does not exist (no mouse_callback_1 registered) + ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYPRESS, mouse_callback_1); + ASSERT_RESULT(emscripten_remove_callback); + + checkCount(4); + + // removing keypress / userData=NULL / key_callback_2 + ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYPRESS, key_callback_2); + ASSERT_RESULT(emscripten_remove_callback); + + checkCount(3); + + // removing keypress / userData=NULL / key_callback_1 + ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYPRESS, key_callback_1); + ASSERT_RESULT(emscripten_remove_callback); + + checkCount(2); + + // removing keypress / userData=3 / key_callback_2 + ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, userData3, EMSCRIPTEN_EVENT_KEYPRESS, key_callback_2); + ASSERT_RESULT(emscripten_remove_callback); + + checkCount(1); + + // adding the same mouse down callback to 2 different targets + ret = emscripten_set_mousedown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, mouse_callback_1); + ASSERT_RESULT(emscripten_set_mousedown_callback); + ret = emscripten_set_mousedown_callback("#canvas", NULL, useCapture, mouse_callback_1); + ASSERT_RESULT(emscripten_set_mousedown_callback); + + checkCount(3); + + // removing mousedown / userData=NULL / mouse_callback_1 on the window target + ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_MOUSEDOWN, mouse_callback_1); + ASSERT_RESULT(emscripten_remove_callback); + + checkCount(2); + + emscripten_async_call(test_done, 0, 5000); + + + return 0; +} From 4260b0b675121e6ff1a1bd65ef05f0c4a5ac0596 Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Thu, 9 Oct 2025 10:50:29 -0700 Subject: [PATCH 2/6] Added missing signature --- src/lib/libsigs.js | 1 + test/test_html5_remove_callback.c | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 04e36d080ee3f..3078715a905e4 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -731,6 +731,7 @@ sigs = { emscripten_promise_resolve__sig: 'vpip', emscripten_promise_then__sig: 'ppppp', emscripten_random__sig: 'f', + emscripten_remove_callback__sig: 'ippip', emscripten_request_animation_frame__sig: 'ipp', emscripten_request_animation_frame_loop__sig: 'vpp', emscripten_request_fullscreen__sig: 'ipi', diff --git a/test/test_html5_remove_callback.c b/test/test_html5_remove_callback.c index 23932f1fe4dfa..b583775f89e39 100644 --- a/test/test_html5_remove_callback.c +++ b/test/test_html5_remove_callback.c @@ -85,6 +85,7 @@ int main() { // adding another keypress callback on the same target ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_2); ASSERT_RESULT(emscripten_set_keypress_callback); + checkCount(3); // adding another keypress callback on the same target with different user data @@ -131,8 +132,5 @@ int main() { checkCount(2); - emscripten_async_call(test_done, 0, 5000); - - return 0; } From 6925d655c73d46e964afd39f300ca91a8c27a5e8 Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Fri, 10 Oct 2025 05:29:36 -0700 Subject: [PATCH 3/6] fixes test failure --- test/test_browser.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/test_browser.py b/test/test_browser.py index 3462e607a5679..6bdaa8facbf1f 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -2634,17 +2634,8 @@ def test_html5_core(self, opts): self.cflags.append('--pre-js=pre.js') self.btest_exit('test_html5_core.c', cflags=opts) - @parameterized({ - '': ([],), - 'closure': (['-O2', '-g1', '--closure=1'],), - 'pthread': (['-pthread'],), - 'proxy_to_pthread': (['-pthread', '-sPROXY_TO_PTHREAD'],), - 'legacy': (['-sMIN_FIREFOX_VERSION=0', '-sMIN_SAFARI_VERSION=0', '-sMIN_CHROME_VERSION=0', '-Wno-transpile'],), - }) - def test_html5_remove_callback(self, opts): - if self.is_wasm64() and '-sMIN_CHROME_VERSION=0' in opts: - self.skipTest('wasm64 does not support older browsers') - self.btest_exit('test_html5_remove_callback.c', cflags=opts) + def test_html5_remove_callback(self): + self.btest_exit('test_html5_remove_callback.c') @parameterized({ '': ([],), From 6583129c28345b9bbb16a7aeae066cbe261aa444 Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Sat, 8 Nov 2025 11:54:47 -0800 Subject: [PATCH 4/6] Added emscripten_remove_callback to doc --- site/source/docs/api_reference/html5.h.rst | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/site/source/docs/api_reference/html5.h.rst b/site/source/docs/api_reference/html5.h.rst index 23155eefd4307..40e0536aab3ad 100644 --- a/site/source/docs/api_reference/html5.h.rst +++ b/site/source/docs/api_reference/html5.h.rst @@ -90,6 +90,50 @@ The ``useCapture`` parameter maps to ``useCapture`` in `EventTarget.addEventLis Most functions return the result using the type :c:data:`EMSCRIPTEN_RESULT`. Zero and positive values denote success. Negative values signal failure. None of the functions fail or abort by throwing a JavaScript or C++ exception. If a particular browser does not support the given feature, the value :c:data:`EMSCRIPTEN_RESULT_NOT_SUPPORTED` will be returned at the time the callback is registered. +Remove callback function +------------------------ + +In order to remove a callback, previously set via a ``emscripten_set_some_callback`` call, there is a dedicated and generic function for this purpose: + + .. code-block:: cpp + + EMSCRIPTEN_RESULT emscripten_remove_callback( + const char *target, // ID of the target HTML element. + void *userData, // User-defined data (passed to the callback). + int eventTypeId, // The event type ID (EMSCRIPTEN_EVENT_XXX). + void *callback // Callback function. + ); + + +The ``target``, ``userData`` and ``callback`` parameters are the same parameters provided in ``emscripten_set_some_callback`` with the only difference being that, since this function applies to all types of callbacks, the type of ``callback`` is ``void *``. + +The ``eventTypeId`` represents the event type, the same Id received in the callback functions. + + .. code-block:: cpp + + // Example + + bool my_mouse_callback_1(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + // ... + } + + bool my_mouse_callback_2(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + // ... + } + + void main() { + + // 1. set callbacks for mouse down and mouse move + emscripten_set_mousedown_callback("#mydiv", 0, my_mouse_callback_1); + emscripten_set_mousedown_callback("#mydiv", (void *) 34, my_mouse_callback_2); + emscripten_set_mousemove_callback("#mydiv", 0, my_mouse_callback_1); + + // 2. remove these callbacks + emscripten_remove_callback("#mydiv", 0, EMSCRIPTEN_EVENT_MOUSEDOWN, my_mouse_callback_1); + emscripten_remove_callback("#mydiv", (void *) 34, EMSCRIPTEN_EVENT_MOUSEDOWN, my_mouse_callback_2); + emscripten_remove_callback("#mydiv", 0, EMSCRIPTEN_EVENT_MOUSEMOVE, my_mouse_callback_1); + } + Callback functions ------------------ From cdf9b946c76be357c6653031405c53c452ce8f50 Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Sun, 9 Nov 2025 09:04:25 -0800 Subject: [PATCH 5/6] Updated Changelog --- ChangeLog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 612d47b20afa4..3960939981677 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,8 @@ See docs/process.md for more on how version tagging works. 4.0.20 (in development) ----------------------- +- Added `emscripten_remove_callback` function in `html5.h` in order to be + able to remove a single callback. (#25535) 4.0.19 - 11/04/25 ----------------- From 581444b0674ae7b1ff405c19f6c78db7a175ad2d Mon Sep 17 00:00:00 2001 From: Yan Pujante Date: Sun, 9 Nov 2025 09:50:42 -0800 Subject: [PATCH 6/6] Automatic rebaseline of codesize expectations. NFC This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (1) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_codesize_hello_dylink_all.json: 819522 => 819904 [+382 bytes / +0.05%] Average change: +0.05% (+0.05% - +0.05%) ``` --- test/codesize/test_codesize_hello_dylink_all.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index bb29af482213c..ffc74c8ee5782 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 245483, + "a.out.js": 245865, "a.out.nodebug.wasm": 574039, - "total": 819522, + "total": 819904, "sent": [ "IMG_Init", "IMG_Load", @@ -741,6 +741,7 @@ "emscripten_promise_resolve", "emscripten_promise_then", "emscripten_random", + "emscripten_remove_callback", "emscripten_request_animation_frame", "emscripten_request_animation_frame_loop", "emscripten_request_fullscreen",