Skip to content

C API: Add JNI bridge between Java proxy classes and WebKit/WPE GObjects#250

Open
alexgcastro wants to merge 1 commit intomainfrom
alex/capi-add-jni-bridge
Open

C API: Add JNI bridge between Java proxy classes and WebKit/WPE GObjects#250
alexgcastro wants to merge 1 commit intomainfrom
alex/capi-add-jni-bridge

Conversation

@alexgcastro
Copy link
Copy Markdown
Collaborator

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.

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.
@alexgcastro alexgcastro force-pushed the alex/capi-add-jni-bridge branch from 293daca to 5fd90f4 Compare April 6, 2026 19:25
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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Suggested change
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's the same holder that here might take a null value, isn't it?

Comment on lines +40 to +44
GObject* automationSession = static_cast<GObject*>(g_weak_ref_get(&m_automationSession));
if (automationSession) {
g_signal_handler_disconnect(automationSession, m_createWebViewSignalId);
g_object_unref(automationSession);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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

Comment on lines +99 to +103
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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

Comment on lines +105 to +109
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ditto

Suggested change
if (*holder) {
if (holder && *holder) {

g_free(strValue);
}
g_object_unref(value);
if (*holder)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (*holder)
if (holder && *holder)

Comment on lines +72 to +77
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Comment on lines +91 to +94
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants