diff options
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/WindowObject.cpp | 23 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/WindowObject.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/Document.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/Window.cpp | 14 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/Window.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp | 94 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h | 11 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/EventLoop/Task.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h | 8 |
9 files changed, 141 insertions, 18 deletions
diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp index 0be35f2cf6..cf9ae65202 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp +++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -80,6 +80,8 @@ void WindowObject::initialize_global_object() define_native_function("atob", atob, 1, attr); define_native_function("btoa", btoa, 1, attr); + define_native_function("queueMicrotask", queue_microtask, 1, attr); + define_native_function("getComputedStyle", get_computed_style, 1, attr); define_native_function("matchMedia", match_media, 1, attr); @@ -338,6 +340,27 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::cancel_animation_frame) return JS::js_undefined(); } +JS_DEFINE_NATIVE_FUNCTION(WindowObject::queue_microtask) +{ + auto* impl = impl_from(vm, global_object); + if (!impl) + return {}; + if (!vm.argument_count()) { + vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "queueMicrotask"); + return {}; + } + auto* callback_object = vm.argument(0).to_object(global_object); + if (!callback_object) + return {}; + if (!callback_object->is_function()) { + vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAFunctionNoParam); + return {}; + } + + impl->queue_microtask(static_cast<JS::FunctionObject&>(*callback_object)); + return JS::js_undefined(); +} + JS_DEFINE_NATIVE_FUNCTION(WindowObject::atob) { auto* impl = impl_from(vm, global_object); diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.h b/Userland/Libraries/LibWeb/Bindings/WindowObject.h index b38976d9b6..a148d95ee4 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObject.h +++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.h @@ -99,6 +99,8 @@ private: JS_DECLARE_NATIVE_FUNCTION(get_computed_style); JS_DECLARE_NATIVE_FUNCTION(match_media); + JS_DECLARE_NATIVE_FUNCTION(queue_microtask); + #define __ENUMERATE(attribute, event_name) \ JS_DECLARE_NATIVE_FUNCTION(attribute##_getter); \ JS_DECLARE_NATIVE_FUNCTION(attribute##_setter); diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 7864e3a240..ebf5b5b877 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -642,6 +642,10 @@ JS::Interpreter& Document::interpreter() auto& vm = m_interpreter->vm(); vm.run_queued_promise_jobs(); vm.run_queued_finalization_registry_cleanup_jobs(); + + // FIXME: This isn't exactly the right place for this. + HTML::main_thread_event_loop().perform_a_microtask_checkpoint(); + // Note: This is not an exception check for the promise jobs, they will just leave any // exception that already exists intact and never throw a new one (without cleaning it // up, that is). Taking care of any previous unhandled exception just happens to be the diff --git a/Userland/Libraries/LibWeb/DOM/Window.cpp b/Userland/Libraries/LibWeb/DOM/Window.cpp index 5806c8418c..217eb213e5 100644 --- a/Userland/Libraries/LibWeb/DOM/Window.cpp +++ b/Userland/Libraries/LibWeb/DOM/Window.cpp @@ -12,6 +12,7 @@ #include <LibWeb/DOM/EventDispatcher.h> #include <LibWeb/DOM/Timer.h> #include <LibWeb/DOM/Window.h> +#include <LibWeb/HTML/EventLoop/EventLoop.h> #include <LibWeb/HTML/PageTransitionEvent.h> #include <LibWeb/HighResolutionTime/Performance.h> #include <LibWeb/Layout/InitialContainingBlock.h> @@ -295,4 +296,17 @@ void Window::fire_a_page_transition_event(FlyString event_name, bool persisted) dispatch_event(move(event)); } +// https://html.spec.whatwg.org/#dom-queuemicrotask +void Window::queue_microtask(JS::FunctionObject& callback) +{ + // The queueMicrotask(callback) method must queue a microtask to invoke callback, + HTML::queue_a_microtask(associated_document(), [&callback, handle = JS::make_handle(&callback)]() { + auto& vm = callback.vm(); + [[maybe_unused]] auto rc = vm.call(callback, JS::js_null()); + // FIXME: ...and if callback throws an exception, report the exception. + if (vm.exception()) + vm.clear_exception(); + }); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Window.h b/Userland/Libraries/LibWeb/DOM/Window.h index 7800d25cbb..9cd6f952a0 100644 --- a/Userland/Libraries/LibWeb/DOM/Window.h +++ b/Userland/Libraries/LibWeb/DOM/Window.h @@ -55,6 +55,8 @@ public: void clear_timeout(i32); void clear_interval(i32); + void queue_microtask(JS::FunctionObject&); + int inner_width() const; int inner_height() const; diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index dc1de61a03..dfe99831e1 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -14,6 +14,7 @@ namespace Web::HTML { EventLoop::EventLoop() : m_task_queue(*this) + , m_microtask_queue(*this) { } @@ -49,7 +50,12 @@ void EventLoop::spin_until(Function<bool()> goal_condition) { // FIXME: This is an ad-hoc hack until we implement the proper mechanism. Core::EventLoop loop; - loop.spin_until(move(goal_condition)); + loop.spin_until([&]() -> bool { + if (goal_condition()) + return true; + perform_a_microtask_checkpoint(); + return goal_condition(); + }); // Real spec steps: @@ -84,27 +90,24 @@ void EventLoop::process() // 1. Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner, with the constraint that the chosen task queue must contain at least one runnable task. If there is no such task queue, then jump to the microtasks step below. auto& task_queue = m_task_queue; - if (task_queue.is_empty()) - return; - - // 2. Let oldestTask be the first runnable task in taskQueue, and remove it from taskQueue. - auto oldest_task = task_queue.take_first_runnable(); + if (!task_queue.is_empty()) { + // 2. Let oldestTask be the first runnable task in taskQueue, and remove it from taskQueue. + auto oldest_task = task_queue.take_first_runnable(); - // FIXME: Figure out if we need to be here when there's no task. - VERIFY(oldest_task); + // 3. Set the event loop's currently running task to oldestTask. + m_currently_running_task = oldest_task.ptr(); - // 3. Set the event loop's currently running task to oldestTask. - m_currently_running_task = oldest_task.ptr(); + // FIXME: 4. Let taskStartTime be the current high resolution time. - // FIXME: 4. Let taskStartTime be the current high resolution time. + // 5. Perform oldestTask's steps. + oldest_task->execute(); - // 5. Perform oldestTask's steps. - oldest_task->execute(); - - // 6. Set the event loop's currently running task back to null. - m_currently_running_task = nullptr; + // 6. Set the event loop's currently running task back to null. + m_currently_running_task = nullptr; + } - // FIXME: 7. Microtasks: Perform a microtask checkpoint. + // 7. Microtasks: Perform a microtask checkpoint. + perform_a_microtask_checkpoint(); // 8. Let hasARenderingOpportunity be false. [[maybe_unused]] bool has_a_rendering_opportunity = false; @@ -174,7 +177,7 @@ void EventLoop::process() // FIXME: 2. If there are no tasks in the event loop's task queues and the WorkerGlobalScope object's closing flag is true, then destroy the event loop, aborting these steps, resuming the run a worker steps described in the Web workers section below. // If there are tasks in the queue, schedule a new round of processing. :^) - if (!m_task_queue.is_empty()) + if (!m_task_queue.is_empty() || !m_microtask_queue.is_empty()) schedule(); } @@ -185,4 +188,59 @@ void queue_global_task(HTML::Task::Source source, DOM::Document& document, Funct main_thread_event_loop().task_queue().add(HTML::Task::create(source, &document, move(steps))); } +// https://html.spec.whatwg.org/#queue-a-microtask +void queue_a_microtask(DOM::Document& document, Function<void()> steps) +{ + // 1. If event loop was not given, set event loop to the implied event loop. + auto& event_loop = HTML::main_thread_event_loop(); + + // FIXME: 2. If document was not given, set document to the implied document. + + // 3. Let microtask be a new task. + // 4. Set microtask's steps to steps. + // 5. Set microtask's source to the microtask task source. + // 6. Set microtask's document to document. + auto microtask = HTML::Task::create(HTML::Task::Source::Microtask, &document, move(steps)); + + // FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set. + + // 8. Enqueue microtask on event loop's microtask queue. + event_loop.microtask_queue().enqueue(move(microtask)); +} + +// https://html.spec.whatwg.org/#perform-a-microtask-checkpoint +void EventLoop::perform_a_microtask_checkpoint() +{ + // 1. If the event loop's performing a microtask checkpoint is true, then return. + if (m_performing_a_microtask_checkpoint) + return; + + // 2. Set the event loop's performing a microtask checkpoint to true. + m_performing_a_microtask_checkpoint = true; + + // 3. While the event loop's microtask queue is not empty: + while (!m_microtask_queue.is_empty()) { + // 1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue. + auto oldest_microtask = m_microtask_queue.dequeue(); + + // 2. Set the event loop's currently running task to oldestMicrotask. + m_currently_running_task = oldest_microtask; + + // 3. Run oldestMicrotask. + oldest_microtask->execute(); + + // 4. Set the event loop's currently running task back to null. + m_currently_running_task = nullptr; + } + + // FIXME: 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object. + + // FIXME: 5. Cleanup Indexed Database transactions. + + // FIXME: 6. Perform ClearKeptObjects(). + + // 7. Set the event loop's performing a microtask checkpoint to false. + m_performing_a_microtask_checkpoint = false; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h index 62fcba9518..50b9246554 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h @@ -32,6 +32,9 @@ public: TaskQueue& task_queue() { return m_task_queue; } TaskQueue const& task_queue() const { return m_task_queue; } + TaskQueue& microtask_queue() { return m_microtask_queue; } + TaskQueue const& microtask_queue() const { return m_microtask_queue; } + void spin_until(Function<bool()> goal_condition); void process(); @@ -44,10 +47,13 @@ public: void schedule(); + void perform_a_microtask_checkpoint(); + private: Type m_type { Type::Window }; TaskQueue m_task_queue; + TaskQueue m_microtask_queue; // https://html.spec.whatwg.org/multipage/webappapis.html#currently-running-task Task* m_currently_running_task { nullptr }; @@ -55,9 +61,14 @@ private: JS::VM* m_vm { nullptr }; RefPtr<Core::Timer> m_system_event_loop_timer; + + // https://html.spec.whatwg.org/#performing-a-microtask-checkpoint + bool m_performing_a_microtask_checkpoint { false }; }; EventLoop& main_thread_event_loop(); void queue_global_task(HTML::Task::Source, DOM::Document&, Function<void()> steps); +void queue_a_microtask(DOM::Document&, Function<void()> steps); +void perform_a_microtask_checkpoint(); } diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h index 76f9ac7704..36d9f30c5a 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h @@ -24,6 +24,7 @@ public: HistoryTraversal, IdleTask, PostedMessage, + Microtask, }; static NonnullOwnPtr<Task> create(Source source, DOM::Document* document, Function<void()> steps) diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h b/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h index 49a8e1c061..68e971b334 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/TaskQueue.h @@ -21,6 +21,14 @@ public: void add(NonnullOwnPtr<HTML::Task>); OwnPtr<HTML::Task> take_first_runnable() { return m_tasks.dequeue(); } + void enqueue(NonnullOwnPtr<HTML::Task> task) { add(move(task)); } + OwnPtr<HTML::Task> dequeue() + { + if (m_tasks.is_empty()) + return {}; + return m_tasks.dequeue(); + } + private: HTML::EventLoop& m_event_loop; |