1
0
mirror of https://github.com/SerenityOS/serenity synced 2024-06-29 07:00:29 +00:00

Everywhere: Gently remove the ladybird android port

With Ladybird now being its own repository, there's little reason
to keep the Ladybird Android port in the SerenityOS repository.

(The Qt port is useful to be able to test changes to LibWeb in lagom
so it'll stay around. Similar for the AppKit port, since getting
Qt on macOS is a bit annoying. But if the AppKit port is too much
pain to keep working, we should toss that too.

Eventually, the lagom browser ports should move out from Ladybird/
to Meta/Lagom/Contrib, but for now it might make sense to leave them
where they are to keep cherry-picks from ladybird easier.)
This commit is contained in:
Nico Weber 2024-06-09 13:51:50 -04:00
parent ce11613677
commit bb2d80a2bb
84 changed files with 27 additions and 2994 deletions

View File

@ -10,17 +10,11 @@
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#if (defined(AK_OS_LINUX) && !defined(AK_OS_ANDROID)) || defined(AK_LIBC_GLIBC) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_SOLARIS) || defined(AK_OS_HAIKU)
#if defined(AK_OS_LINUX) || defined(AK_LIBC_GLIBC) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_SOLARIS) || defined(AK_OS_HAIKU)
# define EXECINFO_BACKTRACE
#endif
#if defined(AK_OS_ANDROID) && (__ANDROID_API__ >= 33)
# include <android/log.h>
# define EXECINFO_BACKTRACE
# define PRINT_ERROR(s) __android_log_write(ANDROID_LOG_WARN, "AK", (s))
#else
# define PRINT_ERROR(s) (void)fputs((s), stderr)
#endif
#define PRINT_ERROR(s) (void)fputs((s), stderr)
#if defined(EXECINFO_BACKTRACE)
# include <cxxabi.h>
@ -74,9 +68,7 @@ ALWAYS_INLINE void dump_backtrace()
} else {
error_builder.append(sym);
}
# if !defined(AK_OS_ANDROID)
error_builder.append('\n');
# endif
error_builder.append('\0');
PRINT_ERROR(error_builder.string_view().characters_without_null_termination());
}
@ -89,7 +81,7 @@ extern "C" {
void ak_verification_failed(char const* message)
{
# if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID)
# if defined(AK_OS_SERENITY)
bool colorize_output = true;
# elif defined(AK_OS_WINDOWS)
bool colorize_output = false;

View File

@ -28,10 +28,6 @@
# include <time.h>
#endif
#if defined(AK_OS_ANDROID)
# include <android/log.h>
#endif
#ifndef KERNEL
# include <AK/StringFloatingPointConversions.h>
#endif
@ -1090,45 +1086,6 @@ void vout(FILE* file, StringView fmtstr, TypeErasedFormatParams& params, bool ne
}
#endif
#ifdef AK_OS_ANDROID
static char const* s_log_tag_name = "Serenity";
void set_log_tag_name(char const* tag_name)
{
static String s_log_tag_storage;
// NOTE: Make sure to copy the null terminator
s_log_tag_storage = MUST(String::from_utf8({ tag_name, strlen(tag_name) + 1 }));
s_log_tag_name = s_log_tag_storage.bytes_as_string_view().characters_without_null_termination();
}
void vout(LogLevel log_level, StringView fmtstr, TypeErasedFormatParams& params, bool newline)
{
StringBuilder builder;
MUST(vformat(builder, fmtstr, params));
if (newline)
builder.append('\n');
builder.append('\0');
auto const string = builder.string_view();
auto ndk_log_level = ANDROID_LOG_UNKNOWN;
switch (log_level) {
case LogLevel ::Debug:
ndk_log_level = ANDROID_LOG_DEBUG;
break;
case LogLevel ::Info:
ndk_log_level = ANDROID_LOG_INFO;
break;
case LogLevel::Warning:
ndk_log_level = ANDROID_LOG_WARN;
break;
}
__android_log_write(ndk_log_level, s_log_tag_name, string.characters_without_null_termination());
}
#endif
#ifndef KERNEL
// FIXME: Deduplicate with Core::Process:get_name()
[[gnu::used]] static ByteString process_name_helper()
@ -1139,7 +1096,7 @@ void vout(LogLevel log_level, StringView fmtstr, TypeErasedFormatParams& params,
if (rc != 0)
return ByteString {};
return StringView { buffer, strlen(buffer) };
# elif defined(AK_LIBC_GLIBC) || (defined(AK_OS_LINUX) && !defined(AK_OS_ANDROID))
# elif defined(AK_LIBC_GLIBC) || defined(AK_OS_LINUX)
return StringView { program_invocation_name, strlen(program_invocation_name) };
# elif defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU)
auto const* progname = getprogname();
@ -1235,9 +1192,6 @@ void vdbg(StringView fmtstr, TypeErasedFormatParams& params, bool newline)
MUST(vformat(builder, fmtstr, params));
if (newline)
builder.append('\n');
#ifdef AK_OS_ANDROID
builder.append('\0');
#endif
auto const string = builder.string_view();
#ifdef AK_OS_SERENITY
@ -1248,11 +1202,7 @@ void vdbg(StringView fmtstr, TypeErasedFormatParams& params, bool newline)
}
# endif
#endif
#ifdef AK_OS_ANDROID
__android_log_write(ANDROID_LOG_DEBUG, s_log_tag_name, string.characters_without_null_termination());
#else
dbgputstr(string.characters_without_null_termination(), string.length());
#endif
}
#ifdef KERNEL

View File

@ -592,7 +592,6 @@ void outln(FILE* file, CheckedFormatString<Parameters...>&& fmtstr, Parameters c
inline void outln(FILE* file) { fputc('\n', file); }
# ifndef AK_OS_ANDROID
template<typename... Parameters>
void out(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
@ -614,49 +613,6 @@ template<typename... Parameters>
void warnln(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters) { outln(stderr, move(fmtstr), parameters...); }
inline void warnln() { outln(stderr); }
# else // v Android ^ No Android
enum class LogLevel {
Debug,
Info,
Warning,
};
void vout(LogLevel, StringView fmtstr, TypeErasedFormatParams&, bool newline = false);
template<typename... Parameters>
void out(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
VariadicFormatParams<AllowDebugOnlyFormatters::Yes, Parameters...> variadic_format_params { parameters... };
vout(LogLevel::Info, fmtstr.view(), variadic_format_params);
}
template<typename... Parameters>
void outln(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
VariadicFormatParams<AllowDebugOnlyFormatters::Yes, Parameters...> variadic_format_params { parameters... };
vout(LogLevel::Info, fmtstr.view(), variadic_format_params, true);
}
inline void outln() { outln(""); }
template<typename... Parameters>
void warn(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
VariadicFormatParams<AllowDebugOnlyFormatters::Yes, Parameters...> variadic_format_params { parameters... };
vout(LogLevel::Warning, fmtstr.view(), variadic_format_params);
}
template<typename... Parameters>
void warnln(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
{
VariadicFormatParams<AllowDebugOnlyFormatters::Yes, Parameters...> variadic_format_params { parameters... };
vout(LogLevel::Warning, fmtstr.view(), variadic_format_params, true);
}
inline void warnln() { warnln(""); }
void set_log_tag_name(char const*);
# endif // AK_OS_ANDROID
# define outln_if(flag, fmt, ...) \
do { \

View File

@ -127,18 +127,6 @@
# define AK_OS_WINDOWS
#endif
#if defined(__ANDROID__)
# define STR(x) __STR(x)
# define __STR(x) #x
# if __ANDROID_API__ < 30
# pragma message "Invalid android API " STR(__ANDROID_API__)
# error "Build configuration not tested on configured Android API version"
# endif
# undef STR
# undef __STR
# define AK_OS_ANDROID
#endif
#if defined(__EMSCRIPTEN__)
# define AK_OS_EMSCRIPTEN
#endif

View File

@ -20,7 +20,7 @@ namespace AK {
inline void fill_with_random([[maybe_unused]] Bytes bytes)
{
#if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU) || AK_LIBC_GLIBC_PREREQ(2, 36)
#if defined(AK_OS_SERENITY) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU) || AK_LIBC_GLIBC_PREREQ(2, 36)
arc4random_buf(bytes.data(), bytes.size());
#elif defined(OSS_FUZZ)
#else

View File

@ -97,7 +97,7 @@ StackInfo::StackInfo()
m_top = m_base + m_size;
#if defined(AK_OS_LINUX) && !defined(AK_OS_ANDROID) && !defined(AK_LIBC_GLIBC)
#if defined(AK_OS_LINUX) && !defined(AK_LIBC_GLIBC)
// Note: musl libc always gives the initial size of the main thread's stack
if (getpid() == static_cast<pid_t>(gettid())) {
rlimit limit;

View File

@ -28,8 +28,7 @@ namespace AK {
// FIXME: Remove this when OpenBSD Clang fully supports consteval.
// And once oss-fuzz updates to clang >15.
// And once Android ships an NDK with clang >14
#if defined(AK_OS_OPENBSD) || defined(OSS_FUZZ) || defined(AK_OS_ANDROID)
#if defined(AK_OS_OPENBSD) || defined(OSS_FUZZ)
# define AK_SHORT_STRING_CONSTEVAL constexpr
#else
# define AK_SHORT_STRING_CONSTEVAL consteval

View File

@ -408,8 +408,7 @@ struct CaseInsensitiveASCIIStringViewTraits : public Traits<StringView> {
// See: https://github.com/llvm/llvm-project/issues/48230
// Additionally, oss-fuzz currently ships an llvm-project commit that is a pre-release of 15.0.0.
// See: https://github.com/google/oss-fuzz/issues/9989
// Android currently doesn't ship clang-15 in any NDK
#if defined(AK_OS_BSD_GENERIC) || defined(OSS_FUZZ) || defined(AK_OS_ANDROID)
#if defined(AK_OS_BSD_GENERIC) || defined(OSS_FUZZ)
# define AK_STRING_VIEW_LITERAL_CONSTEVAL constexpr
#else
# define AK_STRING_VIEW_LITERAL_CONSTEVAL consteval

View File

@ -1,35 +0,0 @@
## Android Studio Project Configuration
The Android port of Ladybird has straightforward integration with the Android Studio IDE.
## Prerequisites
Ensure that your system has the following tools available:
- Android Studio Jellyfish 2023.3.1 or later
- CMake 3.23 or higher as the default CMake executable
- 20G or more storage space for SDKs + Emulator images + Gradle dependencies + build artifacts
## Opening the project
After opening the ``serenity`` directory in Android Studio (NOT the Ladybird/Android directory!)
there should be a pop-up in the bottom left indicating that an Android Gradle project was detected
in ``Ladybird/Android``.
In the top left of the screen in the Project view, navigate to ``Ladybird/Android``. Or, click the
highlighted text in the notification for that path. Open the ``settings.gradle.kts`` file. At the
top of the file should be a banner that says ``Code Insight unavailable (related Gradle project not
linked).`` Click the ``Link Gradle project`` text on the right side of the banner. After the IDE
loads the Gradle view to the right of the code window, go back to the banner at the top of the
``settings.gradle.kts`` file and click ``Load Script Configurations`` to finish loading the Gradle
project.
Gradle will index the project, and download all the required plugins. If it complains about no NDK,
follow the instructions in Android Studio to install an appropriate NDK version. If it still
complains about the NDK version, open ``File->Invalidate Caches...`` and click ``Invalidate and
Restart``.
## Getting the most out of the IDE
See the sections in the [CLionConfiguration](CLionConfiguration.md) for [Excluding Build Artifacts](CLionConfiguration.md#excluding-build-artifacts),
and [Code Generation Settings](CLionConfiguration.md#code-generation-settings).

View File

@ -84,11 +84,6 @@ On Windows:
WSL2/WSLg are preferred, as they provide a linux environment that matches one of the above distributions.
MinGW/MSYS2 are not supported, but may work with sufficient elbow grease. Native Windows builds are not supported with either clang-cl or MSVC.
For Android:
On a Unix-like platform, install the prerequisites for that platform and then see the [Android Studio guide](AndroidStudioConfiguration.md).
Or, download a version of Gradle >= 8.0.0, and run the ``gradlew`` program in ``Ladybird/Android``
## Build steps
### Using serenity.sh
@ -102,11 +97,10 @@ The simplest way to build and run ladybird is via the serenity.sh script:
```
The above commands will build Ladybird with one of the following browser chromes, depending on the platform:
* [Android UI](https://developer.android.com/develop/ui) - The native chrome on Android.
* [AppKit](https://developer.apple.com/documentation/appkit?language=objc) - The native chrome on macOS.
* [Qt](https://doc.qt.io/qt-6/) - The chrome used on all other platforms.
The Qt chrome is available on platforms where it is not the default as well (except on Android). To build the
The Qt chrome is available on platforms where it is not the default as well. To build the
Qt chrome, install the Qt dependencies for your platform, and enable the Qt chrome via CMake:
```bash

1
Ladybird/.gitignore vendored
View File

@ -5,4 +5,3 @@ moc_*
Build
build
CMakeLists.txt.user
Android/src/main/assets/

View File

@ -1,27 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SERENITY_ROOT="$(realpath "${DIR}"/../..)"
# shellcheck source=/dev/null
. "${SERENITY_ROOT}/Meta/shell_include.sh"
# shellcheck source=/dev/null
. "${SERENITY_ROOT}/Meta/find_compiler.sh"
pick_host_compiler
BUILD_DIR=${BUILD_DIR:-"${SERENITY_ROOT}/Build"}
CACHE_DIR=${CACHE_DIR:-"${BUILD_DIR}/caches"}
cmake -S "$SERENITY_ROOT/Meta/Lagom" -B "$BUILD_DIR/lagom-tools" \
-GNinja -Dpackage=LagomTools \
-DCMAKE_INSTALL_PREFIX="$BUILD_DIR/lagom-tools-install" \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_CXX_COMPILER="$CXX" \
-DSERENITY_CACHE_DIR="$CACHE_DIR"
ninja -C "$BUILD_DIR/lagom-tools" install

View File

@ -1,88 +0,0 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
plugins {
id("com.android.application") version "8.4.0"
id("org.jetbrains.kotlin.android") version "1.9.0"
}
var buildDir = layout.buildDirectory.get()
var cacheDir = System.getenv("SERENITY_CACHE_DIR") ?: "$buildDir/caches"
task<Exec>("buildLagomTools") {
commandLine = listOf("./BuildLagomTools.sh")
environment = mapOf(
"BUILD_DIR" to buildDir,
"CACHE_DIR" to cacheDir,
"PATH" to System.getenv("PATH")!!
)
}
tasks.named("preBuild").dependsOn("buildLagomTools")
tasks.named("prepareKotlinBuildScriptModel").dependsOn("buildLagomTools")
android {
namespace = "org.serenityos.ladybird"
compileSdk = 34
defaultConfig {
applicationId = "org.serenityos.ladybird"
minSdk = 30
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags += "-std=c++23"
arguments += listOf(
"-DLagomTools_DIR=$buildDir/lagom-tools-install/share/LagomTools",
"-DSERENITY_CACHE_DIR=$cacheDir"
)
}
}
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your app.
abiFilters += listOf("x86_64", "arm64-v8a")
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
externalNativeBuild {
cmake {
path = file("../CMakeLists.txt")
version = "3.23.0+"
}
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.12.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.ext:junit-ktx:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

View File

@ -1,23 +0,0 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

View File

@ -1,6 +0,0 @@
#Fri Sep 01 12:36:55 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,185 +0,0 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,16 +0,0 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Ladybird"

View File

@ -1,40 +0,0 @@
package org.serenityos.ladybird
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
import org.junit.Rule
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class SmokeTest {
@get:Rule
var activityScenarioRule = activityScenarioRule<LadybirdActivity>()
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.serenityos.ladybird", appContext.packageName)
}
@Test
fun loadWebView() {
// We can actually load a web view, and it is visible
onView(withId(R.id.web_view)).check(matches(isDisplayed()))
}
}

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"
android:versionCode="001"
android:versionName="head">
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
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"
android:allowNativeHeapPointerTagging="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
android:fullBackupOnly="false"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
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: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" />
<service
android:name=".RequestServerService"
android:enabled="true"
android:exported="false"
android:process=":RequestServer" />
<service
android:name=".ImageDecoderService"
android:enabled="true"
android:exported="false"
android:process=":ImageDecoder" />
</application>
</manifest>

View File

@ -1,259 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ALooperEventLoopImplementation.h"
#include "JNIHelpers.h"
#include <LibCore/EventLoop.h>
#include <LibCore/Notifier.h>
#include <LibCore/ThreadEventQueue.h>
#include <android/log.h>
#include <android/looper.h>
#include <fcntl.h>
#include <jni.h>
namespace Ladybird {
EventLoopThreadData& EventLoopThreadData::the()
{
static thread_local EventLoopThreadData s_thread_data { {}, {}, &Core::ThreadEventQueue::current() };
return s_thread_data;
}
static ALooperEventLoopImplementation& current_impl()
{
return verify_cast<ALooperEventLoopImplementation>(Core::EventLoop::current().impl());
}
static int looper_callback(int fd, int events, void* data);
ALooperEventLoopManager::ALooperEventLoopManager(jobject timer_service)
: m_timer_service(timer_service)
{
JavaEnvironment env(global_vm);
jclass timer_class = env.get()->FindClass("org/serenityos/ladybird/TimerExecutorService$Timer");
if (!timer_class)
TODO();
m_timer_class = reinterpret_cast<jclass>(env.get()->NewGlobalRef(timer_class));
env.get()->DeleteLocalRef(timer_class);
m_timer_constructor = env.get()->GetMethodID(m_timer_class, "<init>", "(J)V");
if (!m_timer_constructor)
TODO();
jclass timer_service_class = env.get()->GetObjectClass(m_timer_service);
m_register_timer = env.get()->GetMethodID(timer_service_class, "registerTimer", "(Lorg/serenityos/ladybird/TimerExecutorService$Timer;ZJ)J");
if (!m_register_timer)
TODO();
m_unregister_timer = env.get()->GetMethodID(timer_service_class, "unregisterTimer", "(J)V");
if (!m_unregister_timer)
TODO();
env.get()->DeleteLocalRef(timer_service_class);
auto ret = pipe2(m_pipe, O_CLOEXEC | O_NONBLOCK);
VERIFY(ret == 0);
m_main_looper = ALooper_forThread();
VERIFY(m_main_looper);
ALooper_acquire(m_main_looper);
ret = ALooper_addFd(m_main_looper, m_pipe[0], ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, &looper_callback, this);
VERIFY(ret == 1);
}
ALooperEventLoopManager::~ALooperEventLoopManager()
{
JavaEnvironment env(global_vm);
env.get()->DeleteGlobalRef(m_timer_service);
env.get()->DeleteGlobalRef(m_timer_class);
ALooper_removeFd(m_main_looper, m_pipe[0]);
ALooper_release(m_main_looper);
::close(m_pipe[0]);
::close(m_pipe[1]);
}
NonnullOwnPtr<Core::EventLoopImplementation> ALooperEventLoopManager::make_implementation()
{
return ALooperEventLoopImplementation::create();
}
intptr_t ALooperEventLoopManager::register_timer(Core::EventReceiver& receiver, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible visibility)
{
JavaEnvironment env(global_vm);
auto& thread_data = EventLoopThreadData::the();
auto timer = env.get()->NewObject(m_timer_class, m_timer_constructor, reinterpret_cast<long>(&current_impl()));
long millis = milliseconds;
long timer_id = env.get()->CallLongMethod(m_timer_service, m_register_timer, timer, !should_reload, millis);
// FIXME: Is there a race condition here? Maybe we should take a lock on the timers...
thread_data.timers.set(timer_id, { receiver.make_weak_ptr(), visibility });
return timer_id;
}
void ALooperEventLoopManager::unregister_timer(intptr_t timer_id)
{
if (auto timer = EventLoopThreadData::the().timers.take(timer_id); timer.has_value()) {
JavaEnvironment env(global_vm);
env.get()->CallVoidMethod(m_timer_service, m_unregister_timer, timer_id);
}
}
void ALooperEventLoopManager::register_notifier(Core::Notifier& notifier)
{
EventLoopThreadData::the().notifiers.set(&notifier);
current_impl().register_notifier(notifier);
}
void ALooperEventLoopManager::unregister_notifier(Core::Notifier& notifier)
{
EventLoopThreadData::the().notifiers.remove(&notifier);
current_impl().unregister_notifier(notifier);
}
void ALooperEventLoopManager::did_post_event()
{
int msg = 0xCAFEBABE;
(void)write(m_pipe[1], &msg, sizeof(msg));
}
int looper_callback(int fd, int events, void* data)
{
auto& manager = *static_cast<ALooperEventLoopManager*>(data);
if (events & ALOOPER_EVENT_INPUT) {
int msg = 0;
while (read(fd, &msg, sizeof(msg)) == sizeof(msg)) {
// Do nothing, we don't actually care what the message was, just that it was posted
}
manager.on_did_post_event();
}
return 1;
}
ALooperEventLoopImplementation::ALooperEventLoopImplementation()
: m_event_loop(ALooper_prepare(0))
, m_thread_data(&EventLoopThreadData::the())
{
ALooper_acquire(m_event_loop);
}
ALooperEventLoopImplementation::~ALooperEventLoopImplementation()
{
ALooper_release(m_event_loop);
}
EventLoopThreadData& ALooperEventLoopImplementation::thread_data()
{
return *m_thread_data;
}
int ALooperEventLoopImplementation::exec()
{
while (!m_exit_requested.load(MemoryOrder::memory_order_acquire))
pump(PumpMode::WaitForEvents);
return m_exit_code;
}
size_t ALooperEventLoopImplementation::pump(Core::EventLoopImplementation::PumpMode mode)
{
auto num_events = Core::ThreadEventQueue::current().process();
int timeout_ms = mode == Core::EventLoopImplementation::PumpMode::WaitForEvents ? -1 : 0;
auto ret = ALooper_pollAll(timeout_ms, nullptr, nullptr, nullptr);
// We don't expect any non-callback FDs to be ready
VERIFY(ret <= 0);
if (ret == ALOOPER_POLL_ERROR)
m_exit_requested.store(true, MemoryOrder::memory_order_release);
num_events += Core::ThreadEventQueue::current().process();
return num_events;
}
void ALooperEventLoopImplementation::quit(int code)
{
m_exit_code = code;
m_exit_requested.store(true, MemoryOrder::memory_order_release);
wake();
}
void ALooperEventLoopImplementation::wake()
{
ALooper_wake(m_event_loop);
}
void ALooperEventLoopImplementation::post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&& event)
{
m_thread_event_queue.post_event(receiver, move(event));
if (&m_thread_event_queue != &Core::ThreadEventQueue::current())
wake();
}
static int notifier_callback(int fd, int events, void* data)
{
auto& notifier = *static_cast<Core::Notifier*>(data);
VERIFY(fd == notifier.fd());
Core::NotificationType type = Core::NotificationType::None;
if (events & ALOOPER_EVENT_INPUT)
type |= Core::NotificationType::Read;
if (events & ALOOPER_EVENT_OUTPUT)
type |= Core::NotificationType::Write;
if (events & ALOOPER_EVENT_HANGUP)
type |= Core::NotificationType::HangUp;
if (events & ALOOPER_EVENT_ERROR)
type |= Core::NotificationType::Error;
Core::NotifierActivationEvent event(notifier.fd(), type);
notifier.dispatch_event(event);
// Wake up from ALooper_pollAll, and service this event on the event queue
current_impl().wake();
return 1;
}
void ALooperEventLoopImplementation::register_notifier(Core::Notifier& notifier)
{
auto event_flags = 0;
switch (notifier.type()) {
case Core::Notifier::Type::Read:
event_flags = ALOOPER_EVENT_INPUT;
break;
case Core::Notifier::Type::Write:
event_flags = ALOOPER_EVENT_OUTPUT;
break;
case Core::Notifier::Type::Error:
event_flags = ALOOPER_EVENT_ERROR;
break;
case Core::Notifier::Type::HangUp:
event_flags = ALOOPER_EVENT_HANGUP;
break;
case Core::Notifier::Type::None:
TODO();
}
auto ret = ALooper_addFd(m_event_loop, notifier.fd(), ALOOPER_POLL_CALLBACK, event_flags, &notifier_callback, &notifier);
VERIFY(ret == 1);
}
void ALooperEventLoopImplementation::unregister_notifier(Core::Notifier& notifier)
{
ALooper_removeFd(m_event_loop, notifier.fd());
}
}

View File

@ -1,96 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Atomic.h>
#include <AK/HashMap.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/WeakPtr.h>
#include <LibCore/EventLoopImplementation.h>
#include <jni.h>
extern "C" struct ALooper;
namespace Ladybird {
class ALooperEventLoopManager : public Core::EventLoopManager {
public:
ALooperEventLoopManager(jobject timer_service);
virtual ~ALooperEventLoopManager() override;
virtual NonnullOwnPtr<Core::EventLoopImplementation> make_implementation() override;
virtual intptr_t register_timer(Core::EventReceiver&, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible) override;
virtual void unregister_timer(intptr_t timer_id) override;
virtual void register_notifier(Core::Notifier&) override;
virtual void unregister_notifier(Core::Notifier&) override;
virtual void did_post_event() override;
Function<void()> on_did_post_event;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual int register_signal(int, Function<void(int)>) override { return 0; }
virtual void unregister_signal(int) override { }
private:
int m_pipe[2] = {};
ALooper* m_main_looper { nullptr };
jobject m_timer_service { nullptr };
jmethodID m_register_timer { nullptr };
jmethodID m_unregister_timer { nullptr };
jclass m_timer_class { nullptr };
jmethodID m_timer_constructor { nullptr };
};
struct TimerData {
WeakPtr<Core::EventReceiver> receiver;
Core::TimerShouldFireWhenNotVisible visibility;
};
struct EventLoopThreadData {
static EventLoopThreadData& the();
HashMap<long, TimerData> timers;
HashTable<Core::Notifier*> notifiers;
Core::ThreadEventQueue* thread_queue = nullptr;
};
class ALooperEventLoopImplementation : public Core::EventLoopImplementation {
public:
static NonnullOwnPtr<ALooperEventLoopImplementation> create() { return adopt_own(*new ALooperEventLoopImplementation); }
virtual ~ALooperEventLoopImplementation() override;
virtual int exec() override;
virtual size_t pump(PumpMode) override;
virtual void quit(int) override;
virtual void wake() override;
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&&) override;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual void unquit() override { }
virtual bool was_exit_requested() const override { return false; }
virtual void notify_forked_and_in_child() override { }
EventLoopThreadData& thread_data();
private:
friend class ALooperEventLoopManager;
ALooperEventLoopImplementation();
void register_notifier(Core::Notifier&);
void unregister_notifier(Core::Notifier&);
ALooper* m_event_loop { nullptr };
int m_exit_code { 0 };
Atomic<bool> m_exit_requested { false };
EventLoopThreadData* m_thread_data { nullptr };
};
}

View File

@ -1,21 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <ImageDecoder/ConnectionFromClient.h>
#include <LibCore/EventLoop.h>
#include <LibIPC/SingleServer.h>
ErrorOr<int> service_main(int ipc_socket)
{
Core::EventLoop event_loop;
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
auto client = TRY(ImageDecoder::ConnectionFromClient::try_create(move(socket)));
return event_loop.exec();
}

View File

@ -1,16 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "JNIHelpers.h"
#include <AK/Utf16View.h>
namespace Ladybird {
jstring JavaEnvironment::jstring_from_ak_string(String const& str)
{
auto as_utf16 = MUST(AK::utf8_to_utf16(str.code_points()));
return m_env->NewString(as_utf16.data(), as_utf16.size());
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/String.h>
#include <jni.h>
namespace Ladybird {
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; }
jstring jstring_from_ak_string(String const& str);
private:
JavaVM* m_vm = nullptr;
JNIEnv* m_env = nullptr;
bool m_did_attach_thread = false;
};
}
extern JavaVM* global_vm;

View File

@ -1,226 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ALooperEventLoopImplementation.h"
#include "JNIHelpers.h"
#include <AK/ByteString.h>
#include <AK/Format.h>
#include <AK/HashMap.h>
#include <AK/LexicalPath.h>
#include <AK/OwnPtr.h>
#include <Ladybird/Utilities.h>
#include <LibArchive/TarStream.h>
#include <LibCore/DirIterator.h>
#include <LibCore/Directory.h>
#include <LibCore/EventLoop.h>
#include <LibCore/System.h>
#include <LibCore/Timer.h>
#include <LibFileSystem/FileSystem.h>
#include <jni.h>
static ErrorOr<void> extract_tar_archive(String archive_file, ByteString output_directory);
JavaVM* global_vm;
static OwnPtr<Core::EventLoop> s_main_event_loop;
static jobject s_java_instance;
static jmethodID s_schedule_event_loop_method;
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobject thiz, jstring resource_dir, jstring tag_name, jobject timer_service)
{
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);
dbgln("Set resource dir to {}", s_serenity_resource_root);
auto file_or_error = Core::System::open(MUST(String::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root)), O_RDONLY);
if (file_or_error.is_error()) {
dbgln("No resource files, extracting assets...");
MUST(extract_tar_archive(MUST(String::formatted("{}/ladybird-assets.tar", s_serenity_resource_root)), s_serenity_resource_root));
} else {
dbgln("Found app-browser.png, not re-extracting assets.");
dbgln("Hopefully no developer changed the asset files and expected them to be re-extracted!");
}
env->GetJavaVM(&global_vm);
VERIFY(global_vm);
s_java_instance = env->NewGlobalRef(thiz);
jclass clazz = env->GetObjectClass(s_java_instance);
VERIFY(clazz);
s_schedule_event_loop_method = env->GetMethodID(clazz, "scheduleEventLoop", "()V");
VERIFY(s_schedule_event_loop_method);
env->DeleteLocalRef(clazz);
jobject timer_service_ref = env->NewGlobalRef(timer_service);
auto* event_loop_manager = new Ladybird::ALooperEventLoopManager(timer_service_ref);
event_loop_manager->on_did_post_event = [] {
Ladybird::JavaEnvironment env(global_vm);
env.get()->CallVoidMethod(s_java_instance, s_schedule_event_loop_method);
};
Core::EventLoopManager::install(*event_loop_manager);
s_main_event_loop = make<Core::EventLoop>();
}
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_LadybirdActivity_execMainEventLoop(JNIEnv*, jobject /* thiz */)
{
if (s_main_event_loop) {
s_main_event_loop->pump(Core::EventLoop::WaitMode::PollForEvents);
}
}
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_LadybirdActivity_disposeNativeCode(JNIEnv* env, jobject /* thiz */)
{
s_main_event_loop = nullptr;
s_schedule_event_loop_method = nullptr;
env->DeleteGlobalRef(s_java_instance);
delete &Core::EventLoopManager::the();
}
ErrorOr<void> extract_tar_archive(String archive_file, ByteString output_directory)
{
constexpr size_t buffer_size = 4096;
auto file = TRY(Core::InputBufferedFile::create(TRY(Core::File::open(archive_file, Core::File::OpenMode::Read))));
ByteString old_pwd = TRY(Core::System::getcwd());
TRY(Core::System::chdir(output_directory));
ScopeGuard go_back = [&old_pwd] { MUST(Core::System::chdir(old_pwd)); };
auto tar_stream = TRY(Archive::TarInputStream::construct(move(file)));
HashMap<ByteString, ByteString> global_overrides;
HashMap<ByteString, ByteString> local_overrides;
auto get_override = [&](StringView key) -> Optional<ByteString> {
Optional<ByteString> maybe_local = local_overrides.get(key);
if (maybe_local.has_value())
return maybe_local;
Optional<ByteString> maybe_global = global_overrides.get(key);
if (maybe_global.has_value())
return maybe_global;
return {};
};
while (!tar_stream->finished()) {
Archive::TarFileHeader const& header = tar_stream->header();
// Handle meta-entries earlier to avoid consuming the file content stream.
if (header.content_is_like_extended_header()) {
switch (header.type_flag()) {
case Archive::TarFileType::GlobalExtendedHeader: {
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
if (value.length() == 0)
global_overrides.remove(key);
else
global_overrides.set(key, value);
}));
break;
}
case Archive::TarFileType::ExtendedHeader: {
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
local_overrides.set(key, value);
}));
break;
}
default:
warnln("Unknown extended header type '{}' of {}", (char)header.type_flag(), header.filename());
VERIFY_NOT_REACHED();
}
TRY(tar_stream->advance());
continue;
}
Archive::TarFileStream file_stream = tar_stream->file_contents();
// Handle other header types that don't just have an effect on extraction.
switch (header.type_flag()) {
case Archive::TarFileType::LongName: {
StringBuilder long_name;
Array<u8, buffer_size> buffer;
while (!file_stream.is_eof()) {
auto slice = TRY(file_stream.read_some(buffer));
long_name.append(reinterpret_cast<char*>(slice.data()), slice.size());
}
local_overrides.set("path", long_name.to_byte_string());
TRY(tar_stream->advance());
continue;
}
default:
// None of the relevant headers, so continue as normal.
break;
}
LexicalPath path = LexicalPath(header.filename());
if (!header.prefix().is_empty())
path = path.prepend(header.prefix());
ByteString filename = get_override("path"sv).value_or(path.string());
ByteString absolute_path = TRY(FileSystem::absolute_path(filename));
auto parent_path = LexicalPath(absolute_path).parent();
auto header_mode = TRY(header.mode());
switch (header.type_flag()) {
case Archive::TarFileType::NormalFile:
case Archive::TarFileType::AlternateNormalFile: {
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
int fd = TRY(Core::System::open(absolute_path, O_CREAT | O_WRONLY, header_mode));
Array<u8, buffer_size> buffer;
while (!file_stream.is_eof()) {
auto slice = TRY(file_stream.read_some(buffer));
TRY(Core::System::write(fd, slice));
}
TRY(Core::System::close(fd));
break;
}
case Archive::TarFileType::SymLink: {
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
TRY(Core::System::symlink(header.link_name(), absolute_path));
break;
}
case Archive::TarFileType::Directory: {
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
auto result_or_error = Core::System::mkdir(absolute_path, header_mode);
if (result_or_error.is_error() && result_or_error.error().code() != EEXIST)
return result_or_error.release_error();
break;
}
default:
// FIXME: Implement other file types
warnln("file type '{}' of {} is not yet supported", (char)header.type_flag(), header.filename());
VERIFY_NOT_REACHED();
}
// Non-global headers should be cleared after every file.
local_overrides.clear();
TRY(tar_stream->advance());
}
return {};
}

View File

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

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "LadybirdServiceBase.h"
#include <AK/Atomic.h>
#include <AK/Format.h>
#include <Ladybird/Utilities.h>
#include <LibCore/ResourceImplementationFile.h>
#include <jni.h>
JavaVM* global_vm;
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_LadybirdServiceBase_nativeThreadLoop(JNIEnv*, jobject /* thiz */, jint ipc_socket)
{
auto ret = service_main(ipc_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_LadybirdServiceBase_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;
}
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);
// FIXME: Use a custom Android version that uses AssetManager to load files.
Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(MUST(String::formatted("{}/res", s_serenity_resource_root))));
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

