LibWeb: Make event dispatching spec-compliant

Specification: https://dom.spec.whatwg.org/#concept-event-dispatch

This also introduces shadow roots due to it being a requirement of
the event dispatcher.

However, it does not introduce the full shadow DOM, that can be
left for future work.

This changes some event dispatches which require certain attributes
to be initialised to a value.
This commit is contained in:
Luke 2020-11-21 18:32:39 +00:00 committed by Andreas Kling
parent 819f099a8e
commit e8b3a65581
Notes: sideshowbarker 2024-07-19 01:18:46 +09:00
32 changed files with 858 additions and 54 deletions

View file

@ -35,6 +35,7 @@
#include <LibJS/Runtime/Shape.h> #include <LibJS/Runtime/Shape.h>
#include <LibTextCodec/Decoder.h> #include <LibTextCodec/Decoder.h>
#include <LibWeb/Bindings/DocumentWrapper.h> #include <LibWeb/Bindings/DocumentWrapper.h>
#include <LibWeb/Bindings/EventWrapper.h>
#include <LibWeb/Bindings/LocationObject.h> #include <LibWeb/Bindings/LocationObject.h>
#include <LibWeb/Bindings/NavigatorObject.h> #include <LibWeb/Bindings/NavigatorObject.h>
#include <LibWeb/Bindings/NodeWrapperFactory.h> #include <LibWeb/Bindings/NodeWrapperFactory.h>
@ -43,6 +44,7 @@
#include <LibWeb/Bindings/XMLHttpRequestConstructor.h> #include <LibWeb/Bindings/XMLHttpRequestConstructor.h>
#include <LibWeb/Bindings/XMLHttpRequestPrototype.h> #include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/Window.h> #include <LibWeb/DOM/Window.h>
#include <LibWeb/Origin.h> #include <LibWeb/Origin.h>
@ -75,6 +77,9 @@ void WindowObject::initialize()
define_native_function("atob", atob, 1); define_native_function("atob", atob, 1);
define_native_function("btoa", btoa, 1); define_native_function("btoa", btoa, 1);
// Legacy
define_native_property("event", event_getter, nullptr, JS::Attribute::Enumerable);
define_property("navigator", heap().allocate<NavigatorObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable); define_property("navigator", heap().allocate<NavigatorObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
define_property("location", heap().allocate<LocationObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable); define_property("location", heap().allocate<LocationObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
@ -337,5 +342,15 @@ JS_DEFINE_NATIVE_GETTER(WindowObject::performance_getter)
return wrap(global_object, impl->performance()); return wrap(global_object, impl->performance());
} }
JS_DEFINE_NATIVE_GETTER(WindowObject::event_getter)
{
auto* impl = impl_from(vm, global_object);
if (!impl)
return {};
if (!impl->current_event())
return JS::js_undefined();
return wrap(global_object, const_cast<DOM::Event&>(*impl->current_event()));
}
} }
} }

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/TypeCasts.h>
#include <AK/Weakable.h> #include <AK/Weakable.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
@ -58,6 +59,8 @@ private:
JS_DECLARE_NATIVE_GETTER(performance_getter); JS_DECLARE_NATIVE_GETTER(performance_getter);
JS_DECLARE_NATIVE_GETTER(event_getter);
JS_DECLARE_NATIVE_FUNCTION(alert); JS_DECLARE_NATIVE_FUNCTION(alert);
JS_DECLARE_NATIVE_FUNCTION(confirm); JS_DECLARE_NATIVE_FUNCTION(confirm);
JS_DECLARE_NATIVE_FUNCTION(set_interval); JS_DECLARE_NATIVE_FUNCTION(set_interval);
@ -77,3 +80,7 @@ private:
} }
} }
AK_BEGIN_TYPE_TRAITS(Web::Bindings::WindowObject)
static bool is_type(const JS::GlobalObject& global) { return String(global.class_name()) == "WindowObject"; }
AK_END_TYPE_TRAITS()

View file

@ -35,12 +35,14 @@ set(SOURCES
DOM/DOMImplementation.cpp DOM/DOMImplementation.cpp
DOM/Element.cpp DOM/Element.cpp
DOM/ElementFactory.cpp DOM/ElementFactory.cpp
DOM/Event.cpp
DOM/EventDispatcher.cpp DOM/EventDispatcher.cpp
DOM/EventListener.cpp DOM/EventListener.cpp
DOM/EventTarget.cpp DOM/EventTarget.cpp
DOM/Node.cpp DOM/Node.cpp
DOM/ParentNode.cpp DOM/ParentNode.cpp
DOM/Position.cpp DOM/Position.cpp
DOM/ShadowRoot.cpp
DOM/TagNames.cpp DOM/TagNames.cpp
DOM/Text.cpp DOM/Text.cpp
DOM/Text.idl DOM/Text.idl
@ -185,6 +187,7 @@ set(SOURCES
SVG/SVGSVGElement.cpp SVG/SVGSVGElement.cpp
SVG/TagNames.cpp SVG/TagNames.cpp
StylePropertiesModel.cpp StylePropertiesModel.cpp
UIEvents/MouseEvent.cpp
URLEncoder.cpp URLEncoder.cpp
WebContentClient.cpp WebContentClient.cpp
) )
@ -237,6 +240,7 @@ libweb_js_wrapper(DOM/DOMImplementation)
libweb_js_wrapper(DOM/Element) libweb_js_wrapper(DOM/Element)
libweb_js_wrapper(DOM/Event) libweb_js_wrapper(DOM/Event)
libweb_js_wrapper(DOM/EventTarget) libweb_js_wrapper(DOM/EventTarget)
libweb_js_wrapper(DOM/ShadowRoot)
libweb_js_wrapper(DOM/Node) libweb_js_wrapper(DOM/Node)
libweb_js_wrapper(DOM/Text) libweb_js_wrapper(DOM/Text)
libweb_js_wrapper(HTML/CanvasRenderingContext2D) libweb_js_wrapper(HTML/CanvasRenderingContext2D)

