LibWeb: Push promise rejection handling onto UniversalGlobalScopeMixin
This is needed for shadow realms which don't have a window or worker global object. Fixes a crash for the attached test.
This commit is contained in:
parent
fb17f8ff66
commit
6a668f27c7
Notes:
github-actions[bot]
2024-11-30 11:07:17 +00:00
Author: https://github.com/shannonbooth Commit: https://github.com/LadybirdBrowser/ladybird/commit/6a668f27c75 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2631
9 changed files with 129 additions and 109 deletions
|
@ -161,7 +161,7 @@ ErrorOr<void> initialize_main_thread_vm(HTML::EventLoop::Type type)
|
|||
auto& realm = script ? script->realm() : *vm.current_realm();
|
||||
|
||||
// 5. Let global be realm's global object.
|
||||
auto* global_mixin = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
|
||||
auto* global_mixin = dynamic_cast<HTML::UniversalGlobalScopeMixin*>(&realm.global_object());
|
||||
VERIFY(global_mixin);
|
||||
auto& global = global_mixin->this_impl();
|
||||
|
||||
|
|
|
@ -497,7 +497,7 @@ void EventLoop::perform_a_microtask_checkpoint()
|
|||
|
||||
// 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<HTML::WindowOrWorkerGlobalScopeMixin*>(&environment_settings_object->global_object());
|
||||
auto* global = dynamic_cast<HTML::UniversalGlobalScopeMixin*>(&environment_settings_object->global_object());
|
||||
VERIFY(global);
|
||||
global->notify_about_rejected_promises({});
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <LibWeb/Bindings/SyntheticHostDefined.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/FetchRecord.h>
|
||||
#include <LibWeb/HTML/PromiseRejectionEvent.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibGC/Function.h>
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibWeb/HTML/PromiseRejectionEvent.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||
#include <LibWeb/HTML/StructuredSerializeOptions.h>
|
||||
|
@ -32,6 +33,7 @@ void UniversalGlobalScopeMixin::visit_edges(GC::Cell::Visitor& visitor)
|
|||
{
|
||||
visitor.visit(m_count_queuing_strategy_size_function);
|
||||
visitor.visit(m_byte_length_queuing_strategy_size_function);
|
||||
visitor.ignore(m_outstanding_rejected_promises_weak_set);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#dom-btoa
|
||||
|
@ -156,4 +158,89 @@ GC::Ref<WebIDL::CallbackType> UniversalGlobalScopeMixin::byte_length_queuing_str
|
|||
return GC::Ref { *m_byte_length_queuing_strategy_size_function };
|
||||
}
|
||||
|
||||
void UniversalGlobalScopeMixin::push_onto_outstanding_rejected_promises_weak_set(JS::Promise* promise)
|
||||
{
|
||||
m_outstanding_rejected_promises_weak_set.append(promise);
|
||||
}
|
||||
|
||||
bool UniversalGlobalScopeMixin::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 UniversalGlobalScopeMixin::push_onto_about_to_be_notified_rejected_promises_list(GC::Ref<JS::Promise> promise)
|
||||
{
|
||||
m_about_to_be_notified_rejected_promises_list.append(GC::make_root(promise));
|
||||
}
|
||||
|
||||
bool UniversalGlobalScopeMixin::remove_from_about_to_be_notified_rejected_promises_list(GC::Ref<JS::Promise> 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 UniversalGlobalScopeMixin::notify_about_rejected_promises(Badge<EventLoop>)
|
||||
{
|
||||
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<DOM::EventTarget>(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, GC::create_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 = */ *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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,18 @@ public:
|
|||
GC::Ref<WebIDL::CallbackType> count_queuing_strategy_size_function();
|
||||
GC::Ref<WebIDL::CallbackType> byte_length_queuing_strategy_size_function();
|
||||
|
||||
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(GC::Ref<JS::Promise>);
|
||||
|
||||
// Returns true if removed, false otherwise.
|
||||
bool remove_from_about_to_be_notified_rejected_promises_list(GC::Ref<JS::Promise>);
|
||||
|
||||
void notify_about_rejected_promises(Badge<EventLoop>);
|
||||
|
||||
protected:
|
||||
void visit_edges(GC::Cell::Visitor&);
|
||||
|
||||
|
@ -41,6 +53,13 @@ private:
|
|||
|
||||
// https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
|
||||
GC::Ptr<WebIDL::CallbackType> m_byte_length_queuing_strategy_size_function;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
|
||||
Vector<GC::Root<JS::Promise>> 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<GC::Ptr<JS::Promise>> m_outstanding_rejected_promises_weak_set;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||
#include <LibWeb/HTML/EventSource.h>
|
||||
#include <LibWeb/HTML/ImageBitmap.h>
|
||||
#include <LibWeb/HTML/PromiseRejectionEvent.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
||||
|
@ -74,7 +73,6 @@ 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()
|
||||
|
@ -791,89 +789,4 @@ GC::Ref<Crypto::Crypto> WindowOrWorkerGlobalScopeMixin::crypto()
|
|||
return GC::Ref { *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(GC::Ref<JS::Promise> promise)
|
||||
{
|
||||
m_about_to_be_notified_rejected_promises_list.append(GC::make_root(promise));
|
||||
}
|
||||
|
||||
bool WindowOrWorkerGlobalScopeMixin::remove_from_about_to_be_notified_rejected_promises_list(GC::Ref<JS::Promise> 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<EventLoop>)
|
||||
{
|
||||
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<DOM::EventTarget>(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, GC::create_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 = */ *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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -76,18 +76,6 @@ public:
|
|||
|
||||
[[nodiscard]] GC::Ref<Crypto::Crypto> 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(GC::Ref<JS::Promise>);
|
||||
|
||||
// Returns true if removed, false otherwise.
|
||||
bool remove_from_about_to_be_notified_rejected_promises_list(GC::Ref<JS::Promise>);
|
||||
|
||||
void notify_about_rejected_promises(Badge<EventLoop>);
|
||||
|
||||
protected:
|
||||
void initialize(JS::Realm&);
|
||||
void visit_edges(JS::Cell::Visitor&);
|
||||
|
@ -130,13 +118,6 @@ private:
|
|||
GC::Ptr<Crypto::Crypto> m_crypto;
|
||||
|
||||
bool m_error_reporting_mode { false };
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
|
||||
Vector<GC::Root<JS::Promise>> 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<GC::Ptr<JS::Promise>> m_outstanding_rejected_promises_weak_set;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Error: An error!
|
20
Tests/LibWeb/Text/input/HTML/ShadowRealm-rejection.html
Normal file
20
Tests/LibWeb/Text/input/HTML/ShadowRealm-rejection.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script src="../include.js"></script>
|
||||
<script>
|
||||
asyncTest(async done => {
|
||||
const shadowRealm = new ShadowRealm();
|
||||
|
||||
try {
|
||||
const result = await new Promise(shadowRealm.evaluate(`
|
||||
(resolve, reject) => {
|
||||
(async () => {
|
||||
throw new Error('An error!');
|
||||
})().then(resolve, (e) => reject(e.toString()));
|
||||
}
|
||||
`));
|
||||
} catch (e) {
|
||||
println(e);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
</script>
|
Loading…
Add table
Reference in a new issue