From 08373090ae6914d28064eab403caf5774b4564dc Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 24 Apr 2021 18:21:02 +0200 Subject: [PATCH] LibJS: Add VM::on_call_stack_emptied callback Instead of having to run queued promise jobs in LibWeb in various places, this allows us to consolidate that into one function - this is very close to how the spec describes it as well ("at some future point in time, when there is no running execution context and the execution context stack is empty, the implementation must [...]"). Eventually this will also be used to log unhandled exceptions, and possibly other actions that require JS execution to have ended. --- Userland/Libraries/LibJS/Interpreter.cpp | 10 ++++++++-- Userland/Libraries/LibJS/Runtime/VM.h | 8 +++++++- Userland/Libraries/LibWeb/DOM/Document.cpp | 4 ++++ Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp | 2 -- Userland/Libraries/LibWeb/DOM/Window.cpp | 4 ---- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp index 9bbb929e91..f3bd3685e9 100644 --- a/Userland/Libraries/LibJS/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Interpreter.cpp @@ -54,11 +54,17 @@ void Interpreter::run(GlobalObject& global_object, const Program& program) vm.push_call_frame(global_call_frame, global_object); VERIFY(!vm.exception()); program.execute(*this, global_object); + + // Whatever the promise jobs or on_call_stack_emptied do should not affect the effective + // 'last value'. + auto last_value = vm.last_value(); + vm.pop_call_frame(); - // Whatever the promise jobs do should not affect the effective 'last value'. - auto last_value = vm.last_value(); + // At this point we may have already run any queued promise jobs via on_call_stack_emptied, + // in which case this is a no-op. vm.run_queued_promise_jobs(); + vm.set_last_value({}, last_value.value_or(js_undefined())); } diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index d154414b46..e9e8c8f823 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -106,7 +106,12 @@ public: m_call_stack.append(&call_frame); } - void pop_call_frame() { m_call_stack.take_last(); } + void pop_call_frame() + { + m_call_stack.take_last(); + if (m_call_stack.is_empty() && on_call_stack_emptied) + on_call_stack_emptied(); + } CallFrame& call_frame() { return *m_call_stack.last(); } const CallFrame& call_frame() const { return *m_call_stack.last(); } @@ -229,6 +234,7 @@ public: void promise_rejection_tracker(const Promise&, Promise::RejectionOperation) const; + AK::Function on_call_stack_emptied; AK::Function on_promise_unhandled_rejection; AK::Function on_promise_rejection_handled; diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 3e3e8e82a2..e0cffb8965 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -560,6 +560,10 @@ JS::Interpreter& Document::interpreter() auto& vm = Bindings::main_thread_vm(); // TODO: Hook up vm.on_promise_unhandled_rejection and vm.on_promise_rejection_handled // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#promise_rejection_events + vm.on_call_stack_emptied = [this] { + auto& vm = m_interpreter->vm(); + vm.run_queued_promise_jobs(); + }; m_interpreter = JS::Interpreter::create(vm, *m_window); } return *m_interpreter; diff --git a/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp b/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp index 7d4e73616c..78eccef06a 100644 --- a/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp +++ b/Userland/Libraries/LibWeb/DOM/EventDispatcher.cpp @@ -94,8 +94,6 @@ bool EventDispatcher::inner_invoke(Event& event, Vector(global)) { diff --git a/Userland/Libraries/LibWeb/DOM/Window.cpp b/Userland/Libraries/LibWeb/DOM/Window.cpp index 1b9f70e320..a5fc6fe89b 100644 --- a/Userland/Libraries/LibWeb/DOM/Window.cpp +++ b/Userland/Libraries/LibWeb/DOM/Window.cpp @@ -90,8 +90,6 @@ void Window::timer_did_fire(Badge, Timer& timer) [[maybe_unused]] auto rc = vm.call(timer.callback(), wrapper()); if (vm.exception()) vm.clear_exception(); - vm.run_queued_promise_jobs(); - VERIFY(!vm.exception()); } i32 Window::allocate_timer_id(Badge) @@ -126,8 +124,6 @@ i32 Window::request_animation_frame(JS::Function& callback) [[maybe_unused]] auto rc = vm.call(function, JS::js_undefined(), JS::Value(fake_timestamp)); if (vm.exception()) vm.clear_exception(); - vm.run_queued_promise_jobs(); - VERIFY(!vm.exception()); GUI::DisplayLink::unregister_callback(link_id); });