View file

@ -354,8 +354,6 @@ static bool should_emit_wrapper_factory(const IDL::Interface& interface)
return false; return false;
if (interface.name.ends_with("Element")) if (interface.name.ends_with("Element"))
return false; return false;
if (interface.name.ends_with("Event"))
return false;
return true; return true;
} }
@ -510,6 +508,7 @@ void generate_implementation(const IDL::Interface& interface)
#include <LibWeb/Bindings/DocumentFragmentWrapper.h> #include <LibWeb/Bindings/DocumentFragmentWrapper.h>
#include <LibWeb/Bindings/DocumentTypeWrapper.h> #include <LibWeb/Bindings/DocumentTypeWrapper.h>
#include <LibWeb/Bindings/DocumentWrapper.h> #include <LibWeb/Bindings/DocumentWrapper.h>
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h> #include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h> #include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
#include <LibWeb/Bindings/HTMLImageElementWrapper.h> #include <LibWeb/Bindings/HTMLImageElementWrapper.h>
@ -733,7 +732,7 @@ static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_ob
return new_array; return new_array;
)~~~"); )~~~");
} else if (return_type.name == "long" || return_type.name == "double" || return_type.name == "boolean") { } else if (return_type.name == "long" || return_type.name == "double" || return_type.name == "boolean" || return_type.name == "short") {
scoped_generator.append(R"~~~( scoped_generator.append(R"~~~(
return JS::Value(retval); return JS::Value(retval);
)~~~"); )~~~");

View file

@ -44,6 +44,7 @@ const NonnullRefPtr<Document> DOMImplementation::create_htmldocument(const Strin
auto html_document = Document::create(); auto html_document = Document::create();
html_document->set_content_type("text/html"); html_document->set_content_type("text/html");
html_document->set_ready_for_post_load_tasks(true);
auto doctype = adopt(*new DocumentType(html_document)); auto doctype = adopt(*new DocumentType(html_document));
doctype->set_name("html"); doctype->set_name("html");

View file

@ -621,4 +621,18 @@ const Page* Document::page() const
return m_frame ? m_frame->page() : nullptr; return m_frame ? m_frame->page() : nullptr;
} }
EventTarget* Document::get_parent(const Event& event)
{
if (event.type() == "load")
return nullptr;
return &window();
}
void Document::completely_finish_loading()
{
// FIXME: This needs to handle iframes.
dispatch_event(DOM::Event::create("load"));
}
} }

View file

@ -207,7 +207,14 @@ public:
const String& charset() const { return encoding(); } const String& charset() const { return encoding(); }
const String& input_encoding() const { return encoding(); } const String& input_encoding() const { return encoding(); }
const NonnullRefPtr<DOMImplementation> implementation() { return m_implementation; } bool ready_for_post_load_tasks() const { return m_ready_for_post_load_tasks; }
void set_ready_for_post_load_tasks(bool ready) { m_ready_for_post_load_tasks = ready; }
void completely_finish_loading();
const NonnullRefPtr<DOMImplementation> implementation() const { return m_implementation; }
virtual EventTarget* get_parent(const Event&) override;
private: private:
explicit Document(const URL&); explicit Document(const URL&);
@ -272,6 +279,8 @@ private:
String m_content_type { "application/xml" }; String m_content_type { "application/xml" };
String m_encoding { "UTF-8" }; String m_encoding { "UTF-8" };
bool m_ready_for_post_load_tasks { false };
NonnullRefPtr<DOMImplementation> m_implementation; NonnullRefPtr<DOMImplementation> m_implementation;
}; };

View file

@ -44,8 +44,8 @@ public:
virtual FlyString node_name() const override { return "#document-fragment"; } virtual FlyString node_name() const override { return "#document-fragment"; }
Element& host() { return *m_host; } RefPtr<Element> host() { return m_host; }
const Element& host() const { return *m_host; } const RefPtr<Element> host() const { return m_host; }
void set_host(Element& host) { m_host = host; } void set_host(Element& host) { m_host = host; }

View file

@ -90,6 +90,7 @@ public:
const CSS::StyleProperties* resolved_style() const { return m_resolved_style.ptr(); } const CSS::StyleProperties* resolved_style() const { return m_resolved_style.ptr(); }
NonnullRefPtr<CSS::StyleProperties> computed_style(); NonnullRefPtr<CSS::StyleProperties> computed_style();
// FIXME: innerHTML also appears on shadow roots. https://w3c.github.io/DOM-Parsing/#dom-innerhtml
String inner_html() const; String inner_html() const;
void set_inner_html(StringView); void set_inner_html(StringView);

View file

