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
Notes: sideshowbarker 2024-07-17 19:09:19 +09:00
39 changed files with 874 additions and 300 deletions

View File

@ -1243,22 +1243,26 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
}
}
} else if (parameter.type->name == "EventListener") {
// FIXME: Replace this with support for callback interfaces. https://heycam.github.io/webidl/#idl-callback-interface
if (parameter.type->nullable) {
scoped_generator.append(R"~~~(
RefPtr<EventListener> @cpp_name@;
if (!@js_name@@js_suffix@.is_nullish()) {
if (!@js_name@@js_suffix@.is_function())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "Function");
if (!@js_name@@js_suffix@.is_object())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects());
@cpp_name@ = adopt_ref(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
CallbackType callback_type(JS::make_handle(&@js_name@@js_suffix@.as_object()), HTML::incumbent_settings_object());
@cpp_name@ = adopt_ref(*new EventListener(move(callback_type)));
}
)~~~");
} else {
scoped_generator.append(R"~~~(
if (!@js_name@@js_suffix@.is_function())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "Function");
if (!@js_name@@js_suffix@.is_object())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects());
auto @cpp_name@ = adopt_ref(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
CallbackType callback_type(JS::make_handle(&@js_name@@js_suffix@.as_object()), HTML::incumbent_settings_object());
auto @cpp_name@ = adopt_ref(*new EventListener(move(callback_type)));
)~~~");
}
} else if (IDL::is_wrappable_type(*parameter.type)) {
@ -1354,15 +1358,15 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
auto @cpp_name@ = TRY(@js_name@@js_suffix@.to_i32(global_object));
)~~~");
} else if (parameter.type->name == "EventHandler") {
// x.onfoo = function() { ... }
// x.onfoo = function() { ... }, x.onfoo = () => { ... }, x.onfoo = {}
// NOTE: Anything else than an object will be treated as null. This is because EventHandler has the [LegacyTreatNonObjectAsNull] extended attribute.
// Yes, you can store objects in event handler attributes. They just get ignored when there's any attempt to invoke them.
// FIXME: Replace this with proper support for callback function types.
scoped_generator.append(R"~~~(
HTML::EventHandler @cpp_name@;
if (@js_name@@js_suffix@.is_function()) {
@cpp_name@.callback = JS::make_handle(&@js_name@@js_suffix@.as_function());
} else if (@js_name@@js_suffix@.is_string()) {
@cpp_name@.string = @js_name@@js_suffix@.as_string().string();
} else {
return JS::js_undefined();
Optional<Bindings::CallbackType> @cpp_name@;
if (@js_name@@js_suffix@.is_object()) {
@cpp_name@ = Bindings::CallbackType { JS::make_handle(&@js_name@@js_suffix@.as_object()), HTML::incumbent_settings_object() };
}
)~~~");
} else if (parameter.type->name == "Promise") {
@ -2013,11 +2017,15 @@ static void generate_wrap_statement(SourceGenerator& generator, String const& va
@result_expression@ @value@;
)~~~");
} else if (type.name == "EventHandler") {
// FIXME: Replace this with proper support for callback function types.
scoped_generator.append(R"~~~(
if (@value@.callback.is_null())
if (!@value@) {
@result_expression@ JS::js_null();
else
@result_expression@ @value@.callback.cell();
} else {
VERIFY(!@value@->callback.is_null());
@result_expression@ @value@->callback.cell();
}
)~~~");
} else if (is<IDL::UnionType>(type)) {
TODO();

View File

@ -9,10 +9,176 @@
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/Bindings/CallbackType.h>
#include <LibWeb/HTML/Scripting/Environments.h>
namespace Web::Bindings::IDL {
bool is_an_array_index(JS::GlobalObject&, JS::PropertyKey const&);
Optional<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source);
// https://webidl.spec.whatwg.org/#call-user-object-operation-return
inline JS::Completion clean_up_on_return(HTML::EnvironmentSettingsObject& stored_settings, HTML::EnvironmentSettingsObject& relevant_settings, JS::Completion& completion)
{
// Return: at this point completion will be set to an ECMAScript completion value.
// 1. Clean up after running a callback with stored settings.
stored_settings.clean_up_after_running_callback();
// 2. Clean up after running script with relevant settings.
relevant_settings.clean_up_after_running_script();
// 3. If completion is a normal completion, return completion.
if (completion.type() == JS::Completion::Type::Normal)
return completion;
// 4. If completion is an abrupt completion and the operation has a return type that is not a promise type, return completion.
// FIXME: This does not handle promises and thus always returns completion at this point.
return completion;
// FIXME: 5. Let rejectedPromise be ! Call(%Promise.reject%, %Promise%, «completion.[[Value]]»).
// FIXME: 6. Return the result of converting rejectedPromise to the operations return type.
}
// https://webidl.spec.whatwg.org/#call-a-user-objects-operation
template<typename... Args>
JS::Completion call_user_object_operation(Bindings::CallbackType& callback, String const& operation_name, Optional<JS::Value> this_argument, Args&&... args)
{
// 1. Let completion be an uninitialized variable.
JS::Completion completion;
// 2. If thisArg was not given, let thisArg be undefined.
if (!this_argument.has_value())
this_argument = JS::js_undefined();
// 3. Let O be the ECMAScript object corresponding to value.
auto& object = *callback.callback.cell();
// 4. Let realm be Os associated Realm.
auto& global_object = object.global_object();
auto& realm = *global_object.associated_realm();
// 5. Let relevant settings be realms settings object.
auto& relevant_settings = verify_cast<HTML::EnvironmentSettingsObject>(*realm.host_defined());
// 6. Let stored settings be values callback context.
auto& stored_settings = callback.callback_context;
// 7. Prepare to run script with relevant settings.
relevant_settings.prepare_to_run_script();
// 8. Prepare to run a callback with stored settings.
stored_settings.prepare_to_run_callback();
// 9. Let X be O.
auto* actual_function_object = &object;
// 10. If ! IsCallable(O) is false, then:
if (!object.is_function()) {
// 1. Let getResult be Get(O, opName).
auto get_result = object.get(operation_name);
// 2. If getResult is an abrupt completion, set completion to getResult and jump to the step labeled return.
if (get_result.is_throw_completion()) {
completion = get_result.throw_completion();
return clean_up_on_return(stored_settings, relevant_settings, completion);
}
// 4. If ! IsCallable(X) is false, then set completion to a new Completion{[[Type]]: throw, [[Value]]: a newly created TypeError object, [[Target]]: empty}, and jump to the step labeled return.
if (!get_result.value().is_function()) {
completion = realm.vm().template throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAFunction, get_result.value().to_string_without_side_effects());
return clean_up_on_return(stored_settings, relevant_settings, completion);
}
// 3. Set X to getResult.[[Value]].
// NOTE: This is done out of order because `actual_function_object` is of type JS::Object and we cannot assign to it until we know for sure getResult.[[Value]] is a JS::Object.
actual_function_object = &get_result.release_value().as_object();
// 5. Set thisArg to O (overriding the provided value).
this_argument = &object;
}
// FIXME: 11. Let esArgs be the result of converting args to an ECMAScript arguments list. If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return.
// For simplicity, we currently make the caller do this. However, this means we can't throw exceptions at this point like the spec wants us to.
// 12. Let callResult be Call(X, thisArg, esArgs).
VERIFY(actual_function_object);
auto call_result = JS::call(global_object, verify_cast<JS::FunctionObject>(*actual_function_object), this_argument.value(), forward<Args>(args)...);
// 13. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return.
if (call_result.is_throw_completion()) {
completion = call_result.throw_completion();
return clean_up_on_return(stored_settings, relevant_settings, completion);
}
// 14. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operations return type.
// FIXME: This does no conversion.
completion = call_result.value();
return clean_up_on_return(stored_settings, relevant_settings, completion);
}
// https://webidl.spec.whatwg.org/#invoke-a-callback-function
template<typename... Args>
JS::Completion invoke_callback(Bindings::CallbackType& callback, Optional<JS::Value> this_argument, Args&&... args)
{
// 1. Let completion be an uninitialized variable.
JS::Completion completion;
// 2. If thisArg was not given, let thisArg be undefined.
if (!this_argument.has_value())
this_argument = JS::js_undefined();
// 3. Let F be the ECMAScript object corresponding to callable.
auto* function_object = callback.callback.cell();
VERIFY(function_object);
// 4. If ! IsCallable(F) is false:
if (!function_object->is_function()) {
// 1. Note: This is only possible when the callback function came from an attribute marked with [LegacyTreatNonObjectAsNull].
// 2. Return the result of converting undefined to the callback functions return type.
// FIXME: This does no conversion.
return { JS::js_undefined() };
}
// 5. Let realm be Fs associated Realm.
// See the comment about associated realm on step 4 of call_user_object_operation.
auto& global_object = function_object->global_object();
auto& realm = *global_object.associated_realm();
// 6. Let relevant settings be realms settings object.
auto& relevant_settings = verify_cast<HTML::EnvironmentSettingsObject>(*realm.host_defined());
// 7. Let stored settings be values callback context.
auto& stored_settings = callback.callback_context;
// 8. Prepare to run script with relevant settings.
relevant_settings.prepare_to_run_script();
// 9. Prepare to run a callback with stored settings.
stored_settings.prepare_to_run_callback();
// FIXME: 10. Let esArgs be the result of converting args to an ECMAScript arguments list. If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return.
// For simplicity, we currently make the caller do this. However, this means we can't throw exceptions at this point like the spec wants us to.
// 11. Let callResult be Call(F, thisArg, esArgs).
auto call_result = JS::call(global_object, verify_cast<JS::FunctionObject>(*function_object), this_argument.value(), forward<Args>(args)...);
// 12. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return.
if (call_result.is_throw_completion()) {
completion = call_result.throw_completion();
return clean_up_on_return(stored_settings, relevant_settings, completion);
}
// 13. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operations return type.
// FIXME: This does no conversion.
completion = call_result.value();
return clean_up_on_return(stored_settings, relevant_settings, completion);
}
}

