Ladybird/Android: Bind a RequestServerService for networking needs

Add a RequestServerService class that uses the LadybirdServiceBase class
added previously. Bind to it from the WebContentService's service_main()
during startup.
This commit is contained in:
Andrew Kaster 2023-09-15 18:01:23 -06:00 committed by Andrew Kaster
parent da8f450335
commit a243bc465f
Notes: sideshowbarker 2024-07-17 21:11:12 +09:00
15 changed files with 236 additions and 30 deletions

View file

@ -12,6 +12,8 @@
android:smallScreens="true" />
<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
@ -28,27 +30,32 @@
android:supportsRtl="true"
android:theme="@style/Theme.Ladybird"
tools:targetApi="33">
<activity
android:name=".LadybirdActivity"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.extract_android_style"
android:value="minimal" />
</activity>
<service
android:name=".WebContentService"
android:enabled="true"
android:exported="false"
android:process=":WebContent"/>
android:process=":WebContent" />
<service
android:name=".RequestServerService"
android:enabled="true"
android:exported="false"
android:process=":RequestServer" />
</application>
</manifest>

View file

@ -7,5 +7,8 @@
#pragma once
#include <AK/Error.h>
#include <jni.h>
ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket);
extern JavaVM* global_vm;

View file

@ -10,6 +10,8 @@
#include <Ladybird/Utilities.h>
#include <jni.h>
JavaVM* global_vm;
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_LadybirdServiceBase_nativeThreadLoop(JNIEnv*, jobject /* thiz */, jint ipc_socket, jint fd_passing_socket)
{
@ -31,6 +33,8 @@ Java_org_serenityos_ladybird_LadybirdServiceBase_initNativeCode(JNIEnv* env, job
return;
}
env->GetJavaVM(&global_vm);
char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr);
s_serenity_resource_root = raw_resource_dir;
env->ReleaseStringUTFChars(resource_dir, raw_resource_dir);

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <AK/OwnPtr.h>
#include <Ladybird/Utilities.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h>
#include <LibIPC/SingleServer.h>
#include <LibTLS/Certificate.h>
#include <RequestServer/ConnectionFromClient.h>
#include <RequestServer/GeminiProtocol.h>
#include <RequestServer/HttpProtocol.h>
#include <RequestServer/HttpsProtocol.h>
// FIXME: Share b/w RequestServer and WebSocket
ErrorOr<String> find_certificates(StringView serenity_resource_root)
{
auto cert_path = TRY(String::formatted("{}/res/ladybird/cacert.pem", serenity_resource_root));
if (!FileSystem::exists(cert_path)) {
auto app_dir = LexicalPath::dirname(TRY(Core::System::current_executable_path()).to_deprecated_string());
cert_path = TRY(String::formatted("{}/cacert.pem", LexicalPath(app_dir).parent()));
if (!FileSystem::exists(cert_path))
return Error::from_string_view("Don't know how to load certs!"sv);
}
return cert_path;
}
ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket)
{
// Ensure the certificates are read out here.
DefaultRootCACertificates::set_default_certificate_path(TRY(find_certificates(s_serenity_resource_root)));
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
Core::EventLoop event_loop;
// FIXME: Don't leak these :V
[[maybe_unused]] auto* gemini = new RequestServer::GeminiProtocol;
[[maybe_unused]] auto* http = new RequestServer::HttpProtocol;
[[maybe_unused]] auto* https = new RequestServer::HttpsProtocol;
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
auto client = TRY(RequestServer::ConnectionFromClient::try_create(move(socket)));
client->set_fd_passing_socket(TRY(Core::LocalSocket::adopt_fd(fd_passing_socket)));
return event_loop.exec();
}

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebContentService.h"
#include "LadybirdServiceBase.h"
#include <AK/LexicalPath.h>
#include <Ladybird/FontPlugin.h>
@ -28,15 +29,7 @@
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageHost.h>
class NullResourceConnector : public Web::ResourceLoaderConnector {
virtual void prefetch_dns(AK::URL const&) override { }
virtual void preconnect(AK::URL const&) override { }
virtual RefPtr<Web::ResourceLoaderConnectorRequest> start_request(DeprecatedString const&, AK::URL const&, HashMap<DeprecatedString, DeprecatedString> const&, ReadonlyBytes, Core::ProxyData const&) override
{
return nullptr;
}
};
static ErrorOr<NonnullRefPtr<Protocol::RequestClient>> bind_request_server_service();
ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket)
{
@ -52,7 +45,8 @@ ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket)
Web::FrameLoader::set_default_favicon_path(DeprecatedString::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root));
Web::ResourceLoader::initialize(make_ref_counted<NullResourceConnector>());
auto request_server_client = TRY(bind_request_server_service());
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(request_server_client))));
bool is_layout_test_mode = false;
@ -71,3 +65,29 @@ ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket)
return event_loop.exec();
}
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> bind_request_server_service()
{
int socket_fds[2] {};
TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds));
int ui_fd = socket_fds[0];
int server_fd = socket_fds[1];
int fd_passing_socket_fds[2] {};
TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fd_passing_socket_fds));
int ui_fd_passing_fd = fd_passing_socket_fds[0];
int server_fd_passing_fd = fd_passing_socket_fds[1];
// NOTE: The java object takes ownership of the socket fds
bind_request_server_java(server_fd, server_fd_passing_fd);
auto socket = TRY(Core::LocalSocket::adopt_fd(ui_fd));
TRY(socket->set_blocking(true));
auto new_client = TRY(try_make_ref_counted<Protocol::RequestClient>(move(socket)));
new_client->set_fd_passing_socket(TRY(Core::LocalSocket::adopt_fd(ui_fd_passing_fd)));
return new_client;
}