@ -8,7 +8,7 @@ interface Element : Node {
Element? querySelector(DOMString selectors); Element? querySelector(DOMString selectors);
ArrayFromVector querySelectorAll(DOMString selectors); ArrayFromVector querySelectorAll(DOMString selectors);
attribute DOMString innerHTML; [LegacyNullToEmptyString] attribute DOMString innerHTML;
[Reflect] attribute DOMString id; [Reflect] attribute DOMString id;
[Reflect=class] attribute DOMString className; [Reflect=class] attribute DOMString className;

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Assertions.h>
#include <AK/TypeCasts.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/ShadowRoot.h>
namespace Web::DOM {
void Event::append_to_path(EventTarget& invocation_target, RefPtr<EventTarget> shadow_adjusted_target, RefPtr<EventTarget> related_target, TouchTargetList& touch_targets, bool slot_in_closed_tree)
{
bool invocation_target_in_shadow_tree = false;
bool root_of_closed_tree = false;
if (is<Node>(invocation_target)) {
auto& invocation_target_node = downcast<Node>(invocation_target);
if (invocation_target_node.root()->is_shadow_root())
invocation_target_in_shadow_tree = true;
if (is<ShadowRoot>(invocation_target_node)) {
auto& invocation_target_shadow_root = downcast<ShadowRoot>(invocation_target_node);
root_of_closed_tree = invocation_target_shadow_root.closed();
}
}
m_path.append({ invocation_target, invocation_target_in_shadow_tree, shadow_adjusted_target, related_target, touch_targets, root_of_closed_tree, slot_in_closed_tree, m_path.size() });
}
void Event::set_cancelled_flag()
{
if (m_cancelable && !m_in_passive_listener)
m_cancelled = true;
}
}

View file

@ -28,6 +28,7 @@
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <LibWeb/Bindings/Wrappable.h> #include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/DOM/EventTarget.h>
namespace Web::DOM { namespace Web::DOM {
@ -37,6 +38,28 @@ class Event
public: public:
using WrapperType = Bindings::EventWrapper; using WrapperType = Bindings::EventWrapper;
enum Phase : u16 {
None = 0,
CapturingPhase = 1,
AtTarget = 2,
BubblingPhase = 3,
};
using TouchTargetList = Vector<RefPtr<EventTarget>>;
struct PathEntry {
RefPtr<EventTarget> invocation_target;
bool invocation_target_in_shadow_tree { false };
RefPtr<EventTarget> shadow_adjusted_target;
RefPtr<EventTarget> related_target;
TouchTargetList touch_target_list;
bool root_of_closed_tree { false };
bool slot_in_closed_tree { false };
size_t index;
};
using Path = Vector<PathEntry>;
static NonnullRefPtr<Event> create(const FlyString& event_name) static NonnullRefPtr<Event> create(const FlyString& event_name)
{ {
return adopt(*new Event(event_name)); return adopt(*new Event(event_name));
@ -45,6 +68,86 @@ public:
virtual ~Event() { } virtual ~Event() { }
const FlyString& type() const { return m_type; } const FlyString& type() const { return m_type; }
void set_type(const StringView& type) { m_type = type; }
RefPtr<EventTarget> target() const { return m_target; }
void set_target(EventTarget* target) { m_target = target; }
// NOTE: This is intended for the JS bindings.
RefPtr<EventTarget> src_target() const { return target(); }
RefPtr<EventTarget> related_target() const { return m_related_target; }
void set_related_target(EventTarget* related_target) { m_related_target = related_target; }
bool should_stop_propagation() const { return m_stop_propagation; }
void set_stop_propagation(bool stop_propagation) { m_stop_propagation = stop_propagation; }
bool should_stop_immediate_propagation() const { return m_stop_immediate_propagation; }
void set_stop_immediate_propagation(bool stop_immediate_propagation) { m_stop_immediate_propagation = stop_immediate_propagation; }
bool cancelled() const { return m_cancelled; }
void set_cancelled(bool cancelled) { m_cancelled = cancelled; }
bool in_passive_listener() const { return m_in_passive_listener; }
void set_in_passive_listener(bool in_passive_listener) { m_in_passive_listener = in_passive_listener; }
bool composed() const { return m_composed; }
void set_composed(bool composed) { m_composed = composed; }
bool initialized() const { return m_initialized; }
void set_initialized(bool initialized) { m_initialized = initialized; }
bool dispatched() const { return m_dispatch; }
void set_dispatched(bool dispatched) { m_dispatch = dispatched; }
void prevent_default() { set_cancelled_flag(); }
bool default_prevented() const { return cancelled(); }
u16 event_phase() const { return m_phase; }
void set_phase(Phase phase) { m_phase = phase; }
RefPtr<EventTarget> current_target() const { return m_current_target; }
void set_current_target(EventTarget* current_target) { m_current_target = current_target; }
bool return_value() const { return !m_cancelled; }
void set_return_value(bool return_value)
{
if (!return_value)
set_cancelled_flag();
}
void append_to_path(EventTarget&, RefPtr<EventTarget>, RefPtr<EventTarget>, TouchTargetList&, bool);
Path& path() { return m_path; }
const Path& path() const { return m_path; }
void clear_path() { m_path.clear(); }
void set_touch_target_list(TouchTargetList& touch_target_list) { m_touch_target_list = touch_target_list; }
TouchTargetList& touch_target_list() { return m_touch_target_list; };
void clear_touch_target_list() { m_touch_target_list.clear(); }
bool bubbles() const { return m_bubbles; }
void set_bubbles(bool bubbles) { m_bubbles = bubbles; }
bool cancelable() const { return m_cancelable; }
void set_cancelable(bool cancelable) { m_cancelable = cancelable; }
bool is_trusted() const { return m_is_trusted; }
void set_is_trusted(bool is_trusted) { m_is_trusted = is_trusted; }
void stop_propagation() { m_stop_propagation = true; }
bool cancel_bubble() const { return m_stop_propagation; }
void set_cancel_bubble(bool cancel_bubble)
{
if (cancel_bubble)
m_stop_propagation = true;
}
void stop_immediate_propagation()
{
m_stop_propagation = true;
m_stop_immediate_propagation = true;
}
virtual bool is_ui_event() const { return false; } virtual bool is_ui_event() const { return false; }
virtual bool is_mouse_event() const { return false; } virtual bool is_mouse_event() const { return false; }
@ -52,11 +155,35 @@ public:
protected: protected:
explicit Event(const FlyString& type) explicit Event(const FlyString& type)
: m_type(type) : m_type(type)
, m_initialized(true)
{ {
} }
private: private:
FlyString m_type; FlyString m_type;
RefPtr<EventTarget> m_target;
RefPtr<EventTarget> m_related_target;
RefPtr<EventTarget> m_current_target;
Phase m_phase { None };
bool m_bubbles { false };
bool m_cancelable { false };
bool m_stop_propagation { false };
bool m_stop_immediate_propagation { false };
bool m_cancelled { false };
bool m_in_passive_listener { false };
bool m_composed { false };
bool m_initialized { false };
bool m_dispatch { false };
bool m_is_trusted { true };
Path m_path;
TouchTargetList m_touch_target_list;
void set_cancelled_flag();
}; };
} }

View file

@ -1,5 +1,23 @@
interface Event { interface Event {
readonly attribute DOMString type; readonly attribute DOMString type;
readonly attribute EventTarget? target;
readonly attribute EventTarget? srcTarget;
readonly attribute EventTarget? currentTarget;
readonly attribute unsigned short eventPhase;
void stopPropagation();
attribute boolean cancelBubble;
void stopImmediatePropagation();
readonly attribute boolean bubbles;
readonly attribute boolean cancelable;
attribute boolean returnValue;
void preventDefault();
readonly attribute boolean defaultPrevented;
readonly attribute boolean composed;
readonly attribute boolean isTrusted;
} }

View file

@ -24,35 +24,309 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <AK/Assertions.h>
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/Function.h> #include <LibJS/Runtime/Function.h>
#include <LibWeb/Bindings/EventTargetWrapper.h> #include <LibWeb/Bindings/EventTargetWrapper.h>
#include <LibWeb/Bindings/EventTargetWrapperFactory.h> #include <LibWeb/Bindings/EventTargetWrapperFactory.h>
#include <LibWeb/Bindings/EventWrapper.h> #include <LibWeb/Bindings/EventWrapper.h>
#include <LibWeb/Bindings/EventWrapperFactory.h> #include <LibWeb/Bindings/EventWrapperFactory.h>
#include <LibWeb/Bindings/ScriptExecutionContext.h> #include <LibWeb/Bindings/ScriptExecutionContext.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/EventDispatcher.h> #include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/DOM/EventListener.h> #include <LibWeb/DOM/EventListener.h>
#include <LibWeb/DOM/EventTarget.h> #include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/Window.h>
#include <LibWeb/UIEvents/MouseEvent.h>
namespace Web::DOM { namespace Web::DOM {
void EventDispatcher::dispatch(EventTarget& target, NonnullRefPtr<Event> event) // FIXME: This shouldn't be here, as retargeting is not only used by the event dispatcher.
// When moving this function, it needs to be generalized. https://dom.spec.whatwg.org/#retarget
static EventTarget* retarget(EventTarget* left, EventTarget* right)
{ {
auto listeners = target.listeners(); // FIXME
for (auto& listener : listeners) { UNUSED_PARAM(right);
if (listener.event_name != event->type()) for (;;) {
continue; if (!is<Node>(left))
auto& function = listener.listener->function(); return left;
auto& global_object = function.global_object();
auto* this_value = Bindings::wrap(global_object, target);
auto* wrapped_event = Bindings::wrap(global_object, *event);
auto& vm = global_object.vm(); auto* left_node = downcast<Node>(left);
(void)vm.call(function, this_value, wrapped_event); auto* left_root = left_node->root();
if (vm.exception()) if (!left_root->is_shadow_root())
vm.clear_exception(); return left;
// FIXME: If right is a node and lefts root is a shadow-including inclusive ancestor of right, return left.
auto* left_shadow_root = downcast<ShadowRoot>(left_root);
left = left_shadow_root->host();
} }
} }
// 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)
{
bool found = false;
for (auto& listener : listeners) {
if (listener.listener->removed())
continue;
if (event.type() != listener.listener->type())
continue;
found = true;
if (phase == Event::Phase::CapturingPhase && !listener.listener->capture())
continue;
if (phase == Event::Phase::BubblingPhase && listener.listener->capture())
continue;
if (listener.listener->once())
event.current_target()->remove_from_event_listener_list(listener.listener);
auto& function = listener.listener->function();
auto& global = function.global_object();
RefPtr<Event> current_event;
if (is<Bindings::WindowObject>(global)) {
auto& bindings_window_global = downcast<Bindings::WindowObject>(global);
auto& window_impl = bindings_window_global.impl();
current_event = window_impl.current_event();
if (!invocation_target_in_shadow_tree)
window_impl.set_current_event(&event);
}
if (listener.listener->passive())
event.set_in_passive_listener(true);
auto* this_value = Bindings::wrap(global, *event.current_target());
auto* wrapped_event = Bindings::wrap(global, event);
auto& vm = global.vm();
(void)vm.call(listener.listener->function(), this_value, wrapped_event);
if (vm.exception()) {
vm.clear_exception();
// FIXME: Set legacyOutputDidListenersThrowFlag if given. (Only used by IndexedDB currently)
}
event.set_in_passive_listener(false);
if (is<Bindings::WindowObject>(global)) {
auto& bindings_window_global = downcast<Bindings::WindowObject>(global);
auto& window_impl = bindings_window_global.impl();
window_impl.set_current_event(current_event);
}
if (event.should_stop_immediate_propagation())
return found;
}
return found;
}
// https://dom.spec.whatwg.org/#concept-event-listener-invoke
void EventDispatcher::invoke(Event::PathEntry& struct_, Event& event, Event::Phase phase)
{
auto last_valid_shadow_adjusted_target = event.path().last_matching([&struct_](auto& entry) {
return entry.index <= struct_.index && !entry.shadow_adjusted_target.is_null();
});
ASSERT(last_valid_shadow_adjusted_target.has_value());
event.set_target(last_valid_shadow_adjusted_target.value().shadow_adjusted_target);
event.set_related_target(struct_.related_target);
event.set_touch_target_list(struct_.touch_target_list);
if (event.should_stop_propagation())
return;
event.set_current_target(struct_.invocation_target);
// NOTE: This is an intentional copy. Any event listeners added after this point will not be invoked.
auto listeners = event.current_target()->listeners();
bool invocation_target_in_shadow_tree = struct_.invocation_target_in_shadow_tree;
bool found = inner_invoke(event, listeners, phase, invocation_target_in_shadow_tree);
if (!found && event.is_trusted()) {
auto original_event_type = event.type();
if (event.type() == "animationend")
event.set_type("webkitAnimationEnd");
else if (event.type() == "animationiteration")
event.set_type("webkitAnimationIteration");
else if (event.type() == "animationstart")
event.set_type("webkitAnimationStart");
else if (event.type() == "transitionend")
event.set_type("webkitTransitionEnd");
else
return;
inner_invoke(event, listeners, phase, invocation_target_in_shadow_tree);
event.set_type(original_event_type);
}
}
// https://dom.spec.whatwg.org/#concept-event-dispatch
bool EventDispatcher::dispatch(NonnullRefPtr<EventTarget> target, NonnullRefPtr<Event> event, bool legacy_target_override)
{
event->set_dispatched(true);
RefPtr<EventTarget> target_override;
if (!legacy_target_override) {
target_override = target;
} else {
// NOTE: This can be done because legacy_target_override is only set for events targeted at Window.
target_override = downcast<Window>(*target).document();
}
RefPtr<EventTarget> activation_target;
RefPtr<EventTarget> related_target = retarget(event->related_target(), target);
bool clear_targets = false;
if (related_target != target || event->related_target() == target) {
Event::TouchTargetList touch_targets;
for (auto& touch_target : event->touch_target_list()) {
touch_targets.append(retarget(touch_target, target));
}
event->append_to_path(*target, target_override, related_target, touch_targets, false);
bool is_activation_event = is<UIEvents::MouseEvent>(*event) && event->type() == "click";
if (is_activation_event && target->activation_behaviour)
activation_target = target;
// FIXME: Let slottable be target, if target is a slottable and is assigned, and null otherwise.
bool slot_in_closed_tree = false;
auto* parent = target->get_parent(event);
while (parent) {
// FIXME: If slottable is non-null:
// FIXME: If parent is a slottable and is assigned, then set slottable to parent.
related_target = retarget(event->related_target(), parent);
touch_targets.clear();
for (auto& touch_target : event->touch_target_list()) {
touch_targets.append(retarget(touch_target, parent));
}
// FIXME: or parent is a node and targets root is a shadow-including inclusive ancestor of parent, then:
if (is<Window>(parent)) {
if (is_activation_event && event->bubbles() && !activation_target && parent->activation_behaviour)
activation_target = parent;
event->append_to_path(*parent, nullptr, related_target, touch_targets, slot_in_closed_tree);
} else if (related_target == parent) {
parent = nullptr;
} else {
target = *parent;
if (is_activation_event && !activation_target && target->activation_behaviour)
activation_target = target;
event->append_to_path(*parent, target, related_target, touch_targets, slot_in_closed_tree);
}
if (parent) {
parent = parent->get_parent(event);
}
slot_in_closed_tree = false;
}
auto clear_targets_struct = event->path().last_matching([](auto& entry) {
return !entry.shadow_adjusted_target.is_null();
});
ASSERT(clear_targets_struct.has_value());
if (is<Node>(clear_targets_struct.value().shadow_adjusted_target.ptr())) {
auto& shadow_adjusted_target_node = downcast<Node>(*clear_targets_struct.value().shadow_adjusted_target);
if (is<ShadowRoot>(shadow_adjusted_target_node.root()))
clear_targets = true;
}
if (!clear_targets && is<Node>(clear_targets_struct.value().related_target.ptr())) {
auto& related_target_node = downcast<Node>(*clear_targets_struct.value().related_target);
if (is<ShadowRoot>(related_target_node.root()))
clear_targets = true;
}
if (!clear_targets) {
for (auto touch_target : clear_targets_struct.value().touch_target_list) {
if (is<Node>(*touch_target.ptr())) {
auto& touch_target_node = downcast<Node>(*touch_target.ptr());
if (is<ShadowRoot>(touch_target_node.root())) {
clear_targets = true;
break;
}
}
}
}
if (activation_target && activation_target->legacy_pre_activation_behaviour)
activation_target->legacy_pre_activation_behaviour();
for (ssize_t i = event->path().size() - 1; i >= 0; --i) {
auto& entry = event->path().at(i);
if (entry.shadow_adjusted_target)
event->set_phase(Event::Phase::AtTarget);
else
event->set_phase(Event::Phase::CapturingPhase);
invoke(entry, event, Event::Phase::CapturingPhase);
}
for (auto& entry : event->path()) {
if (entry.shadow_adjusted_target) {
event->set_phase(Event::Phase::AtTarget);
} else {
if (!event->bubbles())
continue;
event->set_phase(Event::Phase::BubblingPhase);
}
invoke(entry, event, Event::Phase::BubblingPhase);
}
}
event->set_phase(Event::Phase::None);
event->set_current_target(nullptr);
event->clear_path();
event->set_dispatched(false);
event->set_stop_propagation(false);
event->set_stop_immediate_propagation(false);
if (clear_targets) {
event->set_target(nullptr);
event->set_related_target(nullptr);
event->clear_touch_target_list();
}
if (activation_target) {
if (!event->cancelled()) {
// NOTE: Since activation_target is set, it will have activation behaviour.
activation_target->activation_behaviour(event);
} else {
if (activation_target->legacy_cancelled_activation_behaviour)
activation_target->legacy_cancelled_activation_behaviour();
}
}
return !event->cancelled();
}
} }

View file

@ -33,7 +33,11 @@ namespace Web::DOM {
class EventDispatcher { class EventDispatcher {
public: public:
static void dispatch(EventTarget&, NonnullRefPtr<Event>); static bool dispatch(NonnullRefPtr<EventTarget>, NonnullRefPtr<Event>, bool legacy_target_override = false);
private:
static void invoke(Event::PathEntry&, Event&, Event::Phase);
static bool inner_invoke(Event&, Vector<EventTarget::EventListenerRegistration>&, Event::Phase, bool);
}; };
} }