@ -1,48 +0,0 @@
/*
* 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<ByteString> find_certificates(StringView serenity_resource_root)
{
auto cert_path = ByteString::formatted("{}/res/ladybird/cacert.pem", serenity_resource_root);
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)
{
// Ensure the certificates are read out here.
DefaultRootCACertificates::set_default_certificate_paths(Vector { TRY(find_certificates(s_serenity_resource_root)) });
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
Core::EventLoop event_loop;
RequestServer::GeminiProtocol::install();
RequestServer::HttpProtocol::install();
RequestServer::HttpsProtocol::install();
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
auto client = TRY(RequestServer::ConnectionFromClient::try_create(move(socket)));
return event_loop.exec();
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ALooperEventLoopImplementation.h"
#include <LibCore/EventLoop.h>
#include <LibCore/ThreadEventQueue.h>
#include <jni.h>
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_TimerExecutorService_00024Timer_nativeRun(JNIEnv*, jobject /* thiz */, jlong native_data, jlong id)
{
static Core::EventLoop s_event_loop; // Here to exist for this thread
auto& event_loop_impl = *reinterpret_cast<Ladybird::ALooperEventLoopImplementation*>(native_data);
auto& thread_data = event_loop_impl.thread_data();
if (auto timer_data = thread_data.timers.get(id); timer_data.has_value()) {
auto receiver = timer_data->receiver.strong_ref();
if (!receiver)
return;
if (timer_data->visibility == Core::TimerShouldFireWhenNotVisible::No)
if (!receiver->is_visible_for_timer_purposes())
return;
event_loop_impl.post_event(*receiver, make<Core::TimerEvent>());
}
// Flush the event loop on this thread to keep any garbage from building up
if (auto num_events = s_event_loop.pump(Core::EventLoop::WaitMode::PollForEvents); num_events != 0) {
dbgln("BUG: Processed {} events on Timer thread!", num_events);
}
}

