From 14ee9e46ffa6c5f34c5aa998faed6d5a1810b111 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 6 Nov 2025 17:02:44 +0000 Subject: [PATCH] WebBrowserComponent: Fix bug where Linux browser process could segfault during shutdown This issue could be observed when loading the WebViewPluginDemo in the AudioPluginHost, opening its editor, and then attempting to close the editor window using the titlebar button. Tested on Fedora 42 with libwebkit2gtk-4.1. Unloading the webkit library with dlclose seems to cause the webview process to crash, even if gtk_main() has already exited at that point. Maybe this points to a need to manually clean up the webview somehow before calling gtk_main_quit, but I can't see any obvious candidate functions in the docs (gtk_widget_destroy doesn't seem to help). The workaround presented here just opens the webkit library with RTLD_NODELETE, which isn't ideal but might be necessary if this library hasn't been designed with dynamic load/unload in mind. --- .../native/juce_WebBrowserComponent_linux.cpp | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp index 2e8bdaa391..28bb4993c4 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp @@ -264,6 +264,44 @@ public: JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL_INLINE (WebKitSymbols) private: + struct DylibHandle + { + DylibHandle() = default; + + explicit DylibHandle (const char* str) + : DylibHandle (str, RTLD_NOW | RTLD_LOCAL) {} + + DylibHandle (const char* str, int flags) + : handle (dlopen (str, flags)) {} + + ~DylibHandle() + { + if (handle != nullptr) + dlclose (handle); + } + + DylibHandle (DylibHandle&& other) noexcept + : handle (std::exchange (other.handle, nullptr)) {} + + DylibHandle& operator= (DylibHandle&& other) noexcept + { + auto local = std::move (other); + std::swap (local.handle, handle); + return *this; + } + + void* getFunction (const char* name) const + { + jassert (handle != nullptr); + return dlsym (handle, name); + } + + explicit operator bool() const { return handle != nullptr; } + + private: + void* handle = nullptr; + }; + WebKitSymbols() = default; ~WebKitSymbols() @@ -285,7 +323,7 @@ private: } template - bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding) + bool loadSymbols (DylibHandle& lib, SymbolBinding binding) { if (auto* func = lib.getFunction (binding.name)) { @@ -297,7 +335,7 @@ private: } template - bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding, Args... args) + bool loadSymbols (DylibHandle& lib, SymbolBinding binding, Args... args) { return loadSymbols (lib, binding) && loadSymbols (lib, args...); } @@ -395,20 +433,24 @@ private: bool openWebKitAndDependencyLibraries (const WebKitAndDependencyLibraryNames& names) { - if (webkitLib.open (names.webkitLib) && jsLib.open (names.jsLib) && soupLib.open (names.soupLib)) + if ( (webkitLib = DylibHandle (names.webkitLib, RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE)) + && (jsLib = DylibHandle (names.jsLib)) + && (soupLib = DylibHandle (names.soupLib))) + { return true; + } for (auto* l : { &webkitLib, &jsLib, &soupLib }) - l->close(); + *l = {}; return false; } //============================================================================== - DynamicLibrary webkitLib, jsLib, soupLib; + DylibHandle webkitLib, jsLib, soupLib; - DynamicLibrary gtkLib { "libgtk-3.so" }, - glib { "libglib-2.0.so" }; + DylibHandle gtkLib { "libgtk-3.so" }, + glib { "libglib-2.0.so" }; const bool webKitIsAvailable = ( openWebKitAndDependencyLibraries ({ "libwebkit2gtk-4.1.so", "libjavascriptcoregtk-4.1.so",