View file

@ -45,8 +45,28 @@ public:
JS::Function& function(); JS::Function& function();
const FlyString& type() const { return m_type; }
void set_type(const FlyString& type) { m_type = type; }
bool capture() const { return m_capture; }
void set_capture(bool capture) { m_capture = capture; }
bool passive() const { return m_passive; }
void set_passive(bool passive) { m_capture = passive; }
bool once() const { return m_once; }
void set_once(bool once) { m_once = once; }
bool removed() const { return m_removed; }
void set_removed(bool removed) { m_removed = removed; }
private: private:
FlyString m_type;
JS::Handle<JS::Function> m_function; JS::Handle<JS::Function> m_function;
bool m_capture { false };
bool m_passive { false };
bool m_once { false };
bool m_removed { false };
}; };
} }

View file

@ -41,13 +41,29 @@ EventTarget::~EventTarget()
void EventTarget::add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener) void EventTarget::add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
{ {
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();
});
if (existing_listener.has_value())
return;
listener->set_type(event_name);
m_listeners.append({ event_name, move(listener) }); m_listeners.append({ event_name, move(listener) });
} }
void EventTarget::remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener) void EventTarget::remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
{ {
m_listeners.remove_first_matching([&](auto& entry) { m_listeners.remove_first_matching([&](auto& entry) {
return entry.event_name == event_name && &entry.listener->function() == &listener->function(); auto matches = entry.event_name == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
if (matches)
entry.listener->set_removed(true);
return matches;
});
}
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();
}); });
} }

