Ladybird/Android: Move JNI functions into their own files

This should be easier to work on, and keeps the layers of the native
code nice and clean cut.
This commit is contained in:
Andrew Kaster 2023-09-15 12:35:20 -06:00 committed by Andrew Kaster
parent 274fd88242
commit 315ad2d391
Notes: sideshowbarker 2024-07-17 21:16:31 +09:00
8 changed files with 256 additions and 210 deletions

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebContentService.h"
#include <AK/LexicalPath.h>
#include <Ladybird/FontPlugin.h>
#include <Ladybird/HelperProcess.h>
@ -26,8 +27,6 @@
#include <LibWebView/WebSocketClientAdapter.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageHost.h>
#include <jni.h>
#include <unistd.h>
class NullResourceConnector : public Web::ResourceLoaderConnector {
virtual void prefetch_dns(AK::URL const&) override { }
@ -72,33 +71,3 @@ ErrorOr<int> 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<bool> 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);
}

View file

@ -0,0 +1,11 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
ErrorOr<int> web_content_main(int ipc_socket, int fd_passing_socket);

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebContentService.h"
#include <AK/Atomic.h>
#include <AK/Format.h>
#include <Ladybird/Utilities.h>
#include <jni.h>
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<bool> 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);
}

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebViewImplementationNative.h"
#include "JNIHelpers.h"
#include <Userland/Libraries/LibGfx/Bitmap.h>
#include <Userland/Libraries/LibGfx/Painter.h>
@ -12,9 +13,8 @@
#include <android/bitmap.h>
#include <jni.h>
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<WebView::WebContentClient> 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<WebView::WebContentClient> 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<WebView::WebContentClient> 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<jclass>(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<jlong>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(instance);
impl->set_device_pixel_ratio(ratio);
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Userland/Libraries/LibWebView/ViewImplementation.h>
#include <android/bitmap.h>
#include <jni.h>
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<WebView::WebContentClient> 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;
};
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebViewImplementationNative.h"
#include <jni.h>
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<jclass>(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<jlong>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(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<WebViewImplementationNative*>(instance);
impl->set_device_pixel_ratio(ratio);
}

View file

@ -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

View file

@ -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)