1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18:07:34 +00:00

LibWeb: Rewrite EventTarget to more closely match the spec

This isn't perfect (especially the global object situation in
activate_event_handler), but I believe it's in a much more complete
state now :^)

This fixes the issue of crashing in prepare_for_ordinary_call with the
`i < m_size` crash, as it now uses the IDL callback functions which
requires the Environment Settings Object. The environment settings
object for the callback is fetched at the time the callback is created,
for example, WrapperGenerator gets the incumbent settings object for
the callback at the time of wrapping. This allows us to remove passing
in ScriptExecutionContext into EventTarget's constructor.

With this, we can now drop ScriptExecutionContext.
This commit is contained in:
Luke Wilde 2021-10-14 18:03:08 +01:00 committed by Linus Groh
parent 3bb5c6207f
commit 5aacec65ab
39 changed files with 874 additions and 300 deletions

View file

@ -12,7 +12,7 @@
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
#include <LibWeb/Bindings/EventWrapper.h>
#include <LibWeb/Bindings/EventWrapperFactory.h>
#include <LibWeb/Bindings/ScriptExecutionContext.h>
#include <LibWeb/Bindings/IDLAbstractOperations.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
@ -52,68 +52,87 @@ static EventTarget* retarget(EventTarget* left, EventTarget* right)
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
bool EventDispatcher::inner_invoke(Event& event, Vector<EventTarget::EventListenerRegistration>& listeners, Event::Phase phase, bool invocation_target_in_shadow_tree)
{
// 1. Let found be false.
bool found = false;
// 2. For each listener in listeners, whose removed is false:
for (auto& listener : listeners) {
if (listener.listener->removed())
continue;
// 1. If events type attribute value is not listeners type, then continue.
if (event.type() != listener.listener->type())
continue;
// 2. Set found to true.
found = true;
// 3. If phase is "capturing" and listeners capture is false, then continue.
if (phase == Event::Phase::CapturingPhase && !listener.listener->capture())
continue;
// 4. If phase is "bubbling" and listeners capture is true, then continue.
if (phase == Event::Phase::BubblingPhase && listener.listener->capture())
continue;
// 5. If listeners once is true, then remove listener from events currentTarget attribute values event listener list.
if (listener.listener->once())
event.current_target()->remove_from_event_listener_list(listener.listener);
auto& function = listener.listener->function();
auto& global = function.global_object();
// 6. Let global be listener callbacks associated Realms global object.
auto& callback = listener.listener->callback();
auto& global = callback.callback.cell()->global_object();
// 7. Let currentEvent be undefined.
RefPtr<Event> current_event;
// 8. If global is a Window object, then:
if (is<Bindings::WindowObject>(global)) {
auto& bindings_window_global = verify_cast<Bindings::WindowObject>(global);
auto& window_impl = bindings_window_global.impl();
// 1. Set currentEvent to globals current event.
current_event = window_impl.current_event();
// 2. If invocationTargetInShadowTree is false, then set globals current event to event.
if (!invocation_target_in_shadow_tree)
window_impl.set_current_event(&event);
}
// 9. If listeners passive is true, then set events in passive listener flag.
if (listener.listener->passive())
event.set_in_passive_listener(true);
// 10. Call a user objects operation with listeners callback, "handleEvent", « event », and events currentTarget attribute value. If this throws an exception, then:
// FIXME: These should be wrapped for us in call_user_object_operation, but it currently doesn't do that.
auto* this_value = Bindings::wrap(global, *event.current_target());
auto* wrapped_event = Bindings::wrap(global, event);
// 10. Call a user objects operation with listeners callback, "handleEvent", « event », and events currentTarget attribute value.
auto result = JS::call(global, function, this_value, wrapped_event);
auto result = Bindings::IDL::call_user_object_operation(callback, "handleEvent", this_value, wrapped_event);
// If this throws an exception, then:
if (result.is_error()) {
// 1. Report the exception.
VERIFY(result.throw_completion().value().has_value());
HTML::report_exception(*result.throw_completion().value());
HTML::report_exception(result);
// FIXME: 2. Set legacyOutputDidListenersThrowFlag if given. (Only used by IndexedDB currently)
}
// 11. Unset events in passive listener flag.
event.set_in_passive_listener(false);
// 12. If global is a Window object, then set globals current event to currentEvent.
if (is<Bindings::WindowObject>(global)) {
auto& bindings_window_global = verify_cast<Bindings::WindowObject>(global);
auto& window_impl = bindings_window_global.impl();
window_impl.set_current_event(current_event);
}
// 13. If events stop immediate propagation flag is set, then return found.
if (event.should_stop_immediate_propagation())
return found;
}
// 3. Return found.
return found;
}