View file

@ -27,6 +27,7 @@
#pragma once #pragma once
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/Function.h>
#include <AK/Noncopyable.h> #include <AK/Noncopyable.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
@ -47,10 +48,14 @@ public:
void add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>); void add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
void remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>); void remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
virtual void dispatch_event(NonnullRefPtr<Event>) = 0; void remove_from_event_listener_list(NonnullRefPtr<EventListener>);
virtual bool dispatch_event(NonnullRefPtr<Event>) = 0;
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) = 0; virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) = 0;
Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; } Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; }
virtual EventTarget* get_parent(const Event&) { return nullptr; }
struct EventListenerRegistration { struct EventListenerRegistration {
FlyString event_name; FlyString event_name;
NonnullRefPtr<EventListener> listener; NonnullRefPtr<EventListener> listener;
@ -58,6 +63,15 @@ public:
const Vector<EventListenerRegistration>& listeners() const { return m_listeners; } const Vector<EventListenerRegistration>& listeners() const { return m_listeners; }
virtual bool is_node() const { return false; }
virtual bool is_window() const { return false; }
Function<void(const Event&)> activation_behaviour;
// NOTE: These only exist for checkbox and radio input elements.
Function<void()> legacy_pre_activation_behaviour;
Function<void()> legacy_cancelled_activation_behaviour;
protected: protected:
explicit EventTarget(Bindings::ScriptExecutionContext&); explicit EventTarget(Bindings::ScriptExecutionContext&);

View file

@ -38,6 +38,7 @@
#include <LibWeb/DOM/EventDispatcher.h> #include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/DOM/EventListener.h> #include <LibWeb/DOM/EventListener.h>
#include <LibWeb/DOM/Node.h> #include <LibWeb/DOM/Node.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/HTMLAnchorElement.h> #include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/Layout/BlockBox.h> #include <LibWeb/Layout/BlockBox.h>
#include <LibWeb/Layout/InitialContainingBlockBox.h> #include <LibWeb/Layout/InitialContainingBlockBox.h>
@ -124,12 +125,9 @@ bool Node::is_link() const
return enclosing_link_element(); return enclosing_link_element();
} }
void Node::dispatch_event(NonnullRefPtr<Event> event) bool Node::dispatch_event(NonnullRefPtr<Event> event)
{ {
EventDispatcher::dispatch(*this, event); return EventDispatcher::dispatch(*this, event);
// FIXME: This is a hack. We should follow the real rules of event bubbling.
if (parent())
parent()->dispatch_event(move(event));
} }
String Node::child_text_content() const String Node::child_text_content() const
@ -145,17 +143,25 @@ String Node::child_text_content() const
return builder.build(); return builder.build();
} }
const Node* Node::root() const Node* Node::root()
{ {
const Node* root = this; Node* root = this;
while (root->parent()) while (root->parent())
root = root->parent(); root = root->parent();
return root; return root;
} }
Node* Node::shadow_including_root()
{
auto node_root = root();
if (is<ShadowRoot>(node_root))
return downcast<ShadowRoot>(node_root)->host()->shadow_including_root();
return node_root;
}
bool Node::is_connected() const bool Node::is_connected() const
{ {
return root() && root()->is_document(); return shadow_including_root() && shadow_including_root()->is_document();
} }
Element* Node::parent_element() Element* Node::parent_element()
@ -238,4 +244,10 @@ void Node::set_layout_node(Badge<Layout::Node>, Layout::Node* layout_node) const
m_layout_node = nullptr; m_layout_node = nullptr;
} }
EventTarget* Node::get_parent(const Event&)
{
// FIXME: returns the nodes assigned slot, if node is assigned, and nodes parent otherwise.
return parent();
}
} }