View file

@ -0,0 +1,9 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
void bind_request_server_java(int ipc_socket, int fd_passing_socket);

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "JNIHelpers.h"
#include "LadybirdServiceBase.h"
#include <jni.h>
jobject global_instance;
jclass global_class_reference;
jmethodID bind_request_server_method;
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_WebContentService_nativeInit(JNIEnv* env, jobject thiz)
{
global_instance = env->NewGlobalRef(thiz);
auto local_class = env->FindClass("org/serenityos/ladybird/WebContentService");
if (!local_class)
TODO();
global_class_reference = reinterpret_cast<jclass>(env->NewGlobalRef(local_class));
env->DeleteLocalRef(local_class);
auto method = env->GetMethodID(global_class_reference, "bindRequestServer", "(II)V");
if (!method)
TODO();
bind_request_server_method = method;
}
void bind_request_server_java(int ipc_socket, int fd_passing_socket)
{
JavaEnvironment env(global_vm);
env.get()->CallVoidMethod(global_instance, bind_request_server_method, ipc_socket, fd_passing_socket);
}

View file

@ -9,6 +9,7 @@ package org.serenityos.ladybird
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import org.serenityos.ladybird.databinding.ActivityMainBinding
import java.net.URL
import kotlin.io.path.Path
class LadybirdActivity : AppCompatActivity() {
@ -37,7 +38,7 @@ class LadybirdActivity : AppCompatActivity() {
super.onStart()
// FIXME: This is not the right place to load the homepage :^)
val initialURL = Path(resourceDir, "res/html/misc/welcome.html").toUri().toURL()
val initialURL = URL("https://ladybird.dev")
view.loadURL(initialURL)
}

View file

@ -23,7 +23,7 @@ const val MSG_TRANSFER_SOCKETS = 2
abstract class LadybirdServiceBase(protected val TAG: String) : Service() {
private val threadPool = Executors.newCachedThreadPool()
private lateinit var resourceDir: String
protected lateinit var resourceDir: String
override fun onCreate() {
super.onCreate()

View file

@ -0,0 +1,21 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
package org.serenityos.ladybird
import android.os.Message
class RequestServerService : LadybirdServiceBase("RequestServerService") {
override fun handleServiceSpecificMessage(msg: Message): Boolean {
return false
}
companion object {
init {
System.loadLibrary("requestserver")
}
}
}

View file

@ -6,13 +6,37 @@
package org.serenityos.ladybird
import android.content.Context
import android.content.Intent
import android.os.Message
import android.util.Log
class WebContentService : LadybirdServiceBase("WebContentService") {
override fun handleServiceSpecificMessage(msg: Message): Boolean {
return false
}
init {
nativeInit();
}
private fun bindRequestServer(ipcFd: Int, fdPassingFd: Int)
{
val connector = LadybirdServiceConnection(ipcFd, fdPassingFd, resourceDir)
connector.onDisconnect = {
// FIXME: Notify impl that service is dead and might need restarted
Log.e(TAG, "RequestServer Died! :(")
}
// FIXME: Unbind this at some point maybe
bindService(
Intent(this, RequestServerService::class.java),
connector,
Context.BIND_AUTO_CREATE
)
}
external fun nativeInit()
companion object {
init {
System.loadLibrary("webcontent")

View file

@ -1,5 +1,9 @@
set(REQUESTSERVER_SOURCE_DIR ${SERENITY_SOURCE_DIR}/Userland/Services/RequestServer)
set(CMAKE_AUTOMOC OFF)
set(CMAKE_AUTORCC OFF)
set(CMAKE_AUTOUIC OFF)
set(REQUESTSERVER_SOURCES
${REQUESTSERVER_SOURCE_DIR}/ConnectionFromClient.cpp
${REQUESTSERVER_SOURCE_DIR}/ConnectionCache.cpp
@ -11,17 +15,28 @@ set(REQUESTSERVER_SOURCES
${REQUESTSERVER_SOURCE_DIR}/HttpsRequest.cpp
${REQUESTSERVER_SOURCE_DIR}/HttpsProtocol.cpp
${REQUESTSERVER_SOURCE_DIR}/Protocol.cpp
main.cpp
)
add_executable(RequestServer ${REQUESTSERVER_SOURCES})
if (ANDROID)
add_library(requestserver SHARED
${REQUESTSERVER_SOURCES}
../Android/src/main/cpp/RequestServerService.cpp
../Android/src/main/cpp/LadybirdServiceBaseJNI.cpp
../Utilities.cpp
)
else()
add_library(requestserver STATIC ${REQUESTSERVER_SOURCES})
endif()
target_include_directories(RequestServer PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/)
target_include_directories(RequestServer PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
target_link_libraries(RequestServer PRIVATE LibCore LibMain LibCrypto LibFileSystem LibGemini LibHTTP LibIPC LibMain LibTLS LibWebView)
add_executable(RequestServer main.cpp)
target_link_libraries(RequestServer PRIVATE requestserver)
target_include_directories(requestserver PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/)
target_include_directories(requestserver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
target_link_libraries(requestserver PUBLIC LibCore LibMain LibCrypto LibFileSystem LibGemini LibHTTP LibIPC LibMain LibTLS LibWebView)
if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
# Solaris has socket and networking related functions in two extra libraries
target_link_libraries(RequestServer PRIVATE nsl socket)
target_link_libraries(requestserver PUBLIC nsl socket)
endif()
if (HAIKU)
# Haiku has networking related functions in the network library

View file

@ -55,6 +55,7 @@ else()
if (ANDROID)
target_sources(webcontent PRIVATE
../Android/src/main/cpp/WebContentService.cpp
../Android/src/main/cpp/WebContentServiceJNI.cpp
../Android/src/main/cpp/LadybirdServiceBaseJNI.cpp
)
target_link_libraries(webcontent PRIVATE android)

View file

@ -33,7 +33,12 @@ add_custom_target(copy-content-filters
"${SERENITY_SOURCE_DIR}/Base/home/anon/.config/BrowserContentFilters.txt"
"asset-bundle/res/ladybird/BrowserContentFilters.txt"
)
add_dependencies(archive-assets copy-autoplay-allowlist copy-content-filters)
add_custom_target(copy-certs
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${Lagom_BINARY_DIR}/cacert.pem"
"asset-bundle/res/ladybird/cacert.pem"
)
add_dependencies(archive-assets copy-autoplay-allowlist copy-content-filters copy-certs)
add_custom_target(copy-assets COMMAND ${CMAKE_COMMAND} -E copy_if_different ladybird-assets.tar.gz "${CMAKE_SOURCE_DIR}/Android/src/main/assets/")
add_dependencies(copy-assets archive-assets)
add_dependencies(ladybird copy-assets)

View file

@ -36,13 +36,18 @@ list(REMOVE_DUPLICATES all_required_lagom_libraries)
# Remove ladybird shlib if it exists
list(REMOVE_ITEM all_required_lagom_libraries ladybird)
# Install webcontent impl library if it exists
if (TARGET webcontent)
get_target_property(target_type webcontent TYPE)
if ("${target_type}" STREQUAL STATIC_LIBRARY)
list(APPEND all_required_lagom_libraries webcontent)
endif()
endif()
# Install service impl libraries if they exist
macro(install_service_lib service)
if (TARGET ${service})
get_target_property(target_type ${service} TYPE)
if ("${target_type}" STREQUAL STATIC_LIBRARY)
list(APPEND all_required_lagom_libraries ${service})
endif()
endif()
endmacro()
foreach(service IN LISTS webcontent requestserver)
install_service_lib(${service})
endforeach()
install(TARGETS ${all_required_lagom_libraries}
EXPORT ladybirdTargets