浏览代码

Ladybird: Run the Core::EventLoop with a Qt backend

This patch adds EventLoopImplementationQt which is a full replacement
for the Core::EventLoopImplementationUnix that uses Qt's event loop
as a backend instead.

This means that Core::Timer, Core::Notifier, and Core::Event delivery
are all driven by Qt primitives in the Ladybird UI and WC processes.
Andreas Kling 2 年之前
父节点
当前提交
3494c2382d

+ 1 - 0
Ladybird/CMakeLists.txt

@@ -83,6 +83,7 @@ set(SOURCES
     ${BROWSER_SOURCE_DIR}/History.cpp
     ${BROWSER_SOURCE_DIR}/History.cpp
     BrowserWindow.cpp
     BrowserWindow.cpp
     ConsoleWidget.cpp
     ConsoleWidget.cpp
+    EventLoopImplementationQt.cpp
     HelperProcess.cpp
     HelperProcess.cpp
     InspectorWidget.cpp
     InspectorWidget.cpp
     LocationEdit.cpp
     LocationEdit.cpp

+ 135 - 0
Ladybird/EventLoopImplementationQt.cpp

@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "EventLoopImplementationQt.h"
+#include <AK/IDAllocator.h>
+#include <LibCore/Event.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+#include <LibCore/ThreadEventQueue.h>
+#include <QTimer>
+
+namespace Ladybird {
+
+struct ThreadData;
+static thread_local ThreadData* s_thread_data;
+
+struct ThreadData {
+    static ThreadData& the()
+    {
+        if (!s_thread_data) {
+            // FIXME: Don't leak this.
+            s_thread_data = new ThreadData;
+        }
+        return *s_thread_data;
+    }
+
+    IDAllocator timer_id_allocator;
+    HashMap<int, NonnullOwnPtr<QTimer>> timers;
+    HashMap<Core::Notifier*, NonnullOwnPtr<QSocketNotifier>> notifiers;
+};
+
+EventLoopImplementationQt::EventLoopImplementationQt()
+{
+}
+
+EventLoopImplementationQt::~EventLoopImplementationQt() = default;
+
+int EventLoopImplementationQt::exec()
+{
+    // NOTE: We don't use QEventLoop::exec() here since it wouldn't process the Core::ThreadEventQueue.
+    while (!m_exit_code.has_value()) {
+        pump(PumpMode::WaitForEvents);
+    }
+    return m_exit_code.value();
+}
+
+size_t EventLoopImplementationQt::pump(PumpMode mode)
+{
+    bool result = Core::ThreadEventQueue::current().process() != 0;
+    if (mode == PumpMode::WaitForEvents)
+        result |= m_event_loop.processEvents(QEventLoop::WaitForMoreEvents);
+    else
+        result |= m_event_loop.processEvents();
+    Core::ThreadEventQueue::current().process();
+    return result;
+}
+
+void EventLoopImplementationQt::quit(int code)
+{
+    m_exit_code = code;
+}
+
+void EventLoopImplementationQt::wake()
+{
+    m_event_loop.wakeUp();
+}
+
+void EventLoopImplementationQt::deferred_invoke(Function<void()> function)
+{
+    VERIFY(function);
+    QTimer::singleShot(0, [function = move(function)] {
+        function();
+    });
+}
+
+int EventLoopImplementationQt::register_timer(Core::Object& object, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible)
+{
+    auto& thread_data = ThreadData::the();
+    auto timer = make<QTimer>();
+    timer->setInterval(milliseconds);
+    timer->setSingleShot(!should_reload);
+    auto timer_id = thread_data.timer_id_allocator.allocate();
+    auto weak_object = object.make_weak_ptr();
+    QObject::connect(timer, &QTimer::timeout, [timer_id, should_fire_when_not_visible, weak_object = move(weak_object)] {
+        auto object = weak_object.strong_ref();
+        if (!object)
+            return;
+        if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) {
+            if (!object->is_visible_for_timer_purposes())
+                return;
+        }
+        Core::ThreadEventQueue::current().post_event(*object, make<Core::TimerEvent>(timer_id));
+    });
+    timer->start();
+    thread_data.timers.set(timer_id, move(timer));
+    return timer_id;
+}
+
+bool EventLoopImplementationQt::unregister_timer(int timer_id)
+{
+    auto& thread_data = ThreadData::the();
+    thread_data.timer_id_allocator.deallocate(timer_id);
+    return thread_data.timers.remove(timer_id);
+}
+
+void EventLoopImplementationQt::register_notifier(Core::Notifier& notifier)
+{
+    QSocketNotifier::Type type;
+    switch (notifier.type()) {
+    case Core::Notifier::Type::Read:
+        type = QSocketNotifier::Read;
+        break;
+    case Core::Notifier::Type::Write:
+        type = QSocketNotifier::Write;
+        break;
+    default:
+        TODO();
+    }
+    auto socket_notifier = make<QSocketNotifier>(notifier.fd(), type);
+    QObject::connect(socket_notifier, &QSocketNotifier::activated, [fd = notifier.fd(), &notifier] {
+        Core::ThreadEventQueue::current().post_event(notifier, make<Core::NotifierActivationEvent>(fd));
+    });
+
+    ThreadData::the().notifiers.set(&notifier, move(socket_notifier));
+}
+
+void EventLoopImplementationQt::unregister_notifier(Core::Notifier& notifier)
+{
+    ThreadData::the().notifiers.remove(&notifier);
+}
+
+}