View File

@ -1,159 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebContentService.h"
#include "LadybirdServiceBase.h"
#include <AK/LexicalPath.h>
#include <Ladybird/FontPlugin.h>
#include <Ladybird/HelperProcess.h>
#include <Ladybird/ImageCodecPlugin.h>
#include <Ladybird/Utilities.h>
#include <LibAudio/Loader.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibCore/System.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibImageDecoderClient/Client.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibProtocol/RequestClient.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
#include <LibWebView/RequestServerAdapter.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageHost.h>
static ErrorOr<NonnullRefPtr<Protocol::RequestClient>> bind_request_server_service()
{
return bind_service<Protocol::RequestClient>(&bind_request_server_java);
}
template ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>, Error>
bind_service<ImageDecoderClient::Client>(void (*)(int));
static ErrorOr<void> load_content_filters();
static ErrorOr<void> load_autoplay_allowlist();
ErrorOr<int> service_main(int ipc_socket)
{
Core::EventLoop event_loop;
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPlugin);
Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
(void)loader;
return Error::from_string_literal("Don't know how to initialize audio in this configuration!");
});
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;
Web::HTML::Window::set_internals_object_exposed(is_layout_test_mode);
Web::Platform::FontPlugin::install(*new Ladybird::FontPlugin(is_layout_test_mode));
TRY(Web::Bindings::initialize_main_thread_vm());
auto maybe_content_filter_error = load_content_filters();
if (maybe_content_filter_error.is_error())
dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());
auto maybe_autoplay_allowlist_error = load_autoplay_allowlist();
if (maybe_autoplay_allowlist_error.is_error())
dbgln("Failed to load autoplay allowlist: {}", maybe_autoplay_allowlist_error.error());
auto webcontent_socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
auto webcontent_client = TRY(WebContent::ConnectionFromClient::try_create(move(webcontent_socket)));
return event_loop.exec();
}
template<typename Client>
ErrorOr<NonnullRefPtr<Client>> bind_service(void (*bind_method)(int))
{
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];
// NOTE: The java object takes ownership of the socket fds
(*bind_method)(server_fd);
auto socket = TRY(Core::LocalSocket::adopt_fd(ui_fd));
TRY(socket->set_blocking(true));
auto new_client = TRY(try_make_ref_counted<Client>(move(socket)));
return new_client;
}
static ErrorOr<void> load_content_filters()
{
auto file_or_error = Core::File::open(ByteString::formatted("{}/home/anon/.config/BrowserContentFilters.txt", s_serenity_resource_root), Core::File::OpenMode::Read);
if (file_or_error.is_error())
file_or_error = Core::File::open(ByteString::formatted("{}/res/ladybird/BrowserContentFilters.txt", s_serenity_resource_root), Core::File::OpenMode::Read);
if (file_or_error.is_error())
return file_or_error.release_error();
auto file = file_or_error.release_value();
auto ad_filter_list = TRY(Core::InputBufferedFile::create(move(file)));
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
Vector<String> patterns;
while (TRY(ad_filter_list->can_read_line())) {
auto line = TRY(ad_filter_list->read_line(buffer));
if (line.is_empty())
continue;
auto pattern = TRY(String::from_utf8(line));
TRY(patterns.try_append(move(pattern)));
}
auto& content_filter = Web::ContentFilter::the();
TRY(content_filter.set_patterns(patterns));
return {};
}
static ErrorOr<void> load_autoplay_allowlist()
{
auto file_or_error = Core::File::open(TRY(String::formatted("{}/home/anon/.config/BrowserAutoplayAllowlist.txt", s_serenity_resource_root)), Core::File::OpenMode::Read);
if (file_or_error.is_error())
file_or_error = Core::File::open(TRY(String::formatted("{}/res/ladybird/BrowserAutoplayAllowlist.txt", s_serenity_resource_root)), Core::File::OpenMode::Read);
if (file_or_error.is_error())
return file_or_error.release_error();
auto file = file_or_error.release_value();
auto allowlist = TRY(Core::InputBufferedFile::create(move(file)));
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
Vector<String> origins;
while (TRY(allowlist->can_read_line())) {
auto line = TRY(allowlist->read_line(buffer));
if (line.is_empty())
continue;
auto domain = TRY(String::from_utf8(line));
TRY(origins.try_append(move(domain)));
}
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
TRY(autoplay_allowlist.enable_for_origins(origins));
return {};
}

