mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 05:25:32 +03:00
Ladybird/Android: Add EventLoopImplementation for ALooper
Timers run in their own thread, to take advantage of existing Java Executor features. By hooking into ALooper, we can spin the main Activity's UI thread event loop without causing a fuss, or spinning the CPU by just polling our event loop constantly.
This commit is contained in:
parent
d63fb8fa61
commit
d93911928b
Notes:
sideshowbarker
2024-07-18 03:35:30 +09:00
Author: https://github.com/ADKaster Commit: https://github.com/SerenityOS/serenity/commit/d93911928b Pull-request: https://github.com/SerenityOS/serenity/pull/21007
237
Ladybird/Android/src/main/cpp/ALooperEventLoopImplementation.cpp
Normal file
237
Ladybird/Android/src/main/cpp/ALooperEventLoopImplementation.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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;
|
||||
return s_thread_data;
|
||||
}
|
||||
|
||||
static ALooperEventLoopImplementation& current_impl()
|
||||
{
|
||||
return verify_cast<ALooperEventLoopImplementation>(Core::EventLoop::current().impl());
|
||||
}
|
||||
|
||||
ALooperEventLoopManager::ALooperEventLoopManager(JavaVM* vm, jobject timer_service)
|
||||
: m_vm(vm)
|
||||
, m_timer_service(timer_service)
|
||||
{
|
||||
JavaEnvironment env(m_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)Z");
|
||||
if (!m_unregister_timer)
|
||||
TODO();
|
||||
env.get()->DeleteLocalRef(timer_service_class);
|
||||
}
|
||||
|
||||
ALooperEventLoopManager::~ALooperEventLoopManager()
|
||||
{
|
||||
JavaEnvironment env(m_vm);
|
||||
|
||||
env.get()->DeleteGlobalRef(m_timer_service);
|
||||
env.get()->DeleteGlobalRef(m_timer_class);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<Core::EventLoopImplementation> ALooperEventLoopManager::make_implementation()
|
||||
{
|
||||
return ALooperEventLoopImplementation::create();
|
||||
}
|
||||
|
||||
int ALooperEventLoopManager::register_timer(Core::EventReceiver& receiver, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible visibility)
|
||||
{
|
||||
JavaEnvironment env(m_vm);
|
||||
auto& thread_data = EventLoopThreadData::the();
|
||||
|
||||
auto timer = env.get()->NewObject(m_timer_class, m_timer_constructor, reinterpret_cast<long>(&thread_data));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool ALooperEventLoopManager::unregister_timer(int timer_id)
|
||||
{
|
||||
if (auto timer = EventLoopThreadData::the().timers.take(timer_id); timer.has_value()) {
|
||||
JavaEnvironment env(m_vm);
|
||||
return env.get()->CallBooleanMethod(m_timer_service, m_unregister_timer, timer_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ALooperEventLoopManager::register_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
EventLoopThreadData::the().notifiers.set(¬ifier);
|
||||
current_impl().register_notifier(notifier);
|
||||
}
|
||||
|
||||
void ALooperEventLoopManager::unregister_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
EventLoopThreadData::the().notifiers.remove(¬ifier);
|
||||
current_impl().unregister_notifier(notifier);
|
||||
}
|
||||
|
||||
void ALooperEventLoopManager::did_post_event()
|
||||
{
|
||||
current_impl().poke();
|
||||
}
|
||||
|
||||
ALooperEventLoopImplementation::ALooperEventLoopImplementation()
|
||||
: m_event_loop(ALooper_prepare(0))
|
||||
{
|
||||
auto ret = pipe2(m_pipe, O_CLOEXEC | O_NONBLOCK);
|
||||
VERIFY(ret == 0);
|
||||
|
||||
ALooper_acquire(m_event_loop);
|
||||
|
||||
ret = ALooper_addFd(m_event_loop, m_pipe[0], ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, &ALooperEventLoopImplementation::looper_callback, this);
|
||||
VERIFY(ret == 1);
|
||||
}
|
||||
|
||||
ALooperEventLoopImplementation::~ALooperEventLoopImplementation()
|
||||
{
|
||||
ALooper_removeFd(m_event_loop, m_pipe[0]);
|
||||
ALooper_release(m_event_loop);
|
||||
|
||||
::close(m_pipe[0]);
|
||||
::close(m_pipe[1]);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
int ALooperEventLoopImplementation::looper_callback(int fd, int events, void* data)
|
||||
{
|
||||
auto& impl = *static_cast<ALooperEventLoopImplementation*>(data);
|
||||
(void)impl; // FIXME: Do we need to do anything with the instance here?
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ALooperEventLoopImplementation::poke()
|
||||
{
|
||||
int msg = 0xCAFEBABE;
|
||||
(void)write(m_pipe[1], &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
static int notifier_callback(int fd, int, void* data)
|
||||
{
|
||||
auto& notifier = *static_cast<Core::Notifier*>(data);
|
||||
|
||||
VERIFY(fd == notifier.fd());
|
||||
|
||||
Core::NotifierActivationEvent event(notifier.fd());
|
||||
notifier.dispatch_event(event);
|
||||
|
||||
// Wake up from ALooper_pollAll, and service this event on the event queue
|
||||
current_impl().poke();
|
||||
|
||||
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::Exceptional:
|
||||
case Core::Notifier::Type::None:
|
||||
TODO();
|
||||
}
|
||||
|
||||
auto ret = ALooper_addFd(m_event_loop, notifier.fd(), ALOOPER_POLL_CALLBACK, event_flags, ¬ifier_callback, ¬ifier);
|
||||
VERIFY(ret == 1);
|
||||
}
|
||||
|
||||
void ALooperEventLoopImplementation::unregister_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
ALooper_removeFd(m_event_loop, notifier.fd());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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(JavaVM*, jobject timer_service);
|
||||
virtual ~ALooperEventLoopManager() override;
|
||||
virtual NonnullOwnPtr<Core::EventLoopImplementation> make_implementation() override;
|
||||
|
||||
virtual int register_timer(Core::EventReceiver&, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible) override;
|
||||
virtual bool unregister_timer(int timer_id) override;
|
||||
|
||||
virtual void register_notifier(Core::Notifier&) override;
|
||||
virtual void unregister_notifier(Core::Notifier&) override;
|
||||
|
||||
virtual void did_post_event() override;
|
||||
|
||||
// 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:
|
||||
JavaVM* m_vm { 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 };
|
||||
};
|
||||
|
||||
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 { }
|
||||
|
||||
void poke();
|
||||
|
||||
private:
|
||||
friend class ALooperEventLoopManager;
|
||||
|
||||
ALooperEventLoopImplementation();
|
||||
|
||||
static int looper_callback(int fd, int events, void* data);
|
||||
|
||||
void register_notifier(Core::Notifier&);
|
||||
void unregister_notifier(Core::Notifier&);
|
||||
|
||||
ALooper* m_event_loop { nullptr };
|
||||
int m_pipe[2] {};
|
||||
int m_exit_code { 0 };
|
||||
Atomic<bool> m_exit_requested { false };
|
||||
};
|
||||
|
||||
struct TimerData {
|
||||
WeakPtr<Core::EventReceiver> receiver;
|
||||
Core::TimerShouldFireWhenNotVisible visibility;
|
||||
};
|
||||
|
||||
struct EventLoopThreadData {
|
||||
static EventLoopThreadData& the();
|
||||
|
||||
HashMap<long, TimerData> timers;
|
||||
HashTable<Core::Notifier*> notifiers;
|
||||
};
|
||||
|
||||
}
|
43
Ladybird/Android/src/main/cpp/JNIHelpers.h
Normal file
43
Ladybird/Android/src/main/cpp/JNIHelpers.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <jni.h>
|
||||
|
||||
class JavaEnvironment {
|
||||
public:
|
||||
JavaEnvironment(JavaVM* vm)
|
||||
: m_vm(vm)
|
||||
{
|
||||
auto ret = m_vm->GetEnv(reinterpret_cast<void**>(&m_env), JNI_VERSION_1_6);
|
||||
if (ret == JNI_EDETACHED) {
|
||||
ret = m_vm->AttachCurrentThread(&m_env, nullptr);
|
||||
VERIFY(ret == JNI_OK);
|
||||
m_did_attach_thread = true;
|
||||
} else if (ret == JNI_EVERSION) {
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
VERIFY(ret == JNI_OK);
|
||||
}
|
||||
|
||||
VERIFY(m_env != nullptr);
|
||||
}
|
||||
|
||||
~JavaEnvironment()
|
||||
{
|
||||
if (m_did_attach_thread)
|
||||
m_vm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
JNIEnv* get() const { return m_env; }
|
||||
|
||||
private:
|
||||
JavaVM* m_vm = nullptr;
|
||||
JNIEnv* m_env = nullptr;
|
||||
bool m_did_attach_thread = false;
|
||||
};
|
@ -4,15 +4,40 @@
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ALooperEventLoopImplementation.h"
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <Ladybird/Utilities.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
OwnPtr<Core::EventLoop> s_main_event_loop;
|
||||
RefPtr<Core::Timer> s_timer;
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobject /* thiz */, jstring resource_dir)
|
||||
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobject /* thiz */, jstring resource_dir, jobject timer_service)
|
||||
{
|
||||
char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr);
|
||||
s_serenity_resource_root = raw_resource_dir;
|
||||
__android_log_print(ANDROID_LOG_INFO, "Ladybird", "Serenity resource dir is %s", s_serenity_resource_root.characters());
|
||||
env->ReleaseStringUTFChars(resource_dir, raw_resource_dir);
|
||||
|
||||
jobject timer_service_ref = env->NewGlobalRef(timer_service);
|
||||
JavaVM* vm = nullptr;
|
||||
jint ret = env->GetJavaVM(&vm);
|
||||
VERIFY(ret == 0);
|
||||
Core::EventLoopManager::install(*new Ladybird::ALooperEventLoopManager(vm, timer_service_ref));
|
||||
s_main_event_loop = make<Core::EventLoop>();
|
||||
|
||||
s_timer = MUST(Core::Timer::create_repeating(1000, [] {
|
||||
__android_log_print(ANDROID_LOG_DEBUG, "Ladybird", "EventLoop is alive!");
|
||||
}));
|
||||
s_timer->start();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_serenityos_ladybird_LadybirdActivity_execMainEventLoop(JNIEnv*, jobject /* thiz */)
|
||||
{
|
||||
s_main_event_loop->pump(Core::EventLoop::WaitMode::PollForEvents);
|
||||
}
|
||||
|
29
Ladybird/Android/src/main/cpp/TimerExecutorService.cpp
Normal file
29
Ladybird/Android/src/main/cpp/TimerExecutorService.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ALooperEventLoopImplementation.h"
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <jni.h>
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_org_serenityos_ladybird_TimerExecutorService_00024Timer_nativeRun(JNIEnv*, jobject /* thiz */, jlong native_data, jlong id)
|
||||
{
|
||||
auto& thread_data = *reinterpret_cast<Ladybird::EventLoopThreadData*>(native_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;
|
||||
|
||||
Core::TimerEvent event(id);
|
||||
|
||||
// FIXME: Should the dispatch happen on the thread that registered the timer?
|
||||
receiver->dispatch_event(event);
|
||||
}
|
||||
}
|
@ -14,4 +14,7 @@ Java_org_serenityos_ladybird_WebContentService_nativeHandleTransferSockets(JNIEn
|
||||
__android_log_print(ANDROID_LOG_INFO, "WebContent", "New binding received, sockets %d and %d", ipc_socket, fd_passing_socket);
|
||||
::close(ipc_socket);
|
||||
::close(fd_passing_socket);
|
||||
|
||||
// FIXME: Create a new thread to start WebContent processing
|
||||
// Make sure to create IPC sockets *in that thread*!
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "JNIHelpers.h"
|
||||
#include <Userland/Libraries/LibGfx/Bitmap.h>
|
||||
#include <Userland/Libraries/LibGfx/Painter.h>
|
||||
#include <Userland/Libraries/LibWeb/Crypto/Crypto.h>
|
||||
@ -24,39 +25,6 @@ Gfx::BitmapFormat to_gfx_bitmap_format(i32 f)
|
||||
}
|
||||
}
|
||||
|
||||
class JavaEnvironment {
|
||||
public:
|
||||
JavaEnvironment(JavaVM* vm)
|
||||
: m_vm(vm)
|
||||
{
|
||||
auto ret = m_vm->GetEnv(reinterpret_cast<void**>(&m_env), JNI_VERSION_1_6);
|
||||
if (ret == JNI_EDETACHED) {
|
||||
ret = m_vm->AttachCurrentThread(&m_env, nullptr);
|
||||
VERIFY(ret == JNI_OK);
|
||||
m_did_attach_thread = true;
|
||||
} else if (ret == JNI_EVERSION) {
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
VERIFY(ret == JNI_OK);
|
||||
}
|
||||
|
||||
VERIFY(m_env != nullptr);
|
||||
}
|
||||
|
||||
~JavaEnvironment()
|
||||
{
|
||||
if (m_did_attach_thread)
|
||||
m_vm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
JNIEnv* get() const { return m_env; }
|
||||
|
||||
private:
|
||||
JavaVM* m_vm = nullptr;
|
||||
JNIEnv* m_env = nullptr;
|
||||
bool m_did_attach_thread = false;
|
||||
};
|
||||
|
||||
class WebViewImplementationNative : public WebView::ViewImplementation {
|
||||
public:
|
||||
WebViewImplementationNative(jobject thiz)
|
||||
|
@ -8,7 +8,6 @@ package org.serenityos.ladybird
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import org.serenityos.ladybird.databinding.ActivityMainBinding
|
||||
|
||||
class LadybirdActivity : AppCompatActivity() {
|
||||
@ -20,12 +19,16 @@ class LadybirdActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
resourceDir = TransferAssets.transferAssets(this)
|
||||
initNativeCode(resourceDir)
|
||||
initNativeCode(resourceDir, timerService)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
view = binding.webView
|
||||
|
||||
mainExecutor.execute {
|
||||
callNativeEventLoopForever()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@ -34,12 +37,23 @@ class LadybirdActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private lateinit var view: WebView
|
||||
private var timerService = TimerExecutorService()
|
||||
|
||||
/**
|
||||
* A native method that is implemented by the 'ladybird' native library,
|
||||
* which is packaged with this application.
|
||||
*/
|
||||
private external fun initNativeCode(resourceDir: String)
|
||||
private external fun initNativeCode(resourceDir: String, timerService: TimerExecutorService)
|
||||
|
||||
// FIXME: Instead of doing this, can we push a message to the message queue of the java Looper
|
||||
// when an event is pushed to the main thread, and use that to clear out the
|
||||
// Core::ThreadEventQueues?
|
||||
private fun callNativeEventLoopForever() {
|
||||
execMainEventLoop()
|
||||
mainExecutor.execute { callNativeEventLoopForever() }
|
||||
}
|
||||
|
||||
private external fun execMainEventLoop();
|
||||
|
||||
companion object {
|
||||
// Used to load the 'ladybird' library on application startup.
|
||||
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.scheduleAtFixedRate(
|
||||
timer,
|
||||
milliseconds,
|
||||
milliseconds,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
timers[id] = handle
|
||||
return id
|
||||
}
|
||||
|
||||
fun unregisterTimer(id: Long): Boolean {
|
||||
val timer = timers[id] ?: return false
|
||||
return timer.cancel(false)
|
||||
}
|
||||
|
||||
private var nextId: Long = 0
|
||||
private val timers: HashMap<Long, ScheduledFuture<*>> = hashMapOf()
|
||||
|
||||
}
|
@ -148,8 +148,10 @@ elseif(ANDROID)
|
||||
${SOURCES}
|
||||
Android/src/main/cpp/LadybirdActivity.cpp
|
||||
Android/src/main/cpp/WebViewImplementationNative.cpp
|
||||
Android/src/main/cpp/ALooperEventLoopImplementation.cpp
|
||||
Android/src/main/cpp/TimerExecutorService.cpp
|
||||
)
|
||||
target_link_libraries(ladybird PRIVATE log jnigraphics)
|
||||
target_link_libraries(ladybird PRIVATE log 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
|
||||
|
@ -53,8 +53,12 @@ else()
|
||||
)
|
||||
|
||||
if (ANDROID)
|
||||
target_sources(webcontent PRIVATE ../Android/src/main/cpp/WebContentService.cpp)
|
||||
target_link_libraries(webcontent PRIVATE log)
|
||||
target_sources(webcontent PRIVATE
|
||||
../Android/src/main/cpp/WebContentService.cpp
|
||||
../Android/src/main/cpp/ALooperEventLoopImplementation.cpp
|
||||
../Android/src/main/cpp/TimerExecutorService.cpp
|
||||
)
|
||||
target_link_libraries(webcontent PRIVATE log android)
|
||||
endif()
|
||||
|
||||
add_executable(WebContent main.cpp)
|
||||
|
Loading…
Reference in New Issue
Block a user