Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/lib/libhtml5.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -298,6 +317,8 @@ var LibraryHTML5 = {
var eventHandler = {
target: findEventTarget(target),
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: keyEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -599,6 +634,8 @@ var LibraryHTML5 = {
allowsDeferredCalls: true,
#endif
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: wheelHandlerFunc,
useCapture
Expand Down Expand Up @@ -674,6 +711,8 @@ var LibraryHTML5 = {
var eventHandler = {
target,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: uiEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -721,6 +760,8 @@ var LibraryHTML5 = {
var eventHandler = {
target: findEventTarget(target),
eventTypeString,
eventTypeId,
userData,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really love all the repetition here, or the fact that we need to store two extra fields just for this new API, but I guess it makes sense, and the repetitive nature of this file is kind of a pre-existing condition :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I don't love it either, but you are right, this code is replicated over and over (17 times!). I feel like the API could have been simplified in the first place (having just 1 function call for all the calls using the same callback type and providing the eventTypeId... which would have reduced, for example, 9 mouse related calls into just 1...). It is what is now unfortunately. So that is the only solution I can think of to be able to implement this new API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me correct what I just said in regards to the count. There are indeed 17 places where I had to change the code, but there are way many more "emscripten_set_xxx_callback". There are 17 types of events supported, so we could not really simplyif this since they all have different callback (function pointer) types.

The example I gave for mouse counts as just 1 on these 17 places because there are 9 "emscripten_set_mousexxx_callback" functions which all funnel into 1 registerMouseEventCallback function call. I feel like this API is way too verbose and having just one emscripten_set_mouse_callback with the eventTypeId as a parameter (which is exactly what the internal registerMouseEventCallback is) would have been plenty in the first place...

callbackfunc,
handlerFunc: focusEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -779,6 +820,8 @@ var LibraryHTML5 = {
var eventHandler = {
target: findEventTarget(target),
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: deviceOrientationEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -849,6 +892,8 @@ var LibraryHTML5 = {
var eventHandler = {
target: findEventTarget(target),
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: deviceMotionEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -935,6 +980,8 @@ var LibraryHTML5 = {
var eventHandler = {
target,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: orientationChangeEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -1046,6 +1093,8 @@ var LibraryHTML5 = {
var eventHandler = {
target,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: fullscreenChangeEventhandlerFunc,
useCapture
Expand Down Expand Up @@ -1547,6 +1596,8 @@ var LibraryHTML5 = {
var eventHandler = {
target,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: pointerlockChangeEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -1591,6 +1642,8 @@ var LibraryHTML5 = {
var eventHandler = {
target,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: pointerlockErrorEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -1745,6 +1798,8 @@ var LibraryHTML5 = {
var eventHandler = {
target,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: visibilityChangeEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -1863,6 +1918,8 @@ var LibraryHTML5 = {
allowsDeferredCalls: eventTypeString == 'touchstart' || eventTypeString == 'touchend',
#endif
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: touchEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -1949,6 +2006,8 @@ var LibraryHTML5 = {
allowsDeferredCalls: true,
#endif
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: gamepadEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -2035,6 +2094,8 @@ var LibraryHTML5 = {
var eventHandler = {
target: findEventTarget(target),
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: beforeUnloadEventHandlerFunc,
useCapture
Expand Down Expand Up @@ -2088,6 +2149,8 @@ var LibraryHTML5 = {
var eventHandler = {
target: battery,
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: batteryEventHandlerFunc,
useCapture
Expand Down
2 changes: 2 additions & 0 deletions src/lib/libhtml5_webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ var LibraryHtml5WebGL = {
var eventHandler = {
target: findEventTarget(target),
eventTypeString,
eventTypeId,
userData,
callbackfunc,
handlerFunc: webGlEventHandlerFunc,
useCapture
Expand Down
1 change: 1 addition & 0 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions system/include/emscripten/html5.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2634,6 +2634,9 @@ def test_html5_core(self, opts):
self.cflags.append('--pre-js=pre.js')
self.btest_exit('test_html5_core.c', cflags=opts)

def test_html5_remove_callback(self):
self.btest_exit('test_html5_remove_callback.c')

@parameterized({
'': ([],),
'closure': (['-O2', '-g1', '--closure=1'],),
Expand Down
136 changes: 136 additions & 0 deletions test/test_html5_remove_callback.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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 <assert.h>
#include <stdio.h>
#include <emscripten.h>
#include <string.h>
#include <emscripten/html5.h>

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);

return 0;
}
Loading