View File

@ -1,15 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
template<typename Client>
ErrorOr<NonnullRefPtr<Client>> bind_service(void (*bind_method)(int));
void bind_request_server_java(int ipc_socket);
void bind_image_decoder_java(int ipc_socket);

View File

@ -1,48 +0,0 @@
/*
* 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;
jmethodID bind_image_decoder_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", "(I)V");
if (!method)
TODO();
bind_request_server_method = method;
method = env->GetMethodID(global_class_reference, "bindImageDecoder", "(I)V");
if (!method)
TODO();
bind_image_decoder_method = method;
}
void bind_request_server_java(int ipc_socket)
{
Ladybird::JavaEnvironment env(global_vm);
env.get()->CallVoidMethod(global_instance, bind_request_server_method, ipc_socket);
}
void bind_image_decoder_java(int ipc_socket)
{
Ladybird::JavaEnvironment env(global_vm);
env.get()->CallVoidMethod(global_instance, bind_image_decoder_method, ipc_socket);
}

View File

@ -1,127 +0,0 @@
/*
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebViewImplementationNative.h"
#include "JNIHelpers.h"
#include <LibWebView/WebContentClient.h>
#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>
#include <android/bitmap.h>
#include <jni.h>
namespace Ladybird {
static Gfx::BitmapFormat to_gfx_bitmap_format(i32 f)
{
switch (f) {
case ANDROID_BITMAP_FORMAT_RGBA_8888:
return Gfx::BitmapFormat::BGRA8888;
default:
VERIFY_NOT_REACHED();
}
}
WebViewImplementationNative::WebViewImplementationNative(jobject thiz)
: m_java_instance(thiz)
{
// NOTE: m_java_instance's global ref is controlled by the JNI bindings
initialize_client(CreateNewClient::Yes);
on_ready_to_paint = [this]() {
JavaEnvironment env(global_vm);
env.get()->CallVoidMethod(m_java_instance, invalidate_layout_method);
};
on_load_start = [this](URL::URL const& url, bool is_redirect) {
JavaEnvironment env(global_vm);
auto url_string = env.jstring_from_ak_string(MUST(url.to_string()));
env.get()->CallVoidMethod(m_java_instance, on_load_start_method, url_string, is_redirect);
env.get()->DeleteLocalRef(url_string);
};
}
void WebViewImplementationNative::initialize_client(WebView::ViewImplementation::CreateNewClient)
{
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 = [] {
warnln("WebContent crashed!");
// FIXME: launch a new client
};
m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid());
client().async_set_window_handle(0, m_client_state.client_handle);
client().async_set_device_pixels_per_css_pixel(0, m_device_pixel_ratio);
// FIXME: update_palette, update system fonts
}
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);
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 WebViewImplementationNative::set_viewport_geometry(int w, int h)
{
m_viewport_rect = { { 0, 0 }, { w, h } };
client().async_set_viewport_rect(0, m_viewport_rect);
handle_resize();
}
void WebViewImplementationNative::set_device_pixel_ratio(float f)
{
m_device_pixel_ratio = f;
client().async_set_device_pixels_per_css_pixel(0, m_device_pixel_ratio);
}
NonnullRefPtr<WebView::WebContentClient> WebViewImplementationNative::bind_web_content_client()
{
JavaEnvironment env(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];
// NOTE: The java object takes ownership of the socket fds
env.get()->CallVoidMethod(m_java_instance, bind_webcontent_method, wc_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);
return new_client;
}
}

View File

@ -1,43 +0,0 @@
/*
* 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 Web::DevicePixelRect 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 initialize_client(CreateNewClient) 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 jmethodID on_load_start_method;
jobject java_instance() const { return m_java_instance; }
private:
jobject m_java_instance = nullptr;
Web::DevicePixelRect m_viewport_rect;
};
}

View File

@ -1,95 +0,0 @@
/*
* 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;
jmethodID WebViewImplementationNative::on_load_start_method;
extern "C" JNIEXPORT void JNICALL
Java_org_serenityos_ladybird_WebViewImplementation_00024Companion_nativeClassInit(JNIEnv* env, jobject /* thiz */)
{
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", "(I)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;
method = env->GetMethodID(WebViewImplementationNative::global_class_reference, "onLoadStart", "(Ljava/lang/String;Z)V");
if (!method)
TODO();
WebViewImplementationNative::on_load_start_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 = 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

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

View File

@ -1,78 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
package org.serenityos.ladybird
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.KeyEvent
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.TextView
import org.serenityos.ladybird.databinding.ActivityMainBinding
class LadybirdActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var resourceDir: String
private lateinit var view: WebView
private lateinit var urlEditText: EditText
private var timerService = TimerExecutorService()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
resourceDir = TransferAssets.transferAssets(this)
initNativeCode(resourceDir, "Ladybird", timerService)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
urlEditText = binding.urlEditText
view = binding.webView
view.onLoadStart = { url: String, _ ->
urlEditText.setText(url, TextView.BufferType.EDITABLE)
}
urlEditText.setOnEditorActionListener { textView: TextView, actionId: Int, _: KeyEvent? ->
when (actionId) {
EditorInfo.IME_ACTION_GO, EditorInfo.IME_ACTION_SEARCH -> view.loadURL(textView.text.toString())
}
false
}
view.initialize(resourceDir)
view.loadURL(intent.dataString ?: "https://ladybird.dev")
}
override fun onStart() {
super.onStart()
}
override fun onDestroy() {
view.dispose()
disposeNativeCode()
super.onDestroy()
}
private fun scheduleEventLoop() {
mainExecutor.execute {
execMainEventLoop()
}
}
private external fun initNativeCode(
resourceDir: String, tag: String, timerService: TimerExecutorService
)
private external fun disposeNativeCode()
private external fun execMainEventLoop()
companion object {
// Used to load the 'ladybird' library on application startup.
init {
System.loadLibrary("Ladybird")
}
}
}