View File

@ -1,15 +0,0 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/ScriptExecutionContext.h>
namespace Web::Bindings {
ScriptExecutionContext::~ScriptExecutionContext()
{
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Weakable.h>
#include <LibJS/Forward.h>
#include <LibWeb/Forward.h>
namespace Web::Bindings {
class ScriptExecutionContext {
public:
virtual ~ScriptExecutionContext();
virtual JS::Realm& realm() = 0;
};
}

View File

@ -223,16 +223,16 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_interval)
auto* impl = TRY(impl_from(vm, global_object));
if (!vm.argument_count())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "setInterval");
JS::FunctionObject* callback;
JS::Object* callback_object;
if (vm.argument(0).is_function()) {
callback = &vm.argument(0).as_function();
callback_object = &vm.argument(0).as_function();
} else {
auto script_source = TRY(vm.argument(0).to_string(global_object));
// FIXME: This needs more work once we have a environment settings object.
// The spec wants us to use a task for the "run function or script string" part,
// using a NativeFunction for the latter is a workaround so that we can reuse the
// DOM::Timer API unaltered (always expects a JS::FunctionObject).
callback = JS::NativeFunction::create(global_object, "", [impl, script_source = move(script_source)](auto&, auto&) mutable {
callback_object = JS::NativeFunction::create(global_object, "", [impl, script_source = move(script_source)](auto&, auto&) mutable {
auto& settings_object = verify_cast<HTML::EnvironmentSettingsObject>(*impl->associated_document().realm().host_defined());
auto script = HTML::ClassicScript::create(impl->associated_document().url().to_string(), script_source, settings_object, AK::URL());
return script->run();
@ -244,8 +244,10 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_interval)
if (interval < 0)
interval = 0;
}
NonnullOwnPtr<Bindings::CallbackType> callback = adopt_own(*new Bindings::CallbackType(JS::make_handle(callback_object), HTML::incumbent_settings_object()));
// FIXME: Pass ...arguments to the callback function when it's invoked
auto timer_id = impl->set_interval(*callback, interval);
auto timer_id = impl->set_interval(move(callback), interval);
return JS::Value(timer_id);
}
@ -257,16 +259,16 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_timeout)
auto* impl = TRY(impl_from(vm, global_object));
if (!vm.argument_count())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::BadArgCountAtLeastOne, "setTimeout");
JS::FunctionObject* callback;
JS::Object* callback_object;
if (vm.argument(0).is_function()) {
callback = &vm.argument(0).as_function();
callback_object = &vm.argument(0).as_function();
} else {
auto script_source = TRY(vm.argument(0).to_string(global_object));
// FIXME: This needs more work once we have a environment settings object.
// The spec wants us to use a task for the "run function or script string" part,
// using a NativeFunction for the latter is a workaround so that we can reuse the
// DOM::Timer API unaltered (always expects a JS::FunctionObject).
callback = JS::NativeFunction::create(global_object, "", [impl, script_source = move(script_source)](auto&, auto&) mutable {
callback_object = JS::NativeFunction::create(global_object, "", [impl, script_source = move(script_source)](auto&, auto&) mutable {
auto& settings_object = verify_cast<HTML::EnvironmentSettingsObject>(*impl->associated_document().realm().host_defined());
auto script = HTML::ClassicScript::create(impl->associated_document().url().to_string(), script_source, settings_object, AK::URL());
return script->run();
@ -278,8 +280,11 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_timeout)
if (interval < 0)
interval = 0;
}
NonnullOwnPtr<Bindings::CallbackType> callback = adopt_own(*new Bindings::CallbackType(JS::make_handle(callback_object), HTML::incumbent_settings_object()));
// FIXME: Pass ...arguments to the callback function when it's invoked
auto timer_id = impl->set_timeout(*callback, interval);
auto timer_id = impl->set_timeout(move(callback), interval);
return JS::Value(timer_id);
}
@ -311,7 +316,8 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::request_animation_frame)
auto* callback_object = TRY(vm.argument(0).to_object(global_object));
if (!callback_object->is_function())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAFunctionNoParam);
return JS::Value(impl->request_animation_frame(*static_cast<JS::FunctionObject*>(callback_object)));
NonnullOwnPtr<Bindings::CallbackType> callback = adopt_own(*new Bindings::CallbackType(JS::make_handle(callback_object), HTML::incumbent_settings_object()));
return JS::Value(impl->request_animation_frame(move(callback)));
}
JS_DEFINE_NATIVE_FUNCTION(WindowObject::cancel_animation_frame)
@ -333,7 +339,9 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::queue_microtask)
if (!callback_object->is_function())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAFunctionNoParam);
impl->queue_microtask(static_cast<JS::FunctionObject&>(*callback_object));
auto callback = adopt_own(*new Bindings::CallbackType(JS::make_handle(callback_object), HTML::incumbent_settings_object()));
impl->queue_microtask(move(callback));
return JS::js_undefined();
}
@ -638,31 +646,25 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::screen_y_getter)
return JS::Value(impl->screen_y());
}
#define __ENUMERATE(attribute, event_name) \
JS_DEFINE_NATIVE_FUNCTION(WindowObject::attribute##_getter) \
{ \
auto* impl = TRY(impl_from(vm, global_object)); \
auto retval = impl->attribute(); \
if (retval.callback.is_null()) \
return JS::js_null(); \
return retval.callback.cell(); \
} \
JS_DEFINE_NATIVE_FUNCTION(WindowObject::attribute##_setter) \
{ \
auto* impl = TRY(impl_from(vm, global_object)); \
auto value = vm.argument(0); \
HTML::EventHandler cpp_value; \
if (value.is_function()) { \
cpp_value.callback = JS::make_handle(&value.as_function()); \
} else if (value.is_string()) { \
cpp_value.string = value.as_string().string(); \
} else { \
return JS::js_undefined(); \
} \
TRY(throw_dom_exception_if_needed(global_object, [&] { \
return impl->set_##attribute(cpp_value); \
})); \
return JS::js_undefined(); \
#define __ENUMERATE(attribute, event_name) \
JS_DEFINE_NATIVE_FUNCTION(WindowObject::attribute##_getter) \
{ \
auto* impl = TRY(impl_from(vm, global_object)); \
auto retval = impl->attribute(); \
if (!retval) \
return JS::js_null(); \
return retval->callback.cell(); \
} \
JS_DEFINE_NATIVE_FUNCTION(WindowObject::attribute##_setter) \
{ \
auto* impl = TRY(impl_from(vm, global_object)); \
auto value = vm.argument(0); \
Optional<Bindings::CallbackType> cpp_value; \
if (value.is_object()) { \
cpp_value = Bindings::CallbackType { JS::make_handle(&value.as_object()), HTML::incumbent_settings_object() }; \
} \
impl->set_##attribute(cpp_value); \
return JS::js_undefined(); \
}
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -11,7 +11,6 @@ set(SOURCES
Bindings/MainThreadVM.cpp
Bindings/NavigatorObject.cpp
Bindings/NodeWrapperFactory.cpp
Bindings/ScriptExecutionContext.cpp
Bindings/WindowObject.cpp
Bindings/Wrappable.cpp
Crypto/Crypto.cpp
@ -74,7 +73,6 @@ set(SOURCES
DOM/ElementFactory.cpp
DOM/Event.cpp
DOM/EventDispatcher.cpp
DOM/EventListener.cpp
DOM/EventTarget.cpp
DOM/HTMLCollection.cpp
DOM/LiveNodeList.cpp

View File

@ -15,7 +15,7 @@
namespace Web::CSS {
MediaQueryList::MediaQueryList(DOM::Document& document, NonnullRefPtrVector<MediaQuery>&& media)
: DOM::EventTarget(static_cast<Bindings::ScriptExecutionContext&>(document))
: DOM::EventTarget()
, m_document(document)
, m_media(move(media))
{
@ -83,12 +83,12 @@ void MediaQueryList::remove_listener(RefPtr<DOM::EventListener> listener)
remove_event_listener(HTML::EventNames::change, listener);
}
void MediaQueryList::set_onchange(HTML::EventHandler event_handler)
void MediaQueryList::set_onchange(Optional<Bindings::CallbackType> event_handler)
{
set_event_handler_attribute(HTML::EventNames::change, event_handler);
}
HTML::EventHandler MediaQueryList::onchange()
Bindings::CallbackType* MediaQueryList::onchange()
{
return event_handler_attribute(HTML::EventNames::change);
}

View File

@ -48,8 +48,8 @@ public:
void add_listener(RefPtr<DOM::EventListener> listener);
void remove_listener(RefPtr<DOM::EventListener> listener);
void set_onchange(HTML::EventHandler);
HTML::EventHandler onchange();
void set_onchange(Optional<Bindings::CallbackType>);
Bindings::CallbackType* onchange();
private:
MediaQueryList(DOM::Document&, NonnullRefPtrVector<MediaQuery>&&);

View File

@ -10,8 +10,8 @@
namespace Web::DOM {
// https://dom.spec.whatwg.org/#dom-abortcontroller-abortcontroller
AbortController::AbortController(Document& document)
: m_signal(AbortSignal::create(document))
AbortController::AbortController()
: m_signal(AbortSignal::create())
{
}

View File

@ -24,14 +24,14 @@ class AbortController final
public:
using WrapperType = Bindings::AbortControllerWrapper;
static NonnullRefPtr<AbortController> create(Document& document)
static NonnullRefPtr<AbortController> create()
{
return adopt_ref(*new AbortController(document));
return adopt_ref(*new AbortController());
}
static NonnullRefPtr<AbortController> create_with_global_object(Bindings::WindowObject& window_object)
static NonnullRefPtr<AbortController> create_with_global_object(Bindings::WindowObject&)
{
return AbortController::create(window_object.impl().associated_document());
return AbortController::create();
}
virtual ~AbortController() override;
@ -42,7 +42,7 @@ public:
void abort(JS::Value reason);
private:
AbortController(Document& document);
AbortController();
// https://dom.spec.whatwg.org/#abortcontroller-signal
NonnullRefPtr<AbortSignal> m_signal;

View File

@ -14,8 +14,8 @@
namespace Web::DOM {
AbortSignal::AbortSignal(Document& document)
: EventTarget(static_cast<Bindings::ScriptExecutionContext&>(document))
AbortSignal::AbortSignal()
: EventTarget()
{
}
@ -63,12 +63,12 @@ void AbortSignal::signal_abort(JS::Value reason)
dispatch_event(Event::create(HTML::EventNames::abort));
}
void AbortSignal::set_onabort(HTML::EventHandler event_handler)
void AbortSignal::set_onabort(Optional<Bindings::CallbackType> event_handler)
{
set_event_handler_attribute(HTML::EventNames::abort, event_handler);
set_event_handler_attribute(HTML::EventNames::abort, move(event_handler));
}
HTML::EventHandler AbortSignal::onabort()
Bindings::CallbackType* AbortSignal::onabort()
{
return event_handler_attribute(HTML::EventNames::abort);
}

View File

@ -27,14 +27,14 @@ public:
using RefCounted::ref;
using RefCounted::unref;
static NonnullRefPtr<AbortSignal> create(Document& document)
static NonnullRefPtr<AbortSignal> create()
{
return adopt_ref(*new AbortSignal(document));
return adopt_ref(*new AbortSignal());
}
static NonnullRefPtr<AbortSignal> create_with_global_object(Bindings::WindowObject& window_object)
static NonnullRefPtr<AbortSignal> create_with_global_object(Bindings::WindowObject&)
{
return AbortSignal::create(window_object.impl().associated_document());
return AbortSignal::create();
}
virtual ~AbortSignal() override;
@ -47,8 +47,8 @@ public:
void signal_abort(JS::Value reason);
void set_onabort(HTML::EventHandler);
HTML::EventHandler onabort();
void set_onabort(Optional<Bindings::CallbackType>);
Bindings::CallbackType* onabort();
// https://dom.spec.whatwg.org/#dom-abortsignal-reason
JS::Value reason() const { return m_abort_reason; }
@ -63,7 +63,7 @@ public:
virtual JS::Object* create_wrapper(JS::GlobalObject&) override;
private:
AbortSignal(Document& document);
AbortSignal();
// https://dom.spec.whatwg.org/#abortsignal-abort-reason
// An AbortSignal object has an associated abort reason, which is a JavaScript value. It is undefined unless specified otherwise.

View File

@ -16,7 +16,6 @@
#include <AK/WeakPtr.h>
#include <LibCore/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/ScriptExecutionContext.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/StyleComputer.h>
@ -41,8 +40,7 @@ enum class QuirksMode {
class Document
: public ParentNode
, public NonElementParentNode<Document>
, public HTML::GlobalEventHandlers
, public Bindings::ScriptExecutionContext {
, public HTML::GlobalEventHandlers {
public:
using WrapperType = Bindings::DocumentWrapper;

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

View File

@ -1,18 +0,0 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/DOM/EventListener.h>
namespace Web::DOM {
JS::FunctionObject& EventListener::function()
{
VERIFY(m_function.cell());
return *m_function.cell();
}
}

View File

@ -8,6 +8,7 @@
#include <AK/RefCounted.h>
#include <LibJS/Heap/Handle.h>
#include <LibWeb/Bindings/CallbackType.h>
#include <LibWeb/Bindings/Wrappable.h>
namespace Web::DOM {
@ -18,13 +19,14 @@ class EventListener
public:
using WrapperType = Bindings::EventListenerWrapper;
explicit EventListener(JS::Handle<JS::FunctionObject> function, bool is_attribute = false)
: m_function(move(function))
, m_attribute(is_attribute)
explicit EventListener(Bindings::CallbackType callback)
: m_callback(move(callback))
{
}
JS::FunctionObject& function();
virtual ~EventListener() = default;
Bindings::CallbackType& callback() { return m_callback; }
const FlyString& type() const { return m_type; }
void set_type(const FlyString& type) { m_type = type; }
@ -41,16 +43,13 @@ public:
bool removed() const { return m_removed; }
void set_removed(bool removed) { m_removed = removed; }
bool is_attribute() const { return m_attribute; }
private:
FlyString m_type;
JS::Handle<JS::FunctionObject> m_function;
Bindings::CallbackType m_callback;
bool m_capture { false };
bool m_passive { false };
bool m_once { false };
bool m_removed { false };
bool m_attribute { false };
};
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,24 +8,35 @@
#include <AK/StringBuilder.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibWeb/Bindings/ScriptExecutionContext.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/DocumentWrapper.h>
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
#include <LibWeb/Bindings/EventWrapper.h>
#include <LibWeb/Bindings/EventWrapperFactory.h>
#include <LibWeb/Bindings/IDLAbstractOperations.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/DOM/EventListener.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/DOM/Window.h>
#include <LibWeb/HTML/ErrorEvent.h>
#include <LibWeb/HTML/EventHandler.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/FormAssociatedElement.h>
#include <LibWeb/HTML/HTMLBodyElement.h>
#include <LibWeb/HTML/HTMLFormElement.h>
#include <LibWeb/HTML/HTMLFrameSetElement.h>
#include <LibWeb/UIEvents/EventNames.h>
namespace Web::DOM {
EventTarget::EventTarget(Bindings::ScriptExecutionContext& script_execution_context)
: m_script_execution_context(&script_execution_context)
EventTarget::EventTarget()
{
}
@ -38,7 +50,7 @@ void EventTarget::add_event_listener(const FlyString& event_name, RefPtr<EventLi
if (listener.is_null())
return;
auto existing_listener = m_listeners.first_matching([&](auto& entry) {
return entry.listener->type() == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
return entry.listener->type() == event_name && entry.listener->callback().callback.cell() == listener->callback().callback.cell() && entry.listener->capture() == listener->capture();
});
if (existing_listener.has_value())
return;
@ -52,7 +64,7 @@ void EventTarget::remove_event_listener(const FlyString& event_name, RefPtr<Even
if (listener.is_null())
return;
m_listeners.remove_first_matching([&](auto& entry) {
auto matches = entry.event_name == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
auto matches = entry.event_name == event_name && entry.listener->callback().callback.cell() == listener->callback().callback.cell() && entry.listener->capture() == listener->capture();
if (matches)
entry.listener->set_removed(true);
return matches;
@ -62,7 +74,7 @@ void EventTarget::remove_event_listener(const FlyString& event_name, RefPtr<Even
void EventTarget::remove_from_event_listener_list(NonnullRefPtr<EventListener> listener)
{
m_listeners.remove_first_matching([&](auto& entry) {
return entry.listener->type() == listener->type() && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
return entry.listener->type() == listener->type() && &entry.listener->callback() == &listener->callback() && entry.listener->capture() == listener->capture();
});
}
@ -139,51 +151,460 @@ static EventTarget* determine_target_of_event_handler(EventTarget& event_target,
return &event_target_element.document().window();
}
HTML::EventHandler EventTarget::event_handler_attribute(FlyString const& name)
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-2
Bindings::CallbackType* EventTarget::event_handler_attribute(FlyString const& name)
{
// 1. Let eventTarget be the result of determining the target of an event handler given this object and name.
auto target = determine_target_of_event_handler(*this, name);
// 2. If eventTarget is null, then return null.
if (!target)
return nullptr;
// 3. Return the result of getting the current value of the event handler given eventTarget and name.
return target->get_current_value_of_event_handler(name);
}
// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
Bindings::CallbackType* EventTarget::get_current_value_of_event_handler(FlyString const& name)
{
// 1. Let handlerMap be eventTarget's event handler map. (NOTE: Not necessary)
// 2. Let eventHandler be handlerMap[name].
auto event_handler_iterator = m_event_handler_map.find(name);
// Optimization: The spec creates all the event handlers exposed on an object up front and has the initial value of the handler set to null.
// If the event handler hasn't been set, null would be returned in step 4.
// However, this would be very allocation heavy. For example, each DOM::Element includes GlobalEventHandlers, which defines 60+(!) event handler attributes.
// Plus, the vast majority of these allocations would be likely wasted, as I imagine web content will only use a handful of these attributes on certain elements, if any at all.
// Thus, we treat the event handler not being in the event handler map as being equivalent to an event handler with an initial null value.
if (event_handler_iterator == m_event_handler_map.end())
return nullptr;
auto& event_handler = event_handler_iterator->value;
// 3. If eventHandler's value is an internal raw uncompiled handler, then:
if (event_handler.value.has<String>()) {
// 1. If eventTarget is an element, then let element be eventTarget, and document be element's node document.
// Otherwise, eventTarget is a Window object, let element be null, and document be eventTarget's associated Document.
RefPtr<Element> element;
RefPtr<Document> document;
if (is<Element>(this)) {
auto* element_event_target = verify_cast<Element>(this);
element = element_event_target;
document = element_event_target->document();
} else {
VERIFY(is<Window>(this));
auto* window_event_target = verify_cast<Window>(this);
document = window_event_target->associated_document();
}
VERIFY(document);
// 2. If scripting is disabled for document, then return null.
if (document->is_scripting_disabled())
return nullptr;
// 3. Let body be the uncompiled script body in eventHandler's value.
auto& body = event_handler.value.get<String>();
// FIXME: 4. Let location be the location where the script body originated, as given by eventHandler's value.
// 5. If element is not null and element has a form owner, let form owner be that form owner. Otherwise, let form owner be null.
RefPtr<HTML::HTMLFormElement> form_owner;
if (is<HTML::FormAssociatedElement>(element.ptr())) {
auto& form_associated_element = verify_cast<HTML::FormAssociatedElement>(*element);
if (form_associated_element.form())
form_owner = form_associated_element.form();
}
// 6. Let settings object be the relevant settings object of document.
auto& settings_object = document->relevant_settings_object();
// NOTE: ECMAScriptFunctionObject::create expects a parsed body as input, so we must do the spec's sourceText steps here.
StringBuilder builder;
// sourceText
if (name == HTML::EventNames::error && is<Window>(this)) {
// -> If name is onerror and eventTarget is a Window object
// The string formed by concatenating "function ", name, "(event, source, lineno, colno, error) {", U+000A LF, body, U+000A LF, and "}".
builder.appendff("function {}(event, source, lineno, colno, error) {{\n{}\n}}", name, body);
} else {
// -> Otherwise
// The string formed by concatenating "function ", name, "(event) {", U+000A LF, body, U+000A LF, and "}".
builder.appendff("function {}(event) {{\n{}\n}}", name, body);
}
auto source_text = builder.to_string();
auto parser = JS::Parser(JS::Lexer(source_text));
// FIXME: This should only be parsing the `body` instead of `source_text` and therefore use `JS::FunctionBody` instead of `JS::FunctionExpression`.
// However, JS::ECMAScriptFunctionObject::create wants parameters and length and JS::FunctionBody does not inherit JS::FunctionNode.
auto program = parser.parse_function_node<JS::FunctionExpression>();
// 7. If body is not parsable as FunctionBody or if parsing detects an early error, then follow these substeps:
if (parser.has_errors()) {
// 1. Set eventHandler's value to null.
// Note: This does not deactivate the event handler, which additionally removes the event handler's listener (if present).
m_event_handler_map.remove(event_handler_iterator);
// FIXME: 2. Report the error for the appropriate script and with the appropriate position (line number and column number) given by location, using settings object's global object.
// If the error is still not handled after this, then the error may be reported to a developer console.
// 3. Return null.
return nullptr;
}
// 8. Push settings object's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context.
auto& global_object = settings_object.global_object();
global_object.vm().push_execution_context(settings_object.realm_execution_context(), global_object);
// 9. Let function be the result of calling OrdinaryFunctionCreate, with arguments:
// functionPrototype
// %Function.prototype% (This is enforced by using JS::ECMAScriptFunctionObject)
// sourceText was handled above.
// ParameterList
// If name is onerror and eventTarget is a Window object
// Let the function have five arguments, named event, source, lineno, colno, and error.
// Otherwise
// Let the function have a single argument called event.
// (This was handled above for us by the parser using sourceText)
// body
// The result of parsing body above. (This is given by program->body())
// thisMode
// non-lexical-this (For JS::ECMAScriptFunctionObject, this means passing is_arrow_function as false)
constexpr bool is_arrow_function = false;
// scope
// 1. Let realm be settings object's Realm.
auto& realm = settings_object.realm();
// 2. Let scope be realm.[[GlobalEnv]].
JS::Environment* scope = &realm.global_environment();
// 3. If eventHandler is an element's event handler, then set scope to NewObjectEnvironment(document, true, scope).
// (Otherwise, eventHandler is a Window object's event handler.)
if (is<Element>(this)) {
auto* wrapped_document = Bindings::wrap(global_object, *document);
scope = JS::new_object_environment(*wrapped_document, true, scope);
}
// 4. If form owner is not null, then set scope to NewObjectEnvironment(form owner, true, scope).
if (form_owner) {
auto* wrapped_form_owner = Bindings::wrap(global_object, *form_owner);
scope = JS::new_object_environment(*wrapped_form_owner, true, scope);
}
// 5. If element is not null, then set scope to NewObjectEnvironment(element, true, scope).
if (element) {
auto* wrapped_element = Bindings::wrap(global_object, *element);
scope = JS::new_object_environment(*wrapped_element, true, scope);
}
// 6. Return scope. (NOTE: Not necessary)
auto* function = JS::ECMAScriptFunctionObject::create(global_object, name, builder.to_string(), program->body(), program->parameters(), program->function_length(), scope, nullptr, JS::FunctionKind::Normal, program->is_strict_mode(), program->might_need_arguments_object(), is_arrow_function);
VERIFY(function);
// 10. Remove settings object's realm execution context from the JavaScript execution context stack.
VERIFY(global_object.vm().execution_context_stack().last() == &settings_object.realm_execution_context());
global_object.vm().pop_execution_context();
// 11. Set function.[[ScriptOrModule]] to null.
function->set_script_or_module({});
// 12. Set eventHandler's value to the result of creating a Web IDL EventHandler callback function object whose object reference is function and whose callback context is settings object.
event_handler.value = Bindings::CallbackType { JS::make_handle(static_cast<JS::Object*>(function)), settings_object };
}
// 4. Return eventHandler's value.
VERIFY(event_handler.value.has<Bindings::CallbackType>());
return event_handler.value.get_pointer<Bindings::CallbackType>();
}
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:event-handler-idl-attributes-3
void EventTarget::set_event_handler_attribute(FlyString const& name, Optional<Bindings::CallbackType> value)
{
// 1. Let eventTarget be the result of determining the target of an event handler given this object and name.
auto event_target = determine_target_of_event_handler(*this, name);
// 2. If eventTarget is null, then return.
if (!event_target)
return;
// 3. If the given value is null, then deactivate an event handler given eventTarget and name.
if (!value.has_value()) {
event_target->deactivate_event_handler(name);
return;
}
// 4. Otherwise:
// 1. Let handlerMap be eventTarget's event handler map.
auto& handler_map = event_target->m_event_handler_map;
// 2. Let eventHandler be handlerMap[name].
auto event_handler_iterator = handler_map.find(name);
// 3. Set eventHandler's value to the given value.
if (event_handler_iterator == handler_map.end()) {
// NOTE: See the optimization comment in get_current_value_of_event_handler about why this is done.
HTML::EventHandler new_event_handler { move(value.value()) };
// 4. Activate an event handler given eventTarget and name.
// Optimization: We pass in the event handler here instead of having activate_event_handler do another hash map lookup just to get the same object.
// This handles a new event handler while the other path handles an existing event handler. As such, both paths must have their own
// unique call to activate_event_handler.
event_target->activate_event_handler(name, new_event_handler, IsAttribute::No);
handler_map.set(name, move(new_event_handler));
return;
}
auto& event_handler = event_handler_iterator->value;
event_handler.value = move(value.value());
// 4. Activate an event handler given eventTarget and name.
// NOTE: See the optimization comment above.
event_target->activate_event_handler(name, event_handler, IsAttribute::No);
}
// https://html.spec.whatwg.org/multipage/webappapis.html#activate-an-event-handler
void EventTarget::activate_event_handler(FlyString const& name, HTML::EventHandler& event_handler, IsAttribute is_attribute)
{
// 1. Let handlerMap be eventTarget's event handler map.
// 2. Let eventHandler be handlerMap[name].
// NOTE: These are achieved by using the passed in event handler.
// 3. If eventHandler's listener is not null, then return.
if (event_handler.listener)
return;
// 4. Let callback be the result of creating a Web IDL EventListener instance representing a reference to a function of one argument that executes the steps of the event handler processing algorithm, given eventTarget, name, and its argument.
// The EventListener's callback context can be arbitrary; it does not impact the steps of the event handler processing algorithm. [DOM]
// FIXME: This is guess work on what global object the NativeFunction should be allocated on.
// For <body> or <frameset> elements who just had an element attribute set, it will be this's wrapper, as `this` is the result of determine_target_of_event_handler
// returning the element's document's global object, which is the DOM::Window object.
// For any other HTMLElement who just had an element attribute set, `this` will be that HTMLElement, so the global object is this's document's realm's global object.
// For anything else, it came from JavaScript, so use the global object of the provided callback function.
// Sadly, this doesn't work if an element attribute is set on a <body> element before any script is run, as Window::wrapper() will be null.
JS::GlobalObject* global_object = nullptr;
if (is_attribute == IsAttribute::Yes) {
if (is<Window>(this)) {
auto* window_global_object = verify_cast<Window>(this)->wrapper();
global_object = static_cast<JS::GlobalObject*>(window_global_object);
} else {
auto* html_element = verify_cast<HTML::HTMLElement>(this);
global_object = &html_element->document().realm().global_object();
}
} else {
global_object = &event_handler.value.get<Bindings::CallbackType>().callback.cell()->global_object();
}
VERIFY(global_object);
// NOTE: The callback must keep `this` alive. For example:
// document.body.onunload = () => { console.log("onunload called!"); }
// document.body.remove();
// location.reload();
// The body element is no longer in the DOM and there is no variable holding onto it. However, the onunload handler is still called, meaning the callback keeps the body element alive.
auto callback_function = JS::NativeFunction::create(*global_object, "", [event_target = NonnullRefPtr(*this), name](JS::VM& vm, auto&) mutable -> JS::ThrowCompletionOr<JS::Value> {
// The event dispatcher should only call this with one argument.
VERIFY(vm.argument_count() == 1);
// The argument must be an object and it must be an EventWrapper.
auto event_wrapper_argument = vm.argument(0);
VERIFY(event_wrapper_argument.is_object());
auto& event_wrapper = verify_cast<Bindings::EventWrapper>(event_wrapper_argument.as_object());
auto& event = event_wrapper.impl();
TRY(event_target->process_event_handler_for_event(name, event));
return JS::js_undefined();
});
// NOTE: As per the spec, the callback context is arbitrary.
Bindings::CallbackType callback { JS::make_handle(static_cast<JS::Object*>(callback_function)), verify_cast<HTML::EnvironmentSettingsObject>(*global_object->associated_realm()->host_defined()) };
// 5. Let listener be a new event listener whose type is the event handler event type corresponding to eventHandler and callback is callback.
auto listener = adopt_ref(*new EventListener(move(callback)));
// 6. Add an event listener with eventTarget and listener.
// FIXME: Make add_event_listener follow the spec more tightly. (Namely, don't allow taking a name and having a separate bindings version)
add_event_listener(name, listener);
// 7. Set eventHandler's listener to listener.
event_handler.listener = listener;
}
void EventTarget::deactivate_event_handler(FlyString const& name)
{
// 1. Let handlerMap be eventTarget's event handler map. (NOTE: Not necessary)
// 2. Let eventHandler be handlerMap[name].
auto event_handler_iterator = m_event_handler_map.find(name);
// NOTE: See the optimization comment in get_current_value_of_event_handler about why this is done.
if (event_handler_iterator == m_event_handler_map.end())
return;
auto& event_handler = event_handler_iterator->value;
// 4. Let listener be eventHandler's listener. (NOTE: Not necessary)
// 5. If listener is not null, then remove an event listener with eventTarget and listener.
if (event_handler.listener) {
// FIXME: Make remove_event_listener follow the spec more tightly. (Namely, don't allow taking a name and having a separate bindings version)
remove_event_listener(name, event_handler.listener);
}
// 6. Set eventHandler's listener to null.
event_handler.listener = nullptr;
// 3. Set eventHandler's value to null.
// NOTE: This is done out of order since our equivalent of setting value to null is removing the event handler from the map.
// Given that event_handler is a reference to an entry, this would invalidate event_handler if we did it in order.
m_event_handler_map.remove(event_handler_iterator);
}
// https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm
JS::ThrowCompletionOr<void> EventTarget::process_event_handler_for_event(FlyString const& name, Event& event)
{
// 1. Let callback be the result of getting the current value of the event handler given eventTarget and name.
auto* callback = get_current_value_of_event_handler(name);
// 2. If callback is null, then return.
if (!callback)
return {};
for (auto& listener : target->listeners()) {
if (listener.event_name == name && listener.listener->is_attribute()) {
return HTML::EventHandler { JS::make_handle(&listener.listener->function()) };
}
// 3. Let special error event handling be true if event is an ErrorEvent object, event's type is error, and event's currentTarget implements the WindowOrWorkerGlobalScope mixin.
// Otherwise, let special error event handling be false.
// FIXME: This doesn't check for WorkerGlobalScape as we don't currently have it.
bool special_error_event_handling = is<HTML::ErrorEvent>(event) && event.type() == HTML::EventNames::error && is<Window>(event.current_target().ptr());
// 4. Process the Event object event as follows:
JS::Completion return_value_or_error;
// Needed for wrapping.
auto* callback_object = callback->callback.cell();
if (special_error_event_handling) {
// -> If special error event handling is true
// Invoke callback with five arguments, the first one having the value of event's message attribute, the second having the value of event's filename attribute, the third having the value of event's lineno attribute,
// the fourth having the value of event's colno attribute, the fifth having the value of event's error attribute, and with the callback this value set to event's currentTarget.
// Let return value be the callback's return value. [WEBIDL]
auto& error_event = verify_cast<HTML::ErrorEvent>(event);
auto* wrapped_message = JS::js_string(callback_object->heap(), error_event.message());
auto* wrapped_filename = JS::js_string(callback_object->heap(), error_event.filename());
auto wrapped_lineno = JS::Value(error_event.lineno());
auto wrapped_colno = JS::Value(error_event.colno());
// NOTE: error_event.error() is a JS::Value, so it does not require wrapping.
// NOTE: current_target is always non-null here, as the event dispatcher takes care to make sure it's non-null (and uses it as the this value for the callback!)
// FIXME: This is rewrapping the this value of the callback defined in activate_event_handler. While I don't think this is observable as the event dispatcher
// calls directly into the callback without considering things such as proxies, it is a waste. However, if it observable, then we must reuse the this_value that was given to the callback.
auto* this_value = Bindings::wrap(callback_object->global_object(), *error_event.current_target());
return_value_or_error = Bindings::IDL::invoke_callback(*callback, this_value, wrapped_message, wrapped_filename, wrapped_lineno, wrapped_colno, error_event.error());
} else {
// -> Otherwise
// Invoke callback with one argument, the value of which is the Event object event, with the callback this value set to event's currentTarget. Let return value be the callback's return value. [WEBIDL]
// FIXME: This has the same rewrapping issue as this_value.
auto* wrapped_event = Bindings::wrap(callback_object->global_object(), event);
// FIXME: The comments about this in the special_error_event_handling path also apply here.
auto* this_value = Bindings::wrap(callback_object->global_object(), *event.current_target());
return_value_or_error = Bindings::IDL::invoke_callback(*callback, this_value, wrapped_event);
}
// If an exception gets thrown by the callback, end these steps and allow the exception to propagate. (It will propagate to the DOM event dispatch logic, which will then report the exception.)
if (return_value_or_error.is_error())
return return_value_or_error.release_error();
// FIXME: Ideally, invoke_callback would convert JS::Value to the appropriate return type for us as per the spec, but it doesn't currently.
auto return_value = *return_value_or_error.value();
// FIXME: If event is a BeforeUnloadEvent object and event's type is beforeunload
// If return value is not null, then: (NOTE: When implementing, if we still return a JS::Value from invoke_callback, use is_nullish instead of is_null, as "null" refers to IDL null, which is JS null or undefined)
// 1. Set event's canceled flag.
// 2. If event's returnValue attribute's value is the empty string, then set event's returnValue attribute's value to return value.
if (special_error_event_handling) {
// -> If special error event handling is true
// If return value is true, then set event's canceled flag.
// NOTE: the return type of EventHandler is `any`, so no coercion happens, meaning we have to check if it's a boolean first.
if (return_value.is_boolean() && return_value.as_bool())
event.set_cancelled(true);
} else {
// -> Otherwise
// If return value is false, then set event's canceled flag.
// NOTE: the return type of EventHandler is `any`, so no coercion happens, meaning we have to check if it's a boolean first.
if (return_value.is_boolean() && !return_value.as_bool())
event.set_cancelled(true);
}
return {};
}
void EventTarget::set_event_handler_attribute(FlyString const& name, HTML::EventHandler value)
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes:concept-element-attributes-change-ext
void EventTarget::element_event_handler_attribute_changed(FlyString const& local_name, String const& value)
{
auto target = determine_target_of_event_handler(*this, name);
if (!target)
// NOTE: Step 1 of this algorithm was handled in HTMLElement::parse_attribute.
// 2. Let eventTarget be the result of determining the target of an event handler given element and localName.
// NOTE: element is `this`.
auto* event_target = determine_target_of_event_handler(*this, local_name);
// 3. If eventTarget is null, then return.
if (!event_target)
return;
RefPtr<DOM::EventListener> listener;
if (!value.callback.is_null()) {
listener = adopt_ref(*new DOM::EventListener(move(value.callback), true));
} else {
StringBuilder builder;
builder.appendff("function {}(event) {{\n{}\n}}", name, value.string);
auto parser = JS::Parser(JS::Lexer(builder.string_view()));
auto program = parser.parse_function_node<JS::FunctionExpression>();
if (parser.has_errors()) {
dbgln("Failed to parse script in event handler attribute '{}'", name);
return;
}
auto* function = JS::ECMAScriptFunctionObject::create(target->script_execution_context()->realm().global_object(), name, program->source_text(), program->body(), program->parameters(), program->function_length(), nullptr, nullptr, JS::FunctionKind::Normal, false, false);
VERIFY(function);
listener = adopt_ref(*new DOM::EventListener(JS::make_handle(static_cast<JS::FunctionObject*>(function)), true));
// 4. If value is null, then deactivate an event handler given eventTarget and localName.
if (value.is_null()) {
event_target->deactivate_event_handler(local_name);
return;
}
if (listener) {
for (auto& registered_listener : target->listeners()) {
if (registered_listener.event_name == name && registered_listener.listener->is_attribute()) {
target->remove_event_listener(name, registered_listener.listener);
break;
}
}
target->add_event_listener(name, listener.release_nonnull());
// 5. Otherwise:
// FIXME: 1. If the Should element's inline behavior be blocked by Content Security Policy? algorithm returns "Blocked" when executed upon element, "script attribute", and value, then return. [CSP]
// 2. Let handlerMap be eventTarget's event handler map.
auto& handler_map = event_target->m_event_handler_map;
// 3. Let eventHandler be handlerMap[localName].
auto event_handler_iterator = handler_map.find(local_name);
// FIXME: 4. Let location be the script location that triggered the execution of these steps.
// FIXME: 5. Set eventHandler's value to the internal raw uncompiled handler value/location.
// (This currently sets the value to the uncompiled source code instead of the named struct)
// NOTE: See the optimization comments in set_event_handler_attribute.
if (event_handler_iterator == handler_map.end()) {
HTML::EventHandler new_event_handler { value };
// 6. Activate an event handler given eventTarget and name.
event_target->activate_event_handler(local_name, new_event_handler, IsAttribute::Yes);
handler_map.set(local_name, move(new_event_handler));
return;
}
auto& event_handler = event_handler_iterator->value;
// 6. Activate an event handler given eventTarget and name.
event_handler.value = value;
event_target->activate_event_handler(local_name, event_handler, IsAttribute::Yes);
}
bool EventTarget::dispatch_event(NonnullRefPtr<Event> event)

View File

@ -13,6 +13,7 @@
#include <LibJS/Forward.h>
#include <LibWeb/DOM/ExceptionOr.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/EventHandler.h>
namespace Web::DOM {
@ -37,7 +38,6 @@ public:
ExceptionOr<bool> dispatch_event_binding(NonnullRefPtr<Event>);
virtual JS::Object* create_wrapper(JS::GlobalObject&) = 0;
Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; }
virtual EventTarget* get_parent(const Event&) { return nullptr; }
@ -55,20 +55,33 @@ public:
Function<void()> legacy_pre_activation_behavior;
Function<void()> legacy_cancelled_activation_behavior;
HTML::EventHandler event_handler_attribute(FlyString const& name);
void set_event_handler_attribute(FlyString const& name, HTML::EventHandler);
Bindings::CallbackType* event_handler_attribute(FlyString const& name);
void set_event_handler_attribute(FlyString const& name, Optional<Bindings::CallbackType>);
protected:
explicit EventTarget(Bindings::ScriptExecutionContext&);
EventTarget();
virtual void ref_event_target() = 0;
virtual void unref_event_target() = 0;
private:
// FIXME: This should not be a raw pointer.
Bindings::ScriptExecutionContext* m_script_execution_context { nullptr };
void element_event_handler_attribute_changed(FlyString const& local_name, String const& value);
private:
Vector<EventListenerRegistration> m_listeners;
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-map
// Spec Note: The order of the entries of event handler map could be arbitrary. It is not observable through any algorithms that operate on the map.
HashMap<FlyString, HTML::EventHandler> m_event_handler_map;
enum class IsAttribute {
No,
Yes,
};
Bindings::CallbackType* get_current_value_of_event_handler(FlyString const& name);
void activate_event_handler(FlyString const& name, HTML::EventHandler& event_handler, IsAttribute is_attribute);
void deactivate_event_handler(FlyString const& name);
JS::ThrowCompletionOr<void> process_event_handler_for_event(FlyString const& name, Event& event);
};
}

View File

@ -57,7 +57,7 @@ Node* Node::from_id(i32 node_id)
}
Node::Node(Document& document, NodeType type)
: EventTarget(static_cast<Bindings::ScriptExecutionContext&>(document))
: EventTarget()
, m_document(&document)
, m_type(type)
, m_id(allocate_node_id(this))

View File

@ -11,20 +11,20 @@
namespace Web::DOM {
NonnullRefPtr<Timer> Timer::create_interval(Window& window, int milliseconds, JS::FunctionObject& callback)
NonnullRefPtr<Timer> Timer::create_interval(Window& window, int milliseconds, NonnullOwnPtr<Bindings::CallbackType> callback)
{
return adopt_ref(*new Timer(window, Type::Interval, milliseconds, callback));
return adopt_ref(*new Timer(window, Type::Interval, milliseconds, move(callback)));
}
NonnullRefPtr<Timer> Timer::create_timeout(Window& window, int milliseconds, JS::FunctionObject& callback)
NonnullRefPtr<Timer> Timer::create_timeout(Window& window, int milliseconds, NonnullOwnPtr<Bindings::CallbackType> callback)
{
return adopt_ref(*new Timer(window, Type::Timeout, milliseconds, callback));
return adopt_ref(*new Timer(window, Type::Timeout, milliseconds, move(callback)));
}
Timer::Timer(Window& window, Type type, int milliseconds, JS::FunctionObject& callback)
Timer::Timer(Window& window, Type type, int milliseconds, NonnullOwnPtr<Bindings::CallbackType> callback)
: m_window(window)
, m_type(type)
, m_callback(JS::make_handle(&callback))
, m_callback(move(callback))
{
m_id = window.allocate_timer_id({});
m_timer = Core::Timer::construct(milliseconds, [this] { m_window.timer_did_fire({}, *this); });

View File

@ -21,24 +21,24 @@ public:
Timeout,
};
static NonnullRefPtr<Timer> create_interval(Window&, int milliseconds, JS::FunctionObject&);
static NonnullRefPtr<Timer> create_timeout(Window&, int milliseconds, JS::FunctionObject&);
static NonnullRefPtr<Timer> create_interval(Window&, int milliseconds, NonnullOwnPtr<Bindings::CallbackType> callback);
static NonnullRefPtr<Timer> create_timeout(Window&, int milliseconds, NonnullOwnPtr<Bindings::CallbackType> callback);
~Timer();
i32 id() const { return m_id; }
Type type() const { return m_type; }
JS::FunctionObject& callback() { return *m_callback.cell(); }
Bindings::CallbackType& callback() { return *m_callback; }
private:
Timer(Window&, Type, int ms, JS::FunctionObject&);
Timer(Window&, Type, int ms, NonnullOwnPtr<Bindings::CallbackType> callback);
Window& m_window;
RefPtr<Core::Timer> m_timer;
Type m_type;
int m_id { 0 };
JS::Handle<JS::FunctionObject> m_callback;
NonnullOwnPtr<Bindings::CallbackType> m_callback;
};
}

View File

@ -8,6 +8,7 @@
#include <LibGUI/DisplayLink.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/Bindings/IDLAbstractOperations.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
#include <LibWeb/Crypto/Crypto.h>
@ -109,7 +110,7 @@ NonnullRefPtr<Window> Window::create_with_document(Document& document)
}
Window::Window(Document& document)
: EventTarget(static_cast<Bindings::ScriptExecutionContext&>(document))
: EventTarget()
, m_associated_document(document)
, m_performance(make<HighResolutionTime::Performance>(*this))
, m_crypto(Crypto::Crypto::create())
@ -146,16 +147,16 @@ String Window::prompt(String const& message, String const& default_)
return {};
}
i32 Window::set_interval(JS::FunctionObject& callback, i32 interval)
i32 Window::set_interval(NonnullOwnPtr<Bindings::CallbackType> callback, i32 interval)
{
auto timer = Timer::create_interval(*this, interval, callback);
auto timer = Timer::create_interval(*this, interval, move(callback));
m_timers.set(timer->id(), timer);
return timer->id();
}
i32 Window::set_timeout(JS::FunctionObject& callback, i32 interval)
i32 Window::set_timeout(NonnullOwnPtr<Bindings::CallbackType> callback, i32 interval)
{
auto timer = Timer::create_timeout(*this, interval, callback);
auto timer = Timer::create_timeout(*this, interval, move(callback));
m_timers.set(timer->id(), timer);
return timer->id();
}
@ -172,7 +173,7 @@ void Window::timer_did_fire(Badge<Timer>, Timer& timer)
VERIFY(wrapper());
HTML::queue_global_task(HTML::Task::Source::TimerTask, *wrapper(), [this, strong_this = NonnullRefPtr(*this), strong_timer = NonnullRefPtr(timer)]() mutable {
auto result = JS::call(wrapper()->global_object(), strong_timer->callback(), wrapper());
auto result = Bindings::IDL::invoke_callback(strong_timer->callback(), wrapper());
if (result.is_error())
HTML::report_exception(result);
});
@ -199,17 +200,15 @@ void Window::clear_interval(i32 timer_id)
}
// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#run-the-animation-frame-callbacks
i32 Window::request_animation_frame(JS::FunctionObject& js_callback)
i32 Window::request_animation_frame(NonnullOwnPtr<Bindings::CallbackType> js_callback)
{
auto callback = request_animation_frame_driver().add([this, handle = JS::make_handle(&js_callback)](i32 id) mutable {
auto& function = *handle.cell();
auto callback = request_animation_frame_driver().add([this, js_callback = move(js_callback)](i32 id) mutable {
// 3. Invoke callback, passing now as the only argument,
auto result = JS::call(function.global_object(), function, JS::js_undefined(), JS::Value(performance().now()));
auto result = Bindings::IDL::invoke_callback(*js_callback, {}, JS::Value(performance().now()));
// and if an exception is thrown, report the exception.
if (result.is_error())
HTML::report_exception(result);
m_request_animation_frame_callbacks.remove(id);
});
m_request_animation_frame_callbacks.set(callback->id(), callback);
@ -399,10 +398,11 @@ void Window::fire_a_page_transition_event(FlyString const& event_name, bool pers
}
// https://html.spec.whatwg.org/#dom-queuemicrotask
void Window::queue_microtask(JS::FunctionObject& callback)
void Window::queue_microtask(NonnullOwnPtr<Bindings::CallbackType> callback)
{
HTML::queue_a_microtask(&associated_document(), [&callback, handle = JS::make_handle(&callback)]() {
auto result = JS::call(callback.global_object(), callback, JS::js_null());
// The queueMicrotask(callback) method must queue a microtask to invoke callback,
HTML::queue_a_microtask(&associated_document(), [callback = move(callback)]() mutable {
auto result = Bindings::IDL::invoke_callback(*callback, {});
// and if callback throws an exception, report the exception.
if (result.is_error())
HTML::report_exception(result);

View File

@ -47,15 +47,15 @@ public:
void alert(String const&);
bool confirm(String const&);
String prompt(String const&, String const&);
i32 request_animation_frame(JS::FunctionObject&);
i32 request_animation_frame(NonnullOwnPtr<Bindings::CallbackType> js_callback);
void cancel_animation_frame(i32);
i32 set_timeout(JS::FunctionObject&, i32);
i32 set_interval(JS::FunctionObject&, i32);
i32 set_timeout(NonnullOwnPtr<Bindings::CallbackType> callback, i32);
i32 set_interval(NonnullOwnPtr<Bindings::CallbackType> callback, i32);
void clear_timeout(i32);
void clear_interval(i32);
void queue_microtask(JS::FunctionObject&);
void queue_microtask(NonnullOwnPtr<Bindings::CallbackType> callback);
int inner_width() const;
int inner_height() const;

View File

@ -312,6 +312,7 @@ namespace Web::Bindings {
class AbortControllerWrapper;
class AbortSignalWrapper;
class AttributeWrapper;
struct CallbackType;
class CanvasGradientWrapper;
class CanvasRenderingContext2DWrapper;
class CharacterDataWrapper;
@ -438,7 +439,6 @@ class RangePrototype;
class RangeWrapper;
class ResizeObserverWrapper;
class ScreenWrapper;
class ScriptExecutionContext;
class SelectionWrapper;
class StyleSheetListWrapper;
class StyleSheetWrapper;

View File

@ -7,28 +7,34 @@
#pragma once
#include <AK/String.h>
#include <AK/Variant.h>
#include <LibJS/Heap/Handle.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibWeb/Bindings/CallbackType.h>
#include <LibWeb/DOM/EventListener.h>
namespace Web::HTML {
struct EventHandler {
EventHandler()
{
}
EventHandler(String s)
: string(move(s))
: value(move(s))
{
}
EventHandler(JS::Handle<JS::FunctionObject> c)
: callback(move(c))
EventHandler(Bindings::CallbackType c)
: value(move(c))
{
}
String string;
JS::Handle<JS::FunctionObject> callback;
// Either uncompiled source code or a callback.
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-value
// NOTE: This does not contain Empty as part of the optimization of not allocating all event handler attributes up front.
// FIXME: The string should actually be an "internal raw uncompiled handler" struct. This struct is just the uncompiled source code plus a source location for reporting parse errors.
// https://html.spec.whatwg.org/multipage/webappapis.html#internal-raw-uncompiled-handler
Variant<String, Bindings::CallbackType> value;
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-listener
RefPtr<DOM::EventListener> listener;
};
}

View File

@ -17,11 +17,11 @@ namespace Web::HTML {
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void GlobalEventHandlers::set_##attribute_name(HTML::EventHandler value) \
void GlobalEventHandlers::set_##attribute_name(Optional<Bindings::CallbackType> value) \
{ \
global_event_handlers_to_event_target().set_event_handler_attribute(event_name, move(value)); \
} \
HTML::EventHandler GlobalEventHandlers::attribute_name() \
Bindings::CallbackType* GlobalEventHandlers::attribute_name() \
{ \
return global_event_handlers_to_event_target().event_handler_attribute(event_name); \
}

View File

@ -84,9 +84,9 @@ public:
virtual ~GlobalEventHandlers();
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(HTML::EventHandler); \
HTML::EventHandler attribute_name();
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(Optional<Bindings::CallbackType>); \
Bindings::CallbackType* attribute_name();
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -172,10 +172,12 @@ void HTMLElement::parse_attribute(const FlyString& name, const String& value)
{
Element::parse_attribute(name, value);
// 1. If namespace is not null, or localName is not the name of an event handler content attribute on element, then return.
// FIXME: Add the namespace part once we support attribute namespaces.
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
if (name == HTML::AttributeNames::attribute_name) { \
set_event_handler_attribute(event_name, EventHandler { value }); \
#define __ENUMERATE(attribute_name, event_name) \
if (name == HTML::AttributeNames::attribute_name) { \
element_event_handler_attribute_changed(event_name, value); \
}
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -10,15 +10,13 @@
namespace Web::HTML {
MessageChannel::MessageChannel(Bindings::WindowObject& global_object)
MessageChannel::MessageChannel()
{
auto& context = global_object.impl().associated_document();
// 1. Set this's port 1 to a new MessagePort in this's relevant Realm.
m_port1 = MessagePort::create(context);
m_port1 = MessagePort::create();
// 2. Set this's port 2 to a new MessagePort in this's relevant Realm.
m_port2 = MessagePort::create(context);
m_port2 = MessagePort::create();
// 3. Entangle this's port 1 and this's port 2.
m_port1->entangle_with({}, *m_port2);

View File

@ -24,9 +24,9 @@ public:
using RefCounted::ref;
using RefCounted::unref;
static NonnullRefPtr<MessageChannel> create_with_global_object(Bindings::WindowObject& global_object)
static NonnullRefPtr<MessageChannel> create_with_global_object(Bindings::WindowObject&)
{
return adopt_ref(*new MessageChannel(global_object));
return adopt_ref(*new MessageChannel());
}
virtual ~MessageChannel() override;
@ -38,7 +38,7 @@ public:
MessagePort const* port2() const { return m_port2; }
private:
explicit MessageChannel(Bindings::WindowObject&);
MessageChannel();
RefPtr<MessagePort> m_port1;
RefPtr<MessagePort> m_port2;

View File

@ -5,7 +5,6 @@
*/
#include <LibWeb/Bindings/MessagePortWrapper.h>
#include <LibWeb/Bindings/ScriptExecutionContext.h>
#include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/HTML/EventHandler.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
@ -15,8 +14,8 @@
namespace Web::HTML {
MessagePort::MessagePort(Bindings::ScriptExecutionContext& context)
: DOM::EventTarget(context)
MessagePort::MessagePort()
: DOM::EventTarget()
{
}
@ -109,14 +108,14 @@ void MessagePort::close()
}
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void MessagePort::set_##attribute_name(HTML::EventHandler value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
HTML::EventHandler MessagePort::attribute_name() \
{ \
return event_handler_attribute(event_name); \
#define __ENUMERATE(attribute_name, event_name) \
void MessagePort::set_##attribute_name(Optional<Bindings::CallbackType> value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
Bindings::CallbackType* MessagePort::attribute_name() \
{ \
return event_handler_attribute(event_name); \
}
ENUMERATE_MESSAGE_PORT_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -30,9 +30,9 @@ public:
using RefCounted::ref;
using RefCounted::unref;
static NonnullRefPtr<MessagePort> create(Bindings::ScriptExecutionContext& context)
static NonnullRefPtr<MessagePort> create()
{
return adopt_ref(*new MessagePort(context));
return adopt_ref(*new MessagePort());
}
virtual ~MessagePort() override;
@ -53,14 +53,14 @@ public:
void close();
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(HTML::EventHandler); \
HTML::EventHandler attribute_name();
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(Optional<Bindings::CallbackType>); \
Bindings::CallbackType* attribute_name();
ENUMERATE_MESSAGE_PORT_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
private:
explicit MessagePort(Bindings::ScriptExecutionContext&);
MessagePort();
bool is_entangled() const { return m_remote_port; }
void disentangle();

View File

@ -67,7 +67,7 @@ DOM::ExceptionOr<NonnullRefPtr<WebSocket>> WebSocket::create_with_global_object(
}
WebSocket::WebSocket(DOM::Window& window, AK::URL& url)
: EventTarget(static_cast<Bindings::ScriptExecutionContext&>(window.associated_document()))
: EventTarget()
, m_window(window)
{
// FIXME: Integrate properly with FETCH as per https://fetch.spec.whatwg.org/#websocket-opening-handshake
@ -226,14 +226,14 @@ JS::Object* WebSocket::create_wrapper(JS::GlobalObject& global_object)
}
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void WebSocket::set_##attribute_name(HTML::EventHandler value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
HTML::EventHandler WebSocket::attribute_name() \
{ \
return event_handler_attribute(event_name); \
#define __ENUMERATE(attribute_name, event_name) \
void WebSocket::set_##attribute_name(Optional<Bindings::CallbackType> value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
Bindings::CallbackType* WebSocket::attribute_name() \
{ \
return event_handler_attribute(event_name); \
}
ENUMERATE_WEBSOCKET_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -74,9 +74,9 @@ public:
String url() const { return m_url.to_string(); }
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(HTML::EventHandler); \
HTML::EventHandler attribute_name();
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(Optional<Bindings::CallbackType>); \
Bindings::CallbackType* attribute_name();
ENUMERATE_WEBSOCKET_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -14,7 +14,7 @@
namespace Web::HighResolutionTime {
Performance::Performance(DOM::Window& window)
: DOM::EventTarget(static_cast<Bindings::ScriptExecutionContext&>(window.associated_document()))
: DOM::EventTarget()
, m_window(window)
, m_timing(make<NavigationTiming::PerformanceTiming>(window))
{

View File

@ -28,7 +28,7 @@
namespace Web::XHR {
XMLHttpRequest::XMLHttpRequest(DOM::Window& window)
: XMLHttpRequestEventTarget(static_cast<Bindings::ScriptExecutionContext&>(window.associated_document()))
: XMLHttpRequestEventTarget()
, m_window(window)
{
}
@ -263,12 +263,12 @@ JS::Object* XMLHttpRequest::create_wrapper(JS::GlobalObject& global_object)
return wrap(global_object, *this);
}
HTML::EventHandler XMLHttpRequest::onreadystatechange()
Bindings::CallbackType* XMLHttpRequest::onreadystatechange()
{
return event_handler_attribute(Web::XHR::EventNames::readystatechange);
}
void XMLHttpRequest::set_onreadystatechange(HTML::EventHandler value)
void XMLHttpRequest::set_onreadystatechange(Optional<Bindings::CallbackType> value)
{
set_event_handler_attribute(Web::XHR::EventNames::readystatechange, move(value));
}

View File

@ -59,8 +59,8 @@ public:
String get_response_header(const String& name) { return m_response_headers.get(name).value_or({}); }
String get_all_response_headers() const;
HTML::EventHandler onreadystatechange();
void set_onreadystatechange(HTML::EventHandler);
Bindings::CallbackType* onreadystatechange();
void set_onreadystatechange(Optional<Bindings::CallbackType>);
private:
virtual void ref_event_target() override { ref(); }

View File

@ -11,14 +11,14 @@
namespace Web::XHR {
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void XMLHttpRequestEventTarget::set_##attribute_name(HTML::EventHandler value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
HTML::EventHandler XMLHttpRequestEventTarget::attribute_name() \
{ \
return event_handler_attribute(event_name); \
#define __ENUMERATE(attribute_name, event_name) \
void XMLHttpRequestEventTarget::set_##attribute_name(Optional<Bindings::CallbackType> value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
Bindings::CallbackType* XMLHttpRequestEventTarget::attribute_name() \
{ \
return event_handler_attribute(event_name); \
}
ENUMERATE_XML_HTTP_REQUEST_EVENT_TARGET_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE

View File

@ -30,15 +30,15 @@ public:
virtual ~XMLHttpRequestEventTarget() override {};
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(HTML::EventHandler); \
HTML::EventHandler attribute_name();
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(Optional<Bindings::CallbackType>); \
Bindings::CallbackType* attribute_name();
ENUMERATE_XML_HTTP_REQUEST_EVENT_TARGET_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
protected:
explicit XMLHttpRequestEventTarget(Bindings::ScriptExecutionContext& script_execution_context)
: DOM::EventTarget(script_execution_context)
XMLHttpRequestEventTarget()
: DOM::EventTarget()
{
}