diff --git a/wpeview/src/main/cpp/CMakeLists.txt b/wpeview/src/main/cpp/CMakeLists.txt index ac7b50f5a..2847b1541 100644 --- a/wpeview/src/main/cpp/CMakeLists.txt +++ b/wpeview/src/main/cpp/CMakeLists.txt @@ -141,9 +141,20 @@ add_library( Runtime/WKWebContext.cpp Runtime/WKSettings.cpp Runtime/WKWebsiteDataManager.cpp - Runtime/WKWebView.cpp) + Runtime/WKWebView.cpp + capi/JNIMappings.cpp + capi/WebKitCookieManager.cpp + capi/WebKitNetworkSession.cpp + capi/WebKitSettings.cpp + capi/WebKitWebContext.cpp + capi/WebKitWebView.cpp + capi/WebKitWebsiteDataManager.cpp + capi/WPEDisplay.cpp + capi/WPEScreen.cpp + capi/WPEToplevel.cpp + capi/WPEView.cpp) target_configure_quality(WPEAndroidRuntime) -target_include_directories(WPEAndroidRuntime PRIVATE Platform Runtime) +target_include_directories(WPEAndroidRuntime PRIVATE Platform Runtime capi) target_compile_definitions(WPEAndroidRuntime PRIVATE WPE_ENABLE_PROCESS) target_link_libraries( WPEAndroidRuntime diff --git a/wpeview/src/main/cpp/Common/JNI/JNI.h b/wpeview/src/main/cpp/Common/JNI/JNI.h index d1b4d632d..b2fa005ae 100644 --- a/wpeview/src/main/cpp/Common/JNI/JNI.h +++ b/wpeview/src/main/cpp/Common/JNI/JNI.h @@ -20,6 +20,7 @@ #pragma once #include "JNIClass.h" +#include "JNIEnv.h" #include "JNIObjectArray.h" #include "JNIScalarArray.h" #include "JNIString.h" diff --git a/wpeview/src/main/cpp/Common/JNI/JNIEnv.h b/wpeview/src/main/cpp/Common/JNI/JNIEnv.h index 6a7f18ad5..a5b9a63cc 100644 --- a/wpeview/src/main/cpp/Common/JNI/JNIEnv.h +++ b/wpeview/src/main/cpp/Common/JNI/JNIEnv.h @@ -51,4 +51,69 @@ inline EnableIfObjectType> createTypedProtectedRef(JNIEnv* e env, std::move(std::forward(obj)), useGlobalRef)); // NOLINT(bugprone-move-forwarding-reference) } +// Move-only RAII owner of a single Java global reference. +// Analogous to Chromium's ScopedJavaGlobalRef and preferred for async callback +// holders plus native-to-Java back-references that outlive the current JNI call. +template > class GlobalRef final { +public: + GlobalRef() noexcept + : m_ref(nullptr) + { + } + GlobalRef(JNIEnv* env, T obj) + : m_ref(obj ? env->NewGlobalRef(obj) : nullptr) + { + } + GlobalRef(JNIEnv* env, jobject obj) + : m_ref(obj ? env->NewGlobalRef(obj) : nullptr) + { + } + ~GlobalRef() + { + reset(); + } + + GlobalRef(GlobalRef&& other) noexcept + : m_ref(other.release()) + { + } + GlobalRef& operator=(GlobalRef&& other) noexcept + { + if (this != &other) { + reset(); + m_ref = other.release(); + } + return *this; + } + GlobalRef(const GlobalRef&) = delete; + GlobalRef& operator=(const GlobalRef&) = delete; + + T get() const noexcept + { + return static_cast(m_ref); + } + explicit operator bool() const noexcept + { + return m_ref != nullptr; + } + + T release() noexcept + { + auto* ref = static_cast(m_ref); + m_ref = nullptr; + return ref; + } + + void reset() noexcept + { + if (m_ref) { + getCurrentThreadJNIEnv()->DeleteGlobalRef(m_ref); + m_ref = nullptr; + } + } + +private: + jobject m_ref; +}; + } // namespace JNI diff --git a/wpeview/src/main/cpp/Common/JNI/JNITypes.h b/wpeview/src/main/cpp/Common/JNI/JNITypes.h index 73946f6c4..65a84ec32 100644 --- a/wpeview/src/main/cpp/Common/JNI/JNITypes.h +++ b/wpeview/src/main/cpp/Common/JNI/JNITypes.h @@ -139,4 +139,9 @@ template struct FunctionSignature::value>::value; }; +template inline T* from_jlong(jlong ptr) +{ + return reinterpret_cast(ptr); +} + } // namespace JNI diff --git a/wpeview/src/main/cpp/Platform/WPEToplevelAndroid.cpp b/wpeview/src/main/cpp/Platform/WPEToplevelAndroid.cpp index 8f5438e8c..a0700da40 100644 --- a/wpeview/src/main/cpp/Platform/WPEToplevelAndroid.cpp +++ b/wpeview/src/main/cpp/Platform/WPEToplevelAndroid.cpp @@ -115,10 +115,18 @@ WPEToplevel* wpe_toplevel_android_new(WPEDisplay* display, ANativeWindow* window return WPE_TOPLEVEL(toplevel); } +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void wpe_toplevel_android_set_window(WPEToplevelAndroid* toplevel, ANativeWindow* window) { g_return_if_fail(WPE_IS_TOPLEVEL_ANDROID(toplevel)); + if (toplevel->window == window && window) { + int physicalWidth = ANativeWindow_getWidth(window); + int physicalHeight = ANativeWindow_getHeight(window); + wpeToplevelAndroidSyncScaleAndSize(toplevel, physicalWidth, physicalHeight); + return; + } + if (window) ANativeWindow_acquire(window); @@ -135,11 +143,10 @@ void wpe_toplevel_android_set_window(WPEToplevelAndroid* toplevel, ANativeWindow ASurfaceControl* newRoot = ASurfaceControl_createFromWindow(window, "WPEToplevelRoot"); if (newRoot) { - if (!toplevel->surfaceControl) { + if (!toplevel->surfaceControl) toplevel->surfaceControl = ASurfaceControl_create(newRoot, "WPEWebLayer"); - } else { + else ASurfaceTransaction_reparent(transaction, toplevel->surfaceControl, newRoot); - } if (toplevel->surfaceControl) { ASurfaceTransaction_setVisibility( transaction, toplevel->surfaceControl, ASURFACE_TRANSACTION_VISIBILITY_SHOW); diff --git a/wpeview/src/main/cpp/Runtime/EntryPoint.cpp b/wpeview/src/main/cpp/Runtime/EntryPoint.cpp index df4c17b49..db7563a05 100644 --- a/wpeview/src/main/cpp/Runtime/EntryPoint.cpp +++ b/wpeview/src/main/cpp/Runtime/EntryPoint.cpp @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "../capi/JNIMappings.h" #include "Init.h" #include "Logging.h" #include "WKCallback.h" @@ -42,6 +43,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* javaVM, void* /*reserved*/) WKWebView::configureJNIMappings(); WKSettings::configureJNIMappings(); + WebKit::configureJNIMappings(); + return JNI::VERSION; } catch (const std::exception& e) { Logging::logError("Runtime: JNI_OnLoad: %s", e.what()); diff --git a/wpeview/src/main/cpp/capi/JNIMappings.cpp b/wpeview/src/main/cpp/capi/JNIMappings.cpp new file mode 100644 index 000000000..fdf443f08 --- /dev/null +++ b/wpeview/src/main/cpp/capi/JNIMappings.cpp @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNIMappings.h" + +namespace WebKit { + +void configureWebKitCookieManagerJNIMappings(); +void configureWebKitCookieManagerAcceptPolicyCallbackHolderJNIMappings(); +void configureWebKitNetworkSessionJNIMappings(); +void configureWebKitSettingsJNIMappings(); +void configureWebKitWebContextJNIMappings(); +void configureWebKitWebViewJNIMappings(); +void configureWebKitWebViewEvalCallbackHolderJNIMappings(); +void configureWebKitWebsiteDataManagerJNIMappings(); +void configureWebKitWebsiteDataManagerCallbackHolderJNIMappings(); +void configureWPEDisplayJNIMappings(); +void configureWPEScreenJNIMappings(); +void configureWPEToplevelJNIMappings(); +void configureWPEViewJNIMappings(); + +void configureJNIMappings() +{ + configureWebKitCookieManagerJNIMappings(); + configureWebKitCookieManagerAcceptPolicyCallbackHolderJNIMappings(); + configureWebKitNetworkSessionJNIMappings(); + configureWebKitSettingsJNIMappings(); + configureWebKitWebContextJNIMappings(); + configureWebKitWebViewJNIMappings(); + configureWebKitWebViewEvalCallbackHolderJNIMappings(); + configureWebKitWebsiteDataManagerJNIMappings(); + configureWebKitWebsiteDataManagerCallbackHolderJNIMappings(); + configureWPEDisplayJNIMappings(); + configureWPEScreenJNIMappings(); + configureWPEToplevelJNIMappings(); + configureWPEViewJNIMappings(); +} + +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/JNIMappings.h b/wpeview/src/main/cpp/capi/JNIMappings.h new file mode 100644 index 000000000..c34b319a3 --- /dev/null +++ b/wpeview/src/main/cpp/capi/JNIMappings.h @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +namespace WebKit { + +void configureJNIMappings(); + +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WPEDisplay.cpp b/wpeview/src/main/cpp/capi/WPEDisplay.cpp new file mode 100644 index 000000000..b2f466423 --- /dev/null +++ b/wpeview/src/main/cpp/capi/WPEDisplay.cpp @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" +#include "WPEDisplayAndroid.h" + +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWPEDisplay, "org/wpewebkit/wpe/WPEDisplay"); + +namespace WebKit { + +class JNIWPEDisplayCache final : public JNI::TypedClass { +public: + JNIWPEDisplayCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod("nativeGetScreen", nativeGetScreen)); + } + +private: + static jlong nativeInit(JNIEnv*, jobject) + { + auto* display = wpe_display_android_new(); + + g_autoptr(GError) error = nullptr; + if (!wpe_display_connect(WPE_DISPLAY(display), &error)) { + Logging::logError("WPEDisplay: failed to connect: %s", error ? error->message : "unknown"); + g_object_unref(display); + return 0; + } + + return reinterpret_cast(display); + } + + static void nativeDestroy(JNIEnv*, jobject, jlong displayPtr) + { + g_object_unref(JNI::from_jlong(displayPtr)); + } + + static jlong nativeGetScreen(JNIEnv*, jobject, jlong displayPtr) + { + return reinterpret_cast(wpe_display_get_screen(JNI::from_jlong(displayPtr), 0)); + } +}; + +const JNIWPEDisplayCache& getJNIWPEDisplayCache() +{ + static const JNIWPEDisplayCache s_singleton; + return s_singleton; +} + +void configureWPEDisplayJNIMappings() +{ + getJNIWPEDisplayCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WPEScreen.cpp b/wpeview/src/main/cpp/capi/WPEScreen.cpp new file mode 100644 index 000000000..63f26b24b --- /dev/null +++ b/wpeview/src/main/cpp/capi/WPEScreen.cpp @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWPEScreen, "org/wpewebkit/wpe/WPEScreen"); + +namespace WebKit { + +class JNIWPEScreenCache final : public JNI::TypedClass { +public: + JNIWPEScreenCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod("nativeGetScale", nativeGetScale), + JNI::NativeMethod("nativeSetScale", nativeSetScale)); + } + +private: + static jfloat nativeGetScale(JNIEnv*, jobject, jlong nativePtr) + { + return static_cast(wpe_screen_get_scale(JNI::from_jlong(nativePtr))); + } + + static void nativeSetScale(JNIEnv*, jobject, jlong nativePtr, jfloat scale) + { + wpe_screen_set_scale(JNI::from_jlong(nativePtr), scale); + } +}; + +const JNIWPEScreenCache& getJNIWPEScreenCache() +{ + static const JNIWPEScreenCache s_singleton; + return s_singleton; +} + +void configureWPEScreenJNIMappings() +{ + getJNIWPEScreenCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WPEToplevel.cpp b/wpeview/src/main/cpp/capi/WPEToplevel.cpp new file mode 100644 index 000000000..294a86fb9 --- /dev/null +++ b/wpeview/src/main/cpp/capi/WPEToplevel.cpp @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" +#include "WPEToplevelAndroid.h" + +#include +#include +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWPEToplevel, "org/wpewebkit/wpe/WPEToplevel"); +DECLARE_JNI_CLASS_SIGNATURE(JNISurface, "android/view/Surface"); + +namespace WebKit { + +class JNIWPEToplevelCache final : public JNI::TypedClass { +public: + JNIWPEToplevelCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod("nativeSetPhysicalSize", nativeSetPhysicalSize), + JNI::NativeMethod("nativeGetSize", nativeGetSize), + JNI::NativeMethod("nativeSetSurface", nativeSetSurface)); + } + +private: + static jlong nativeInit(JNIEnv* env, jobject, jlong displayPtr, JNISurface surface) + { + auto* display = JNI::from_jlong(displayPtr); + ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr; + + auto* toplevel = wpe_toplevel_android_new(display, window); + if (window) + ANativeWindow_release(window); + + return reinterpret_cast(toplevel); + } + + static void nativeDestroy(JNIEnv*, jobject, jlong nativePtr) + { + g_object_unref(JNI::from_jlong(nativePtr)); + } + + static void nativeSetPhysicalSize(JNIEnv*, jobject, jlong nativePtr, jint width, jint height) + { + wpe_toplevel_android_set_physical_size( + WPE_TOPLEVEL_ANDROID(JNI::from_jlong(nativePtr)), width, height); + } + + static void nativeGetSize(JNIEnv* env, jobject, jlong nativePtr, jintArray out) + { + int width = 0, height = 0; + wpe_toplevel_get_size(JNI::from_jlong(nativePtr), &width, &height); + jint data[2] = {static_cast(width), static_cast(height)}; + env->SetIntArrayRegion(out, 0, 2, data); + } + + static void nativeSetSurface(JNIEnv* env, jobject, jlong nativePtr, JNISurface surface) + { + auto* toplevel = JNI::from_jlong(nativePtr); + ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr; + + wpe_toplevel_android_set_window(WPE_TOPLEVEL_ANDROID(toplevel), window); + + if (window) + ANativeWindow_release(window); + } +}; + +const JNIWPEToplevelCache& getJNIWPEToplevelCache() +{ + static const JNIWPEToplevelCache s_singleton; + return s_singleton; +} + +void configureWPEToplevelJNIMappings() +{ + getJNIWPEToplevelCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WPEView.cpp b/wpeview/src/main/cpp/capi/WPEView.cpp new file mode 100644 index 000000000..54bb9764d --- /dev/null +++ b/wpeview/src/main/cpp/capi/WPEView.cpp @@ -0,0 +1,106 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" +#include "WPEKeymapAndroid.h" + +#include +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWPEView, "org/wpewebkit/wpe/WPEView"); + +namespace WebKit { + +class JNIWPEViewCache final : public JNI::TypedClass { +public: + JNIWPEViewCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod("nativeSetToplevel", nativeSetToplevel), + JNI::NativeMethod("nativeResized", nativeResized), + JNI::NativeMethod("nativeSetMapped", nativeSetMapped), + JNI::NativeMethod( + "nativeDispatchTouchEvent", nativeDispatchTouchEvent), + JNI::NativeMethod( + "nativeDispatchKeyEvent", nativeDispatchKeyEvent)); + } + +private: + static void nativeSetToplevel(JNIEnv*, jobject, jlong viewPtr, jlong toplevelPtr) + { + wpe_view_set_toplevel(JNI::from_jlong(viewPtr), JNI::from_jlong(toplevelPtr)); + } + + static void nativeResized(JNIEnv*, jobject, jlong viewPtr, jint width, jint height) + { + wpe_view_resized(JNI::from_jlong(viewPtr), static_cast(std::max(0, width)), + static_cast(std::max(0, height))); + } + + static void nativeSetMapped(JNIEnv*, jobject, jlong viewPtr, jboolean mapped) + { + auto* view = JNI::from_jlong(viewPtr); + mapped ? wpe_view_map(view) : wpe_view_unmap(view); + } + + static void nativeDispatchTouchEvent(JNIEnv* env, jobject, jlong viewPtr, jlong time, jint type, jint pointerCount, + jintArray ids, jfloatArray xs, jfloatArray ys) + { + auto* view = JNI::from_jlong(viewPtr); + JNI::ScalarArray pointerIds(ids); + JNI::ScalarArray pointerXs(xs); + JNI::ScalarArray pointerYs(ys); + + auto eventType = static_cast(type); + for (int i = 0; i < pointerCount; i++) { + g_autoptr(WPEEvent) event = wpe_event_touch_new(eventType, view, WPE_INPUT_SOURCE_TOUCHSCREEN, + static_cast(time), WPEModifiers {}, static_cast(pointerIds.getReadOnlyContent()[i]), + static_cast(pointerXs.getReadOnlyContent()[i]), + static_cast(pointerYs.getReadOnlyContent()[i])); + wpe_view_event(view, event); + } + + UNUSED_PARAM(env); + } + + static void nativeDispatchKeyEvent( + JNIEnv*, jobject, jlong viewPtr, jlong time, jint type, jint keyCode, jint unicodeChar, jint metaState) + { + auto* view = JNI::from_jlong(viewPtr); + uint32_t hardwareKeyCode = androidToXkbKeycode(keyCode); + uint32_t keySym = androidToKeysym(keyCode, unicodeChar); + WPEModifiers modifiers = androidToWpeModifiers(metaState); + + g_autoptr(WPEEvent) event = wpe_event_keyboard_new(static_cast(type), view, + WPE_INPUT_SOURCE_KEYBOARD, static_cast(time), modifiers, hardwareKeyCode, keySym); + wpe_view_event(view, event); + } +}; + +const JNIWPEViewCache& getJNIWPEViewCache() +{ + static const JNIWPEViewCache s_singleton; + return s_singleton; +} + +void configureWPEViewJNIMappings() +{ + getJNIWPEViewCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WebKitCookieManager.cpp b/wpeview/src/main/cpp/capi/WebKitCookieManager.cpp new file mode 100644 index 000000000..8facd39e6 --- /dev/null +++ b/wpeview/src/main/cpp/capi/WebKitCookieManager.cpp @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitCookieManager, "org/wpewebkit/wpe/WebKitCookieManager"); +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitCookieManagerAcceptPolicyCallbackHolder, + "org/wpewebkit/wpe/WebKitCookieManager$AcceptPolicyCallbackHolder"); + +namespace WebKit { + +class JNIWebKitCookieManagerAcceptPolicyCallbackHolderCache final + : public JNI::TypedClass { +public: + JNIWebKitCookieManagerAcceptPolicyCallbackHolderCache() + : JNI::TypedClass(true) + , m_commitResult(getMethod("commitResult")) + { + } + + void onResult(JNIWebKitCookieManagerAcceptPolicyCallbackHolder callbackHolder, jint policy) const + { + try { + m_commitResult.invoke(callbackHolder, policy); + } catch (const std::exception& ex) { + Logging::logError("cannot call WebKitCookieManager accept-policy callback (%s)", ex.what()); + } + } + +private: + const JNI::Method m_commitResult; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +const JNIWebKitCookieManagerAcceptPolicyCallbackHolderCache& getJNIWebKitCookieManagerAcceptPolicyCallbackHolderCache() +{ + static const JNIWebKitCookieManagerAcceptPolicyCallbackHolderCache s_singleton; + return s_singleton; +} + +class JNIWebKitCookieManagerCache final : public JNI::TypedClass { +public: + JNIWebKitCookieManagerCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod( + "nativeGetAcceptPolicy", nativeGetAcceptPolicy), + JNI::NativeMethod("nativeSetAcceptPolicy", nativeSetAcceptPolicy)); + } + +private: + static void onGetAcceptPolicyReady(GObject* object, GAsyncResult* result, gpointer userData) + { + std::unique_ptr> holder( + static_cast*>(userData)); + auto policy = webkit_cookie_manager_get_accept_policy_finish(WEBKIT_COOKIE_MANAGER(object), result, nullptr); + if (holder && *holder) { + getJNIWebKitCookieManagerAcceptPolicyCallbackHolderCache().onResult( + holder->get(), static_cast(policy)); + } + } + + static jlong nativeInit(JNIEnv*, jobject, jlong sessionPtr) + { + return reinterpret_cast( + g_object_ref(webkit_network_session_get_cookie_manager(JNI::from_jlong(sessionPtr)))); + } + + static void nativeDestroy(JNIEnv*, jobject, jlong nativePtr) + { + g_object_unref(JNI::from_jlong(nativePtr)); + } + + static void nativeGetAcceptPolicy( + JNIEnv* env, jobject, jlong nativePtr, JNIWebKitCookieManagerAcceptPolicyCallbackHolder callbackHolder) + { + auto* holder = callbackHolder + ? new JNI::GlobalRef(env, callbackHolder) + : nullptr; + webkit_cookie_manager_get_accept_policy( + JNI::from_jlong(nativePtr), nullptr, onGetAcceptPolicyReady, holder); + } + + static void nativeSetAcceptPolicy(JNIEnv*, jobject, jlong nativePtr, jint policy) + { + webkit_cookie_manager_set_accept_policy( + JNI::from_jlong(nativePtr), static_cast(policy)); + } +}; + +const JNIWebKitCookieManagerCache& getJNIWebKitCookieManagerCache() +{ + static const JNIWebKitCookieManagerCache s_singleton; + return s_singleton; +} + +void configureWebKitCookieManagerAcceptPolicyCallbackHolderJNIMappings() +{ + getJNIWebKitCookieManagerAcceptPolicyCallbackHolderCache(); +} + +void configureWebKitCookieManagerJNIMappings() +{ + getJNIWebKitCookieManagerCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WebKitNetworkSession.cpp b/wpeview/src/main/cpp/capi/WebKitNetworkSession.cpp new file mode 100644 index 000000000..c0145250d --- /dev/null +++ b/wpeview/src/main/cpp/capi/WebKitNetworkSession.cpp @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitNetworkSession, "org/wpewebkit/wpe/WebKitNetworkSession"); + +namespace WebKit { + +class JNIWebKitNetworkSessionCache final : public JNI::TypedClass { +public: + JNIWebKitNetworkSessionCache() + : JNI::TypedClass(true) + { + registerNativeMethods( + JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod("nativeWebsiteDataManager", nativeWebsiteDataManager), + JNI::NativeMethod("nativeSetTLSErrorsPolicy", nativeSetTLSErrorsPolicy)); + } + +private: + static jlong nativeInit(JNIEnv*, jobject, jlong contextPtr, jboolean automationMode, jboolean ephemeral, + jstring dataDir, jstring cacheDir) + { + WebKitNetworkSession* session = nullptr; + if (automationMode) { + auto* context = JNI::from_jlong(contextPtr); + if (!context) { + Logging::logError("WebKitNetworkSession: automation mode requires a WebKitWebContext"); + return 0; + } + session = g_object_ref(webkit_web_context_get_network_session_for_automation(context)); + } else if (ephemeral) + session = webkit_network_session_new_ephemeral(); + else { + session = webkit_network_session_new( + JNI::String(dataDir).getContent().get(), JNI::String(cacheDir).getContent().get()); + } + return reinterpret_cast(session); + } + + static void nativeDestroy(JNIEnv*, jobject, jlong sessionPtr) + { + g_object_unref(JNI::from_jlong(sessionPtr)); + } + + static jlong nativeWebsiteDataManager(JNIEnv*, jobject, jlong sessionPtr) + { + return reinterpret_cast( + webkit_network_session_get_website_data_manager(JNI::from_jlong(sessionPtr))); + } + + static void nativeSetTLSErrorsPolicy(JNIEnv*, jobject, jlong sessionPtr, jint policy) + { + webkit_network_session_set_tls_errors_policy( + JNI::from_jlong(sessionPtr), static_cast(policy)); + } +}; + +const JNIWebKitNetworkSessionCache& getJNIWebKitNetworkSessionCache() +{ + static const JNIWebKitNetworkSessionCache s_singleton; + return s_singleton; +} + +void configureWebKitNetworkSessionJNIMappings() +{ + getJNIWebKitNetworkSessionCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WebKitSettings.cpp b/wpeview/src/main/cpp/capi/WebKitSettings.cpp new file mode 100644 index 000000000..028a75622 --- /dev/null +++ b/wpeview/src/main/cpp/capi/WebKitSettings.cpp @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitSettings, "org/wpewebkit/wpe/WebKitSettings"); + +namespace WebKit { + +class JNIWebKitSettingsCache final : public JNI::TypedClass { +public: + JNIWebKitSettingsCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod("nativeGetUserAgentString", nativeGetUserAgentString), + JNI::NativeMethod("nativeSetUserAgentString", nativeSetUserAgentString), + JNI::NativeMethod( + "nativeGetMediaPlaybackRequiresUserGesture", nativeGetMediaPlaybackRequiresUserGesture), + JNI::NativeMethod( + "nativeSetMediaPlaybackRequiresUserGesture", nativeSetMediaPlaybackRequiresUserGesture), + JNI::NativeMethod( + "nativeGetAllowUniversalAccessFromFileURLs", nativeGetAllowUniversalAccessFromFileURLs), + JNI::NativeMethod( + "nativeSetAllowUniversalAccessFromFileURLs", nativeSetAllowUniversalAccessFromFileURLs), + JNI::NativeMethod( + "nativeGetAllowFileAccessFromFileURLs", nativeGetAllowFileAccessFromFileURLs), + JNI::NativeMethod( + "nativeSetAllowFileAccessFromFileURLs", nativeSetAllowFileAccessFromFileURLs), + JNI::NativeMethod("nativeGetDeveloperExtrasEnabled", nativeGetDeveloperExtrasEnabled), + JNI::NativeMethod( + "nativeSetDeveloperExtrasEnabled", nativeSetDeveloperExtrasEnabled), + JNI::NativeMethod("nativeGetDisableWebSecurity", nativeGetDisableWebSecurity), + JNI::NativeMethod("nativeSetDisableWebSecurity", nativeSetDisableWebSecurity)); + } + +private: + static jlong nativeInit(JNIEnv*, jobject) + { + return reinterpret_cast(webkit_settings_new()); + } + + static void nativeDestroy(JNIEnv*, jobject, jlong settingsPtr) + { + g_object_unref(JNI::from_jlong(settingsPtr)); + } + + static jstring nativeGetUserAgentString(JNIEnv*, jobject, jlong settingsPtr) + { + return static_cast( + JNI::String(webkit_settings_get_user_agent(JNI::from_jlong(settingsPtr)))); + } + + static void nativeSetUserAgentString(JNIEnv*, jobject, jlong settingsPtr, jstring userAgent) + { + webkit_settings_set_user_agent( + JNI::from_jlong(settingsPtr), JNI::String(userAgent).getContent().get()); + } + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +#define DEFINE_BOOL_SETTER_GETTER(NativeName, WebKitName) \ + static jboolean nativeGet##NativeName(JNIEnv*, jobject, jlong settingsPtr) \ + { \ + return static_cast(webkit_settings_get_##WebKitName(JNI::from_jlong(settingsPtr))); \ + } \ + static void nativeSet##NativeName(JNIEnv*, jobject, jlong settingsPtr, jboolean flag) \ + { \ + webkit_settings_set_##WebKitName(JNI::from_jlong(settingsPtr), static_cast(flag)); \ + } + + DEFINE_BOOL_SETTER_GETTER(MediaPlaybackRequiresUserGesture, media_playback_requires_user_gesture) + DEFINE_BOOL_SETTER_GETTER(AllowUniversalAccessFromFileURLs, allow_universal_access_from_file_urls) + DEFINE_BOOL_SETTER_GETTER(AllowFileAccessFromFileURLs, allow_file_access_from_file_urls) + DEFINE_BOOL_SETTER_GETTER(DeveloperExtrasEnabled, enable_developer_extras) + DEFINE_BOOL_SETTER_GETTER(DisableWebSecurity, disable_web_security) + +#undef DEFINE_BOOL_SETTER_GETTER + // NOLINTEND(cppcoreguidelines-macro-usage) +}; + +const JNIWebKitSettingsCache& getJNIWebKitSettingsCache() +{ + static const JNIWebKitSettingsCache s_singleton; + return s_singleton; +} + +void configureWebKitSettingsJNIMappings() +{ + getJNIWebKitSettingsCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WebKitWebContext.cpp b/wpeview/src/main/cpp/capi/WebKitWebContext.cpp new file mode 100644 index 000000000..94d7e4d1f --- /dev/null +++ b/wpeview/src/main/cpp/capi/WebKitWebContext.cpp @@ -0,0 +1,159 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitWebContext, "org/wpewebkit/wpe/WebKitWebContext"); + +namespace WebKit { + +struct WebKitWebContextBridge { + explicit WebKitWebContextBridge(WebKitWebContext* context) + : m_context(context) + { + g_weak_ref_init(&m_automationSession, nullptr); + } + + ~WebKitWebContextBridge() + { + if (m_automationSignalId) + g_signal_handler_disconnect(m_context, m_automationSignalId); + if (m_createWebViewSignalId) { + if (GObject* automationSession = static_cast(g_weak_ref_get(&m_automationSession))) { + g_signal_handler_disconnect(automationSession, m_createWebViewSignalId); + g_object_unref(automationSession); + } + } + g_weak_ref_clear(&m_automationSession); + g_object_unref(m_context); + } + + WebKitWebContext* m_context; + JNI::GlobalRef m_javaRef; + GWeakRef m_automationSession; + gulong m_automationSignalId {0}; + gulong m_createWebViewSignalId {0}; +}; + +class JNIWebKitWebContextCache final : public JNI::TypedClass { +public: + JNIWebKitWebContextCache() + : JNI::TypedClass(true) + , m_invokeCreateWebKitWebViewForAutomation(getMethod("createWebKitWebViewForAutomation")) + { + registerNativeMethods(JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod("nativeIsAutomationMode", nativeIsAutomationMode), + JNI::NativeMethod("nativeGetWebKitWebContextPtr", nativeGetWebKitWebContextPtr)); + } + + WebKitWebView* invokeCreateWebKitWebViewForAutomation(JNIWebKitWebContext javaContext) const; + +private: + static void onAutomationStarted( + WebKitWebContext*, WebKitAutomationSession* session, WebKitWebContextBridge* bridge); + static WebKitWebView* onCreateWebViewForAutomation(WebKitWebContextBridge* bridge); + + static jlong nativeInit(JNIEnv* env, jobject jniContext, jboolean automationMode); + static void nativeDestroy(JNIEnv*, jobject, jlong nativePtr); + static jboolean nativeIsAutomationMode(JNIEnv*, jobject, jlong nativePtr); + static jlong nativeGetWebKitWebContextPtr(JNIEnv*, jobject, jlong nativePtr); + + const JNI::Method + m_invokeCreateWebKitWebViewForAutomation; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +const JNIWebKitWebContextCache& getJNIWebKitWebContextCache() +{ + static const JNIWebKitWebContextCache s_singleton; + return s_singleton; +} + +WebKitWebView* JNIWebKitWebContextCache::onCreateWebViewForAutomation(WebKitWebContextBridge* bridge) +{ + return getJNIWebKitWebContextCache().invokeCreateWebKitWebViewForAutomation(bridge->m_javaRef.get()); +} + +void JNIWebKitWebContextCache::onAutomationStarted( + WebKitWebContext*, WebKitAutomationSession* session, WebKitWebContextBridge* bridge) +{ + 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); + + if (GObject* previousAutomationSession = static_cast(g_weak_ref_get(&bridge->m_automationSession))) { + if (bridge->m_createWebViewSignalId) + g_signal_handler_disconnect(previousAutomationSession, bridge->m_createWebViewSignalId); + g_object_unref(previousAutomationSession); + } + + g_weak_ref_set(&bridge->m_automationSession, G_OBJECT(session)); + bridge->m_createWebViewSignalId + = g_signal_connect_swapped(session, "create-web-view", G_CALLBACK(onCreateWebViewForAutomation), bridge); +} + +jlong JNIWebKitWebContextCache::nativeInit(JNIEnv* env, jobject jniContext, jboolean automationMode) +{ + auto* context = webkit_web_context_new(); + webkit_web_context_set_automation_allowed(context, automationMode ? TRUE : FALSE); + + auto* bridge = new WebKitWebContextBridge(context); + if (automationMode) { + bridge->m_javaRef = JNI::GlobalRef(env, jniContext); + bridge->m_automationSignalId + = g_signal_connect(context, "automation-started", G_CALLBACK(onAutomationStarted), bridge); + } + return reinterpret_cast(bridge); +} + +void JNIWebKitWebContextCache::nativeDestroy(JNIEnv*, jobject, jlong nativePtr) +{ + delete JNI::from_jlong(nativePtr); +} + +jboolean JNIWebKitWebContextCache::nativeIsAutomationMode(JNIEnv*, jobject, jlong nativePtr) +{ + return static_cast( + webkit_web_context_is_automation_allowed(JNI::from_jlong(nativePtr)->m_context)); +} + +jlong JNIWebKitWebContextCache::nativeGetWebKitWebContextPtr(JNIEnv*, jobject, jlong nativePtr) +{ + return reinterpret_cast(JNI::from_jlong(nativePtr)->m_context); +} + +WebKitWebView* JNIWebKitWebContextCache::invokeCreateWebKitWebViewForAutomation(JNIWebKitWebContext javaContext) const +{ + try { + const auto webViewPtr = m_invokeCreateWebKitWebViewForAutomation.invoke(javaContext); + return webViewPtr ? reinterpret_cast(webViewPtr) : nullptr; + } catch (const std::exception& ex) { + Logging::logError("cannot create automation web view (%s)", ex.what()); + return nullptr; + } +} + +void configureWebKitWebContextJNIMappings() +{ + getJNIWebKitWebContextCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WebKitWebView.cpp b/wpeview/src/main/cpp/capi/WebKitWebView.cpp new file mode 100644 index 000000000..15b588fd8 --- /dev/null +++ b/wpeview/src/main/cpp/capi/WebKitWebView.cpp @@ -0,0 +1,371 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include +#include +#include +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitWebViewEvalCallbackHolder, "org/wpewebkit/wpe/WebKitWebView$EvalCallbackHolder"); +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitWebView, "org/wpewebkit/wpe/WebKitWebView"); + +namespace WebKit { + +struct WebKitWebViewBridge { + WebKitWebViewBridge(WebKitWebView* webView, JNIEnv* env, jobject javaWebView) + : m_webView(webView) + , m_javaRef(env, javaWebView) + { + } + + ~WebKitWebViewBridge() + { + for (gulong id : m_signalIds) + g_signal_handler_disconnect(m_webView, id); + m_signalIds.clear(); + g_object_unref(m_webView); + } + + WebKitWebView* m_webView; + JNI::GlobalRef m_javaRef; + std::vector m_signalIds; +}; + +class JNIWebKitWebViewEvalCallbackHolderCache final : public JNI::TypedClass { +public: + JNIWebKitWebViewEvalCallbackHolderCache() + : JNI::TypedClass(true) + , m_commitResult(getMethod("commitResult")) + { + } + + void onResult(JNIWebKitWebViewEvalCallbackHolder holder, jstring result) const + { + if (!holder) + return; + + try { + m_commitResult.invoke(holder, result); + } catch (const std::exception& ex) { + Logging::logError("cannot call WebKitWebView eval callback (%s)", ex.what()); + } + } + +private: + const JNI::Method m_commitResult; +}; + +const JNIWebKitWebViewEvalCallbackHolderCache& getJNIWebKitWebViewEvalCallbackHolderCache() +{ + static const JNIWebKitWebViewEvalCallbackHolderCache s_singleton; + return s_singleton; +} + +class JNIWebKitWebViewCache final : public JNI::TypedClass { +public: + JNIWebKitWebViewCache() + : JNI::TypedClass(true) + , m_onClose(getMethod("onClose")) + , m_onLoadStarted(getMethod("onLoadStarted")) + , m_onLoadFinished(getMethod("onLoadFinished")) + , m_onUriChanged(getMethod("onUriChanged")) + , m_onEstimatedLoadProgress(getMethod("onEstimatedLoadProgress")) + , m_onTitleChanged(getMethod("onTitleChanged")) + , m_onReceivedHttpError(getMethod("onReceivedHttpError")) + { + registerNativeMethods(JNI::NativeMethod("nativeInit", nativeInit), + JNI::NativeMethod("nativeDestroy", nativeDestroy), + JNI::NativeMethod("nativeLoadUrl", nativeLoadUrl), + JNI::NativeMethod("nativeLoadHtml", nativeLoadHtml), + JNI::NativeMethod("nativeStopLoading", nativeStopLoading), + JNI::NativeMethod("nativeReload", nativeReload), + JNI::NativeMethod("nativeGoBack", nativeGoBack), + JNI::NativeMethod("nativeGoForward", nativeGoForward), + JNI::NativeMethod("nativeGetWebKitWebViewPtr", nativeGetWebKitWebViewPtr), + JNI::NativeMethod("nativeGetWPEView", nativeGetWPEView), + JNI::NativeMethod("nativeSetZoomLevel", nativeSetZoomLevel), + JNI::NativeMethod( + "nativeEvaluateJavascript", nativeEvaluateJavascript)); + } + + const JNI::Method m_onClose; + const JNI::Method m_onLoadStarted; + const JNI::Method m_onLoadFinished; + const JNI::Method m_onUriChanged; + const JNI::Method m_onEstimatedLoadProgress; + const JNI::Method m_onTitleChanged; + const JNI::Method m_onReceivedHttpError; + +private: + static void onClose(WebKitWebView*, WebKitWebViewBridge* bridge); + static void onLoadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent, WebKitWebViewBridge* bridge); + static void onUriChanged(GObject* obj, GParamSpec*, WebKitWebViewBridge* bridge); + static void onEstimatedLoadProgress(GObject* obj, GParamSpec*, WebKitWebViewBridge* bridge); + static void onTitleChanged(GObject* obj, GParamSpec*, WebKitWebViewBridge* bridge); + static gboolean onDecidePolicy(WebKitWebView*, WebKitPolicyDecision* decision, + WebKitPolicyDecisionType decisionType, WebKitWebViewBridge* bridge); + static void onEvalJavascriptReady(GObject* object, GAsyncResult* result, gpointer userData); + + static jlong nativeInit(JNIEnv* env, jobject jniWebView, jlong displayPtr, jlong contextPtr, jlong toplevelPtr, + jlong networkSessionPtr, jlong settingsPtr); + static void nativeDestroy(JNIEnv*, jobject, jlong nativePtr); + static void nativeLoadUrl(JNIEnv*, jobject, jlong nativePtr, jstring url); + static void nativeLoadHtml(JNIEnv*, jobject, jlong nativePtr, jstring content, jstring baseUri); + static void nativeStopLoading(JNIEnv*, jobject, jlong nativePtr); + static void nativeReload(JNIEnv*, jobject, jlong nativePtr); + static void nativeGoBack(JNIEnv*, jobject, jlong nativePtr); + static void nativeGoForward(JNIEnv*, jobject, jlong nativePtr); + static jlong nativeGetWebKitWebViewPtr(JNIEnv*, jobject, jlong nativePtr); + static jlong nativeGetWPEView(JNIEnv*, jobject, jlong nativePtr); + static void nativeSetZoomLevel(JNIEnv*, jobject, jlong nativePtr, jdouble zoomLevel); + static void nativeEvaluateJavascript( + JNIEnv* env, jobject, jlong nativePtr, jstring script, JNIWebKitWebViewEvalCallbackHolder callbackHolder); +}; + +const JNIWebKitWebViewCache& getJNIWebKitWebViewCache() +{ + static const JNIWebKitWebViewCache s_singleton; + return s_singleton; +} + +void JNIWebKitWebViewCache::onClose(WebKitWebView*, WebKitWebViewBridge* bridge) +{ + try { + getJNIWebKitWebViewCache().m_onClose.invoke(bridge->m_javaRef.get()); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onClose callback (%s)", ex.what()); + } +} + +void JNIWebKitWebViewCache::onLoadChanged( + WebKitWebView* webView, WebKitLoadEvent loadEvent, WebKitWebViewBridge* bridge) +{ + // WEBKIT_LOAD_REDIRECTED and WEBKIT_LOAD_COMMITTED are intentionally not surfaced. + if (loadEvent == WEBKIT_LOAD_STARTED) { + auto jUri = JNI::String(webkit_web_view_get_uri(webView)); + try { + getJNIWebKitWebViewCache().m_onLoadStarted.invoke(bridge->m_javaRef.get(), static_cast(jUri)); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onLoadStarted callback (%s)", ex.what()); + } + } else if (loadEvent == WEBKIT_LOAD_FINISHED) { + auto jUri = JNI::String(webkit_web_view_get_uri(webView)); + try { + getJNIWebKitWebViewCache().m_onLoadFinished.invoke(bridge->m_javaRef.get(), static_cast(jUri)); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onLoadFinished callback (%s)", ex.what()); + } + } +} + +void JNIWebKitWebViewCache::onUriChanged(GObject* obj, GParamSpec*, WebKitWebViewBridge* bridge) +{ + auto jUri = JNI::String(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(obj))); + try { + getJNIWebKitWebViewCache().m_onUriChanged.invoke(bridge->m_javaRef.get(), static_cast(jUri)); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onUriChanged callback (%s)", ex.what()); + } +} + +void JNIWebKitWebViewCache::onEstimatedLoadProgress(GObject* obj, GParamSpec*, WebKitWebViewBridge* bridge) +{ + double progress = webkit_web_view_get_estimated_load_progress(WEBKIT_WEB_VIEW(obj)); + try { + getJNIWebKitWebViewCache().m_onEstimatedLoadProgress.invoke(bridge->m_javaRef.get(), progress); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onEstimatedLoadProgress callback (%s)", ex.what()); + } +} + +void JNIWebKitWebViewCache::onTitleChanged(GObject* obj, GParamSpec*, WebKitWebViewBridge* bridge) +{ + auto* webView = WEBKIT_WEB_VIEW(obj); + auto jTitle = JNI::String(webkit_web_view_get_title(webView)); + try { + getJNIWebKitWebViewCache().m_onTitleChanged.invoke(bridge->m_javaRef.get(), static_cast(jTitle), + static_cast(webkit_web_view_can_go_back(webView)), + static_cast(webkit_web_view_can_go_forward(webView))); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onTitleChanged callback (%s)", ex.what()); + } +} + +gboolean JNIWebKitWebViewCache::onDecidePolicy( + WebKitWebView*, WebKitPolicyDecision* decision, WebKitPolicyDecisionType decisionType, WebKitWebViewBridge* bridge) +{ + if (decisionType != WEBKIT_POLICY_DECISION_TYPE_RESPONSE || !decision) + return FALSE; + + auto* responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision); + auto* uriResponse = webkit_response_policy_decision_get_response(responseDecision); + guint statusCode = webkit_uri_response_get_status_code(uriResponse); + if (statusCode < 400) + return FALSE; + + auto* uriRequest = webkit_response_policy_decision_get_request(responseDecision); + const char* method = webkit_uri_request_get_http_method(uriRequest); + auto jUri = JNI::String(webkit_uri_request_get_uri(uriRequest)); + auto jMethod = JNI::String(method ? method : "GET"); + auto jMimeType = JNI::String(webkit_uri_response_get_mime_type(uriResponse)); + try { + getJNIWebKitWebViewCache().m_onReceivedHttpError.invoke(bridge->m_javaRef.get(), static_cast(jUri), + static_cast(jMethod), static_cast(jMimeType), static_cast(statusCode)); + } catch (const std::exception& ex) { + Logging::logError("cannot deliver WebKitWebView onReceivedHttpError callback (%s)", ex.what()); + } + + return FALSE; +} + +void JNIWebKitWebViewCache::onEvalJavascriptReady(GObject* object, GAsyncResult* result, gpointer userData) +{ + std::unique_ptr> holder( + static_cast*>(userData)); + auto* webView = WEBKIT_WEB_VIEW(object); + + g_autoptr(GError) error = nullptr; + g_autoptr(JSCValue) value = webkit_web_view_evaluate_javascript_finish(webView, result, &error); + if (!value) { + if (*holder) { + auto jNull = JNI::String("null"); + getJNIWebKitWebViewEvalCallbackHolderCache().onResult(holder->get(), static_cast(jNull)); + } + return; + } + + JSCException* exception = jsc_context_get_exception(jsc_value_get_context(value)); + JNI::String resultStr("null"); + if (!exception && !jsc_value_is_null(value) && !jsc_value_is_undefined(value)) { + g_autofree gchar* strValue = jsc_value_to_json(value, 0); + resultStr = JNI::String(strValue); + } + if (*holder) + getJNIWebKitWebViewEvalCallbackHolderCache().onResult(holder->get(), static_cast(resultStr)); +} + +jlong JNIWebKitWebViewCache::nativeInit(JNIEnv* env, jobject jniWebView, jlong displayPtr, jlong contextPtr, + jlong toplevelPtr, jlong networkSessionPtr, jlong settingsPtr) +{ + auto* display = JNI::from_jlong(displayPtr); + auto* context = JNI::from_jlong(contextPtr); + auto* toplevel = JNI::from_jlong(toplevelPtr); + auto* networkSession = JNI::from_jlong(networkSessionPtr); + auto* settings = JNI::from_jlong(settingsPtr); + + g_assert(display); + g_assert(context); + if (!display || !context) + return 0; + + gboolean isAutomation = webkit_web_context_is_automation_allowed(context); + auto* webView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, "display", display, "web-context", context, + "network-session", networkSession, "settings", settings, "is-controlled-by-automation", isAutomation, nullptr)); + + auto* wpeView = webkit_web_view_get_wpe_view(webView); + if (toplevel) + wpe_view_set_toplevel(wpeView, toplevel); + + auto* bridge = new WebKitWebViewBridge(webView, env, jniWebView); + bridge->m_signalIds = { + g_signal_connect(webView, "close", G_CALLBACK(onClose), bridge), + g_signal_connect(webView, "decide-policy", G_CALLBACK(onDecidePolicy), bridge), + g_signal_connect(webView, "load-changed", G_CALLBACK(onLoadChanged), bridge), + g_signal_connect(webView, "notify::estimated-load-progress", G_CALLBACK(onEstimatedLoadProgress), bridge), + g_signal_connect(webView, "notify::title", G_CALLBACK(onTitleChanged), bridge), + g_signal_connect(webView, "notify::uri", G_CALLBACK(onUriChanged), bridge), + }; + + return reinterpret_cast(bridge); +} + +void JNIWebKitWebViewCache::nativeDestroy(JNIEnv*, jobject, jlong nativePtr) +{ + delete JNI::from_jlong(nativePtr); +} + +void JNIWebKitWebViewCache::nativeLoadUrl(JNIEnv*, jobject, jlong nativePtr, jstring url) +{ + webkit_web_view_load_uri( + JNI::from_jlong(nativePtr)->m_webView, JNI::String(url).getContent().get()); +} + +void JNIWebKitWebViewCache::nativeLoadHtml(JNIEnv*, jobject, jlong nativePtr, jstring content, jstring baseUri) +{ + webkit_web_view_load_html(JNI::from_jlong(nativePtr)->m_webView, + JNI::String(content).getContent().get(), JNI::String(baseUri).getContent().get()); +} + +void JNIWebKitWebViewCache::nativeStopLoading(JNIEnv*, jobject, jlong nativePtr) +{ + webkit_web_view_stop_loading(JNI::from_jlong(nativePtr)->m_webView); +} + +void JNIWebKitWebViewCache::nativeReload(JNIEnv*, jobject, jlong nativePtr) +{ + webkit_web_view_reload(JNI::from_jlong(nativePtr)->m_webView); +} + +void JNIWebKitWebViewCache::nativeGoBack(JNIEnv*, jobject, jlong nativePtr) +{ + webkit_web_view_go_back(JNI::from_jlong(nativePtr)->m_webView); +} + +void JNIWebKitWebViewCache::nativeGoForward(JNIEnv*, jobject, jlong nativePtr) +{ + webkit_web_view_go_forward(JNI::from_jlong(nativePtr)->m_webView); +} + +jlong JNIWebKitWebViewCache::nativeGetWebKitWebViewPtr(JNIEnv*, jobject, jlong nativePtr) +{ + return reinterpret_cast(JNI::from_jlong(nativePtr)->m_webView); +} + +jlong JNIWebKitWebViewCache::nativeGetWPEView(JNIEnv*, jobject, jlong nativePtr) +{ + return reinterpret_cast( + webkit_web_view_get_wpe_view(JNI::from_jlong(nativePtr)->m_webView)); +} + +void JNIWebKitWebViewCache::nativeSetZoomLevel(JNIEnv*, jobject, jlong nativePtr, jdouble zoomLevel) +{ + webkit_web_view_set_zoom_level(JNI::from_jlong(nativePtr)->m_webView, zoomLevel); +} + +void JNIWebKitWebViewCache::nativeEvaluateJavascript( + JNIEnv* env, jobject, jlong nativePtr, jstring script, JNIWebKitWebViewEvalCallbackHolder callbackHolder) +{ + auto* bridge = JNI::from_jlong(nativePtr); + auto* holder + = callbackHolder ? new JNI::GlobalRef(env, callbackHolder) : nullptr; + webkit_web_view_evaluate_javascript(bridge->m_webView, JNI::String(script).getContent().get(), -1, nullptr, nullptr, + nullptr, holder ? onEvalJavascriptReady : nullptr, holder); +} + +void configureWebKitWebViewJNIMappings() +{ + getJNIWebKitWebViewCache(); +} +void configureWebKitWebViewEvalCallbackHolderJNIMappings() +{ + getJNIWebKitWebViewEvalCallbackHolderCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/cpp/capi/WebKitWebsiteDataManager.cpp b/wpeview/src/main/cpp/capi/WebKitWebsiteDataManager.cpp new file mode 100644 index 000000000..6f866fef1 --- /dev/null +++ b/wpeview/src/main/cpp/capi/WebKitWebsiteDataManager.cpp @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "JNI/JNI.h" +#include "Logging.h" + +#include +#include + +DECLARE_JNI_CLASS_SIGNATURE(JNIWebKitWebsiteDataManager, "org/wpewebkit/wpe/WebKitWebsiteDataManager"); +DECLARE_JNI_CLASS_SIGNATURE( + JNIWebKitWebsiteDataManagerCallbackHolder, "org/wpewebkit/wpe/WebKitWebsiteDataManager$CallbackHolder"); + +namespace WebKit { + +class JNIWebKitWebsiteDataManagerCallbackHolderCache final + : public JNI::TypedClass { +public: + JNIWebKitWebsiteDataManagerCallbackHolderCache() + : JNI::TypedClass(true) + , m_commitResult(getMethod("commitResult")) + { + } + + void onResult(JNIWebKitWebsiteDataManagerCallbackHolder callbackHolder, jboolean result) const + { + try { + m_commitResult.invoke(callbackHolder, result); + } catch (const std::exception& ex) { + Logging::logError("cannot call WebKitWebsiteDataManager callback result (%s)", ex.what()); + } + } + +private: + const JNI::Method m_commitResult; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +const JNIWebKitWebsiteDataManagerCallbackHolderCache& getJNIWebKitWebsiteDataManagerCallbackHolderCache() +{ + static const JNIWebKitWebsiteDataManagerCallbackHolderCache s_singleton; + return s_singleton; +} + +class JNIWebKitWebsiteDataManagerCache final : public JNI::TypedClass { +public: + JNIWebKitWebsiteDataManagerCache() + : JNI::TypedClass(true) + { + registerNativeMethods(JNI::NativeMethod( + "nativeClear", JNIWebKitWebsiteDataManagerCache::nativeClear)); + } + +private: + static void onClearReady(GObject* object, GAsyncResult* result, gpointer userData) + { + std::unique_ptr> holder( + static_cast*>(userData)); + const gboolean clearResult + = webkit_website_data_manager_clear_finish(WEBKIT_WEBSITE_DATA_MANAGER(object), result, nullptr); + if (holder && *holder) { + getJNIWebKitWebsiteDataManagerCallbackHolderCache().onResult( + holder->get(), static_cast(clearResult)); + } + } + + static void nativeClear(JNIEnv* env, jobject, jlong nativePtr, jint typesToClear, + JNIWebKitWebsiteDataManagerCallbackHolder callbackHolder) + { + auto* holder = callbackHolder + ? new JNI::GlobalRef(env, callbackHolder) + : nullptr; + webkit_website_data_manager_clear(JNI::from_jlong(nativePtr), + static_cast(typesToClear), 0, nullptr, onClearReady, holder); + } +}; + +const JNIWebKitWebsiteDataManagerCache& getJNIWebKitWebsiteDataManagerCache() +{ + static const JNIWebKitWebsiteDataManagerCache s_singleton; + return s_singleton; +} + +void configureWebKitWebsiteDataManagerCallbackHolderJNIMappings() +{ + getJNIWebKitWebsiteDataManagerCallbackHolderCache(); +} + +void configureWebKitWebsiteDataManagerJNIMappings() +{ + getJNIWebKitWebsiteDataManagerCache(); +} +} // namespace WebKit diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/MainLooperDispatcher.java b/wpeview/src/main/java/org/wpewebkit/wpe/MainLooperDispatcher.java new file mode 100644 index 000000000..03cfd76d1 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/MainLooperDispatcher.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; + +final class MainLooperDispatcher { + private static final Handler sHandler = new Handler(Looper.getMainLooper()); + + private MainLooperDispatcher() {} + + static void post(@NonNull Runnable task) { + if (Looper.myLooper() == Looper.getMainLooper()) { + task.run(); + return; + } + sHandler.post(task); + } +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WPEDisplay.java b/wpeview/src/main/java/org/wpewebkit/wpe/WPEDisplay.java new file mode 100644 index 000000000..b161580bc --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WPEDisplay.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.NonNull; + +/** + * WPEDisplay is an owning JNI proxy for a native WPEDisplay object. + */ +public final class WPEDisplay { + private long mNativePtr = 0; + private WPEScreen screen; + public long getNativePtr() { return mNativePtr; } + + public WPEDisplay() { mNativePtr = nativeInit(); } + + public @NonNull WPEScreen getScreen() { + if (screen == null) { + screen = new WPEScreen(nativeGetScreen(mNativePtr)); + } + return screen; + } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + if (screen != null) { + screen.invalidate(); + screen = null; + } + } + + private native long nativeInit(); + private native void nativeDestroy(long nativePtr); + private native long nativeGetScreen(long nativePtr); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WPEScreen.java b/wpeview/src/main/java/org/wpewebkit/wpe/WPEScreen.java new file mode 100644 index 000000000..9491154d6 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WPEScreen.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +/** + * WPEScreen is a borrowed JNI proxy for the native WPEScreen object owned by WPEDisplay. + * This wrapper does not own the native pointer and must not outlive the parent WPEDisplay. + */ +public final class WPEScreen { + private long mNativePtr = 0; + public long getNativePtr() { return mNativePtr; } + + // This constructor wraps a borrowed pointer returned by WPEDisplay. + WPEScreen(long nativePtr) { this.mNativePtr = nativePtr; } + + public float getScale() { return nativeGetScale(mNativePtr); } + + public void setScale(float scale) { nativeSetScale(mNativePtr, scale); } + + void invalidate() { mNativePtr = 0; } + + private native float nativeGetScale(long nativePtr); + private native void nativeSetScale(long nativePtr, float scale); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WPEToplevel.java b/wpeview/src/main/java/org/wpewebkit/wpe/WPEToplevel.java new file mode 100644 index 000000000..f07a7fa59 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WPEToplevel.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import android.view.Surface; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * WPEToplevel is an owning JNI proxy for a native WPEToplevel object. + */ +public final class WPEToplevel { + private long mNativePtr = 0; + public long getNativePtr() { return mNativePtr; } + + public WPEToplevel(@NonNull WPEDisplay display, @Nullable Surface surface) { + mNativePtr = nativeInit(display.getNativePtr(), surface); + } + + public void setPhysicalSize(int width, int height) { nativeSetPhysicalSize(mNativePtr, width, height); } + + public void getSize(@NonNull int[] out) { nativeGetSize(mNativePtr, out); } + + public void onSurfaceCreated(@NonNull Surface surface) { setSurface(surface); } + + public void onSurfaceDestroyed() { setSurface(null); } + + public void setSurface(@Nullable Surface surface) { nativeSetSurface(mNativePtr, surface); } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + } + + private native long nativeInit(long displayPtr, Surface surface); + private native void nativeDestroy(long nativePtr); + private native void nativeSetPhysicalSize(long nativePtr, int width, int height); + private native void nativeGetSize(long nativePtr, int[] out); + private native void nativeSetSurface(long nativePtr, Surface surface); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WPEView.java b/wpeview/src/main/java/org/wpewebkit/wpe/WPEView.java new file mode 100644 index 000000000..3c9a6a3a7 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WPEView.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * WPEView is a borrowed JNI proxy for the native WPEView object owned by WebKitWebView. + * This wrapper does not own the native pointer and must not outlive the parent WebKitWebView. + */ +public final class WPEView { + private long mNativePtr = 0; + public long getNativePtr() { return mNativePtr; } + + // This constructor wraps a borrowed pointer returned by WebKitWebView. + WPEView(long nativePtr) { this.mNativePtr = nativePtr; } + + public void setToplevel(@Nullable WPEToplevel toplevel) { + nativeSetToplevel(mNativePtr, toplevel != null ? toplevel.getNativePtr() : 0); + } + + public void resized(int width, int height) { nativeResized(mNativePtr, width, height); } + + public void setMapped(boolean mapped) { nativeSetMapped(mNativePtr, mapped); } + + public void dispatchTouchEvent(long time, int type, int pointerCount, @NonNull int[] ids, @NonNull float[] xs, + @NonNull float[] ys) { + if (pointerCount <= 0 || ids.length < pointerCount || xs.length < pointerCount || ys.length < pointerCount) + return; + nativeDispatchTouchEvent(mNativePtr, time, type, pointerCount, ids, xs, ys); + } + + public void dispatchKeyEvent(long time, int type, int keyCode, int unicodeChar, int metaState) { + nativeDispatchKeyEvent(mNativePtr, time, type, keyCode, unicodeChar, metaState); + } + + void invalidate() { mNativePtr = 0; } + + private native void nativeSetToplevel(long nativePtr, long toplevelPtr); + private native void nativeResized(long nativePtr, int width, int height); + private native void nativeSetMapped(long nativePtr, boolean mapped); + private native void nativeDispatchTouchEvent(long nativePtr, long time, int type, int pointerCount, int[] ids, + float[] xs, float[] ys); + private native void nativeDispatchKeyEvent(long nativePtr, long time, int type, int keyCode, int unicodeChar, + int metaState); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WebKitCookieManager.java b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitCookieManager.java new file mode 100644 index 000000000..d0980efbe --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitCookieManager.java @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * WebKitCookieManager is an owning JNI proxy for a native WebKitCookieManager reference. + * Website-data operations are exposed through WebKitWebsiteDataManager, not this wrapper. + */ +public final class WebKitCookieManager { + public static final int WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS = 0; + public static final int WEBKIT_COOKIE_POLICY_ACCEPT_NEVER = 1; + public static final int WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY = 2; + + private long mNativePtr = 0; + + public long getNativePtr() { return mNativePtr; } + + public WebKitCookieManager(@NonNull WebKitNetworkSession networkSession) { + mNativePtr = nativeInit(networkSession.getNativePtr()); + } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + } + + @FunctionalInterface + public interface AcceptPolicyCallback { + void onResult(int policy); + } + + static final class AcceptPolicyCallbackHolder { + private final AcceptPolicyCallback callback; + + AcceptPolicyCallbackHolder(@Nullable AcceptPolicyCallback callback) { this.callback = callback; } + + @Keep + void commitResult(int policy) { + if (callback != null) + MainLooperDispatcher.post(() -> callback.onResult(policy)); + } + } + + public void getAcceptPolicy(@Nullable AcceptPolicyCallback callback) { + if (mNativePtr == 0) { + if (callback != null) + MainLooperDispatcher.post(() -> callback.onResult(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS)); + return; + } + nativeGetAcceptPolicy(mNativePtr, callback != null ? new AcceptPolicyCallbackHolder(callback) : null); + } + + public void setAcceptPolicy(int policy) { nativeSetAcceptPolicy(mNativePtr, policy); } + + private native long nativeInit(long sessionPtr); + private native void nativeDestroy(long nativePtr); + private native void nativeGetAcceptPolicy(long nativePtr, @Nullable AcceptPolicyCallbackHolder callbackHolder); + private native void nativeSetAcceptPolicy(long nativePtr, int policy); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WebKitNetworkSession.java b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitNetworkSession.java new file mode 100644 index 000000000..ba41a3210 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitNetworkSession.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * WebKitNetworkSession is an owning JNI proxy for a native WebKitNetworkSession instance. + */ +public final class WebKitNetworkSession { + private long mNativePtr = 0; + private WebKitWebsiteDataManager websiteDataManager; + public long getNativePtr() { return mNativePtr; } + + public WebKitNetworkSession(@Nullable WebKitWebContext webContext, boolean automationMode, boolean ephemeral, + @NonNull String dataDir, @NonNull String cacheDir) { + if (automationMode && webContext == null) + throw new IllegalArgumentException("automationMode requires a WebKitWebContext"); + + mNativePtr = nativeInit(webContext != null ? webContext.getWebKitWebContextPtr() : 0, automationMode, ephemeral, + dataDir, cacheDir); + } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + if (websiteDataManager != null) { + websiteDataManager.invalidate(); + websiteDataManager = null; + } + } + + public @NonNull WebKitWebsiteDataManager getWebsiteDataManager() { + if (websiteDataManager == null) { + websiteDataManager = new WebKitWebsiteDataManager(nativeWebsiteDataManager(mNativePtr)); + } + return websiteDataManager; + } + + public void setTLSErrorsPolicy(int policy) { nativeSetTLSErrorsPolicy(mNativePtr, policy); } + + private native long nativeInit(long contextPtr, boolean automationMode, boolean ephemeral, String dataDir, + String cacheDir); + private native void nativeDestroy(long nativePtr); + private native long nativeWebsiteDataManager(long nativePtr); + private native void nativeSetTLSErrorsPolicy(long nativePtr, int policy); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WebKitSettings.java b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitSettings.java new file mode 100644 index 000000000..7902714d4 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitSettings.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.NonNull; + +/** + * WebKitSettings is an owning JNI proxy for a native WebKitSettings instance. + */ +public final class WebKitSettings { + private long mNativePtr = 0; + public long getNativePtr() { return mNativePtr; } + + public WebKitSettings() { mNativePtr = nativeInit(); } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + } + + public @NonNull String getUserAgentString() { return nativeGetUserAgentString(mNativePtr); } + public void setUserAgentString(@NonNull String userAgent) { nativeSetUserAgentString(mNativePtr, userAgent); } + + public boolean getMediaPlaybackRequiresUserGesture() { + return nativeGetMediaPlaybackRequiresUserGesture(mNativePtr); + } + public void setMediaPlaybackRequiresUserGesture(boolean requires) { + nativeSetMediaPlaybackRequiresUserGesture(mNativePtr, requires); + } + + public boolean getAllowUniversalAccessFromFileURLs() { + return nativeGetAllowUniversalAccessFromFileURLs(mNativePtr); + } + public void setAllowUniversalAccessFromFileURLs(boolean flag) { + nativeSetAllowUniversalAccessFromFileURLs(mNativePtr, flag); + } + + public boolean getAllowFileAccessFromFileURLs() { return nativeGetAllowFileAccessFromFileURLs(mNativePtr); } + public void setAllowFileAccessFromFileURLs(boolean flag) { nativeSetAllowFileAccessFromFileURLs(mNativePtr, flag); } + + public boolean getDeveloperExtrasEnabled() { return nativeGetDeveloperExtrasEnabled(mNativePtr); } + public void setDeveloperExtrasEnabled(boolean flag) { nativeSetDeveloperExtrasEnabled(mNativePtr, flag); } + + public boolean getDisableWebSecurity() { return nativeGetDisableWebSecurity(mNativePtr); } + public void setDisableWebSecurity(boolean disable) { nativeSetDisableWebSecurity(mNativePtr, disable); } + + private native long nativeInit(); + private native void nativeDestroy(long nativePtr); + private native String nativeGetUserAgentString(long nativePtr); + private native void nativeSetUserAgentString(long nativePtr, String userAgent); + private native boolean nativeGetMediaPlaybackRequiresUserGesture(long nativePtr); + private native void nativeSetMediaPlaybackRequiresUserGesture(long nativePtr, boolean requires); + private native boolean nativeGetAllowUniversalAccessFromFileURLs(long nativePtr); + private native void nativeSetAllowUniversalAccessFromFileURLs(long nativePtr, boolean flag); + private native boolean nativeGetAllowFileAccessFromFileURLs(long nativePtr); + private native void nativeSetAllowFileAccessFromFileURLs(long nativePtr, boolean flag); + private native boolean nativeGetDeveloperExtrasEnabled(long nativePtr); + private native void nativeSetDeveloperExtrasEnabled(long nativePtr, boolean flag); + private native boolean nativeGetDisableWebSecurity(long nativePtr); + private native void nativeSetDisableWebSecurity(long nativePtr, boolean disable); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebContext.java b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebContext.java new file mode 100644 index 000000000..4a670ff79 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebContext.java @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import android.os.Looper; + +import androidx.annotation.Keep; +import androidx.annotation.Nullable; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; + +/** + * WebKitWebContext is an owning JNI proxy for a native WebKitWebContext instance. + */ +public final class WebKitWebContext { + public interface Client { + @Nullable + WebKitWebView createWebKitWebViewForAutomation(); + } + + private long mNativePtr = 0; + public long getNativePtr() { return mNativePtr; } + private @Nullable Client client; + + public WebKitWebContext(boolean automationMode) { mNativePtr = nativeInit(automationMode); } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + } + + public boolean isAutomationMode() { return nativeIsAutomationMode(mNativePtr); } + public void setClient(@Nullable Client client) { this.client = client; } + + @Keep + private long createWebKitWebViewForAutomation() { + Client currentClient = client; + if (currentClient == null) + return 0; + + if (Looper.myLooper() == Looper.getMainLooper()) { + WebKitWebView webView = currentClient.createWebKitWebViewForAutomation(); + return webView != null ? webView.getWebKitWebViewPtr() : 0; + } + + CountDownLatch latch = new CountDownLatch(1); + AtomicLong webViewPtr = new AtomicLong(0); + MainLooperDispatcher.post(() -> { + WebKitWebView webView = currentClient.createWebKitWebViewForAutomation(); + webViewPtr.set(webView != null ? webView.getWebKitWebViewPtr() : 0); + latch.countDown(); + }); + + try { + latch.await(); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + return 0; + } + + return webViewPtr.get(); + } + + public long getWebKitWebContextPtr() { return nativeGetWebKitWebContextPtr(mNativePtr); } + + private native long nativeInit(boolean automationMode); + private native void nativeDestroy(long nativePtr); + private native boolean nativeIsAutomationMode(long nativePtr); + private native long nativeGetWebKitWebContextPtr(long nativePtr); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebView.java b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebView.java new file mode 100644 index 000000000..e0eb1e652 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebView.java @@ -0,0 +1,202 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * WebKitWebView is an owning JNI proxy for a native WebKitWebView object. + */ +public final class WebKitWebView { + private long mNativePtr = 0; + private final WPEView wpeView; + private final WebKitSettings settings; + private final WebKitNetworkSession networkSession; + + public long getNativePtr() { return mNativePtr; } + + long getWebKitWebViewPtr() { return nativeGetWebKitWebViewPtr(mNativePtr); } + + public WebKitWebView(@NonNull WPEDisplay display, @NonNull WebKitWebContext context, @Nullable WPEToplevel toplevel, + @Nullable WebKitNetworkSession networkSession, @Nullable WebKitSettings settings) { + this.networkSession = networkSession; + this.settings = settings; + mNativePtr = nativeInit( + display.getNativePtr(), context.getWebKitWebContextPtr(), toplevel != null ? toplevel.getNativePtr() : 0, + networkSession != null ? networkSession.getNativePtr() : 0, settings != null ? settings.getNativePtr() : 0); + wpeView = new WPEView(nativeGetWPEView(mNativePtr)); + } + + public @NonNull WPEView getWPEView() { return wpeView; } + + public @Nullable WebKitSettings getSettings() { return settings; } + + public @Nullable WebKitNetworkSession getNetworkSession() { return networkSession; } + + public void loadUrl(@NonNull String url) { + if (mNativePtr != 0) { + nativeLoadUrl(mNativePtr, url); + } + } + + public void loadHtml(@NonNull String content, @NonNull String baseUri) { + if (mNativePtr != 0) { + nativeLoadHtml(mNativePtr, content, baseUri); + } + } + + public void stopLoading() { + if (mNativePtr != 0) { + nativeStopLoading(mNativePtr); + } + } + + public void reload() { + if (mNativePtr != 0) { + nativeReload(mNativePtr); + } + } + + public void goBack() { + if (mNativePtr != 0) { + nativeGoBack(mNativePtr); + } + } + + public void goForward() { + if (mNativePtr != 0) { + nativeGoForward(mNativePtr); + } + } + + public void setZoomLevel(double zoomLevel) { nativeSetZoomLevel(mNativePtr, zoomLevel); } + + public void evaluateJavascript(@NonNull String script, @Nullable EvalCallback callback) { + if (mNativePtr == 0) { + if (callback != null) { + callback.onResult("null"); + } + return; + } + nativeEvaluateJavascript(mNativePtr, script, callback != null ? new EvalCallbackHolder(callback) : null); + } + + public void destroy() { + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + wpeView.invalidate(); + } + + public interface EvalCallback { + void onResult(@NonNull String result); + } + + public interface Listener { + void onClose(); + void onPageStarted(@NonNull String url); + void onPageFinished(@NonNull String url); + void onURLChanged(@NonNull String uri); + void onLoadProgress(double progress); + void onTitleChanged(@NonNull String title, boolean canGoBack, boolean canGoForward); + void onReceivedHttpError(@NonNull String uri, @NonNull String method, @NonNull String mimeType, int statusCode); + } + + private @Nullable Listener listener; + + public void setListener(@Nullable Listener listener) { this.listener = listener; } + + @Keep + private void onClose() { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(currentListener::onClose); + } + + @Keep + private void onLoadStarted(String url) { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(() -> currentListener.onPageStarted(url)); + } + + @Keep + private void onLoadFinished(String url) { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(() -> currentListener.onPageFinished(url)); + } + + @Keep + private void onUriChanged(String uri) { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(() -> currentListener.onURLChanged(uri)); + } + + @Keep + private void onEstimatedLoadProgress(double progress) { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(() -> currentListener.onLoadProgress(progress)); + } + + @Keep + private void onTitleChanged(String title, boolean canGoBack, boolean canGoForward) { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(() -> currentListener.onTitleChanged(title, canGoBack, canGoForward)); + } + + @Keep + private void onReceivedHttpError(String uri, String method, String mimeType, int statusCode) { + Listener currentListener = listener; + if (currentListener != null) + MainLooperDispatcher.post(() -> currentListener.onReceivedHttpError(uri, method, mimeType, statusCode)); + } + + private static class EvalCallbackHolder { + private final EvalCallback callback; + + EvalCallbackHolder(EvalCallback callback) { this.callback = callback; } + + @Keep + public void commitResult(String result) { + MainLooperDispatcher.post(() -> callback.onResult(result)); + } + } + + private native long nativeInit(long displayPtr, long contextPtr, long toplevelPtr, long networkSessionPtr, + long settingsPtr); + private native void nativeDestroy(long nativePtr); + private native void nativeLoadUrl(long nativePtr, String url); + private native void nativeLoadHtml(long nativePtr, String content, String baseUri); + private native void nativeStopLoading(long nativePtr); + private native void nativeReload(long nativePtr); + private native void nativeGoBack(long nativePtr); + private native void nativeGoForward(long nativePtr); + private native long nativeGetWPEView(long nativePtr); + private native long nativeGetWebKitWebViewPtr(long nativePtr); + private native void nativeSetZoomLevel(long nativePtr, double zoomLevel); + private native void nativeEvaluateJavascript(long nativePtr, String script, + @Nullable EvalCallbackHolder callbackHolder); +} diff --git a/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebsiteDataManager.java b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebsiteDataManager.java new file mode 100644 index 000000000..f10ef13e6 --- /dev/null +++ b/wpeview/src/main/java/org/wpewebkit/wpe/WebKitWebsiteDataManager.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2026 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package org.wpewebkit.wpe; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.EnumSet; + +public final class WebKitWebsiteDataManager { + public enum WebsiteDataType { + MemoryCache(1 << 0), + DiskCache(1 << 1), + OfflineApplicationCache(1 << 2), + SessionStorage(1 << 3), + LocalStorage(1 << 4), + IndexedDBDatabases(1 << 5), + Cookies(1 << 6), + DeviceIDHashSalt(1 << 7), + HSTSCache(1 << 8), + ITP(1 << 9), + ServiceWorkerRegistrations(1 << 10), + DOMCache(1 << 11), + All(MemoryCache.value | DiskCache.value | OfflineApplicationCache.value | SessionStorage.value | + LocalStorage.value | IndexedDBDatabases.value | Cookies.value | DeviceIDHashSalt.value | HSTSCache.value | + ITP.value | ServiceWorkerRegistrations.value | DOMCache.value); + + private final int value; + + WebsiteDataType(int value) { this.value = value; } + + int getValue() { return value; } + } + + private long mNativePtr = 0; + public long getNativePtr() { return mNativePtr; } + + WebKitWebsiteDataManager(long nativePtr) { this.mNativePtr = nativePtr; } + + public void clear(@NonNull EnumSet websiteDataTypes, @Nullable Callback callback) { + int flags = 0; + for (WebsiteDataType type : websiteDataTypes) { + flags |= type.getValue(); + } + CallbackHolder callbackHolder = callback != null ? new CallbackHolder(callback) : null; + if (flags == 0) { + if (callbackHolder != null) + callbackHolder.commitResult(true); + return; + } + if (mNativePtr == 0) { + if (callbackHolder != null) + callbackHolder.commitResult(false); + return; + } + nativeClear(mNativePtr, flags, callbackHolder); + } + + @FunctionalInterface + public interface Callback { + void onResult(boolean result); + } + + static final class CallbackHolder { + private final Callback callback; + + CallbackHolder(@Nullable Callback callback) { this.callback = callback; } + + @Keep + void commitResult(boolean result) { + if (callback != null) + MainLooperDispatcher.post(() -> callback.onResult(result)); + } + } + + void invalidate() { mNativePtr = 0; } + + private native void nativeClear(long nativePtr, int typesToClear, @Nullable CallbackHolder callbackHolder); +}