Prechádzať zdrojové kódy

LibWeb+WebContent: Add abstraction layer for event loop and timers

Instead of using Core::EventLoop and Core::Timer directly, LibWeb now
goes through a Web::Platform abstraction layer instead.

This will allow us to plug in Qt's event loop (and QTimer) over in
Ladybird, to avoid having to deal with multiple event loops.
Andreas Kling 2 rokov pred
rodič
commit
9567e211e7
28 zmenil súbory, kde vykonal 365 pridanie a 42 odobranie
  1. 2 0
      Userland/Libraries/LibWeb/CMakeLists.txt
  2. 3 3
      Userland/Libraries/LibWeb/DOM/Document.cpp
  3. 2 2
      Userland/Libraries/LibWeb/DOM/Document.h
  4. 4 0
      Userland/Libraries/LibWeb/Forward.h
  5. 3 3
      Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h
  6. 1 1
      Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp
  7. 2 2
      Userland/Libraries/LibWeb/HTML/BrowsingContext.h
  8. 4 10
      Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp
  9. 1 1
      Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h
  10. 2 2
      Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.cpp
  11. 1 1
      Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.h
  12. 2 2
      Userland/Libraries/LibWeb/HTML/Timer.cpp
  13. 1 1
      Userland/Libraries/LibWeb/HTML/Timer.h
  14. 2 0
      Userland/Libraries/LibWeb/HTML/Window.h
  15. 2 2
      Userland/Libraries/LibWeb/Loader/ImageLoader.cpp
  16. 2 2
      Userland/Libraries/LibWeb/Loader/ImageLoader.h
  17. 2 2
      Userland/Libraries/LibWeb/Loader/Resource.cpp
  18. 6 6
      Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp
  19. 28 0
      Userland/Libraries/LibWeb/Platform/EventLoopPlugin.cpp
  20. 26 0
      Userland/Libraries/LibWeb/Platform/EventLoopPlugin.h
  21. 38 0
      Userland/Libraries/LibWeb/Platform/Timer.cpp
  22. 40 0
      Userland/Libraries/LibWeb/Platform/Timer.h
  23. 4 2
      Userland/Services/WebContent/CMakeLists.txt
  24. 34 0
      Userland/Services/WebContent/EventLoopPluginSerenity.cpp
  25. 23 0
      Userland/Services/WebContent/EventLoopPluginSerenity.h
  26. 84 0
      Userland/Services/WebContent/TimerSerenity.cpp
  27. 42 0
      Userland/Services/WebContent/TimerSerenity.h
  28. 4 0
      Userland/Services/WebContent/main.cpp

