SDL: emscripten: drag-and-drop fixes.

From 267e681a0be6d351020060824da9e2cd48d8fc80 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 9 Feb 2026 21:35:14 -0500
Subject: [PATCH] emscripten: drag-and-drop fixes.

FS.mkdir() will throw a javascript exception if the scratch directory already
exists, so catch/ignore that.

Wrap the rest of the scratch i/o in a try/catch block; the event will only
send if everything works out.

Wrap some calls from Javascript to the C runtime's free() in an
EMSCRIPTEN_KEEPALIVE function, so that the compiler doesn't optimize the
function out and crash at runtime.

Fixes #14999.
---
 src/SDL_utils.c                             |  8 +++++++
 src/SDL_utils_c.h                           |  7 ++++++
 src/main/emscripten/SDL_sysmain_runapp.c    |  9 ++------
 src/video/emscripten/SDL_emscriptenevents.c | 25 +++++++++++++--------
 4 files changed, 33 insertions(+), 16 deletions(-)

diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index fea504ceea0e4..66b043796e538 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -26,6 +26,14 @@
 
 #include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
 
+#ifdef SDL_PLATFORM_EMSCRIPTEN
+#include <emscripten.h>
+
+EMSCRIPTEN_KEEPALIVE void Emscripten_force_free(void *ptr)
+{
+    free(ptr);  // This should NOT be SDL_free()
+}
+#endif
 
 // Common utility functions that aren't in the public API
 
diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h
index 7138b605e2e2d..266eb09249f47 100644
--- a/src/SDL_utils_c.h
+++ b/src/SDL_utils_c.h
@@ -93,4 +93,11 @@ extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *ven
 // Log what backend a subsystem chose, if a hint was set to do so. Useful for debugging.
 extern void SDL_DebugLogBackend(const char *subsystem, const char *backend);
 
+#ifdef SDL_PLATFORM_EMSCRIPTEN
+// even though we reference the C runtime's free() in other places, it appears
+// to be inlined more aggressively in Emscripten 4, so we need a reference to
+// it here, too, so inlined Javascript doesn't fail to find it.
+extern void Emscripten_force_free(void *ptr);
+#endif
+
 #endif // SDL_utils_h_
diff --git a/src/main/emscripten/SDL_sysmain_runapp.c b/src/main/emscripten/SDL_sysmain_runapp.c
index 026075a87375d..0564240ac860c 100644
--- a/src/main/emscripten/SDL_sysmain_runapp.c
+++ b/src/main/emscripten/SDL_sysmain_runapp.c
@@ -28,11 +28,6 @@
 
 EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8");
 
-// even though we reference the C runtime's free() in other places, it appears
-// to be inlined more aggressively in Emscripten 4, so we need a reference to
-// it here, too, so the inlined Javascript doesn't fail to find it.
-EMSCRIPTEN_KEEPALIVE void force_free(void *ptr) { free(ptr); } // This should NOT be SDL_free()
-
 int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved)
 {
     (void)reserved;
@@ -51,8 +46,8 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv
                     //console.log("Setting SDL env var '" + key + "' to '" + value + "' ...");
                     dynCall('iiii', $0, [ckey, cvalue, 1]);
                 }
-                _force_free(ckey);  // these must use free(), not SDL_free()!
-                _force_free(cvalue);
+                _Emscripten_force_free(ckey);  // these must use free(), not SDL_free()!
+                _Emscripten_force_free(cvalue);
             }
         }
     }, SDL_setenv_unsafe);
diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c
index 41a172b33a432..4d91b7904ba35 100644
--- a/src/video/emscripten/SDL_emscriptenevents.c
+++ b/src/video/emscripten/SDL_emscriptenevents.c
@@ -1164,13 +1164,17 @@ static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data)
             target.addEventListener("dragover", SDL3.eventHandlerDropDragover);
 
             SDL3.drop_count = 0;
-            FS.mkdir("/tmp/filedrop");
+
+            // FS.* functions throw exceptions when there are errors (such as the temp dir already existing),
+            //  but we ignore all of these in a catch handler; you just won't get the drop event if there's a problem.
+            try { FS.mkdir("/tmp/filedrop"); } catch (e) {}
+
             SDL3.eventHandlerDropDrop = function(event) {
                 event.preventDefault();
                 if (event.dataTransfer.types.includes("text/plain")) {
                     let plain_text = stringToNewUTF8(event.dataTransfer.getData("text/plain"));
                     _Emscripten_SendDragTextEvent(data, plain_text);
-                    _free(plain_text);
+                    _Emscripten_force_free(plain_text);
                 } else if (event.dataTransfer.types.includes("Files")) {
                     let files_read = 0;
                     const files_to_read = event.dataTransfer.files.length;
@@ -1186,13 +1190,16 @@ static void Emscripten_set_drag_event_callbacks(SDL_WindowData *data)
                             const c_fs_filepath = stringToNewUTF8(fs_filepath);
                             const contents_array8 = new Uint8Array(event.target.result);
 
-                            FS.mkdir(fs_dropdir);
-                            var stream = FS.open(fs_filepath, "w");
-                            FS.write(stream, contents_array8, 0, contents_array8.length, 0);
-                            FS.close(stream);
-
-                            _Emscripten_SendDragFileEvent(data, c_fs_filepath);
-                            _free(c_fs_filepath);
+                            try {
+                                FS.mkdir(fs_dropdir);
+                                var stream = FS.open(fs_filepath, "w");
+                                FS.write(stream, contents_array8, 0, contents_array8.length, 0);
+                                FS.close(stream);
+                                _Emscripten_SendDragFileEvent(data, c_fs_filepath);
+                            } catch (e) {
+                                // if this threw an exception at any point, we skip this drop event. Sorry!
+                            }
+                            _Emscripten_force_free(c_fs_filepath);
                             onFileRead();
                         };
                         file_reader.onerror = function(event) {