Skip to content

Commit adeb50b

Browse files
committed
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
1 parent ac45ded commit adeb50b

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

src/lib/libhtml5.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,25 @@ var LibraryHTML5 = {
205205
return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}};
206206
},
207207

208+
removeSingleHandler(eventHandler) {
209+
if (!eventHandler.target) {
210+
#if ASSERTIONS
211+
err('removeSingleHandler: the target element for event handler registration does not exist, when processing the following event handler registration:');
212+
console.dir(eventHandler);
213+
#endif
214+
return {{{ cDefs.EMSCRIPTEN_RESULT_UNKNOWN_TARGET }}};
215+
}
216+
for (var i = 0; i < JSEvents.eventHandlers.length; ++i) {
217+
if (JSEvents.eventHandlers[i].target === eventHandler.target
218+
&& JSEvents.eventHandlers[i].eventTypeId === eventHandler.eventTypeId
219+
&& JSEvents.eventHandlers[i].callbackfunc === eventHandler.callbackfunc
220+
&& JSEvents.eventHandlers[i].userData === eventHandler.userData) {
221+
JSEvents._removeHandler(i--);
222+
}
223+
}
224+
return {{{ cDefs.EMSCRIPTEN_RESULT_SUCCESS }}};
225+
},
226+
208227
#if PTHREADS
209228
getTargetThreadForEventCallback(targetThread) {
210229
switch (targetThread) {
@@ -298,6 +317,8 @@ var LibraryHTML5 = {
298317
var eventHandler = {
299318
target: findEventTarget(target),
300319
eventTypeString,
320+
eventTypeId,
321+
userData,
301322
callbackfunc,
302323
handlerFunc: keyEventHandlerFunc,
303324
useCapture
@@ -412,6 +433,18 @@ var LibraryHTML5 = {
412433
},
413434
#endif
414435

436+
emscripten_remove_callback__proxy: 'sync',
437+
emscripten_remove_callback__deps: ['$JSEvents', '$findEventTarget'],
438+
emscripten_remove_callback: (target, userData, eventTypeId, callback) => {
439+
var eventHandler = {
440+
target: findEventTarget(target),
441+
userData,
442+
eventTypeId,
443+
callbackfunc: callback,
444+
};
445+
return JSEvents.removeSingleHandler(eventHandler);
446+
},
447+
415448
emscripten_set_keypress_callback_on_thread__proxy: 'sync',
416449
emscripten_set_keypress_callback_on_thread__deps: ['$registerKeyEventCallback'],
417450
emscripten_set_keypress_callback_on_thread: (target, userData, useCapture, callbackfunc, targetThread) =>
@@ -503,6 +536,8 @@ var LibraryHTML5 = {
503536
allowsDeferredCalls: eventTypeString != 'mousemove' && eventTypeString != 'mouseenter' && eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them!
504537
#endif
505538
eventTypeString,
539+
eventTypeId,
540+
userData,
506541
callbackfunc,
507542
handlerFunc: mouseEventHandlerFunc,
508543
useCapture
@@ -599,6 +634,8 @@ var LibraryHTML5 = {
599634
allowsDeferredCalls: true,
600635
#endif
601636
eventTypeString,
637+
eventTypeId,
638+
userData,
602639
callbackfunc,
603640
handlerFunc: wheelHandlerFunc,
604641
useCapture
@@ -674,6 +711,8 @@ var LibraryHTML5 = {
674711
var eventHandler = {
675712
target,
676713
eventTypeString,
714+
eventTypeId,
715+
userData,
677716
callbackfunc,
678717
handlerFunc: uiEventHandlerFunc,
679718
useCapture
@@ -721,6 +760,8 @@ var LibraryHTML5 = {
721760
var eventHandler = {
722761
target: findEventTarget(target),
723762
eventTypeString,
763+
eventTypeId,
764+
userData,
724765
callbackfunc,
725766
handlerFunc: focusEventHandlerFunc,
726767
useCapture
@@ -779,6 +820,8 @@ var LibraryHTML5 = {
779820
var eventHandler = {
780821
target: findEventTarget(target),
781822
eventTypeString,
823+
eventTypeId,
824+
userData,
782825
callbackfunc,
783826
handlerFunc: deviceOrientationEventHandlerFunc,
784827
useCapture
@@ -849,6 +892,8 @@ var LibraryHTML5 = {
849892
var eventHandler = {
850893
target: findEventTarget(target),
851894
eventTypeString,
895+
eventTypeId,
896+
userData,
852897
callbackfunc,
853898
handlerFunc: deviceMotionEventHandlerFunc,
854899
useCapture
@@ -935,6 +980,8 @@ var LibraryHTML5 = {
935980
var eventHandler = {
936981
target,
937982
eventTypeString,
983+
eventTypeId,
984+
userData,
938985
callbackfunc,
939986
handlerFunc: orientationChangeEventHandlerFunc,
940987
useCapture
@@ -1046,6 +1093,8 @@ var LibraryHTML5 = {
10461093
var eventHandler = {
10471094
target,
10481095
eventTypeString,
1096+
eventTypeId,
1097+
userData,
10491098
callbackfunc,
10501099
handlerFunc: fullscreenChangeEventhandlerFunc,
10511100
useCapture
@@ -1547,6 +1596,8 @@ var LibraryHTML5 = {
15471596
var eventHandler = {
15481597
target,
15491598
eventTypeString,
1599+
eventTypeId,
1600+
userData,
15501601
callbackfunc,
15511602
handlerFunc: pointerlockChangeEventHandlerFunc,
15521603
useCapture
@@ -1591,6 +1642,8 @@ var LibraryHTML5 = {
15911642
var eventHandler = {
15921643
target,
15931644
eventTypeString,
1645+
eventTypeId,
1646+
userData,
15941647
callbackfunc,
15951648
handlerFunc: pointerlockErrorEventHandlerFunc,
15961649
useCapture
@@ -1745,6 +1798,8 @@ var LibraryHTML5 = {
17451798
var eventHandler = {
17461799
target,
17471800
eventTypeString,
1801+
eventTypeId,
1802+
userData,
17481803
callbackfunc,
17491804
handlerFunc: visibilityChangeEventHandlerFunc,
17501805
useCapture
@@ -1863,6 +1918,8 @@ var LibraryHTML5 = {
18631918
allowsDeferredCalls: eventTypeString == 'touchstart' || eventTypeString == 'touchend',
18641919
#endif
18651920
eventTypeString,
1921+
eventTypeId,
1922+
userData,
18661923
callbackfunc,
18671924
handlerFunc: touchEventHandlerFunc,
18681925
useCapture
@@ -1949,6 +2006,8 @@ var LibraryHTML5 = {
19492006
allowsDeferredCalls: true,
19502007
#endif
19512008
eventTypeString,
2009+
eventTypeId,
2010+
userData,
19522011
callbackfunc,
19532012
handlerFunc: gamepadEventHandlerFunc,
19542013
useCapture
@@ -2035,6 +2094,8 @@ var LibraryHTML5 = {
20352094
var eventHandler = {
20362095
target: findEventTarget(target),
20372096
eventTypeString,
2097+
eventTypeId,
2098+
userData,
20382099
callbackfunc,
20392100
handlerFunc: beforeUnloadEventHandlerFunc,
20402101
useCapture
@@ -2088,6 +2149,8 @@ var LibraryHTML5 = {
20882149
var eventHandler = {
20892150
target: battery,
20902151
eventTypeString,
2152+
eventTypeId,
2153+
userData,
20912154
callbackfunc,
20922155
handlerFunc: batteryEventHandlerFunc,
20932156
useCapture

src/lib/libhtml5_webgl.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ var LibraryHtml5WebGL = {
441441
var eventHandler = {
442442
target: findEventTarget(target),
443443
eventTypeString,
444+
eventTypeId,
445+
userData,
444446
callbackfunc,
445447
handlerFunc: webGlEventHandlerFunc,
446448
useCapture

system/include/emscripten/html5.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target __attribute
420420

421421
void emscripten_html5_remove_all_event_listeners(void);
422422

423+
EMSCRIPTEN_RESULT emscripten_remove_callback(const char *target __attribute__((nonnull)), void *userData, int eventTypeId, void *callback __attribute__((nonnull)));
424+
423425
#define EM_CALLBACK_THREAD_CONTEXT_MAIN_RUNTIME_THREAD ((pthread_t)0x1)
424426
#define EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD ((pthread_t)0x2)
425427

test/test_browser.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,18 @@ def test_html5_core(self, opts):
26342634
self.cflags.append('--pre-js=pre.js')
26352635
self.btest_exit('test_html5_core.c', cflags=opts)
26362636

2637+
@parameterized({
2638+
'': ([],),
2639+
'closure': (['-O2', '-g1', '--closure=1'],),
2640+
'pthread': (['-pthread'],),
2641+
'proxy_to_pthread': (['-pthread', '-sPROXY_TO_PTHREAD'],),
2642+
'legacy': (['-sMIN_FIREFOX_VERSION=0', '-sMIN_SAFARI_VERSION=0', '-sMIN_CHROME_VERSION=0', '-Wno-transpile'],),
2643+
})
2644+
def test_html5_remove_callback(self, opts):
2645+
if self.is_wasm64() and '-sMIN_CHROME_VERSION=0' in opts:
2646+
self.skipTest('wasm64 does not support older browsers')
2647+
self.btest_exit('test_html5_remove_callback.c', cflags=opts)
2648+
26372649
@parameterized({
26382650
'': ([],),
26392651
'closure': (['-O2', '-g1', '--closure=1'],),

test/test_html5_remove_callback.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2025 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <assert.h>
9+
#include <stdio.h>
10+
#include <emscripten.h>
11+
#include <string.h>
12+
#include <emscripten/html5.h>
13+
14+
const char *emscripten_result_to_string(EMSCRIPTEN_RESULT result) {
15+
if (result == EMSCRIPTEN_RESULT_SUCCESS) return "EMSCRIPTEN_RESULT_SUCCESS";
16+
if (result == EMSCRIPTEN_RESULT_DEFERRED) return "EMSCRIPTEN_RESULT_DEFERRED";
17+
if (result == EMSCRIPTEN_RESULT_NOT_SUPPORTED) return "EMSCRIPTEN_RESULT_NOT_SUPPORTED";
18+
if (result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED) return "EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED";
19+
if (result == EMSCRIPTEN_RESULT_INVALID_TARGET) return "EMSCRIPTEN_RESULT_INVALID_TARGET";
20+
if (result == EMSCRIPTEN_RESULT_UNKNOWN_TARGET) return "EMSCRIPTEN_RESULT_UNKNOWN_TARGET";
21+
if (result == EMSCRIPTEN_RESULT_INVALID_PARAM) return "EMSCRIPTEN_RESULT_INVALID_PARAM";
22+
if (result == EMSCRIPTEN_RESULT_FAILED) return "EMSCRIPTEN_RESULT_FAILED";
23+
if (result == EMSCRIPTEN_RESULT_NO_DATA) return "EMSCRIPTEN_RESULT_NO_DATA";
24+
return "Unknown EMSCRIPTEN_RESULT!";
25+
}
26+
27+
// Report API failure
28+
#define TEST_RESULT(x) if (ret != EMSCRIPTEN_RESULT_SUCCESS) printf("%s returned %s.\n", #x, emscripten_result_to_string(ret));
29+
30+
// Like above above but also assert API success
31+
#define ASSERT_RESULT(x) TEST_RESULT(x); assert(ret == EMSCRIPTEN_RESULT_SUCCESS);
32+
33+
char const *userDataToString(void *userData) {
34+
return userData ? (char const *) userData : "nullptr";
35+
}
36+
37+
bool key_callback_1(int eventType, const EmscriptenKeyboardEvent *e, void *userData) {
38+
printf("key_callback_1: eventType=%d, userData=%s\n", eventType, userDataToString(userData));
39+
return 0;
40+
}
41+
42+
bool key_callback_2(int eventType, const EmscriptenKeyboardEvent *e, void *userData) {
43+
printf("key_callback_2: eventType=%d, userData=%s\n", eventType, userDataToString(userData));
44+
return 0;
45+
}
46+
47+
bool mouse_callback_1(int eventType, const EmscriptenMouseEvent *e, void *userData) {
48+
printf("mouse_callback_1: eventType=%d, userData=%s\n", eventType, userDataToString(userData));
49+
return 0;
50+
}
51+
52+
void checkCount(int count)
53+
{
54+
int eventHandlersCount = EM_ASM_INT({ return JSEvents.eventHandlers.length; });
55+
printf("Detected [%d] handlers\n", eventHandlersCount);
56+
assert(count == eventHandlersCount);
57+
}
58+
59+
void test_done() {}
60+
61+
int main() {
62+
bool useCapture = true;
63+
void *userData3 = "3";
64+
65+
// first we check for invalid parameters
66+
assert(emscripten_remove_callback("this_dom_element_does_not_exist", NULL, 0, key_callback_1) == EMSCRIPTEN_RESULT_UNKNOWN_TARGET);
67+
68+
checkCount(0);
69+
70+
EMSCRIPTEN_RESULT ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_1);
71+
ASSERT_RESULT(emscripten_set_keypress_callback);
72+
ret = emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_1);
73+
ASSERT_RESULT(emscripten_set_keydown_callback);
74+
ret = emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_1);
75+
ASSERT_RESULT(emscripten_set_keyup_callback);
76+
77+
checkCount(3);
78+
79+
// removing keydown event
80+
ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYDOWN, key_callback_1);
81+
ASSERT_RESULT(emscripten_remove_callback);
82+
83+
checkCount(2);
84+
85+
// adding another keypress callback on the same target
86+
ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, key_callback_2);
87+
ASSERT_RESULT(emscripten_set_keypress_callback);
88+
checkCount(3);
89+
90+
// adding another keypress callback on the same target with different user data
91+
ret = emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, userData3, useCapture, key_callback_2);
92+
ASSERT_RESULT(emscripten_set_keypress_callback);
93+
94+
checkCount(4);
95+
96+
// removing a combination that does not exist (no mouse_callback_1 registered)
97+
ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYPRESS, mouse_callback_1);
98+
ASSERT_RESULT(emscripten_remove_callback);
99+
100+
checkCount(4);
101+
102+
// removing keypress / userData=NULL / key_callback_2
103+
ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYPRESS, key_callback_2);
104+
ASSERT_RESULT(emscripten_remove_callback);
105+
106+
checkCount(3);
107+
108+
// removing keypress / userData=NULL / key_callback_1
109+
ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_KEYPRESS, key_callback_1);
110+
ASSERT_RESULT(emscripten_remove_callback);
111+
112+
checkCount(2);
113+
114+
// removing keypress / userData=3 / key_callback_2
115+
ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, userData3, EMSCRIPTEN_EVENT_KEYPRESS, key_callback_2);
116+
ASSERT_RESULT(emscripten_remove_callback);
117+
118+
checkCount(1);
119+
120+
// adding the same mouse down callback to 2 different targets
121+
ret = emscripten_set_mousedown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, useCapture, mouse_callback_1);
122+
ASSERT_RESULT(emscripten_set_mousedown_callback);
123+
ret = emscripten_set_mousedown_callback("#canvas", NULL, useCapture, mouse_callback_1);
124+
ASSERT_RESULT(emscripten_set_mousedown_callback);
125+
126+
checkCount(3);
127+
128+
// removing mousedown / userData=NULL / mouse_callback_1 on the window target
129+
ret = emscripten_remove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EMSCRIPTEN_EVENT_MOUSEDOWN, mouse_callback_1);
130+
ASSERT_RESULT(emscripten_remove_callback);
131+
132+
checkCount(2);
133+
134+
emscripten_async_call(test_done, 0, 5000);
135+
136+
137+
return 0;
138+
}

0 commit comments

Comments
 (0)