View File

@ -1,95 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* 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.Looper
import android.os.Message
import android.os.Messenger
import java.lang.ref.WeakReference
import java.util.concurrent.Executors
const val MSG_SET_RESOURCE_ROOT = 1
const val MSG_TRANSFER_SOCKET = 2
abstract class LadybirdServiceBase(protected val TAG: String) : Service() {
private val threadPool = Executors.newCachedThreadPool()
protected lateinit var resourceDir: String
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<ParcelFileDescriptor>("IPC_SOCKET")!!
createThread(ipcSocket)
}
private fun handleSetResourceRoot(msg: Message) {
// FIXME: Handle this being already set, not being present, etc
resourceDir = msg.data.getString("PATH")!!
initNativeCode(resourceDir, TAG)
}
override fun onBind(p0: Intent?): IBinder? {
// FIXME: Check the intent to make sure it's legit
return Messenger(IncomingHandler(WeakReference(this))).binder
}
private fun createThread(ipcSocket: ParcelFileDescriptor) {
threadPool.execute {
nativeThreadLoop(ipcSocket.detachFd())
}
}
private external fun nativeThreadLoop(ipcSocket: Int)
private external fun initNativeCode(resourceDir: String, tagName: String);
abstract fun handleServiceSpecificMessage(msg: Message): Boolean
companion object {
class IncomingHandler(private val service: WeakReference<LadybirdServiceBase>) :
Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_TRANSFER_SOCKET -> service.get()?.handleTransferSockets(msg)
?: super.handleMessage(msg)
MSG_SET_RESOURCE_ROOT -> service.get()?.handleSetResourceRoot(msg)
?: super.handleMessage(msg)
else -> {
val ret = service.get()?.handleServiceSpecificMessage(msg)
if (ret == null || !ret)
super.handleMessage(msg)
}
}
}
}
}
}