+ 2 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -351,6 +351,8 @@ set(SOURCES
     Painting/ShadowPainting.cpp
     Painting/StackingContext.cpp
     Painting/TextPaintable.cpp
+    Platform/EventLoopPlugin.cpp
+    Platform/Timer.cpp
     RequestIdleCallback/IdleDeadline.cpp
     ResizeObserver/ResizeObserver.cpp
     SVG/AttributeNames.cpp

+ 3 - 3
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -10,7 +10,6 @@
 #include <AK/CharacterTypes.h>
 #include <AK/StringBuilder.h>
 #include <AK/Utf8View.h>
-#include <LibCore/Timer.h>
 #include <LibJS/Interpreter.h>
 #include <LibJS/Parser.h>
 #include <LibJS/Runtime/FunctionObject.h>
@@ -67,6 +66,7 @@
 #include <LibWeb/Layout/TreeBuilder.h>
 #include <LibWeb/Namespace.h>
 #include <LibWeb/Page/Page.h>
+#include <LibWeb/Platform/Timer.h>
 #include <LibWeb/SVG/TagNames.h>
 #include <LibWeb/UIEvents/EventNames.h>
 #include <LibWeb/UIEvents/FocusEvent.h>
@@ -286,11 +286,11 @@ Document::Document(HTML::Window& window, const AK::URL& url)
 
     HTML::main_thread_event_loop().register_document({}, *this);
 
-    m_style_update_timer = Core::Timer::create_single_shot(0, [this] {
+    m_style_update_timer = Platform::Timer::create_single_shot(0, [this] {
         update_style();
     });
 
-    m_layout_update_timer = Core::Timer::create_single_shot(0, [this] {
+    m_layout_update_timer = Platform::Timer::create_single_shot(0, [this] {
         force_layout();
     });
 }

+ 2 - 2
Userland/Libraries/LibWeb/DOM/Document.h

@@ -386,8 +386,8 @@ private:
     Optional<Color> m_active_link_color;
     Optional<Color> m_visited_link_color;
 
-    RefPtr<Core::Timer> m_style_update_timer;
-    RefPtr<Core::Timer> m_layout_update_timer;
+    RefPtr<Platform::Timer> m_style_update_timer;
+    RefPtr<Platform::Timer> m_layout_update_timer;
 
     RefPtr<HTML::HTMLParser> m_parser;
     bool m_active_parser_was_aborted { false };

+ 4 - 0
Userland/Libraries/LibWeb/Forward.h

@@ -351,6 +351,10 @@ struct BorderRadiiData;
 struct LinearGradientData;
 }
 
+namespace Web::Platform {
+class Timer;
+}
+
 namespace Web::RequestIdleCallback {
 class IdleDeadline;
 }

+ 3 - 3
Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h

@@ -8,8 +8,8 @@
 
 #include <AK/Function.h>
 #include <AK/IDAllocator.h>
-#include <LibCore/Timer.h>
 #include <LibWeb/HTML/EventLoop/EventLoop.h>
+#include <LibWeb/Platform/Timer.h>
 
 namespace Web::HTML {
 
@@ -18,7 +18,7 @@ struct AnimationFrameCallbackDriver {
 
     AnimationFrameCallbackDriver()
     {
-        m_timer = Core::Timer::create_single_shot(16, [] {
+        m_timer = Platform::Timer::create_single_shot(16, [] {
             HTML::main_thread_event_loop().schedule();
         });
     }
@@ -57,7 +57,7 @@ struct AnimationFrameCallbackDriver {
 private:
     HashMap<i32, Callback> m_callbacks;
     IDAllocator m_id_allocator;
-    RefPtr<Core::Timer> m_timer;
+    RefPtr<Platform::Timer> m_timer;
 };
 
 }

+ 1 - 1
Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp

@@ -229,7 +229,7 @@ BrowsingContext::BrowsingContext(Page& page, HTML::BrowsingContextContainer* con
     , m_event_handler({}, *this)
     , m_container(container)
 {
-    m_cursor_blink_timer = Core::Timer::construct(500, [this] {
+    m_cursor_blink_timer = Platform::Timer::create_repeating(500, [this] {
         if (!is_focused_context())
             return;
         if (m_cursor_position.node() && m_cursor_position.node()->layout_node()) {

+ 2 - 2
Userland/Libraries/LibWeb/HTML/BrowsingContext.h

@@ -10,7 +10,6 @@
 #include <AK/Noncopyable.h>
 #include <AK/RefPtr.h>
 #include <AK/WeakPtr.h>
-#include <LibCore/Timer.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/Rect.h>
 #include <LibGfx/Size.h>
@@ -20,6 +19,7 @@
 #include <LibWeb/HTML/SessionHistoryEntry.h>
 #include <LibWeb/Loader/FrameLoader.h>
 #include <LibWeb/Page/EventHandler.h>
+#include <LibWeb/Platform/Timer.h>
 #include <LibWeb/TreeNode.h>
 
 namespace Web::HTML {
@@ -148,7 +148,7 @@ private:
     Gfx::IntPoint m_viewport_scroll_offset;
 
     DOM::Position m_cursor_position;
-    RefPtr<Core::Timer> m_cursor_blink_timer;
+    RefPtr<Platform::Timer> m_cursor_blink_timer;
     bool m_cursor_blink_state { false };
 
     HashTable<ViewportClient*> m_viewport_clients;

+ 4 - 10
Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp

@@ -5,8 +5,6 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-#include <LibCore/EventLoop.h>
-#include <LibCore/Timer.h>
 #include <LibJS/Runtime/VM.h>
 #include <LibWeb/Bindings/MainThreadVM.h>
 #include <LibWeb/DOM/Document.h>
@@ -15,6 +13,8 @@
 #include <LibWeb/HTML/Scripting/Environments.h>
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/HighResolutionTime/Performance.h>
+#include <LibWeb/Platform/EventLoopPlugin.h>
+#include <LibWeb/Platform/Timer.h>
 
 namespace Web::HTML {
 
@@ -29,7 +29,7 @@ EventLoop::~EventLoop() = default;
 void EventLoop::schedule()
 {
     if (!m_system_event_loop_timer) {
-        m_system_event_loop_timer = Core::Timer::create_single_shot(0, [this] {
+        m_system_event_loop_timer = Platform::Timer::create_single_shot(0, [this] {
             process();
         });
     }
@@ -74,13 +74,7 @@ void EventLoop::spin_until(Function<bool()> goal_condition)
     //       NOTE: This is achieved by returning from the function.
 
     //    1. Wait until the condition goal is met.
-    Core::EventLoop loop;
-    loop.spin_until([&]() -> bool {
-        if (goal_condition())
-            return true;
-
-        return goal_condition();
-    });
+    Platform::EventLoopPlugin::the().spin_until(move(goal_condition));
 
     // 7. Stop task, allowing whatever algorithm that invoked it to resume.
     // NOTE: This is achieved by returning from the function.

+ 1 - 1
Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h

@@ -84,7 +84,7 @@ private:
 
     JS::VM* m_vm { nullptr };
 
-    RefPtr<Core::Timer> m_system_event_loop_timer;
+    RefPtr<Platform::Timer> m_system_event_loop_timer;
 
     // https://html.spec.whatwg.org/#performing-a-microtask-checkpoint
     bool m_performing_a_microtask_checkpoint { false };

+ 2 - 2
Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.cpp

@@ -4,14 +4,14 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-#include <LibCore/Timer.h>
 #include <LibWeb/HTML/HTMLBlinkElement.h>
+#include <LibWeb/Platform/Timer.h>
 
 namespace Web::HTML {
 
 HTMLBlinkElement::HTMLBlinkElement(DOM::Document& document, DOM::QualifiedName qualified_name)
     : HTMLElement(document, move(qualified_name))
-    , m_timer(Core::Timer::construct())
+    , m_timer(Platform::Timer::create())
 {
     m_timer->set_interval(500);
     m_timer->on_timeout = [this] { blink(); };

+ 1 - 1
Userland/Libraries/LibWeb/HTML/HTMLBlinkElement.h

@@ -22,7 +22,7 @@ private:
 
     void blink();
 
-    NonnullRefPtr<Core::Timer> m_timer;
+    NonnullRefPtr<Platform::Timer> m_timer;
 };
 
 }

+ 2 - 2
Userland/Libraries/LibWeb/HTML/Timer.cpp

@@ -4,9 +4,9 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-#include <LibCore/Timer.h>
 #include <LibWeb/HTML/Timer.h>
 #include <LibWeb/HTML/Window.h>
+#include <LibWeb/Platform/Timer.h>
 
 namespace Web::HTML {
 
@@ -20,7 +20,7 @@ Timer::Timer(Window& window, i32 milliseconds, Function<void()> callback, i32 id
     , m_callback(move(callback))
     , m_id(id)
 {
-    m_timer = Core::Timer::create_single_shot(milliseconds, [this] {
+    m_timer = Platform::Timer::create_single_shot(milliseconds, [this] {
         m_callback();
     });
 }

+ 1 - 1
Userland/Libraries/LibWeb/HTML/Timer.h

@@ -30,7 +30,7 @@ private:
 
     virtual void visit_edges(Cell::Visitor&) override;
 
-    RefPtr<Core::Timer> m_timer;
+    RefPtr<Platform::Timer> m_timer;
     JS::NonnullGCPtr<Window> m_window;
     Function<void()> m_callback;
     i32 m_id { 0 };

+ 2 - 0
Userland/Libraries/LibWeb/HTML/Window.h

@@ -8,7 +8,9 @@
 
 #include <AK/Badge.h>
 #include <AK/IDAllocator.h>
+#include <AK/NonnullRefPtrVector.h>
 #include <AK/RefPtr.h>
+#include <AK/TypeCasts.h>
 #include <AK/URL.h>
 #include <LibJS/Heap/Heap.h>
 #include <LibWeb/Bindings/CrossOriginAbstractOperations.h>

+ 2 - 2
Userland/Libraries/LibWeb/Loader/ImageLoader.cpp

@@ -5,18 +5,18 @@
  */
 
 #include <AK/Debug.h>
-#include <LibCore/Timer.h>
 #include <LibGfx/Bitmap.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Element.h>
 #include <LibWeb/Loader/ImageLoader.h>
 #include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Platform/Timer.h>
 
 namespace Web {
 
 ImageLoader::ImageLoader(DOM::Element& owner_element)
     : m_owner_element(owner_element)
-    , m_timer(Core::Timer::construct())
+    , m_timer(Platform::Timer::create())
 {
 }
 

+ 2 - 2
Userland/Libraries/LibWeb/Loader/ImageLoader.h

@@ -7,8 +7,8 @@
 #pragma once
 
 #include <AK/Function.h>
-#include <LibCore/Timer.h>
 #include <LibWeb/Loader/ImageResource.h>
+#include <LibWeb/Platform/Timer.h>
 
 namespace Web {
 
@@ -60,7 +60,7 @@ private:
     size_t m_current_frame_index { 0 };
     size_t m_loops_completed { 0 };
     LoadingState m_loading_state { LoadingState::Loading };
-    NonnullRefPtr<Core::Timer> m_timer;
+    NonnullRefPtr<Platform::Timer> m_timer;
     size_t m_redirects_count { 0 };
 };
 

+ 2 - 2
Userland/Libraries/LibWeb/Loader/Resource.cpp

@@ -6,12 +6,12 @@
 
 #include <AK/Debug.h>
 #include <AK/Function.h>
-#include <LibCore/EventLoop.h>
 #include <LibCore/MimeData.h>
 #include <LibTextCodec/Decoder.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
 #include <LibWeb/Loader/Resource.h>
 #include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Platform/EventLoopPlugin.h>
 
 namespace Web {
 
@@ -168,7 +168,7 @@ void ResourceClient::set_resource(Resource* resource)
         // This ensures that these callbacks always happen in a consistent way, instead of being invoked
         // synchronously in some cases, and asynchronously in others.
         if (resource->is_loaded() || resource->is_failed()) {
-            Core::deferred_invoke([this, strong_resource = NonnullRefPtr { *m_resource }] {
+            Platform::EventLoopPlugin::the().deferred_invoke([this, strong_resource = NonnullRefPtr { *m_resource }] {
                 if (m_resource != strong_resource.ptr())
                     return;
 

+ 6 - 6
Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp

@@ -9,14 +9,14 @@
 #include <AK/Debug.h>
 #include <AK/JsonObject.h>
 #include <LibCore/ElapsedTimer.h>
-#include <LibCore/EventLoop.h>
 #include <LibCore/File.h>
-#include <LibCore/Timer.h>
 #include <LibWeb/Loader/ContentFilter.h>
 #include <LibWeb/Loader/LoadRequest.h>
 #include <LibWeb/Loader/ProxyMappings.h>
 #include <LibWeb/Loader/Resource.h>
 #include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Platform/EventLoopPlugin.h>
+#include <LibWeb/Platform/Timer.h>
 
 #ifdef __serenity__
 #    include <serenity.h>
@@ -179,7 +179,7 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
         HashMap<String, String, CaseInsensitiveStringTraits> response_headers;
         response_headers.set("Content-Type", "text/html; charset=UTF-8");
 
-        deferred_invoke([success_callback = move(success_callback), response_headers = move(response_headers)] {
+        Platform::EventLoopPlugin::the().deferred_invoke([success_callback = move(success_callback), response_headers = move(response_headers)] {
             success_callback(String::empty().to_byte_buffer(), response_headers, {});
         });
         return;
@@ -206,7 +206,7 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
         }
 
         log_success(request);
-        deferred_invoke([data = move(data), success_callback = move(success_callback)] {
+        Platform::EventLoopPlugin::the().deferred_invoke([data = move(data), success_callback = move(success_callback)] {
             success_callback(data, {}, {});
         });
         return;
@@ -284,7 +284,7 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
         }
 
         if (timeout.has_value() && timeout.value() > 0) {
-            auto timer = Core::Timer::create_single_shot(timeout.value(), nullptr);
+            auto timer = Platform::Timer::create_single_shot(timeout.value(), nullptr);
             timer->on_timeout = [timer, protocol_request, timeout_callback = move(timeout_callback)]() mutable {
                 protocol_request->stop();
                 if (timeout_callback)
@@ -312,7 +312,7 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has
             }
             log_success(request);
             success_callback(payload, response_headers, status_code);
-            deferred_invoke([this, &protocol_request] {
+            Platform::EventLoopPlugin::the().deferred_invoke([this, &protocol_request] {
                 m_active_requests.remove(protocol_request);
             });
         };

+ 28 - 0
Userland/Libraries/LibWeb/Platform/EventLoopPlugin.cpp

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Function.h>
+#include <LibWeb/Platform/EventLoopPlugin.h>
+
+namespace Web::Platform {
+
+EventLoopPlugin* s_the;
+
+EventLoopPlugin& EventLoopPlugin::the()
+{
+    VERIFY(s_the);
+    return *s_the;
+}
+
+void EventLoopPlugin::install(EventLoopPlugin& plugin)
+{
+    VERIFY(!s_the);
+    s_the = &plugin;
+}
+
+EventLoopPlugin::~EventLoopPlugin() = default;
+
+}

+ 26 - 0
Userland/Libraries/LibWeb/Platform/EventLoopPlugin.h

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Platform {
+
+class EventLoopPlugin {
+public:
+    static EventLoopPlugin& the();
+    static void install(EventLoopPlugin&);
+
+    virtual ~EventLoopPlugin();
+
+    virtual void spin_until(Function<bool()> goal_condition) = 0;
+    virtual void deferred_invoke(Function<void()>) = 0;
+    virtual NonnullRefPtr<Timer> create_timer() = 0;
+};
+
+}

+ 38 - 0
Userland/Libraries/LibWeb/Platform/Timer.cpp

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/NonnullRefPtr.h>
+#include <LibWeb/Platform/EventLoopPlugin.h>
+#include <LibWeb/Platform/Timer.h>
+
+namespace Web::Platform {
+
+Timer::~Timer() = default;
+
+NonnullRefPtr<Timer> Timer::create()
+{
+    return EventLoopPlugin::the().create_timer();
+}
+
+NonnullRefPtr<Timer> Timer::create_repeating(int interval_ms, Function<void()>&& timeout_handler)
+{
+    auto timer = EventLoopPlugin::the().create_timer();
+    timer->set_single_shot(false);
+    timer->set_interval(interval_ms);
+    timer->on_timeout = move(timeout_handler);
+    return timer;
+}
+
+NonnullRefPtr<Timer> Timer::create_single_shot(int interval_ms, Function<void()>&& timeout_handler)
+{
+    auto timer = EventLoopPlugin::the().create_timer();
+    timer->set_single_shot(true);
+    timer->set_interval(interval_ms);
+    timer->on_timeout = move(timeout_handler);
+    return timer;
+}
+
+}

+ 40 - 0
Userland/Libraries/LibWeb/Platform/Timer.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/RefCounted.h>
+
+namespace Web::Platform {
+
+class Timer : public RefCounted<Timer> {
+public:
+    static NonnullRefPtr<Timer> create();
+    static NonnullRefPtr<Timer> create_repeating(int interval_ms, Function<void()>&& timeout_handler);
+    static NonnullRefPtr<Timer> create_single_shot(int interval_ms, Function<void()>&& timeout_handler);
+
+    virtual ~Timer();
+
+    virtual void start() = 0;
+    virtual void start(int interval_ms) = 0;
+    virtual void restart() = 0;
+    virtual void restart(int interval_ms) = 0;
+    virtual void stop() = 0;
+
+    virtual void set_active(bool) = 0;
+
+    virtual bool is_active() const = 0;
+    virtual int interval() const = 0;
+    virtual void set_interval(int interval_ms) = 0;
+
+    virtual bool is_single_shot() const = 0;
+    virtual void set_single_shot(bool) = 0;
+
+    Function<void()> on_timeout;
+};
+
+}

+ 4 - 2
Userland/Services/WebContent/CMakeLists.txt

@@ -10,11 +10,13 @@ compile_ipc(WebContentClient.ipc WebContentClientEndpoint.h)
 set(SOURCES
     ConnectionFromClient.cpp
     ConsoleGlobalObject.cpp
-    main.cpp
+    EventLoopPluginSerenity.cpp
     PageHost.cpp
+    TimerSerenity.cpp
+    WebContentClientEndpoint.h
     WebContentConsoleClient.cpp
     WebContentServerEndpoint.h
-    WebContentClientEndpoint.h
+    main.cpp
 )
 
 serenity_bin(WebContent)

+ 34 - 0
Userland/Services/WebContent/EventLoopPluginSerenity.cpp

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "EventLoopPluginSerenity.h"
+#include "TimerSerenity.h"
+#include <AK/Function.h>
+#include <AK/NonnullRefPtr.h>
+#include <LibCore/EventLoop.h>
+
+namespace WebContent {
+
+EventLoopPluginSerenity::EventLoopPluginSerenity() = default;
+EventLoopPluginSerenity::~EventLoopPluginSerenity() = default;
+
+void EventLoopPluginSerenity::spin_until(Function<bool()> goal_condition)
+{
+    Core::EventLoop::current().spin_until(move(goal_condition));
+}
+
+void EventLoopPluginSerenity::deferred_invoke(Function<void()> function)
+{
+    VERIFY(function);
+    Core::deferred_invoke(move(function));
+}
+
+NonnullRefPtr<Web::Platform::Timer> EventLoopPluginSerenity::create_timer()
+{
+    return TimerSerenity::create();
+}
+
+}

+ 23 - 0
Userland/Services/WebContent/EventLoopPluginSerenity.h

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Platform/EventLoopPlugin.h>
+
+namespace WebContent {
+
+class EventLoopPluginSerenity final : public Web::Platform::EventLoopPlugin {
+public:
+    EventLoopPluginSerenity();
+    virtual ~EventLoopPluginSerenity() override;
+
+    virtual void spin_until(Function<bool()> goal_condition) override;
+    virtual void deferred_invoke(Function<void()>) override;
+    virtual NonnullRefPtr<Web::Platform::Timer> create_timer() override;
+};
+
+}

+ 84 - 0
Userland/Services/WebContent/TimerSerenity.cpp

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "TimerSerenity.h"
+#include <AK/NonnullRefPtr.h>
+#include <LibCore/Timer.h>
+
+namespace WebContent {
+
+NonnullRefPtr<TimerSerenity> TimerSerenity::create()
+{
+    return adopt_ref(*new TimerSerenity);
+}
+
+TimerSerenity::TimerSerenity()
+    : m_timer(Core::Timer::construct())
+{
+    m_timer->on_timeout = [this] {
+        if (on_timeout)
+            on_timeout();
+    };
+}
+
+TimerSerenity::~TimerSerenity() = default;
+
+void TimerSerenity::start()
+{
+    m_timer->start();
+}
+
+void TimerSerenity::start(int interval_ms)
+{
+    m_timer->start(interval_ms);
+}
+
+void TimerSerenity::restart()
+{
+    m_timer->restart();
+}
+
+void TimerSerenity::restart(int interval_ms)
+{
+    m_timer->restart(interval_ms);
+}
+
+void TimerSerenity::stop()
+{
+    m_timer->stop();
+}
+
+void TimerSerenity::set_active(bool active)
+{
+    m_timer->set_active(active);
+}
+
+bool TimerSerenity::is_active() const
+{
+    return m_timer->is_active();
+}
+
+int TimerSerenity::interval() const
+{
+    return m_timer->interval();
+}
+
+void TimerSerenity::set_interval(int interval_ms)
+{
+    m_timer->set_interval(interval_ms);
+}
+
+bool TimerSerenity::is_single_shot() const
+{
+    return m_timer->is_single_shot();
+}
+
+void TimerSerenity::set_single_shot(bool single_shot)
+{
+    m_timer->set_single_shot(single_shot);
+}
+
+}

+ 42 - 0
Userland/Services/WebContent/TimerSerenity.h

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/NonnullRefPtr.h>
+#include <LibCore/Forward.h>
+#include <LibWeb/Platform/Timer.h>
+
+namespace WebContent {
+
+class TimerSerenity final : public Web::Platform::Timer {
+public:
+    static NonnullRefPtr<TimerSerenity> create();
+
+    virtual ~TimerSerenity();
+
+    virtual void start() override;
+    virtual void start(int interval_ms) override;
+    virtual void restart() override;
+    virtual void restart(int interval_ms) override;
+    virtual void stop() override;
+
+    virtual void set_active(bool) override;
+
+    virtual bool is_active() const override;
+    virtual int interval() const override;
+    virtual void set_interval(int interval_ms) override;
+
+    virtual bool is_single_shot() const override;
+    virtual void set_single_shot(bool) override;
+
+private:
+    TimerSerenity();
+
+    NonnullRefPtr<Core::Timer> m_timer;
+};
+
+}

+ 4 - 0
Userland/Services/WebContent/main.cpp

@@ -4,6 +4,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include "EventLoopPluginSerenity.h"
 #include <LibCore/EventLoop.h>
 #include <LibCore/LocalServer.h>
 #include <LibCore/System.h>
@@ -11,6 +12,7 @@
 #include <LibMain/Main.h>
 #include <LibWeb/ImageDecoding.h>
 #include <LibWeb/Loader/ResourceLoader.h>
+#include <LibWeb/Platform/EventLoopPlugin.h>
 #include <LibWeb/WebSockets/WebSocket.h>
 #include <LibWebView/ImageDecoderClientAdapter.h>
 #include <LibWebView/RequestServerAdapter.h>
@@ -28,6 +30,8 @@ ErrorOr<int> serenity_main(Main::Arguments)
     TRY(Core::System::unveil("/tmp/user/%uid/portal/websocket", "rw"));
     TRY(Core::System::unveil(nullptr, nullptr));
 
+    Web::Platform::EventLoopPlugin::install(*new WebContent::EventLoopPluginSerenity);
+
     Web::ImageDecoding::Decoder::initialize(WebView::ImageDecoderClientAdapter::create());
     Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create()));
     Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create()));