2023-09-02 12:30:21 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2023-09-06 19:42:16 -03:00
|
|
|
#include <Userland/Libraries/LibGfx/Bitmap.h>
|
|
|
|
|
#include <Userland/Libraries/LibGfx/Painter.h>
|
|
|
|
|
#include <Userland/Libraries/LibWeb/Crypto/Crypto.h>
|
|
|
|
|
#include <Userland/Libraries/LibWebView/ViewImplementation.h>
|
2023-09-03 10:19:41 -03:00
|
|
|
#include <android/bitmap.h>
|
2023-09-02 12:30:21 -03:00
|
|
|
#include <android/log.h>
|
|
|
|
|
#include <jni.h>
|
|
|
|
|
|
|
|
|
|
namespace {
|
2023-09-03 10:19:41 -03:00
|
|
|
|
|
|
|
|
Gfx::BitmapFormat to_gfx_bitmap_format(i32 f)
|
|
|
|
|
{
|
|
|
|
|
switch (f) {
|
|
|
|
|
case ANDROID_BITMAP_FORMAT_RGBA_8888:
|
|
|
|
|
return Gfx::BitmapFormat::BGRA8888;
|
|
|
|
|
default:
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-06 19:42:16 -03:00
|
|
|
class JavaEnvironment {
|
|
|
|
|
public:
|
|
|
|
|
JavaEnvironment(JavaVM* vm)
|
|
|
|
|
: m_vm(vm)
|
|
|
|
|
{
|
|
|
|
|
auto ret = m_vm->GetEnv(reinterpret_cast<void**>(&m_env), JNI_VERSION_1_6);
|
|
|
|
|
if (ret == JNI_EDETACHED) {
|
|
|
|
|
ret = m_vm->AttachCurrentThread(&m_env, nullptr);
|
|
|
|
|
VERIFY(ret == JNI_OK);
|
|
|
|
|
m_did_attach_thread = true;
|
|
|
|
|
} else if (ret == JNI_EVERSION) {
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
} else {
|
|
|
|
|
VERIFY(ret == JNI_OK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VERIFY(m_env != nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~JavaEnvironment()
|
|
|
|
|
{
|
|
|
|
|
if (m_did_attach_thread)
|
|
|
|
|
m_vm->DetachCurrentThread();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JNIEnv* get() const { return m_env; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
JavaVM* m_vm = nullptr;
|
|
|
|
|
JNIEnv* m_env = nullptr;
|
|
|
|
|
bool m_did_attach_thread = false;
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-02 12:30:21 -03:00
|
|
|
class WebViewImplementationNative : public WebView::ViewImplementation {
|
|
|
|
|
public:
|
2023-09-06 19:42:16 -03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-03 10:19:41 -03:00
|
|
|
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; }
|
2023-09-02 12:30:21 -03:00
|
|
|
virtual void update_zoom() override { }
|
|
|
|
|
|
2023-09-06 19:42:16 -03:00
|
|
|
NonnullRefPtr<WebView::WebContentClient> bind_web_content_client();
|
|
|
|
|
|
|
|
|
|
virtual void create_client(WebView::EnableCallgrindProfiling) override
|
|
|
|
|
{
|
|
|
|
|
m_client_state = {};
|
|
|
|
|
|
|
|
|
|
auto new_client = bind_web_content_client();
|
|
|
|
|
|
|
|
|
|
m_client_state.client = new_client;
|
|
|
|
|
m_client_state.client->on_web_content_process_crash = [] {
|
|
|
|
|
__android_log_print(ANDROID_LOG_ERROR, "Ladybird", "WebContent crashed!");
|
|
|
|
|
// FIXME: launch a new client
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid());
|
|
|
|
|
client().async_set_window_handle(m_client_state.client_handle);
|
|
|
|
|
|
|
|
|
|
client().async_set_device_pixels_per_css_pixel(m_device_pixel_ratio);
|
|
|
|
|
|
|
|
|
|
// FIXME: update_palette, update system fonts
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-03 10:19:41 -03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_viewport_geometry(int w, int h)
|
|
|
|
|
{
|
|
|
|
|
m_viewport_rect = { { 0, 0 }, { w, h } };
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-02 12:30:21 -03:00
|
|
|
static jclass global_class_reference;
|
|
|
|
|
static jfieldID instance_pointer_field;
|
2023-09-06 19:42:16 -03:00
|
|
|
static jmethodID bind_webcontent_method;
|
|
|
|
|
static JavaVM* global_vm;
|
|
|
|
|
|
|
|
|
|
jobject java_instance() const { return m_java_instance; }
|
2023-09-03 10:19:41 -03:00
|
|
|
|
|
|
|
|
private:
|
2023-09-06 19:42:16 -03:00
|
|
|
jobject m_java_instance = nullptr;
|
2023-09-03 10:19:41 -03:00
|
|
|
Gfx::IntRect m_viewport_rect;
|
2023-09-02 12:30:21 -03:00
|
|
|
};
|
|
|
|
|
jclass WebViewImplementationNative::global_class_reference;
|
|
|
|
|
jfieldID WebViewImplementationNative::instance_pointer_field;
|
2023-09-06 19:42:16 -03:00
|
|
|
jmethodID WebViewImplementationNative::bind_webcontent_method;
|
|
|
|
|
JavaVM* WebViewImplementationNative::global_vm;
|
|
|
|
|
|
|
|
|
|
NonnullRefPtr<WebView::WebContentClient> WebViewImplementationNative::bind_web_content_client()
|
|
|
|
|
{
|
|
|
|
|
JavaEnvironment env(WebViewImplementationNative::global_vm);
|
|
|
|
|
|
|
|
|
|
int socket_fds[2] {};
|
|
|
|
|
MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds));
|
|
|
|
|
|
|
|
|
|
int ui_fd = socket_fds[0];
|
|
|
|
|
int wc_fd = socket_fds[1];
|
|
|
|
|
|
|
|
|
|
int fd_passing_socket_fds[2] {};
|
|
|
|
|
MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fd_passing_socket_fds));
|
|
|
|
|
|
|
|
|
|
int ui_fd_passing_fd = fd_passing_socket_fds[0];
|
|
|
|
|
int wc_fd_passing_fd = fd_passing_socket_fds[1];
|
|
|
|
|
|
|
|
|
|
// NOTE: The java object takes ownership of the socket fds
|
|
|
|
|
env.get()->CallVoidMethod(m_java_instance, bind_webcontent_method, wc_fd, wc_fd_passing_fd);
|
|
|
|
|
|
|
|
|
|
auto socket = MUST(Core::LocalSocket::adopt_fd(ui_fd));
|
|
|
|
|
MUST(socket->set_blocking(true));
|
|
|
|
|
|
|
|
|
|
auto new_client = make_ref_counted<WebView::WebContentClient>(move(socket), *this);
|
|
|
|
|
new_client->set_fd_passing_socket(MUST(Core::LocalSocket::adopt_fd(ui_fd_passing_fd)));
|
|
|
|
|
|
|
|
|
|
return new_client;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-02 12:30:21 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" JNIEXPORT void JNICALL
|
2023-09-03 10:19:41 -03:00
|
|
|
Java_org_serenityos_ladybird_WebViewImplementation_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */)
|
2023-09-02 12:30:21 -03:00
|
|
|
{
|
2023-09-06 19:42:16 -03:00
|
|
|
auto ret = env->GetJavaVM(&WebViewImplementationNative::global_vm);
|
|
|
|
|
if (ret != 0)
|
|
|
|
|
TODO();
|
|
|
|
|
|
2023-09-03 10:19:41 -03:00
|
|
|
auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementation");
|
2023-09-02 12:30:21 -03:00
|
|
|
if (!local_class)
|
|
|
|
|
TODO();
|
|
|
|
|
WebViewImplementationNative::global_class_reference = reinterpret_cast<jclass>(env->NewGlobalRef(local_class));
|
2023-09-06 19:42:16 -03:00
|
|
|
env->DeleteLocalRef(local_class);
|
2023-09-02 12:30:21 -03:00
|
|
|
|
|
|
|
|
auto field = env->GetFieldID(WebViewImplementationNative::global_class_reference, "nativeInstance", "J");
|
|
|
|
|
if (!field)
|
|
|
|
|
TODO();
|
|
|
|
|
WebViewImplementationNative::instance_pointer_field = field;
|
2023-09-06 19:42:16 -03:00
|
|
|
|
|
|
|
|
auto method = env->GetMethodID(WebViewImplementationNative::global_class_reference, "bindWebContentService", "(II)V");
|
|
|
|
|
if (!method)
|
|
|
|
|
TODO();
|
|
|
|
|
WebViewImplementationNative::bind_webcontent_method = method;
|
2023-09-02 12:30:21 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" JNIEXPORT jlong JNICALL
|
2023-09-06 19:42:16 -03:00
|
|
|
Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv* env, jobject thiz)
|
2023-09-02 12:30:21 -03:00
|
|
|
{
|
2023-09-06 19:42:16 -03:00
|
|
|
auto ref = env->NewGlobalRef(thiz);
|
|
|
|
|
auto instance = reinterpret_cast<jlong>(new WebViewImplementationNative(ref));
|
2023-09-02 12:30:21 -03:00
|
|
|
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "New WebViewImplementationNative at %p", reinterpret_cast<void*>(instance));
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" JNIEXPORT void JNICALL
|
2023-09-06 19:42:16 -03:00
|
|
|
Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv* env, jobject /* thiz */, jlong instance)
|
2023-09-02 12:30:21 -03:00
|
|
|
{
|
2023-09-06 19:42:16 -03:00
|
|
|
auto* impl = reinterpret_cast<WebViewImplementationNative*>(instance);
|
|
|
|
|
env->DeleteGlobalRef(impl->java_instance());
|
|
|
|
|
delete impl;
|
2023-09-02 12:30:21 -03:00
|
|
|
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "Destroyed WebViewImplementationNative at %p", reinterpret_cast<void*>(instance));
|
|
|
|
|
}
|
2023-09-03 10:19:41 -03:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|