View File

@ -1,52 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* 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 LadybirdServiceConnection(
private var ipcFd: Int,
private var resourceDir: String
) :
ServiceConnection {
var boundToService: Boolean = false
var onDisconnect: () -> Unit = {}
private var service: 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.
service = Messenger(svc)
boundToService = true
val init = Message.obtain(null, MSG_SET_RESOURCE_ROOT)
init.data.putString("PATH", resourceDir)
service!!.send(init)
val msg = Message.obtain(null, MSG_TRANSFER_SOCKET)
msg.data.putParcelable("IPC_SOCKET", ParcelFileDescriptor.adoptFd(ipcFd))
service!!.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.
service = null
boundToService = false
// Notify owner that the service is dead
onDisconnect()
}
}

View File

@ -1,21 +0,0 @@
/**
* 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

@ -1,51 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
package org.serenityos.ladybird
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
class TimerExecutorService {
private val executor = Executors.newSingleThreadScheduledExecutor()
class Timer(private var nativeData: Long) : Runnable {
override fun run() {
nativeRun(nativeData, id)
}
private external fun nativeRun(nativeData: Long, id: Long)
var id: Long = 0
}
fun registerTimer(timer: Timer, singleShot: Boolean, milliseconds: Long): Long {
val id = ++nextId
timer.id = id
val handle: ScheduledFuture<*> = if (singleShot) executor.schedule(
timer,
milliseconds,
TimeUnit.MILLISECONDS
) else executor.scheduleWithFixedDelay(
timer,
milliseconds,
milliseconds,
TimeUnit.MILLISECONDS
)
timers[id] = handle
return id
}
fun unregisterTimer(id: Long) {
val timer = timers[id] ?: return
timer.cancel(false)
}
private var nextId: Long = 0
private val timers: HashMap<Long, ScheduledFuture<*>> = hashMapOf()
}

View File

@ -1,63 +0,0 @@
/**
* Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
* <p>
* SPDX-License-Identifier: BSD-2-Clause
*/
package org.serenityos.ladybird;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.String;
public class TransferAssets {
/**
* @return new ladybird resource root
*/
static public String transferAssets(Context context) {
Log.d("Ladybird", "Hello from java");
Context applicationContext = context.getApplicationContext();
File assetDir = applicationContext.getFilesDir();
AssetManager assetManager = applicationContext.getAssets();
if (!copyAsset(assetManager, "ladybird-assets.tar", assetDir.getAbsolutePath() + "/ladybird-assets.tar")) {
Log.e("Ladybird", "Unable to copy assets");
return "Invalid Assets, this won't work";
}
Log.d("Ladybird", "Copied ladybird-assets.tar to app-specific storage path");
return assetDir.getAbsolutePath();
}
// ty to https://stackoverflow.com/a/22903693 for the sauce
private static boolean copyAsset(AssetManager assetManager,
String fromAssetPath, String toPath) {
try {
InputStream in = assetManager.open(fromAssetPath);
new File(toPath).createNewFile();
OutputStream out = new FileOutputStream(toPath);
copyFile(in, out);
in.close();
out.flush();
out.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private static void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[4096];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
}

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
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)
{
val connector = LadybirdServiceConnection(ipcFd, 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
)
}
private fun bindImageDecoder(ipcFd: Int)
{
val connector = LadybirdServiceConnection(ipcFd, resourceDir)
connector.onDisconnect = {
// FIXME: Notify impl that service is dead and might need restarted
Log.e(TAG, "ImageDecoder Died! :(")
}
// FIXME: Unbind this at some point maybe
bindService(
Intent(this, ImageDecoderService::class.java),
connector,
Context.BIND_AUTO_CREATE
)
}
external fun nativeInit()
companion object {
init {
System.loadLibrary("webcontent")
}
}
}

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
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(this)
private lateinit var contentBitmap: Bitmap
var onLoadStart: (url: String, isRedirect: Boolean) -> Unit = { _, _ -> }
fun initialize(resourceDir: String) {
viewImpl.initialize(resourceDir)
}
fun dispose() {
viewImpl.dispose()
}
fun loadURL(url: String) {
viewImpl.loadURL(url)
}
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)
val pixelDensity = context.resources.displayMetrics.density
viewImpl.setDevicePixelRatio(pixelDensity)
// 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)
}
}

