C API: Add JNI bridge between Java proxy classes and WebKit/WPE GObjects#250
C API: Add JNI bridge between Java proxy classes and WebKit/WPE GObjects#250alexgcastro wants to merge 1 commit intomainfrom
Conversation
Add Java proxy wrappers plus JNI mappings for WPEDisplay, WPEToplevel, WPEView, WebKitWebContext, WebKitNetworkSession, WebKitSettings, WebKitCookieManager, WebKitWebsiteDataManager, and WebKitWebView. Each bridged class uses a JNI::TypedClass<T> singleton to register native methods and cache Java method IDs at library load time, avoiding repeated JNI lookups in GLib and async callbacks. JNIMappings initialises all singletons during JNI_OnLoad. WebKitWebView and WebKitWebContext use per-object bridge structs that own the native GObject reference, keep a Java GlobalRef back-reference where needed, and track GLib signal handlers for orderly disconnection on destruction. Async operations such as cookie-manager queries, website-data clears, and evaluateJavascript() keep callback holders alive with heap-allocated GlobalRef<T> objects released when the callback runs. WPEToplevelAndroid updates its SurfaceControl tree as native windows are attached and detached, and treats repeated set_window() calls for the same ANativeWindow as a size resync instead of rebuilding the layer tree. Native callbacks may arrive off the main thread; Java-side listener and async result delivery is reposted to the main looper before application-facing callback code runs.
293daca to
5fd90f4
Compare
| std::unique_ptr<JNI::GlobalRef<JNIWebKitCookieManagerAcceptPolicyCallbackHolder>> holder( | ||
| static_cast<JNI::GlobalRef<JNIWebKitCookieManagerAcceptPolicyCallbackHolder>*>(userData)); | ||
| auto policy = webkit_cookie_manager_get_accept_policy_finish(WEBKIT_COOKIE_MANAGER(object), result, nullptr); | ||
| if (*holder) { |
There was a problem hiding this comment.
userData might be null -- it's passed from nativeGetAcceptPolicy into webkit_cookie_manager_get_accept_policy, and it takes holder, which could be nullptr if the received callbackHolder is null.
If that happens, the unique_ptr holder here will also be null after doing the cast, so de-referencing it here would trigger undefined behaviour.
We can't do a g_return_if_fail, because you still want to call webkit_cookie_manager_get_accept_policy_finish, so you probably need to turn this into
| if (*holder) { | |
| if (holder && *holder) { |
| ? new JNI::GlobalRef<JNIWebKitCookieManagerAcceptPolicyCallbackHolder>(env, callbackHolder) | ||
| : nullptr; | ||
| webkit_cookie_manager_get_accept_policy( | ||
| JNI::from_jlong<WebKitCookieManager>(nativePtr), nullptr, onGetAcceptPolicyReady, holder); |
There was a problem hiding this comment.
It's the same holder that here might take a null value, isn't it?
| GObject* automationSession = static_cast<GObject*>(g_weak_ref_get(&m_automationSession)); | ||
| if (automationSession) { | ||
| g_signal_handler_disconnect(automationSession, m_createWebViewSignalId); | ||
| g_object_unref(automationSession); | ||
| } |
There was a problem hiding this comment.
| GObject* automationSession = static_cast<GObject*>(g_weak_ref_get(&m_automationSession)); | |
| if (automationSession) { | |
| g_signal_handler_disconnect(automationSession, m_createWebViewSignalId); | |
| g_object_unref(automationSession); | |
| } | |
| if (GObject* automationSession = static_cast<GObject*>(g_weak_ref_get(&m_automationSession))) { | |
| g_signal_handler_disconnect(automationSession, m_createWebViewSignalId); | |
| g_object_unref(automationSession); | |
| } |
You could do this, so that automationSession gets a more restricted scope and a future refactor doesn't mix things up down below
| auto* info = webkit_application_info_new(); | ||
| webkit_application_info_set_name(info, "WPEWebView"); | ||
| webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION); | ||
| webkit_automation_session_set_application_info(session, info); | ||
| webkit_application_info_unref(info); |
There was a problem hiding this comment.
| auto* info = webkit_application_info_new(); | |
| webkit_application_info_set_name(info, "WPEWebView"); | |
| webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION); | |
| webkit_automation_session_set_application_info(session, info); | |
| webkit_application_info_unref(info); | |
| g_autoptr(WebKitApplicationInfo) info = webkit_application_info_new(); | |
| webkit_application_info_set_name(info, "WPEWebView"); | |
| webkit_application_info_set_version(info, WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION); | |
| webkit_automation_session_set_application_info(session, info); |
(and if you want it to be deleted earlier, you can nest it into a separate scope
| GObject* previousAutomationSession = static_cast<GObject*>(g_weak_ref_get(&bridge->m_automationSession)); | ||
| if (previousAutomationSession && bridge->m_createWebViewSignalId) | ||
| g_signal_handler_disconnect(previousAutomationSession, bridge->m_createWebViewSignalId); | ||
| if (previousAutomationSession) | ||
| g_object_unref(previousAutomationSession); |
There was a problem hiding this comment.
| GObject* previousAutomationSession = static_cast<GObject*>(g_weak_ref_get(&bridge->m_automationSession)); | |
| if (previousAutomationSession && bridge->m_createWebViewSignalId) | |
| g_signal_handler_disconnect(previousAutomationSession, bridge->m_createWebViewSignalId); | |
| if (previousAutomationSession) | |
| g_object_unref(previousAutomationSession); | |
| if (GObject* previousAutomationSession = static_cast<GObject*>(g_weak_ref_get(&bridge->m_automationSession)) { | |
| if (bridge->m_createWebViewSignalId) | |
| g_signal_handler_disconnect(previousAutomationSession, bridge->m_createWebViewSignalId); | |
| g_object_unref(previousAutomationSession); | |
| } |
| resultStr = JNI::String(strValue); | ||
| g_free(strValue); | ||
| } | ||
| g_object_unref(value); |
There was a problem hiding this comment.
I'd also suggest g_autoptr for the JSCValue, but that would mess up the GError and gchar* suggestions.
| if (!value) { | ||
| if (error) | ||
| g_error_free(error); | ||
| if (*holder) { |
There was a problem hiding this comment.
ditto
| if (*holder) { | |
| if (holder && *holder) { |
| g_free(strValue); | ||
| } | ||
| g_object_unref(value); | ||
| if (*holder) |
There was a problem hiding this comment.
| if (*holder) | |
| if (holder && *holder) |
| WPEEvent* event = wpe_event_touch_new(eventType, view, WPE_INPUT_SOURCE_TOUCHSCREEN, | ||
| static_cast<uint32_t>(time), WPEModifiers {}, static_cast<uint32_t>(pointerIds.getReadOnlyContent()[i]), | ||
| static_cast<double>(pointerXs.getReadOnlyContent()[i]), | ||
| static_cast<double>(pointerYs.getReadOnlyContent()[i])); | ||
| wpe_view_event(view, event); | ||
| wpe_event_unref(event); |
There was a problem hiding this comment.
| WPEEvent* event = wpe_event_touch_new(eventType, view, WPE_INPUT_SOURCE_TOUCHSCREEN, | |
| static_cast<uint32_t>(time), WPEModifiers {}, static_cast<uint32_t>(pointerIds.getReadOnlyContent()[i]), | |
| static_cast<double>(pointerXs.getReadOnlyContent()[i]), | |
| static_cast<double>(pointerYs.getReadOnlyContent()[i])); | |
| wpe_view_event(view, event); | |
| wpe_event_unref(event); | |
| g_autoptr(WPEEvent) event = wpe_event_touch_new(eventType, view, WPE_INPUT_SOURCE_TOUCHSCREEN, | |
| static_cast<uint32_t>(time), WPEModifiers {}, static_cast<uint32_t>(pointerIds.getReadOnlyContent()[i]), | |
| static_cast<double>(pointerXs.getReadOnlyContent()[i]), | |
| static_cast<double>(pointerYs.getReadOnlyContent()[i])); | |
| wpe_view_event(view, event); |
| WPEEvent* event = wpe_event_keyboard_new(static_cast<WPEEventType>(type), view, WPE_INPUT_SOURCE_KEYBOARD, | ||
| static_cast<uint32_t>(time), modifiers, hardwareKeyCode, keySym); | ||
| wpe_view_event(view, event); | ||
| wpe_event_unref(event); |
There was a problem hiding this comment.
| WPEEvent* event = wpe_event_keyboard_new(static_cast<WPEEventType>(type), view, WPE_INPUT_SOURCE_KEYBOARD, | |
| static_cast<uint32_t>(time), modifiers, hardwareKeyCode, keySym); | |
| wpe_view_event(view, event); | |
| wpe_event_unref(event); | |
| g_autoptr(WPEEvent) event = wpe_event_keyboard_new(static_cast<WPEEventType>(type), view, WPE_INPUT_SOURCE_KEYBOARD, | |
| static_cast<uint32_t>(time), modifiers, hardwareKeyCode, keySym); | |
| wpe_view_event(view, event); |
Add Java proxy wrappers plus JNI mappings for WPEDisplay, WPEToplevel, WPEView, WebKitWebContext, WebKitNetworkSession, WebKitSettings, WebKitCookieManager, WebKitWebsiteDataManager, and WebKitWebView.
Each bridged class uses a JNI::TypedClass singleton to register native methods and cache Java method IDs at library load time, avoiding repeated JNI lookups in GLib and async callbacks. JNIMappings initialises all singletons during JNI_OnLoad.
WebKitWebView and WebKitWebContext use per-object bridge structs that own the native GObject reference, keep a Java GlobalRef back-reference where needed, and track GLib signal handlers for orderly disconnection on destruction. Async operations such as cookie-manager queries, website-data clears, and evaluateJavascript() keep callback holders alive with heap-allocated GlobalRef objects released when the callback runs.
WPEToplevelAndroid updates its SurfaceControl tree as native windows are attached and detached, and treats repeated set_window() calls for the same ANativeWindow as a size resync instead of rebuilding the layer tree.
Native callbacks may arrive off the main thread; Java-side listener and async result delivery is reposted to the main looper before application-facing callback code runs.