diff --git a/Ladybird/Android/src/main/AndroidManifest.xml b/Ladybird/Android/src/main/AndroidManifest.xml
index a4fb6fed93..c48a1e683a 100644
--- a/Ladybird/Android/src/main/AndroidManifest.xml
+++ b/Ladybird/Android/src/main/AndroidManifest.xml
@@ -1,41 +1,54 @@
-
-
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
-
+ android:screenOrientation="unspecified">
+
+
+
+
+
+
+
+
+
+
diff --git a/Ladybird/Android/src/main/cpp/WebContentService.cpp b/Ladybird/Android/src/main/cpp/WebContentService.cpp
new file mode 100644
index 0000000000..bcb3de600d
--- /dev/null
+++ b/Ladybird/Android/src/main/cpp/WebContentService.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2023, Andrew Kaster
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+
+extern "C" JNIEXPORT void JNICALL
+Java_org_serenityos_ladybird_WebContentService_nativeHandleTransferSockets(JNIEnv*, jobject /* thiz */, jint ipc_socket, jint fd_passing_socket)
+{
+ __android_log_print(ANDROID_LOG_INFO, "WebContent", "New binding received, sockets %d and %d", ipc_socket, fd_passing_socket);
+ ::close(ipc_socket);
+ ::close(fd_passing_socket);
+}
diff --git a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp
index c26f8dd83c..740020a490 100644
--- a/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp
+++ b/Ladybird/Android/src/main/cpp/WebViewImplementationNative.cpp
@@ -4,9 +4,10 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
-#include
-#include
-#include
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -23,13 +24,75 @@ Gfx::BitmapFormat to_gfx_bitmap_format(i32 f)
}
}
+class JavaEnvironment {
+public:
+ JavaEnvironment(JavaVM* vm)
+ : m_vm(vm)
+ {
+ auto ret = m_vm->GetEnv(reinterpret_cast(&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;
+};
+
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);
+ }
+
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
+ {
+ 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
+ }
+
void paint_into_bitmap(void* android_bitmap_raw, AndroidBitmapInfo const& info)
{
// Software bitmaps only for now!
@@ -64,40 +127,89 @@ public:
static jclass global_class_reference;
static jfieldID instance_pointer_field;
+ static jmethodID bind_webcontent_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;
+JavaVM* WebViewImplementationNative::global_vm;
+
+NonnullRefPtr 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(move(socket), *this);
+ new_client->set_fd_passing_socket(MUST(Core::LocalSocket::adopt_fd(ui_fd_passing_fd)));
+
+ 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;
}
extern "C" JNIEXPORT jlong JNICALL
-Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv*, jobject /* thiz */)
+Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectInit(JNIEnv* env, jobject thiz)
{
- auto instance = reinterpret_cast(new WebViewImplementationNative);
+ auto ref = env->NewGlobalRef(thiz);
+ auto instance = reinterpret_cast(new WebViewImplementationNative(ref));
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "New WebViewImplementationNative at %p", reinterpret_cast(instance));
return instance;
}
extern "C" JNIEXPORT void JNICALL
-Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv*, jobject /* thiz */, jlong instance)
+Java_org_serenityos_ladybird_WebViewImplementation_nativeObjectDispose(JNIEnv* env, jobject /* thiz */, jlong instance)
{
- delete reinterpret_cast(instance);
+ auto* impl = reinterpret_cast(instance);
+ env->DeleteGlobalRef(impl->java_instance());
+ delete impl;
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "Destroyed WebViewImplementationNative at %p", reinterpret_cast(instance));
}
diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebContentService.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebContentService.kt
new file mode 100644
index 0000000000..5b2625cf63
--- /dev/null
+++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebContentService.kt
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2023, Andrew Kaster
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+package org.serenityos.ladybird
+
+import android.app.Service
+import android.content.Intent
+import android.util.Log
+import android.os.ParcelFileDescriptor
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+
+const val MSG_TRANSFER_SOCKETS = 1
+
+class WebContentService : Service() {
+ private val TAG = "WebContentService"
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.i(TAG, "Creating Service")
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ Log.i(TAG, "Destroying Service")
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ Log.i(TAG, "Start command received")
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ private fun handleTransferSockets(msg: Message) {
+ val bundle = msg.data
+ // FIXME: Handle garbage messages from wierd clients
+ val ipcSocket = bundle.getParcelable("IPC_SOCKET")!!
+ val fdSocket = bundle.getParcelable("FD_PASSING_SOCKET")!!
+ nativeHandleTransferSockets(ipcSocket.detachFd(), fdSocket.detachFd())
+ }
+
+ private external fun nativeHandleTransferSockets(ipcSocket: Int, fdPassingSocket: Int)
+
+ internal class IncomingHandler(
+ context: WebContentService,
+ private val owner: WebContentService = context
+ ) : Handler() {
+ override fun handleMessage(msg: Message) {
+ when (msg.what) {
+ MSG_TRANSFER_SOCKETS -> this.owner.handleTransferSockets(msg)
+ else -> super.handleMessage(msg)
+ }
+ }
+ }
+
+ override fun onBind(p0: Intent?): IBinder? {
+ // FIXME: Check the intent to make sure it's legit
+ return Messenger(IncomingHandler(this)).binder
+ }
+
+ companion object {
+ init {
+ System.loadLibrary("webcontent")
+ }
+ }
+}
diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebContentServiceConnection.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebContentServiceConnection.kt
new file mode 100644
index 0000000000..b75a4c0184
--- /dev/null
+++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebContentServiceConnection.kt
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2023, Andrew Kaster
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+package org.serenityos.ladybird
+
+import android.content.ComponentName
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.os.ParcelFileDescriptor
+
+class WebContentServiceConnection(private var ipcFd: Int, private var fdPassingFd: Int) :
+ ServiceConnection {
+ var boundToWebContent: Boolean = false
+ var onDisconnect: () -> Unit = {}
+ private var webContentService: Messenger? = null
+
+ override fun onServiceConnected(className: ComponentName, svc: IBinder) {
+ // This is called when the connection with the service has been
+ // established, giving us the object we can use to
+ // interact with the service. We are communicating with the
+ // service using a Messenger, so here we get a client-side
+ // representation of that from the raw IBinder object.
+ webContentService = Messenger(svc)
+ boundToWebContent = true
+
+ var msg = Message.obtain(null, MSG_TRANSFER_SOCKETS)
+ msg.data.putParcelable("IPC_SOCKET", ParcelFileDescriptor.adoptFd(ipcFd))
+ msg.data.putParcelable("FD_PASSING_SOCKET", ParcelFileDescriptor.adoptFd(fdPassingFd))
+ webContentService!!.send(msg)
+ }
+
+ override fun onServiceDisconnected(className: ComponentName) {
+ // This is called when the connection with the service has been
+ // unexpectedly disconnected; that is, its process crashed.
+ webContentService = null
+ boundToWebContent = false
+
+ // Notify owner that the service is dead
+ onDisconnect()
+ }
+}
diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt
index f60bff6979..7a638ff400 100644
--- a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt
+++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebView.kt
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023, Andrew Kaster
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
package org.serenityos.ladybird
import android.content.Context
@@ -8,7 +14,7 @@ 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 val viewImpl = WebViewImplementation(context)
private lateinit var contentBitmap: Bitmap
fun dispose() {
diff --git a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt
index b3561027b5..da8816ca09 100644
--- a/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt
+++ b/Ladybird/Android/src/main/java/org/serenityos/ladybird/WebViewImplementation.kt
@@ -6,13 +6,18 @@
package org.serenityos.ladybird
+import android.content.Context
+import android.content.Intent
import android.graphics.Bitmap
import android.util.Log
/**
* Wrapper around WebView::ViewImplementation for use by Kotlin
*/
-class WebViewImplementation {
+class WebViewImplementation(
+ context: Context,
+ private var appContext: Context = context.applicationContext
+) {
// Instance Pointer to native object, very unsafe :)
private var nativeInstance = nativeObjectInit()
@@ -36,6 +41,20 @@ class WebViewImplementation {
nativeSetViewportGeometry(nativeInstance, w, h)
}
+ fun bindWebContentService(ipcFd: Int, fdPassingFd: Int) {
+ var connector = WebContentServiceConnection(ipcFd, fdPassingFd)
+ connector.onDisconnect = {
+ // FIXME: Notify impl that service is dead and might need restarted
+ Log.e("WebContentView", "WebContent Died! :(")
+ }
+ // FIXME: Unbind this at some point maybe
+ appContext.bindService(
+ Intent(appContext, WebContentService::class.java),
+ connector,
+ Context.BIND_AUTO_CREATE
+ )
+ }
+
private external fun nativeObjectInit(): Long
private external fun nativeObjectDispose(instance: Long)
diff --git a/Ladybird/WebContent/CMakeLists.txt b/Ladybird/WebContent/CMakeLists.txt
index d28dcbca60..5654bdc5f4 100644
--- a/Ladybird/WebContent/CMakeLists.txt
+++ b/Ladybird/WebContent/CMakeLists.txt
@@ -29,8 +29,11 @@ if (ENABLE_QT)
target_link_libraries(WebContent PRIVATE Qt::Core Qt::Network Qt::Multimedia)
target_compile_definitions(WebContent PRIVATE HAVE_QT=1)
else()
- # FIXME: Remove when chromes are upstreamed
- add_library(webcontent STATIC ${WEBCONTENT_SOURCES})
+ set(LIB_TYPE STATIC)
+ if (ANDROID)
+ set(LIB_TYPE SHARED)
+ endif()
+ add_library(webcontent ${LIB_TYPE} ${WEBCONTENT_SOURCES})
target_include_directories(webcontent PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/)
target_include_directories(webcontent PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
target_link_libraries(webcontent PRIVATE LibAudio LibCore LibFileSystem LibGfx LibIPC LibJS LibMain LibWeb LibWebSocket LibProtocol LibWebView)
@@ -49,6 +52,11 @@ else()
${WEBCONTENT_SOURCE_DIR}/WebDriverConnection.h
)
+ if (ANDROID)
+ target_sources(webcontent PRIVATE ../Android/src/main/cpp/WebContentService.cpp)
+ target_link_libraries(webcontent PRIVATE log)
+ endif()
+
add_executable(WebContent main.cpp)
target_link_libraries(WebContent PRIVATE webcontent)
endif()
diff --git a/Ladybird/cmake/InstallRules.cmake b/Ladybird/cmake/InstallRules.cmake
index 237cab15a6..c428b17fbc 100644
--- a/Ladybird/cmake/InstallRules.cmake
+++ b/Ladybird/cmake/InstallRules.cmake
@@ -38,7 +38,10 @@ list(REMOVE_ITEM all_required_lagom_libraries ladybird)
# Install webcontent impl library if it exists
if (TARGET webcontent)
- list(APPEND all_required_lagom_libraries webcontent)
+ get_target_property(target_type webcontent TYPE)
+ if ("${target_type}" STREQUAL STATIC_LIBRARY)
+ list(APPEND all_required_lagom_libraries webcontent)
+ endif()
endif()
install(TARGETS ${all_required_lagom_libraries}