View File

@ -1,98 +0,0 @@
/**
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
package org.serenityos.ladybird
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Bitmap
import android.util.Log
import android.view.View
import java.net.URL
/**
* Wrapper around WebView::ViewImplementation for use by Kotlin
*/
class WebViewImplementation(private val view: WebView) {
// Instance Pointer to native object, very unsafe :)
private var nativeInstance: Long = 0
private lateinit var resourceDir: String
private lateinit var connection: ServiceConnection
fun initialize(resourceDir: String) {
this.resourceDir = resourceDir
nativeInstance = nativeObjectInit()
}
fun dispose() {
nativeObjectDispose(nativeInstance)
nativeInstance = 0
}
fun loadURL(url: String) {
nativeLoadURL(nativeInstance, url)
}
fun drawIntoBitmap(bitmap: Bitmap) {
nativeDrawIntoBitmap(nativeInstance, bitmap)
}
fun setViewportGeometry(w: Int, h: Int) {
nativeSetViewportGeometry(nativeInstance, w, h)
}
fun setDevicePixelRatio(ratio: Float) {
nativeSetDevicePixelRatio(nativeInstance, ratio)
}
// Functions called from native code
fun bindWebContentService(ipcFd: Int) {
val connector = LadybirdServiceConnection(ipcFd, resourceDir)
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
view.context.bindService(
Intent(view.context, WebContentService::class.java),
connector,
Context.BIND_AUTO_CREATE
)
connection = connector
}
fun invalidateLayout() {
view.requestLayout()
view.invalidate()
}
fun onLoadStart(url: String, isRedirect: Boolean) {
view.onLoadStart(url, isRedirect)
}
// Functions implemented in native code
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)
private external fun nativeSetDevicePixelRatio(instance: Long, ratio: Float)
private external fun nativeLoadURL(instance: Long, url: String)
companion object {
/*
* We use a static class initializer to allow the native code to cache some
* field offsets. This native function looks up and caches interesting
* class/field/method IDs. Throws on failure.
*/
private external fun nativeClassInit()
init {
nativeClassInit()
}
}
};

View File

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".LadybirdActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<!-- FIXME: Add Navigation, URL bar, Tab interactions, etc -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|snap|enterAlways">
<EditText
android:id="@+id/urlEditText"
style="@style/Widget.AppCompat.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="url"
android:ems="10"
android:hint="@string/url_edit_default"
android:imeOptions="actionGo|actionSearch"
android:inputType="textUri"
android:singleLine="true" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<org.serenityos.ladybird.WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LadybirdActivity" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,19 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Ladybird" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@color/grey</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="grey">#FF6B6B6B</color>
</resources>

View File

@ -1,4 +0,0 @@
<resources>
<string name="app_name">Ladybird</string>
<string name="url_edit_default">Enter URL...</string>
</resources>

View File