View file

@ -60,7 +60,7 @@ public:
// ^EventTarget // ^EventTarget
virtual void ref_event_target() final { ref(); } virtual void ref_event_target() final { ref(); }
virtual void unref_event_target() final { unref(); } virtual void unref_event_target() final { unref(); }
virtual void dispatch_event(NonnullRefPtr<Event>) final; virtual bool dispatch_event(NonnullRefPtr<Event>) final;
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override; virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
virtual ~Node(); virtual ~Node();
@ -76,7 +76,11 @@ public:
bool is_character_data() const { return type() == NodeType::TEXT_NODE || type() == NodeType::COMMENT_NODE; } bool is_character_data() const { return type() == NodeType::TEXT_NODE || type() == NodeType::COMMENT_NODE; }
bool is_document_fragment() const { return type() == NodeType::DOCUMENT_FRAGMENT_NODE; } bool is_document_fragment() const { return type() == NodeType::DOCUMENT_FRAGMENT_NODE; }
bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); } bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
bool is_slottable() const { return is_element() || is_text(); }
virtual bool is_svg_element() const { return false; } virtual bool is_svg_element() const { return false; }
virtual bool is_shadow_root() const { return false; }
virtual bool is_node() const final { return true; }
virtual bool is_editable() const; virtual bool is_editable() const;
@ -102,7 +106,18 @@ public:
virtual bool is_html_element() const { return false; } virtual bool is_html_element() const { return false; }
virtual bool is_unknown_html_element() const { return false; } virtual bool is_unknown_html_element() const { return false; }
const Node* root() const; Node* root();
const Node* root() const
{
return const_cast<Node*>(this)->root();
}
Node* shadow_including_root();
const Node* shadow_including_root() const
{
return const_cast<Node*>(this)->shadow_including_root();
}
bool is_connected() const; bool is_connected() const;
Node* parent_node() { return parent(); } Node* parent_node() { return parent(); }
@ -134,6 +149,8 @@ public:
void set_document(Badge<Document>, Document&); void set_document(Badge<Document>, Document&);
virtual EventTarget* get_parent(const Event&) override;
protected: protected:
Node(Document&, NodeType); Node(Document&, NodeType);
@ -144,3 +161,7 @@ protected:
}; };
} }
AK_BEGIN_TYPE_TRAITS(Web::DOM::Node)
static bool is_type(const Web::DOM::EventTarget& event_target) { return event_target.is_node(); }
AK_END_TYPE_TRAITS()

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/ShadowRoot.h>
namespace Web::DOM {
ShadowRoot::ShadowRoot(Document& document, Element& host)
: DocumentFragment(document)
{
set_host(host);
}
EventTarget* ShadowRoot::get_parent(const Event& event)
{
if (!event.composed()) {
auto& events_first_invocation_target = downcast<Node>(*event.path().first().invocation_target);
if (events_first_invocation_target.root() == this)
return nullptr;
}
return host();
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibWeb/DOM/DocumentFragment.h>
namespace Web::DOM {
class ShadowRoot final : public DocumentFragment {
public:
ShadowRoot(Document&, Element&);
bool closed() const { return m_closed; }
bool delegates_focus() const { return m_delegates_focus; }
void set_delegates_focus(bool delegates_focus) { m_delegates_focus = delegates_focus; }
bool available_to_element_internals() const { return m_available_to_element_internals; }
void set_available_to_element_internals(bool available_to_element_internals) { m_available_to_element_internals = available_to_element_internals; }
// ^Node
virtual bool is_shadow_root() const override { return true; }
// ^EventTarget
virtual EventTarget* get_parent(const Event&) override;
// NOTE: This is intended for the JS bindings.
String mode() const { return m_closed ? "closed" : "open"; }
private:
// NOTE: The specification doesn't seem to specify a default value for closed. Assuming false for now.
bool m_closed { false };
bool m_delegates_focus { false };
bool m_available_to_element_internals { false };
};
}
AK_BEGIN_TYPE_TRAITS(Web::DOM::ShadowRoot)
static bool is_type(const Web::DOM::Node& node) { return node.is_shadow_root(); }
AK_END_TYPE_TRAITS()

View file

@ -0,0 +1,6 @@
interface ShadowRoot : DocumentFragment {
readonly attribute DOMString mode;
readonly attribute Element host;
}

View file

@ -159,9 +159,9 @@ void Window::did_call_location_reload(Badge<Bindings::LocationObject>)
frame->loader().load(document().url(), FrameLoader::Type::Reload); frame->loader().load(document().url(), FrameLoader::Type::Reload);
} }
void Window::dispatch_event(NonnullRefPtr<Event> event) bool Window::dispatch_event(NonnullRefPtr<Event> event)
{ {
EventDispatcher::dispatch(*this, event); return EventDispatcher::dispatch(*this, event, true);
} }
Bindings::EventTargetWrapper* Window::create_wrapper(JS::GlobalObject&) Bindings::EventTargetWrapper* Window::create_wrapper(JS::GlobalObject&)

