diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp index d63181d20bd..0ab0ebe61dc 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -150,31 +150,33 @@ ErrorOr initialize_main_thread_vm(HTML::EventLoop::Type type) // 4. If script is not null, then set settings object to script's settings object. auto& settings_object = script ? script->settings_object() : HTML::current_settings_object(); + // 5. Let global be settingsObject's global object. + auto* global_mixin = dynamic_cast(&settings_object.global_object()); + VERIFY(global_mixin); + auto& global = global_mixin->this_impl(); + switch (operation) { + // 6. If operation is "reject", case JS::Promise::RejectionOperation::Reject: - // 4. If operation is "reject", - // 1. Add promise to settings object's about-to-be-notified rejected promises list. - settings_object.push_onto_about_to_be_notified_rejected_promises_list(promise); + // 1. Append promise to global's about-to-be-notified rejected promises list. + global_mixin->push_onto_about_to_be_notified_rejected_promises_list(promise); break; + // 7. If operation is "handle", case JS::Promise::RejectionOperation::Handle: { - // 5. If operation is "handle", - // 1. If settings object's about-to-be-notified rejected promises list contains promise, then remove promise from that list and return. - bool removed_about_to_be_notified_rejected_promise = settings_object.remove_from_about_to_be_notified_rejected_promises_list(promise); + // 1. If global's about-to-be-notified rejected promises list contains promise, then remove promise from that list and return. + bool removed_about_to_be_notified_rejected_promise = global_mixin->remove_from_about_to_be_notified_rejected_promises_list(promise); if (removed_about_to_be_notified_rejected_promise) return; - // 3. Remove promise from settings object's outstanding rejected promises weak set. - bool removed_outstanding_rejected_promise = settings_object.remove_from_outstanding_rejected_promises_weak_set(&promise); + // 3. Remove promise from global's outstanding rejected promises weak set. + bool removed_outstanding_rejected_promise = global_mixin->remove_from_outstanding_rejected_promises_weak_set(&promise); - // 2. If settings object's outstanding rejected promises weak set does not contain promise, then return. + // 2. If global's outstanding rejected promises weak set does not contain promise, then return. // NOTE: This is done out of order because removed_outstanding_rejected_promise will be false if the promise wasn't in the set or true if it was and got removed. if (!removed_outstanding_rejected_promise) return; - // 4. Let global be settings object's global object. - auto& global = settings_object.global_object(); - - // 5. Queue a global task on the DOM manipulation task source given global to fire an event named rejectionhandled at global, using PromiseRejectionEvent, + // 4. Queue a global task on the DOM manipulation task source given global to fire an event named rejectionhandled at global, using PromiseRejectionEvent, // with the promise attribute initialized to promise, and the reason attribute initialized to the value of promise's [[PromiseResult]] internal slot. HTML::queue_global_task(HTML::Task::Source::DOMManipulation, global, JS::create_heap_function(s_main_thread_vm->heap(), [&global, &promise] { // FIXME: This currently assumes that global is a WindowObject. diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index f1ec448d71b..0e43b48688e 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -488,9 +488,12 @@ void EventLoop::perform_a_microtask_checkpoint() m_currently_running_task = nullptr; } - // 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object. - for (auto& environment_settings_object : m_related_environment_settings_objects) - environment_settings_object->notify_about_rejected_promises({}); + // 4. For each environment settings object settingsObject whose responsible event loop is this event loop, notify about rejected promises given settingsObject's global object. + for (auto& environment_settings_object : m_related_environment_settings_objects) { + auto* global = dynamic_cast(&environment_settings_object->global_object()); + VERIFY(global); + global->notify_about_rejected_promises({}); + } // FIXME: 5. Cleanup Indexed Database transactions. @@ -499,6 +502,8 @@ void EventLoop::perform_a_microtask_checkpoint() // 7. Set the event loop's performing a microtask checkpoint to false. m_performing_a_microtask_checkpoint = false; + + // FIXME: 8. Record timing info for microtask checkpoint. } Vector> EventLoop::documents_in_this_event_loop() const diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp index 0777a8daf9b..7becdf0d7d6 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp @@ -54,7 +54,6 @@ void EnvironmentSettingsObject::visit_edges(Cell::Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_responsible_event_loop); visitor.visit(m_module_map); - visitor.ignore(m_outstanding_rejected_promises_weak_set); m_realm_execution_context->visit_edges(visitor); visitor.visit(m_fetch_group); visitor.visit(m_storage_manager); @@ -208,89 +207,6 @@ void EnvironmentSettingsObject::clean_up_after_running_callback() event_loop.pop_backup_incumbent_settings_object_stack({}); } -void EnvironmentSettingsObject::push_onto_outstanding_rejected_promises_weak_set(JS::Promise* promise) -{ - m_outstanding_rejected_promises_weak_set.append(promise); -} - -bool EnvironmentSettingsObject::remove_from_outstanding_rejected_promises_weak_set(JS::Promise* promise) -{ - return m_outstanding_rejected_promises_weak_set.remove_first_matching([&](JS::Promise* promise_in_set) { - return promise == promise_in_set; - }); -} - -void EnvironmentSettingsObject::push_onto_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr promise) -{ - m_about_to_be_notified_rejected_promises_list.append(JS::make_handle(promise)); -} - -bool EnvironmentSettingsObject::remove_from_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr promise) -{ - return m_about_to_be_notified_rejected_promises_list.remove_first_matching([&](auto& promise_in_list) { - return promise == promise_in_list; - }); -} - -// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises -void EnvironmentSettingsObject::notify_about_rejected_promises(Badge) -{ - // 1. Let list be a copy of settings object's about-to-be-notified rejected promises list. - auto list = m_about_to_be_notified_rejected_promises_list; - - // 2. If list is empty, return. - if (list.is_empty()) - return; - - // 3. Clear settings object's about-to-be-notified rejected promises list. - m_about_to_be_notified_rejected_promises_list.clear(); - - // 4. Let global be settings object's global object. - // We need this as an event target for the unhandledrejection event below - auto& global = verify_cast(global_object()); - - // 5. Queue a global task on the DOM manipulation task source given global to run the following substep: - queue_global_task(Task::Source::DOMManipulation, global, JS::create_heap_function(heap(), [this, &global, list = move(list)] { - auto& realm = global.realm(); - - // 1. For each promise p in list: - for (auto const& promise : list) { - - // 1. If p's [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop. - if (promise->is_handled()) - continue; - - // 2. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true, - // the promise attribute initialized to p, and the reason attribute initialized to the value of p's [[PromiseResult]] internal slot. - PromiseRejectionEventInit event_init { - { - .bubbles = false, - .cancelable = true, - .composed = false, - }, - // Sadly we can't use .promise and .reason here, as we can't use the designator on the initialization of DOM::EventInit above. - /* .promise = */ JS::make_handle(*promise), - /* .reason = */ promise->result(), - }; - - auto promise_rejection_event = PromiseRejectionEvent::create(realm, HTML::EventNames::unhandledrejection, event_init); - - bool not_handled = global.dispatch_event(*promise_rejection_event); - - // 3. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled. - - // 4. If p's [[PromiseIsHandled]] internal slot is false, add p to settings object's outstanding rejected promises weak set. - if (!promise->is_handled()) - m_outstanding_rejected_promises_weak_set.append(*promise); - - // This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors. - // If a rejection is still not handled after this, then the rejection may be reported to a developer console. - if (not_handled) - HTML::report_exception_to_console(promise->result(), realm, ErrorInPromise::Yes); - } - })); -} - // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-script bool EnvironmentSettingsObject::is_scripting_enabled() const { diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h index 8b360306d34..d2ef0104945 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h @@ -101,18 +101,6 @@ public: void prepare_to_run_callback(); void clean_up_after_running_callback(); - void push_onto_outstanding_rejected_promises_weak_set(JS::Promise*); - - // Returns true if removed, false otherwise. - bool remove_from_outstanding_rejected_promises_weak_set(JS::Promise*); - - void push_onto_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr); - - // Returns true if removed, false otherwise. - bool remove_from_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr); - - void notify_about_rejected_promises(Badge); - bool is_scripting_enabled() const; bool is_scripting_disabled() const; @@ -138,13 +126,6 @@ private: JS::GCPtr m_responsible_event_loop; - // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set - // The outstanding rejected promises weak set must not create strong references to any of its members, and implementations are free to limit its size, e.g. by removing old entries from it when new ones are added. - Vector> m_outstanding_rejected_promises_weak_set; - - // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list - Vector> m_about_to_be_notified_rejected_promises_list; - // https://fetch.spec.whatwg.org/#concept-fetch-record // A fetch group holds an ordered list of fetch records Vector> m_fetch_group; diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp index afaee045208..bee9b77ff64 100644 --- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp +++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,7 @@ void WindowOrWorkerGlobalScopeMixin::visit_edges(JS::Cell::Visitor& visitor) entry.value.visit_edges(visitor); visitor.visit(m_registered_event_sources); visitor.visit(m_crypto); + visitor.ignore(m_outstanding_rejected_promises_weak_set); } void WindowOrWorkerGlobalScopeMixin::finalize() @@ -861,4 +863,89 @@ JS::NonnullGCPtr WindowOrWorkerGlobalScopeMixin::crypto() return JS::NonnullGCPtr { *m_crypto }; } +void WindowOrWorkerGlobalScopeMixin::push_onto_outstanding_rejected_promises_weak_set(JS::Promise* promise) +{ + m_outstanding_rejected_promises_weak_set.append(promise); +} + +bool WindowOrWorkerGlobalScopeMixin::remove_from_outstanding_rejected_promises_weak_set(JS::Promise* promise) +{ + return m_outstanding_rejected_promises_weak_set.remove_first_matching([&](JS::Promise* promise_in_set) { + return promise == promise_in_set; + }); +} + +void WindowOrWorkerGlobalScopeMixin::push_onto_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr promise) +{ + m_about_to_be_notified_rejected_promises_list.append(JS::make_handle(promise)); +} + +bool WindowOrWorkerGlobalScopeMixin::remove_from_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr promise) +{ + return m_about_to_be_notified_rejected_promises_list.remove_first_matching([&](auto& promise_in_list) { + return promise == promise_in_list; + }); +} + +// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises +void WindowOrWorkerGlobalScopeMixin::notify_about_rejected_promises(Badge) +{ + auto& realm = this_impl().realm(); + + // 1. Let list be a copy of settings object's about-to-be-notified rejected promises list. + auto list = m_about_to_be_notified_rejected_promises_list; + + // 2. If list is empty, return. + if (list.is_empty()) + return; + + // 3. Clear settings object's about-to-be-notified rejected promises list. + m_about_to_be_notified_rejected_promises_list.clear(); + + // 4. Let global be settings object's global object. + // We need this as an event target for the unhandledrejection event below + auto& global = verify_cast(this_impl()); + + // 5. Queue a global task on the DOM manipulation task source given global to run the following substep: + queue_global_task(Task::Source::DOMManipulation, global, JS::create_heap_function(realm.heap(), [this, &global, list = move(list)] { + auto& realm = global.realm(); + + // 1. For each promise p in list: + for (auto const& promise : list) { + + // 1. If p's [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop. + if (promise->is_handled()) + continue; + + // 2. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true, + // the promise attribute initialized to p, and the reason attribute initialized to the value of p's [[PromiseResult]] internal slot. + PromiseRejectionEventInit event_init { + { + .bubbles = false, + .cancelable = true, + .composed = false, + }, + // Sadly we can't use .promise and .reason here, as we can't use the designator on the initialization of DOM::EventInit above. + /* .promise = */ JS::make_handle(*promise), + /* .reason = */ promise->result(), + }; + + auto promise_rejection_event = PromiseRejectionEvent::create(realm, HTML::EventNames::unhandledrejection, event_init); + + bool not_handled = global.dispatch_event(*promise_rejection_event); + + // 3. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled. + + // 4. If p's [[PromiseIsHandled]] internal slot is false, add p to settings object's outstanding rejected promises weak set. + if (!promise->is_handled()) + m_outstanding_rejected_promises_weak_set.append(*promise); + + // This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors. + // If a rejection is still not handled after this, then the rejection may be reported to a developer console. + if (not_handled) + HTML::report_exception_to_console(promise->result(), realm, ErrorInPromise::Yes); + } + })); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h index 8005c2ec9f7..5414c0b1163 100644 --- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h +++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h @@ -80,6 +80,18 @@ public: [[nodiscard]] JS::NonnullGCPtr crypto(); + void push_onto_outstanding_rejected_promises_weak_set(JS::Promise*); + + // Returns true if removed, false otherwise. + bool remove_from_outstanding_rejected_promises_weak_set(JS::Promise*); + + void push_onto_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr); + + // Returns true if removed, false otherwise. + bool remove_from_about_to_be_notified_rejected_promises_list(JS::NonnullGCPtr); + + void notify_about_rejected_promises(Badge); + protected: void initialize(JS::Realm&); void visit_edges(JS::Cell::Visitor&); @@ -122,6 +134,13 @@ private: JS::GCPtr m_crypto; bool m_error_reporting_mode { false }; + + // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list + Vector> m_about_to_be_notified_rejected_promises_list; + + // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set + // The outstanding rejected promises weak set must not create strong references to any of its members, and implementations are free to limit its size, e.g. by removing old entries from it when new ones are added. + Vector> m_outstanding_rejected_promises_weak_set; }; }