From 1d369cb7224556cf3d2f05717541daa3f2965872 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sun, 3 Sep 2023 15:19:41 +0200 Subject: [PATCH] Ladybird: Add Android View for WebView and paint into it from bindings This allows our WebViewImplementationNative class to paint into an Android Bitmap class from the onDraw() view event. We also set the viewport geometry from the view since we're setting the Kotlin Bitmap at the same time. --- Ladybird/Android/build.gradle.kts | 1 + .../main/cpp/WebViewImplementationNative.cpp | 87 +++++++++++++++++-- .../serenityos/ladybird/LadybirdActivity.kt | 13 +-- .../serenityos/ladybird/TransferAssets.java | 2 +- .../java/org/serenityos/ladybird/WebView.kt | 31 +++++++ ...tionNative.kt => WebViewImplementation.kt} | 16 +++- .../src/main/res/layout/activity_main.xml | 39 ++++++--- .../Android/src/main/res/values/themes.xml | 2 + Ladybird/CMakeLists.txt | 2 +- 9 files changed, 162 insertions(+), 31 deletions(-) create mode 100644 Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt rename Ladybird/Android/src/main/java/org/serenityos/ladybird/{WebViewImplementationNative.kt => WebViewImplementation.kt} (65%) diff --git a/Ladybird/Android/build.gradle.kts b/Ladybird/Android/build.gradle.kts index 76e7de66c2..19b2eb7c70 100644 --- a/Ladybird/Android/build.gradle.kts +++ b/Ladybird/Android/build.gradle.kts @@ -72,6 +72,7 @@ dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.9.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp index e454768b2c..c26f8dd83c 100644 --- a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp +++ b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp @@ -4,31 +4,78 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include +#include #include #include -using namespace WebView; - namespace { + +Gfx::BitmapFormat to_gfx_bitmap_format(i32 f) +{ + switch (f) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + return Gfx::BitmapFormat::BGRA8888; + default: + VERIFY_NOT_REACHED(); + } +} + class WebViewImplementationNative : public WebView::ViewImplementation { public: - virtual Gfx::IntRect viewport_rect() const override { return {}; } - virtual Gfx::IntPoint to_content_position(Gfx::IntPoint) const override { return {}; } - virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint) const override { return {}; } + 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 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 } }; + } + static jclass global_class_reference; static jfieldID instance_pointer_field; + +private: + Gfx::IntRect m_viewport_rect; }; jclass WebViewImplementationNative::global_class_reference; jfieldID WebViewImplementationNative::instance_pointer_field; } extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementationNative_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */) +Java_org_serenityos_ladybird_WebViewImplementation_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */) { - auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementationNative"); + auto local_class = env->FindClass("org/serenityos/ladybird/WebViewImplementation"); if (!local_class) TODO(); WebViewImplementationNative::global_class_reference = reinterpret_cast(env->NewGlobalRef(local_class)); @@ -40,7 +87,7 @@ Java_org_serenityos_ladybird_WebViewImplementationNative_00024Companion_nativeCl } extern "C" JNIEXPORT jlong JNICALL -Java_org_serenityos_ladybird_WebViewImplementationNative_nativeObjectInit(JNIEnv*, jobject /* thiz */) +Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv*, jobject /* thiz */) { auto instance = reinterpret_cast(new WebViewImplementationNative); __android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "New WebViewImplementationNative at %p", reinterpret_cast(instance)); @@ -48,8 +95,30 @@ Java_org_serenityos_ladybird_WebViewImplementationNative_nativeObjectInit(JNIEnv } extern "C" JNIEXPORT void JNICALL -Java_org_serenityos_ladybird_WebViewImplementationNative_nativeObjectDispose(JNIEnv*, jobject /* thiz */, jlong instance) +Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv*, jobject /* thiz */, jlong instance) { delete reinterpret_cast(instance); __android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "Destroyed WebViewImplementationNative at %p", reinterpret_cast(instance)); } + +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); +} diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/LadybirdActivity.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/LadybirdActivity.kt index f3e640ad6d..75ca1e00d2 100644 --- a/Ladybird/Android/src/main/java/org/serenityos/ladybird/LadybirdActivity.kt +++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/LadybirdActivity.kt @@ -8,9 +8,8 @@ package org.serenityos.ladybird import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.widget.TextView +import android.util.AttributeSet import org.serenityos.ladybird.databinding.ActivityMainBinding -import org.serenityos.ladybird.TransferAssets class LadybirdActivity : AppCompatActivity() { @@ -20,11 +19,13 @@ class LadybirdActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) resourceDir = TransferAssets.transferAssets(this) initNativeCode(resourceDir) - view = WebViewImplementationNative() + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + view = binding.webView } override fun onDestroy() { @@ -32,7 +33,7 @@ class LadybirdActivity : AppCompatActivity() { super.onDestroy() } - private lateinit var view: WebViewImplementationNative + private lateinit var view: WebView /** * A native method that is implemented by the 'ladybird' native library, diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/TransferAssets.java b/Ladybird/Android/src/main/java/org/serenityos/ladybird/TransferAssets.java index 975e8a4fa5..b034188621 100644 --- a/Ladybird/Android/src/main/java/org/serenityos/ladybird/TransferAssets.java +++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/TransferAssets.java @@ -20,7 +20,7 @@ import java.lang.String; public class TransferAssets { /** - @return new ladybird resource root + * @return new ladybird resource root */ static public String transferAssets(Context context) { Log.d("Ladybird", "Hello from java"); diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt new file mode 100644 index 0000000000..f60bff6979 --- /dev/null +++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt @@ -0,0 +1,31 @@ +package org.serenityos.ladybird + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.util.AttributeSet +import android.view.View + +// FIXME: This should (eventually) implement NestedScrollingChild3 and ScrollingView +class WebView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) { + private val viewImpl = WebViewImplementation() + private lateinit var contentBitmap: Bitmap + + fun dispose() { + viewImpl.dispose() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + contentBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + // FIXME: Account for scroll offset when view supports scrolling + viewImpl.setViewportGeometry(w, h) + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + + viewImpl.drawIntoBitmap(contentBitmap); + canvas?.drawBitmap(contentBitmap, 0f, 0f, null) + } +} diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementationNative.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt similarity index 65% rename from Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementationNative.kt rename to Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt index 8852b56eab..b3561027b5 100644 --- a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementationNative.kt +++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt @@ -6,19 +6,20 @@ package org.serenityos.ladybird +import android.graphics.Bitmap import android.util.Log /** * Wrapper around WebView::ViewImplementation for use by Kotlin */ -class WebViewImplementationNative { +class WebViewImplementation { // Instance Pointer to native object, very unsafe :) private var nativeInstance = nativeObjectInit() init { Log.d( "Ladybird", - "New WebViewImplementationNative (Kotlin) with nativeInstance ${this.nativeInstance}" + "New WebViewImplementation (Kotlin) with nativeInstance ${this.nativeInstance}" ) } @@ -27,9 +28,20 @@ class WebViewImplementationNative { nativeInstance = 0 } + fun drawIntoBitmap(bitmap: Bitmap) { + nativeDrawIntoBitmap(nativeInstance, bitmap) + } + + fun setViewportGeometry(w: Int, h: Int) { + nativeSetViewportGeometry(nativeInstance, w, h) + } + private external fun nativeObjectInit(): Long private external fun nativeObjectDispose(instance: Long) + private external fun nativeDrawIntoBitmap(instance: Long, bitmap: Bitmap) + private external fun nativeSetViewportGeometry(instance: Long, w: Int, h: Int) + companion object { /* * We use a static class initializer to allow the native code to cache some diff --git a/Ladybird/Android/src/main/res/layout/activity_main.xml b/Ladybird/Android/src/main/res/layout/activity_main.xml index 6043061dbd..c3a0cec9b1 100644 --- a/Ladybird/Android/src/main/res/layout/activity_main.xml +++ b/Ladybird/Android/src/main/res/layout/activity_main.xml @@ -1,19 +1,34 @@ - + android:fitsSystemWindows="true" + tools:context=".LadybirdActivity"> - + + + + - + + + + + diff --git a/Ladybird/Android/src/main/res/values/themes.xml b/Ladybird/Android/src/main/res/values/themes.xml index cbb2295eba..5413fb17ef 100644 --- a/Ladybird/Android/src/main/res/values/themes.xml +++ b/Ladybird/Android/src/main/res/values/themes.xml @@ -12,5 +12,7 @@ ?attr/colorPrimaryVariant + false + true diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 508b7ce4a6..a007f56c40 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -150,7 +150,7 @@ elseif(ANDROID) Android/src/main/cpp/LadybirdActivity.cpp Android/src/main/cpp/WebViewImplementationNative.cpp ) - target_link_libraries(ladybird PRIVATE log) + target_link_libraries(ladybird PRIVATE log jnigraphics) 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