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); });