+ 53 - 0
Ladybird/EventLoopImplementationQt.h

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/NonnullOwnPtr.h>
+#include <AK/OwnPtr.h>
+#include <LibCore/EventLoopImplementation.h>
+#include <QEventLoop>
+#include <QSocketNotifier>
+#include <QTimer>
+
+namespace Ladybird {
+
+class EventLoopImplementationQt final : public Core::EventLoopImplementation {
+public:
+    static NonnullOwnPtr<EventLoopImplementationQt> create() { return adopt_own(*new EventLoopImplementationQt); }
+
+    virtual ~EventLoopImplementationQt() override;
+
+    virtual int exec() override;
+    virtual size_t pump(PumpMode) override;
+    virtual void quit(int) override;
+    virtual void wake() override;
+
+    virtual void deferred_invoke(Function<void()>) override;
+
+    virtual int register_timer(Core::Object&, 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;
+
+    // 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 { }
+    virtual int register_signal(int, Function<void(int)>) override { return 0; }
+    virtual void unregister_signal(int) override { }
+
+protected:
+    EventLoopImplementationQt();
+
+private:
+    QEventLoop m_event_loop;
+    Optional<int> m_exit_code;
+};
+
+}

+ 1 - 0
Ladybird/WebContent/CMakeLists.txt

@@ -6,6 +6,7 @@ set(WEBCONTENT_SOURCES
     ${WEBCONTENT_SOURCE_DIR}/PageHost.cpp
     ${WEBCONTENT_SOURCE_DIR}/PageHost.cpp
     ${WEBCONTENT_SOURCE_DIR}/WebContentConsoleClient.cpp
     ${WEBCONTENT_SOURCE_DIR}/WebContentConsoleClient.cpp
     ${WEBCONTENT_SOURCE_DIR}/WebDriverConnection.cpp
     ${WEBCONTENT_SOURCE_DIR}/WebDriverConnection.cpp
+    ../EventLoopImplementationQt.cpp
     ../EventLoopPluginQt.cpp
     ../EventLoopPluginQt.cpp
     ../FontPluginQt.cpp
     ../FontPluginQt.cpp
     ../ImageCodecPluginLadybird.cpp
     ../ImageCodecPluginLadybird.cpp

+ 6 - 5
Ladybird/WebContent/main.cpp

@@ -4,6 +4,8 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
+
+#include "../EventLoopImplementationQt.h"
 #include "../EventLoopPluginQt.h"
 #include "../EventLoopPluginQt.h"
 #include "../FontPluginQt.h"
 #include "../FontPluginQt.h"
 #include "../ImageCodecPluginLadybird.h"
 #include "../ImageCodecPluginLadybird.h"
@@ -59,12 +61,11 @@ static void proxy_socket_through_notifier(ClientType& client, QSocketNotifier& n
 
 
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 {
 {
-    // NOTE: This is only used for the Core::Socket inside the IPC connection.
-    // FIXME: Refactor things so we can get rid of this somehow.
-    Core::EventLoop event_loop;
-
     QGuiApplication app(arguments.argc, arguments.argv);
     QGuiApplication app(arguments.argc, arguments.argv);
 
 
+    Core::EventLoop::make_implementation = Ladybird::EventLoopImplementationQt::create;
+    Core::EventLoop event_loop;
+
     platform_init();
     platform_init();
 
 
     Web::Platform::EventLoopPlugin::install(*new Ladybird::EventLoopPluginQt);
     Web::Platform::EventLoopPlugin::install(*new Ladybird::EventLoopPluginQt);
@@ -109,7 +110,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         proxy_socket_through_notifier(webdriver, webdriver_notifier);
         proxy_socket_through_notifier(webdriver, webdriver_notifier);
     };
     };
 
 
-    return app.exec();
+    return event_loop.exec();
 }
 }
 
 
 static ErrorOr<void> load_content_filters()
 static ErrorOr<void> load_content_filters()

+ 5 - 5
Ladybird/main.cpp

@@ -5,6 +5,7 @@
  */
  */
 
 
 #include "BrowserWindow.h"
 #include "BrowserWindow.h"
+#include "EventLoopImplementationQt.h"
 #include "HelperProcess.h"
 #include "HelperProcess.h"
 #include "Settings.h"
 #include "Settings.h"
 #include "Utilities.h"
 #include "Utilities.h"
@@ -51,14 +52,13 @@ static ErrorOr<void> handle_attached_debugger()
 
 
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 {
 {
-    // NOTE: This is only used for the Core::Socket inside the IPC connections.
-    // FIXME: Refactor things so we can get rid of this somehow.
+    QApplication app(arguments.argc, arguments.argv);
+
+    Core::EventLoop::make_implementation = Ladybird::EventLoopImplementationQt::create;
     Core::EventLoop event_loop;
     Core::EventLoop event_loop;
 
 
     TRY(handle_attached_debugger());
     TRY(handle_attached_debugger());
 
 
-    QApplication app(arguments.argc, arguments.argv);
-
     platform_init();
     platform_init();
 
 
     // NOTE: We only instantiate this to ensure that Gfx::FontDatabase has its default queries initialized.
     // NOTE: We only instantiate this to ensure that Gfx::FontDatabase has its default queries initialized.
@@ -110,5 +110,5 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         window.view().load(TRY(get_formatted_url(home_url.bytes_as_string_view())));
         window.view().load(TRY(get_formatted_url(home_url.bytes_as_string_view())));
     }
     }
 
 
-    return app.exec();
+    return event_loop.exec();
 }
 }