@ -1,19 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Ladybird" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@color/white</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -8,7 +8,7 @@ project(ladybird
include(GNUInstallDirs)
if (ANDROID OR IOS)
if (IOS)
set(BUILD_SHARED_LIBS OFF)
endif()
@ -79,17 +79,12 @@ add_compile_options(-DAK_DONT_REPLACE_STD)
add_compile_options(-Wno-expansion-to-defined)
add_compile_options(-Wno-user-defined-literals)
if (ANDROID OR APPLE)
if (APPLE)
serenity_option(ENABLE_QT OFF CACHE BOOL "Build ladybird application using Qt GUI")
else()
serenity_option(ENABLE_QT ON CACHE BOOL "Build ladybird application using Qt GUI")
endif()
if (ANDROID AND ENABLE_QT)
message(STATUS "Disabling Qt for Android")
set(ENABLE_QT OFF CACHE BOOL "" FORCE)
endif()
if (ENABLE_QT)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
@ -158,17 +153,6 @@ elseif (APPLE)
-fobjc-arc
-Wno-deprecated-anon-enum-enum-conversion # Required for CGImageCreate
)
elseif(ANDROID)
add_library(ladybird SHARED
${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
Android/src/main/cpp/JNIHelpers.cpp
)
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
@ -215,10 +199,6 @@ target_include_directories(headless-browser PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(headless-browser PRIVATE ${SERENITY_SOURCE_DIR}/Userland/)
target_link_libraries(headless-browser PRIVATE AK LibCore LibWeb LibWebView LibWebSocket LibCrypto LibFileSystem LibGemini LibHTTP LibImageDecoderClient LibJS LibGfx LibMain LibSQL LibTLS LibIPC LibDiff LibProtocol LibURL)
if (ANDROID)
include(cmake/AndroidExtras.cmake)
endif()
add_custom_target(run${LADYBIRD_CUSTOM_TARGET_SUFFIX}
COMMAND "${CMAKE_COMMAND}" -E env "SERENITY_SOURCE_DIR=${SERENITY_SOURCE_DIR}" "$<TARGET_FILE:ladybird>" $ENV{LAGOM_ARGS}
USES_TERMINAL

View File

@ -6,11 +6,7 @@
*/
#include "ImageCodecPlugin.h"
#ifdef AK_OS_ANDROID
# include <Ladybird/Android/src/main/cpp/WebContentService.h>
#else
# include "HelperProcess.h"
#endif
#include "HelperProcess.h"
#include "Utilities.h"
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
@ -23,12 +19,8 @@ ImageCodecPlugin::~ImageCodecPlugin() = default;
NonnullRefPtr<Core::Promise<Web::Platform::DecodedImage>> ImageCodecPlugin::decode_image(ReadonlyBytes bytes, Function<ErrorOr<void>(Web::Platform::DecodedImage&)> on_resolved, Function<void(Error&)> on_rejected)
{
if (!m_client) {
#ifdef AK_OS_ANDROID
m_client = MUST(bind_service<ImageDecoderClient::Client>(&bind_image_decoder_java));
#else
auto candidate_image_decoder_paths = get_paths_for_helper_process("ImageDecoder"sv).release_value_but_fixme_should_propagate_errors();
m_client = launch_image_decoder_process(candidate_image_decoder_paths).release_value_but_fixme_should_propagate_errors();
#endif
m_client->on_death = [&] {
m_client = nullptr;
};

View File

@ -8,16 +8,7 @@ set(IMAGE_DECODER_SOURCES
${IMAGE_DECODER_SOURCE_DIR}/ConnectionFromClient.cpp
)
if (ANDROID)
add_library(imagedecoder SHARED
${IMAGE_DECODER_SOURCES}
../Android/src/main/cpp/ImageDecoderService.cpp
../Android/src/main/cpp/LadybirdServiceBaseJNI.cpp
../Utilities.cpp
)
else()
add_library(imagedecoder STATIC ${IMAGE_DECODER_SOURCES})
endif()
add_library(imagedecoder STATIC ${IMAGE_DECODER_SOURCES})
add_executable(ImageDecoder main.cpp)
target_link_libraries(ImageDecoder PRIVATE imagedecoder LibCore LibMain)

View File

@ -17,16 +17,7 @@ set(REQUESTSERVER_SOURCES
${REQUESTSERVER_SOURCE_DIR}/Protocol.cpp
)
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()
add_library(requestserver STATIC ${REQUESTSERVER_SOURCES})
add_executable(RequestServer main.cpp)
target_link_libraries(RequestServer PRIVATE requestserver)

View File

@ -41,7 +41,7 @@ if (ENABLE_QT)
endif()
else()
set(LIB_TYPE STATIC)
if (ANDROID OR IOS)
if (IOS)
set(LIB_TYPE SHARED)
endif()
add_library(webcontent ${LIB_TYPE} ${WEBCONTENT_SOURCES})
@ -64,16 +64,6 @@ else()
${WEBCONTENT_SOURCE_DIR}/WebDriverConnection.h
)
if (ANDROID)
target_sources(webcontent PRIVATE
../Android/src/main/cpp/WebContentService.cpp
../Android/src/main/cpp/WebContentServiceJNI.cpp
../Android/src/main/cpp/LadybirdServiceBaseJNI.cpp
../Android/src/main/cpp/JNIHelpers.cpp
)
target_link_libraries(webcontent PRIVATE android)
endif()
add_executable(WebContent main.cpp)
target_link_libraries(WebContent PRIVATE webcontent)
endif()

View File

@ -12,8 +12,6 @@ set(WEBWORKER_SOURCES
../Utilities.cpp
)
# FIXME: Add Android service
add_library(webworker STATIC ${WEBWORKER_SOURCES})
target_include_directories(webworker PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/)

View File

@ -1,44 +0,0 @@
# Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
#
# SPDX-License-Identifier: BSD-2-Clause
#
#
# Copy resources into tarball for inclusion in /assets of APK
#
set(LADYBIRD_RESOURCE_ROOT "${SERENITY_SOURCE_DIR}/Base/res")
macro(copy_res_folder folder)
add_custom_target(copy-${folder}
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${LADYBIRD_RESOURCE_ROOT}/${folder}"
"asset-bundle/res/${folder}"
)
add_dependencies(archive-assets copy-${folder})
endmacro()
add_custom_target(archive-assets COMMAND ${CMAKE_COMMAND} -E chdir asset-bundle tar czf ../ladybird-assets.tar.gz ./ )
copy_res_folder(html)
copy_res_folder(fonts)
copy_res_folder(icons)
copy_res_folder(emoji)
copy_res_folder(themes)
copy_res_folder(color-palettes)
copy_res_folder(cursor-themes)
add_custom_target(copy-autoplay-allowlist
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SERENITY_SOURCE_DIR}/Base/home/anon/.config/BrowserAutoplayAllowlist.txt"
"asset-bundle/res/ladybird/BrowserAutoplayAllowlist.txt"
)
add_custom_target(copy-content-filters
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SERENITY_SOURCE_DIR}/Base/home/anon/.config/BrowserContentFilters.txt"
"asset-bundle/res/ladybird/BrowserContentFilters.txt"
)
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

@ -4,8 +4,5 @@ config("pthread_config") {
}
group("pthread") {
# On Android, bionic has built-in support for pthreads.
if (current_os != "android") {
public_configs = [ ":pthread_config" ]
}
public_configs = [ ":pthread_config" ]
}

View File

@ -93,8 +93,8 @@ Optional<StringView> get(StringView name, [[maybe_unused]] SecureOnly secure)
builder.append('\0');
// Note the explicit null terminators above.
// FreeBSD < 14, Android, and generic BSDs do not support secure_getenv.
#if (defined(__FreeBSD__) && __FreeBSD__ >= 14) || (!defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID))
// FreeBSD < 14, and generic BSDs do not support secure_getenv.
#if (defined(__FreeBSD__) && __FreeBSD__ >= 14) || !defined(AK_OS_BSD_GENERIC)
char* result;
if (secure == SecureOnly::Yes) {
result = ::secure_getenv(builder.string_view().characters_without_null_termination());

View File

@ -67,7 +67,7 @@ ErrorOr<void> Group::sync()
return {};
}
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID) && !defined(AK_OS_HAIKU)
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_HAIKU)
ErrorOr<void> Group::add_group(Group& group)
{
if (group.name().is_empty())

View File

@ -15,7 +15,7 @@ namespace Core {
class Group {
public:
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID) && !defined(AK_OS_HAIKU)
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_HAIKU)
static ErrorOr<void> add_group(Group& group);
#endif

View File

@ -182,7 +182,7 @@ ErrorOr<String> Process::get_name()
if (rc != 0)
return Error::from_syscall("get_process_name"sv, -rc);
return String::from_utf8(StringView { buffer, strlen(buffer) });
#elif defined(AK_LIBC_GLIBC) || (defined(AK_OS_LINUX) && !defined(AK_OS_ANDROID))
#elif defined(AK_LIBC_GLIBC) || defined(AK_OS_LINUX)
return String::from_utf8(StringView { program_invocation_name, strlen(program_invocation_name) });
#elif defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU)
auto const* progname = getprogname();

View File

@ -374,7 +374,7 @@ ErrorOr<void> profiling_free_buffer(pid_t pid)
}
#endif
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID)
#if !defined(AK_OS_BSD_GENERIC)
ErrorOr<Optional<struct spwd>> getspent()
{
errno = 0;
@ -1264,7 +1264,7 @@ ErrorOr<struct utsname> uname()
return uts;
}
#if !defined(AK_OS_ANDROID) && !defined(AK_OS_HAIKU)
#if !defined(AK_OS_HAIKU)
ErrorOr<void> adjtime(const struct timeval* delta, struct timeval* old_delta)
{
# ifdef AK_OS_SERENITY
@ -1784,7 +1784,7 @@ ErrorOr<String> resolve_executable_from_environment(StringView filename, int fla
ErrorOr<ByteString> current_executable_path()
{
char path[4096] = {};
#if defined(AK_OS_LINUX) || defined(AK_OS_ANDROID) || defined(AK_OS_SERENITY)
#if defined(AK_OS_LINUX) || defined(AK_OS_SERENITY)
auto ret = ::readlink("/proc/self/exe", path, sizeof(path) - 1);
// Ignore error if it wasn't a symlink
if (ret == -1 && errno != EINVAL)

View File

@ -35,7 +35,7 @@
# include <Kernel/API/Jail.h>
#endif
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID)
#if !defined(AK_OS_BSD_GENERIC)
# include <shadow.h>
#endif
@ -100,7 +100,7 @@ ALWAYS_INLINE ErrorOr<void> unveil(nullptr_t, nullptr_t)
return unveil(StringView {}, StringView {});
}
#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_ANDROID)
#if !defined(AK_OS_BSD_GENERIC)
ErrorOr<Optional<struct spwd>> getspent();
ErrorOr<Optional<struct spwd>> getspnam(StringView name);
#endif
@ -193,7 +193,7 @@ ErrorOr<void> utime(StringView path, Optional<struct utimbuf>);
ErrorOr<void> utimensat(int fd, StringView path, struct timespec const times[2], int flag);
ErrorOr<struct utsname> uname();
ErrorOr<Array<int, 2>> pipe2(int flags);
#if !defined(AK_OS_ANDROID) && !defined(AK_OS_HAIKU)
#if !defined(AK_OS_HAIKU)
ErrorOr<void> adjtime(const struct timeval* delta, struct timeval* old_delta);
#endif
enum class SearchInPath {

View File

@ -25,8 +25,6 @@ namespace Web {
#if defined(AK_OS_SERENITY)
# define OS_STRING "SerenityOS"
#elif defined(AK_OS_ANDROID)
# define OS_STRING "Android 10"
#elif defined(AK_OS_LINUX)
# define OS_STRING "Linux"
#elif defined(AK_OS_MACOS)