View file

@ -32,6 +32,7 @@
#include <AK/RefPtr.h> #include <AK/RefPtr.h>
#include <LibWeb/Bindings/WindowObject.h> #include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/Bindings/Wrappable.h> #include <LibWeb/Bindings/Wrappable.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/EventTarget.h> #include <LibWeb/DOM/EventTarget.h>
namespace Web::DOM { namespace Web::DOM {
@ -48,7 +49,7 @@ public:
virtual void ref_event_target() override { RefCounted::ref(); } virtual void ref_event_target() override { RefCounted::ref(); }
virtual void unref_event_target() override { RefCounted::unref(); } virtual void unref_event_target() override { RefCounted::unref(); }
virtual void dispatch_event(NonnullRefPtr<Event>) override; virtual bool dispatch_event(NonnullRefPtr<Event>) override;
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override; virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
const Document& document() const { return m_document; } const Document& document() const { return m_document; }
@ -77,6 +78,11 @@ public:
HighResolutionTime::Performance& performance() { return *m_performance; } HighResolutionTime::Performance& performance() { return *m_performance; }
virtual bool is_window() const override { return true; }
const Event* current_event() const { return m_current_event; }
void set_current_event(Event* event) { m_current_event = event; }
private: private:
explicit Window(Document&); explicit Window(Document&);
@ -87,6 +93,11 @@ private:
HashMap<int, NonnullRefPtr<Timer>> m_timers; HashMap<int, NonnullRefPtr<Timer>> m_timers;
NonnullOwnPtr<HighResolutionTime::Performance> m_performance; NonnullOwnPtr<HighResolutionTime::Performance> m_performance;
RefPtr<Event> m_current_event;
}; };
} }
AK_BEGIN_TYPE_TRAITS(Web::DOM::Window)
static bool is_type(const Web::DOM::EventTarget& event_target) { return event_target.is_window(); }
AK_END_TYPE_TRAITS()

