diff --git a/Ladybird/Android/src/main/cpp/WebContentService.cpp b/Ladybird/Android/src/main/cpp/WebContentService.cpp index ae2e9b27786..33da1060833 100644 --- a/Ladybird/Android/src/main/cpp/WebContentService.cpp +++ b/Ladybird/Android/src/main/cpp/WebContentService.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "WebContentService.h" #include #include #include @@ -26,8 +27,6 @@ #include #include #include -#include -#include class NullResourceConnector : public Web::ResourceLoaderConnector { virtual void prefetch_dns(AK::URL const&) override { } @@ -72,33 +71,3 @@ ErrorOr web_content_main(int ipc_socket, int fd_passing_socket) return event_loop.exec(); } - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebContentService_nativeThreadLoop(JNIEnv*, jobject /* thiz */, jint ipc_socket, jint fd_passing_socket) -{ - dbgln("New binding received, sockets {} and {}", ipc_socket, fd_passing_socket); - auto ret = web_content_main(ipc_socket, fd_passing_socket); - if (ret.is_error()) { - warnln("Runtime Error: {}", ret.release_error()); - } else { - outln("Thread exited with code {}", ret.release_value()); - } -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebContentService_initNativeCode(JNIEnv* env, jobject /* thiz */, jstring resource_dir, jstring tag_name) -{ - static Atomic s_initialized_flag { false }; - if (s_initialized_flag.exchange(true) == true) { - // Skip initializing if someone else already started the process at some point in the past - return; - } - - char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr); - s_serenity_resource_root = raw_resource_dir; - env->ReleaseStringUTFChars(resource_dir, raw_resource_dir); - - char const* raw_tag_name = env->GetStringUTFChars(tag_name, nullptr); - AK::set_log_tag_name(raw_tag_name); - env->ReleaseStringUTFChars(tag_name, raw_tag_name); -} diff --git a/Ladybird/Android/src/main/cpp/WebContentService.h b/Ladybird/Android/src/main/cpp/WebContentService.h new file mode 100644 index 00000000000..ebf22046def --- /dev/null +++ b/Ladybird/Android/src/main/cpp/WebContentService.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +ErrorOr web_content_main(int ipc_socket, int fd_passing_socket); diff --git a/Ladybird/Android/src/main/cpp/WebContentServiceJNI.cpp b/Ladybird/Android/src/main/cpp/WebContentServiceJNI.cpp new file mode 100644 index 00000000000..73d4e2591e3 --- /dev/null +++ b/Ladybird/Android/src/main/cpp/WebContentServiceJNI.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "WebContentService.h" +#include +#include +#include +#include + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebContentService_nativeThreadLoop(JNIEnv*, jobject /* thiz */, jint ipc_socket, jint fd_passing_socket) +{ + dbgln("New binding received, sockets {} and {}", ipc_socket, fd_passing_socket); + auto ret = web_content_main(ipc_socket, fd_passing_socket); + if (ret.is_error()) { + warnln("Runtime Error: {}", ret.release_error()); + } else { + outln("Thread exited with code {}", ret.release_value()); + } +} + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebContentService_initNativeCode(JNIEnv* env, jobject /* thiz */, jstring resource_dir, jstring tag_name) +{ + static Atomic s_initialized_flag { false }; + if (s_initialized_flag.exchange(true) == true) { + // Skip initializing if someone else already started the process at some point in the past + return; + } + + char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr); + s_serenity_resource_root = raw_resource_dir; + env->ReleaseStringUTFChars(resource_dir, raw_resource_dir); + + char const* raw_tag_name = env->GetStringUTFChars(tag_name, nullptr); + AK::set_log_tag_name(raw_tag_name); + env->ReleaseStringUTFChars(tag_name, raw_tag_name); +} diff --git a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp index 5d1e6fc3401..90764a61533 100644 --- a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp +++ b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "WebViewImplementationNative.h" #include "JNIHelpers.h" #include #include @@ -12,9 +13,8 @@ #include #include -namespace { - -Gfx::BitmapFormat to_gfx_bitmap_format(i32 f) +namespace Ladybird { +static Gfx::BitmapFormat to_gfx_bitmap_format(i32 f) { switch (f) { case ANDROID_BITMAP_FORMAT_RGBA_8888: @@ -24,109 +24,82 @@ Gfx::BitmapFormat to_gfx_bitmap_format(i32 f) } } -class WebViewImplementationNative : public WebView::ViewImplementation { -public: - WebViewImplementationNative(jobject thiz) - : m_java_instance(thiz) - { - // NOTE: m_java_instance's global ref is controlled by the JNI bindings - create_client(WebView::EnableCallgrindProfiling::No); +WebViewImplementationNative::WebViewImplementationNative(jobject thiz) + : m_java_instance(thiz) +{ + // NOTE: m_java_instance's global ref is controlled by the JNI bindings + create_client(WebView::EnableCallgrindProfiling::No); - on_ready_to_paint = [this]() { - JavaEnvironment env(global_vm); - env.get()->CallVoidMethod(m_java_instance, invalidate_layout_method); - }; - } + on_ready_to_paint = [this]() { + JavaEnvironment env(global_vm); + env.get()->CallVoidMethod(m_java_instance, invalidate_layout_method); + }; +} - virtual Gfx::IntRect viewport_rect() const override { return m_viewport_rect; } - virtual Gfx::IntPoint to_content_position(Gfx::IntPoint p) const override { return p; } - virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint p) const override { return p; } - virtual void update_zoom() override { } +void WebViewImplementationNative::create_client(WebView::EnableCallgrindProfiling) +{ + m_client_state = {}; - NonnullRefPtr bind_web_content_client(); + auto new_client = bind_web_content_client(); - virtual void create_client(WebView::EnableCallgrindProfiling) override - { - m_client_state = {}; + m_client_state.client = new_client; + m_client_state.client->on_web_content_process_crash = [] { + warnln("WebContent crashed!"); + // FIXME: launch a new client + }; - auto new_client = bind_web_content_client(); + m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid()); + client().async_set_window_handle(m_client_state.client_handle); - m_client_state.client = new_client; - m_client_state.client->on_web_content_process_crash = [] { - warnln("WebContent crashed!"); - // FIXME: launch a new client - }; + client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio); - m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid()); - client().async_set_window_handle(m_client_state.client_handle); + // FIXME: update_palette, update system fonts +} - client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio); +void WebViewImplementationNative::paint_into_bitmap(void* android_bitmap_raw, AndroidBitmapInfo const& info) +{ + // Software bitmaps only for now! + VERIFY((info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE) == 0); - // FIXME: update_palette, update system fonts - } + auto android_bitmap = MUST(Gfx::Bitmap::create_wrapper(to_gfx_bitmap_format(info.format), { info.width, info.height }, 1, info.stride, android_bitmap_raw)); + Gfx::Painter painter(android_bitmap); + if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr()) + painter.blit({ 0, 0 }, *bitmap, bitmap->rect()); + else + painter.clear_rect(painter.clip_rect(), Gfx::Color::Magenta); - void paint_into_bitmap(void* android_bitmap_raw, AndroidBitmapInfo const& info) - { - // Software bitmaps only for now! - VERIFY((info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE) == 0); - - auto android_bitmap = MUST(Gfx::Bitmap::create_wrapper(to_gfx_bitmap_format(info.format), { info.width, info.height }, 1, info.stride, android_bitmap_raw)); - Gfx::Painter painter(android_bitmap); - if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr()) - painter.blit({ 0, 0 }, *bitmap, bitmap->rect()); - else - painter.clear_rect(painter.clip_rect(), Gfx::Color::Magenta); - - // Convert our internal BGRA into RGBA. This will be slowwwwwww - // FIXME: Don't do a color format swap here. - for (auto y = 0; y < android_bitmap->height(); ++y) { - auto* scanline = android_bitmap->scanline(y); - for (auto x = 0; x < android_bitmap->width(); ++x) { - auto current_pixel = scanline[x]; - u32 alpha = (current_pixel & 0xFF000000U) >> 24; - u32 red = (current_pixel & 0x00FF0000U) >> 16; - u32 green = (current_pixel & 0x0000FF00U) >> 8; - u32 blue = (current_pixel & 0x000000FFU); - scanline[x] = (alpha << 24U) | (blue << 16U) | (green << 8U) | red; - } + // Convert our internal BGRA into RGBA. This will be slowwwwwww + // FIXME: Don't do a color format swap here. + for (auto y = 0; y < android_bitmap->height(); ++y) { + auto* scanline = android_bitmap->scanline(y); + for (auto x = 0; x < android_bitmap->width(); ++x) { + auto current_pixel = scanline[x]; + u32 alpha = (current_pixel & 0xFF000000U) >> 24; + u32 red = (current_pixel & 0x00FF0000U) >> 16; + u32 green = (current_pixel & 0x0000FF00U) >> 8; + u32 blue = (current_pixel & 0x000000FFU); + scanline[x] = (alpha << 24U) | (blue << 16U) | (green << 8U) | red; } } +} - void set_viewport_geometry(int w, int h) - { - m_viewport_rect = { { 0, 0 }, { w, h } }; - client().async_set_viewport_rect(m_viewport_rect); - request_repaint(); - handle_resize(); - } +void WebViewImplementationNative::set_viewport_geometry(int w, int h) +{ + m_viewport_rect = { { 0, 0 }, { w, h } }; + client().async_set_viewport_rect(m_viewport_rect); + request_repaint(); + handle_resize(); +} - void set_device_pixel_ratio(float f) - { - m_device_pixel_ratio = f; - client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio); - } - - static jclass global_class_reference; - static jfieldID instance_pointer_field; - static jmethodID bind_webcontent_method; - static jmethodID invalidate_layout_method; - static JavaVM* global_vm; - - jobject java_instance() const { return m_java_instance; } - -private: - jobject m_java_instance = nullptr; - Gfx::IntRect m_viewport_rect; -}; -jclass WebViewImplementationNative::global_class_reference; -jfieldID WebViewImplementationNative::instance_pointer_field; -jmethodID WebViewImplementationNative::bind_webcontent_method; -jmethodID WebViewImplementationNative::invalidate_layout_method; -JavaVM* WebViewImplementationNative::global_vm; +void WebViewImplementationNative::set_device_pixel_ratio(float f) +{ + m_device_pixel_ratio = f; + client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio); +} NonnullRefPtr WebViewImplementationNative::bind_web_content_client() { - JavaEnvironment env(WebViewImplementationNative::global_vm); + JavaEnvironment env(global_vm); int socket_fds[2] {}; MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds)); @@ -151,89 +124,4 @@ NonnullRefPtr WebViewImplementationNative::bind_web_c return new_client; } - -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */) -{ - auto ret = env->GetJavaVM(&WebViewImplementationNative::global_vm); - if (ret != 0) - TODO(); - - auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementation"); - if (!local_class) - TODO(); - WebViewImplementationNative::global_class_reference = reinterpret_cast(env->NewGlobalRef(local_class)); - env->DeleteLocalRef(local_class); - - auto field = env->GetFieldID(WebViewImplementationNative::global_class_reference, "nativeInstance", "J"); - if (!field) - TODO(); - WebViewImplementationNative::instance_pointer_field = field; - - auto method = env->GetMethodID(WebViewImplementationNative::global_class_reference, "bindWebContentService", "(II)V"); - if (!method) - TODO(); - WebViewImplementationNative::bind_webcontent_method = method; - - method = env->GetMethodID(WebViewImplementationNative::global_class_reference, "invalidateLayout", "()V"); - if (!method) - TODO(); - WebViewImplementationNative::invalidate_layout_method = method; -} - -extern "C" JNIEXPORT jlong JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv* env, jobject thiz) -{ - auto ref = env->NewGlobalRef(thiz); - auto instance = reinterpret_cast(new WebViewImplementationNative(ref)); - return instance; -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv* env, jobject /* thiz */, jlong instance) -{ - auto* impl = reinterpret_cast(instance); - env->DeleteGlobalRef(impl->java_instance()); - delete impl; -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_nativeDrawIntoBitmap(JNIEnv* env, jobject /* thiz */, jlong instance, jobject bitmap) -{ - auto* impl = reinterpret_cast(instance); - - AndroidBitmapInfo bitmap_info = {}; - void* pixels = nullptr; - AndroidBitmap_getInfo(env, bitmap, &bitmap_info); - AndroidBitmap_lockPixels(env, bitmap, &pixels); - if (pixels) - impl->paint_into_bitmap(pixels, bitmap_info); - - AndroidBitmap_unlockPixels(env, bitmap); -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_nativeSetViewportGeometry(JNIEnv*, jobject /* thiz */, jlong instance, jint w, jint h) -{ - auto* impl = reinterpret_cast(instance); - impl->set_viewport_geometry(w, h); -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_nativeLoadURL(JNIEnv* env, jobject /* thiz */, jlong instance, jstring url) -{ - auto* impl = reinterpret_cast(instance); - char const* raw_url = env->GetStringUTFChars(url, nullptr); - auto ak_url = AK::URL::create_with_url_or_path(StringView { raw_url, strlen(raw_url) }); - env->ReleaseStringUTFChars(url, raw_url); - impl->load(ak_url); -} - -extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementation_nativeSetDevicePixelRatio(JNIEnv*, jobject /* thiz */, jlong instance, jfloat ratio) -{ - auto* impl = reinterpret_cast(instance); - impl->set_device_pixel_ratio(ratio); } diff --git a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.h b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.h new file mode 100644 index 00000000000..364b70c1e5e --- /dev/null +++ b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Ladybird { +class WebViewImplementationNative : public WebView::ViewImplementation { +public: + WebViewImplementationNative(jobject thiz); + + virtual Gfx::IntRect viewport_rect() const override { return m_viewport_rect; } + virtual Gfx::IntPoint to_content_position(Gfx::IntPoint p) const override { return p; } + virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint p) const override { return p; } + virtual void update_zoom() override { } + + NonnullRefPtr bind_web_content_client(); + + virtual void create_client(WebView::EnableCallgrindProfiling) override; + + void paint_into_bitmap(void* android_bitmap_raw, AndroidBitmapInfo const& info); + + void set_viewport_geometry(int w, int h); + void set_device_pixel_ratio(float f); + + static jclass global_class_reference; + static jmethodID bind_webcontent_method; + static jmethodID invalidate_layout_method; + static JavaVM* global_vm; + + jobject java_instance() const { return m_java_instance; } + +private: + jobject m_java_instance = nullptr; + Gfx::IntRect m_viewport_rect; +}; +} diff --git a/Ladybird/Android/src/main/cpp/WebViewImplementationNativeJNI.cpp b/Ladybird/Android/src/main/cpp/WebViewImplementationNativeJNI.cpp new file mode 100644 index 00000000000..4dbb7a77c5d --- /dev/null +++ b/Ladybird/Android/src/main/cpp/WebViewImplementationNativeJNI.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "WebViewImplementationNative.h" +#include + +using namespace Ladybird; + +jclass WebViewImplementationNative::global_class_reference; +jmethodID WebViewImplementationNative::bind_webcontent_method; +jmethodID WebViewImplementationNative::invalidate_layout_method; +JavaVM* WebViewImplementationNative::global_vm; + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */) +{ + auto ret = env->GetJavaVM(&WebViewImplementationNative::global_vm); + if (ret != 0) + TODO(); + + auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementation"); + if (!local_class) + TODO(); + WebViewImplementationNative::global_class_reference = reinterpret_cast(env->NewGlobalRef(local_class)); + env->DeleteLocalRef(local_class); + + auto method = env->GetMethodID(WebViewImplementationNative::global_class_reference, "bindWebContentService", "(II)V"); + if (!method) + TODO(); + WebViewImplementationNative::bind_webcontent_method = method; + + method = env->GetMethodID(WebViewImplementationNative::global_class_reference, "invalidateLayout", "()V"); + if (!method) + TODO(); + WebViewImplementationNative::invalidate_layout_method = method; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv* env, jobject thiz) +{ + auto ref = env->NewGlobalRef(thiz); + auto instance = reinterpret_cast(new WebViewImplementationNative(ref)); + return instance; +} + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv* env, jobject /* thiz */, jlong instance) +{ + auto* impl = reinterpret_cast(instance); + env->DeleteGlobalRef(impl->java_instance()); + delete impl; +} + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_nativeDrawIntoBitmap(JNIEnv* env, jobject /* thiz */, jlong instance, jobject bitmap) +{ + auto* impl = reinterpret_cast(instance); + + AndroidBitmapInfo bitmap_info = {}; + void* pixels = nullptr; + AndroidBitmap_getInfo(env, bitmap, &bitmap_info); + AndroidBitmap_lockPixels(env, bitmap, &pixels); + if (pixels) + impl->paint_into_bitmap(pixels, bitmap_info); + + AndroidBitmap_unlockPixels(env, bitmap); +} + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_nativeSetViewportGeometry(JNIEnv*, jobject /* thiz */, jlong instance, jint w, jint h) +{ + auto* impl = reinterpret_cast(instance); + impl->set_viewport_geometry(w, h); +} + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_nativeLoadURL(JNIEnv* env, jobject /* thiz */, jlong instance, jstring url) +{ + auto* impl = reinterpret_cast(instance); + char const* raw_url = env->GetStringUTFChars(url, nullptr); + auto ak_url = AK::URL::create_with_url_or_path(StringView { raw_url, strlen(raw_url) }); + env->ReleaseStringUTFChars(url, raw_url); + impl->load(ak_url); +} + +extern "C" JNIEXPORT void JNICALL +Java_org_serenityos_ladybird_WebViewImplementation_nativeSetDevicePixelRatio(JNIEnv*, jobject /* thiz */, jlong instance, jfloat ratio) +{ + auto* impl = reinterpret_cast(instance); + impl->set_device_pixel_ratio(ratio); +} diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 31ddad12973..d5e406759c6 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -155,10 +155,11 @@ elseif(ANDROID) ${SOURCES} Android/src/main/cpp/LadybirdActivity.cpp Android/src/main/cpp/WebViewImplementationNative.cpp + Android/src/main/cpp/WebViewImplementationNativeJNI.cpp Android/src/main/cpp/ALooperEventLoopImplementation.cpp Android/src/main/cpp/TimerExecutorService.cpp ) - target_link_libraries(ladybird PRIVATE LibArchive log jnigraphics android) + target_link_libraries(ladybird PRIVATE LibArchive jnigraphics android) else() # TODO: Check for other GUI frameworks here when we move them in-tree # For now, we can export a static library of common files for chromes to link to diff --git a/Ladybird/WebContent/CMakeLists.txt b/Ladybird/WebContent/CMakeLists.txt index d27a78c444e..40d20a8cf42 100644 --- a/Ladybird/WebContent/CMakeLists.txt +++ b/Ladybird/WebContent/CMakeLists.txt @@ -55,10 +55,9 @@ else() if (ANDROID) target_sources(webcontent PRIVATE ../Android/src/main/cpp/WebContentService.cpp - ../Android/src/main/cpp/ALooperEventLoopImplementation.cpp - ../Android/src/main/cpp/TimerExecutorService.cpp + ../Android/src/main/cpp/WebContentServiceJNI.cpp ) - target_link_libraries(webcontent PRIVATE log android) + target_link_libraries(webcontent PRIVATE android) endif() add_executable(WebContent main.cpp)