View file

@ -107,9 +107,9 @@ void XMLHttpRequest::send()
}); });
} }
void XMLHttpRequest::dispatch_event(NonnullRefPtr<DOM::Event> event) bool XMLHttpRequest::dispatch_event(NonnullRefPtr<DOM::Event> event)
{ {
DOM::EventDispatcher::dispatch(*this, move(event)); return DOM::EventDispatcher::dispatch(*this, move(event));
} }
Bindings::EventTargetWrapper* XMLHttpRequest::create_wrapper(JS::GlobalObject& global_object) Bindings::EventTargetWrapper* XMLHttpRequest::create_wrapper(JS::GlobalObject& global_object)

View file

@ -65,7 +65,7 @@ public:
private: private:
virtual void ref_event_target() override { ref(); } virtual void ref_event_target() override { ref(); }
virtual void unref_event_target() override { unref(); } virtual void unref_event_target() override { unref(); }
virtual void dispatch_event(NonnullRefPtr<DOM::Event>) override; virtual bool dispatch_event(NonnullRefPtr<DOM::Event>) override;
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override; virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
void set_ready_state(ReadyState); void set_ready_state(ReadyState);

View file

@ -180,18 +180,22 @@ void HTMLDocumentParser::run(const URL& url)
script.execute_script(); script.execute_script();
} }
m_document->dispatch_event(DOM::Event::create("DOMContentLoaded")); auto content_loaded_event = DOM::Event::create("DOMContentLoaded");
content_loaded_event->set_bubbles(true);
// FIXME: These are not in the right place, they should only fire once subresources are ready. m_document->dispatch_event(content_loaded_event);
m_document->dispatch_event(DOM::Event::create("load"));
m_document->window().dispatch_event(DOM::Event::create("load"));
auto scripts_to_execute_as_soon_as_possible = m_document->take_scripts_to_execute_as_soon_as_possible({}); auto scripts_to_execute_as_soon_as_possible = m_document->take_scripts_to_execute_as_soon_as_possible({});
for (auto& script : scripts_to_execute_as_soon_as_possible) { for (auto& script : scripts_to_execute_as_soon_as_possible) {
script.execute_script(); script.execute_script();
} }
// FIXME: Spin the event loop until there is nothing that delays the load event in the Document.
m_document->set_ready_state("complete"); m_document->set_ready_state("complete");
m_document->window().dispatch_event(DOM::Event::create("load"));
m_document->set_ready_for_post_load_tasks(true);
m_document->completely_finish_loading();
} }
void HTMLDocumentParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& token) void HTMLDocumentParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& token)

View file

@ -60,9 +60,9 @@ void Performance::unref_event_target()
m_window.unref(); m_window.unref();
} }
void Performance::dispatch_event(NonnullRefPtr<DOM::Event> event) bool Performance::dispatch_event(NonnullRefPtr<DOM::Event> event)
{ {
DOM::EventDispatcher::dispatch(*this, event); return DOM::EventDispatcher::dispatch(*this, event);
} }
Bindings::EventTargetWrapper* Performance::create_wrapper(JS::GlobalObject& global_object) Bindings::EventTargetWrapper* Performance::create_wrapper(JS::GlobalObject& global_object)

View file

@ -49,7 +49,7 @@ public:
virtual void ref_event_target() override; virtual void ref_event_target() override;
virtual void unref_event_target() override; virtual void unref_event_target() override;
virtual void dispatch_event(NonnullRefPtr<DOM::Event>) override; virtual bool dispatch_event(NonnullRefPtr<DOM::Event>) override;
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override; virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
private: private:

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibWeb/UIEvents/MouseEvent.h>
namespace Web::UIEvents {
MouseEvent::MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y)
: UIEvent(event_name)
, m_offset_x(offset_x)
, m_offset_y(offset_y)
{
set_event_characteristics();
}
MouseEvent::~MouseEvent()
{
}
void MouseEvent::set_event_characteristics()
{
if (type() == "mousedown" || type() == "mousemove" || type() == "mouseout" || type() == "mouseover" || type() == "mouseup" || type() == "click") {
set_bubbles(true);
set_cancelable(true);
set_composed(true);
}
}
}

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/TypeCasts.h>
#include <LibWeb/UIEvents/UIEvent.h> #include <LibWeb/UIEvents/UIEvent.h>
namespace Web::UIEvents { namespace Web::UIEvents {
@ -39,24 +40,25 @@ public:
return adopt(*new MouseEvent(event_name, offset_x, offset_y)); return adopt(*new MouseEvent(event_name, offset_x, offset_y));
} }
virtual ~MouseEvent() override { } virtual ~MouseEvent() override;
i32 offset_x() const { return m_offset_x; } i32 offset_x() const { return m_offset_x; }
i32 offset_y() const { return m_offset_y; } i32 offset_y() const { return m_offset_y; }
protected: protected:
MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y) MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y);
: UIEvent(event_name)
, m_offset_x(offset_x)
, m_offset_y(offset_y)
{
}
private: private:
virtual bool is_mouse_event() const override { return true; } virtual bool is_mouse_event() const override { return true; }
void set_event_characteristics();
i32 m_offset_x { 0 }; i32 m_offset_x { 0 };
i32 m_offset_y { 0 }; i32 m_offset_y { 0 };
}; };
} }
AK_BEGIN_TYPE_TRAITS(Web::UIEvents::MouseEvent)
static bool is_type(const Web::DOM::Event& event) { return event.is_mouse_event(); }
AK_END_TYPE_TRAITS()