diff options
author | Luke <luke.wilde@live.co.uk> | 2020-11-21 18:32:39 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-11-22 18:20:56 +0100 |
commit | e8b3a6558191e97ffab28943865a9459f013969e (patch) | |
tree | d332303702f9ab31773668fac51b6b4a03dd4b47 | |
parent | 819f099a8eb595689bc37933c10d2a4fdb7737b1 (diff) | |
download | serenity-e8b3a6558191e97ffab28943865a9459f013969e.zip |
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.
32 files changed, 854 insertions, 50 deletions
diff --git a/Libraries/LibWeb/Bindings/WindowObject.cpp b/Libraries/LibWeb/Bindings/WindowObject.cpp index 44f424c6ec..eec88e5504 100644 --- a/Libraries/LibWeb/Bindings/WindowObject.cpp +++ b/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -35,6 +35,7 @@ #include <LibJS/Runtime/Shape.h> #include <LibTextCodec/Decoder.h> #include <LibWeb/Bindings/DocumentWrapper.h> +#include <LibWeb/Bindings/EventWrapper.h> #include <LibWeb/Bindings/LocationObject.h> #include <LibWeb/Bindings/NavigatorObject.h> #include <LibWeb/Bindings/NodeWrapperFactory.h> @@ -43,6 +44,7 @@ #include <LibWeb/Bindings/XMLHttpRequestConstructor.h> #include <LibWeb/Bindings/XMLHttpRequestPrototype.h> #include <LibWeb/DOM/Document.h> +#include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/Window.h> #include <LibWeb/Origin.h> @@ -75,6 +77,9 @@ void WindowObject::initialize() define_native_function("atob", atob, 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("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()); } +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())); +} + } } diff --git a/Libraries/LibWeb/Bindings/WindowObject.h b/Libraries/LibWeb/Bindings/WindowObject.h index bddabf3334..a1a9a27c9d 100644 --- a/Libraries/LibWeb/Bindings/WindowObject.h +++ b/Libraries/LibWeb/Bindings/WindowObject.h @@ -26,6 +26,7 @@ #pragma once +#include <AK/TypeCasts.h> #include <AK/Weakable.h> #include <LibJS/Runtime/GlobalObject.h> #include <LibWeb/Forward.h> @@ -58,6 +59,8 @@ private: JS_DECLARE_NATIVE_GETTER(performance_getter); + JS_DECLARE_NATIVE_GETTER(event_getter); + JS_DECLARE_NATIVE_FUNCTION(alert); JS_DECLARE_NATIVE_FUNCTION(confirm); 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() diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index f084378cd1..54aa0849ea 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -35,12 +35,14 @@ set(SOURCES DOM/DOMImplementation.cpp DOM/Element.cpp DOM/ElementFactory.cpp + DOM/Event.cpp DOM/EventDispatcher.cpp DOM/EventListener.cpp DOM/EventTarget.cpp DOM/Node.cpp DOM/ParentNode.cpp DOM/Position.cpp + DOM/ShadowRoot.cpp DOM/TagNames.cpp DOM/Text.cpp DOM/Text.idl @@ -185,6 +187,7 @@ set(SOURCES SVG/SVGSVGElement.cpp SVG/TagNames.cpp StylePropertiesModel.cpp + UIEvents/MouseEvent.cpp URLEncoder.cpp WebContentClient.cpp ) @@ -237,6 +240,7 @@ libweb_js_wrapper(DOM/DOMImplementation) libweb_js_wrapper(DOM/Element) libweb_js_wrapper(DOM/Event) libweb_js_wrapper(DOM/EventTarget) +libweb_js_wrapper(DOM/ShadowRoot) libweb_js_wrapper(DOM/Node) libweb_js_wrapper(DOM/Text) libweb_js_wrapper(HTML/CanvasRenderingContext2D) diff --git a/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp index 440bc2a294..a8be5b46b2 100644 --- a/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp +++ b/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp @@ -354,8 +354,6 @@ static bool should_emit_wrapper_factory(const IDL::Interface& interface) return false; if (interface.name.ends_with("Element")) return false; - if (interface.name.ends_with("Event")) - return false; return true; } @@ -510,6 +508,7 @@ void generate_implementation(const IDL::Interface& interface) #include <LibWeb/Bindings/DocumentFragmentWrapper.h> #include <LibWeb/Bindings/DocumentTypeWrapper.h> #include <LibWeb/Bindings/DocumentWrapper.h> +#include <LibWeb/Bindings/EventTargetWrapperFactory.h> #include <LibWeb/Bindings/HTMLCanvasElementWrapper.h> #include <LibWeb/Bindings/HTMLHeadElementWrapper.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; )~~~"); - } 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"~~~( return JS::Value(retval); )~~~"); diff --git a/Libraries/LibWeb/DOM/DOMImplementation.cpp b/Libraries/LibWeb/DOM/DOMImplementation.cpp index 98ca186779..0fdeb062b6 100644 --- a/Libraries/LibWeb/DOM/DOMImplementation.cpp +++ b/Libraries/LibWeb/DOM/DOMImplementation.cpp @@ -44,6 +44,7 @@ const NonnullRefPtr<Document> DOMImplementation::create_htmldocument(const Strin auto html_document = Document::create(); html_document->set_content_type("text/html"); + html_document->set_ready_for_post_load_tasks(true); auto doctype = adopt(*new DocumentType(html_document)); doctype->set_name("html"); diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index c7b00c14ee..358f4b80be 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -621,4 +621,18 @@ const Page* Document::page() const 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")); +} + } diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index cb400bd6e2..77e9b12039 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -207,7 +207,14 @@ public: const String& charset() 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: explicit Document(const URL&); @@ -272,6 +279,8 @@ private: String m_content_type { "application/xml" }; String m_encoding { "UTF-8" }; + bool m_ready_for_post_load_tasks { false }; + NonnullRefPtr<DOMImplementation> m_implementation; }; diff --git a/Libraries/LibWeb/DOM/DocumentFragment.h b/Libraries/LibWeb/DOM/DocumentFragment.h index cc55f2c168..fa5982a134 100644 --- a/Libraries/LibWeb/DOM/DocumentFragment.h +++ b/Libraries/LibWeb/DOM/DocumentFragment.h @@ -44,8 +44,8 @@ public: virtual FlyString node_name() const override { return "#document-fragment"; } - Element& host() { return *m_host; } - const Element& host() const { return *m_host; } + RefPtr<Element> host() { return m_host; } + const RefPtr<Element> host() const { return m_host; } void set_host(Element& host) { m_host = host; } diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index d9b704f841..b47eafdfa3 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -90,6 +90,7 @@ public: const CSS::StyleProperties* resolved_style() const { return m_resolved_style.ptr(); } NonnullRefPtr<CSS::StyleProperties> computed_style(); + // FIXME: innerHTML also appears on shadow roots. https://w3c.github.io/DOM-Parsing/#dom-innerhtml String inner_html() const; void set_inner_html(StringView); diff --git a/Libraries/LibWeb/DOM/Element.idl b/Libraries/LibWeb/DOM/Element.idl index d5c4f03c9c..d4499e5268 100644 --- a/Libraries/LibWeb/DOM/Element.idl +++ b/Libraries/LibWeb/DOM/Element.idl @@ -8,7 +8,7 @@ interface Element : Node { Element? querySelector(DOMString selectors); ArrayFromVector querySelectorAll(DOMString selectors); - attribute DOMString innerHTML; + [LegacyNullToEmptyString] attribute DOMString innerHTML; [Reflect] attribute DOMString id; [Reflect=class] attribute DOMString className; diff --git a/Libraries/LibWeb/DOM/Event.cpp b/Libraries/LibWeb/DOM/Event.cpp new file mode 100644 index 0000000000..17ad7c06b6 --- /dev/null +++ b/Libraries/LibWeb/DOM/Event.cpp @@ -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; +} + +} diff --git a/Libraries/LibWeb/DOM/Event.h b/Libraries/LibWeb/DOM/Event.h index 5e09c16ddd..2ffd95950d 100644 --- a/Libraries/LibWeb/DOM/Event.h +++ b/Libraries/LibWeb/DOM/Event.h @@ -28,6 +28,7 @@ #include <AK/FlyString.h> #include <LibWeb/Bindings/Wrappable.h> +#include <LibWeb/DOM/EventTarget.h> namespace Web::DOM { @@ -37,6 +38,28 @@ class Event public: 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) { return adopt(*new Event(event_name)); @@ -45,6 +68,86 @@ public: virtual ~Event() { } 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_mouse_event() const { return false; } @@ -52,11 +155,35 @@ public: protected: explicit Event(const FlyString& type) : m_type(type) + , m_initialized(true) { } private: 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(); }; } diff --git a/Libraries/LibWeb/DOM/Event.idl b/Libraries/LibWeb/DOM/Event.idl index 6a33752d50..4075db9059 100644 --- a/Libraries/LibWeb/DOM/Event.idl +++ b/Libraries/LibWeb/DOM/Event.idl @@ -1,5 +1,23 @@ interface Event { 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; } diff --git a/Libraries/LibWeb/DOM/EventDispatcher.cpp b/Libraries/LibWeb/DOM/EventDispatcher.cpp index 173fef805f..94b02acb39 100644 --- a/Libraries/LibWeb/DOM/EventDispatcher.cpp +++ b/Libraries/LibWeb/DOM/EventDispatcher.cpp @@ -24,35 +24,309 @@ * 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 <LibWeb/Bindings/EventTargetWrapper.h> #include <LibWeb/Bindings/EventTargetWrapperFactory.h> #include <LibWeb/Bindings/EventWrapper.h> #include <LibWeb/Bindings/EventWrapperFactory.h> #include <LibWeb/Bindings/ScriptExecutionContext.h> +#include <LibWeb/Bindings/WindowObject.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/Node.h> +#include <LibWeb/DOM/ShadowRoot.h> +#include <LibWeb/DOM/Window.h> +#include <LibWeb/UIEvents/MouseEvent.h> 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 + UNUSED_PARAM(right); + for (;;) { + if (!is<Node>(left)) + return left; + + auto* left_node = downcast<Node>(left); + auto* left_root = left_node->root(); + if (!left_root->is_shadow_root()) + return left; + + // FIXME: If right is a node and left’s 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.event_name != event->type()) + 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_object = function.global_object(); - auto* this_value = Bindings::wrap(global_object, target); - auto* wrapped_event = Bindings::wrap(global_object, *event); + 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& vm = global_object.vm(); - (void)vm.call(function, this_value, wrapped_event); - if (vm.exception()) + 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 target’s 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(); +} + } diff --git a/Libraries/LibWeb/DOM/EventDispatcher.h b/Libraries/LibWeb/DOM/EventDispatcher.h index a50c55cace..c5c380c597 100644 --- a/Libraries/LibWeb/DOM/EventDispatcher.h +++ b/Libraries/LibWeb/DOM/EventDispatcher.h @@ -33,7 +33,11 @@ namespace Web::DOM { class EventDispatcher { 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); }; } diff --git a/Libraries/LibWeb/DOM/EventListener.h b/Libraries/LibWeb/DOM/EventListener.h index 2838d4dcfb..1d5705c2b6 100644 --- a/Libraries/LibWeb/DOM/EventListener.h +++ b/Libraries/LibWeb/DOM/EventListener.h @@ -45,8 +45,28 @@ public: 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: + FlyString m_type; JS::Handle<JS::Function> m_function; + bool m_capture { false }; + bool m_passive { false }; + bool m_once { false }; + bool m_removed { false }; }; } diff --git a/Libraries/LibWeb/DOM/EventTarget.cpp b/Libraries/LibWeb/DOM/EventTarget.cpp index 60f7138b25..5f8ca71c8a 100644 --- a/Libraries/LibWeb/DOM/EventTarget.cpp +++ b/Libraries/LibWeb/DOM/EventTarget.cpp @@ -41,13 +41,29 @@ EventTarget::~EventTarget() 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) }); } void EventTarget::remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener) { 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(); }); } diff --git a/Libraries/LibWeb/DOM/EventTarget.h b/Libraries/LibWeb/DOM/EventTarget.h index 69c4efcb74..082a35fd8a 100644 --- a/Libraries/LibWeb/DOM/EventTarget.h +++ b/Libraries/LibWeb/DOM/EventTarget.h @@ -27,6 +27,7 @@ #pragma once #include <AK/FlyString.h> +#include <AK/Function.h> #include <AK/Noncopyable.h> #include <AK/Vector.h> #include <LibJS/Forward.h> @@ -47,10 +48,14 @@ public: void add_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; Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; } + virtual EventTarget* get_parent(const Event&) { return nullptr; } + struct EventListenerRegistration { FlyString event_name; NonnullRefPtr<EventListener> listener; @@ -58,6 +63,15 @@ public: 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: explicit EventTarget(Bindings::ScriptExecutionContext&); diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 318e8ebf98..f87cc07ac9 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -38,6 +38,7 @@ #include <LibWeb/DOM/EventDispatcher.h> #include <LibWeb/DOM/EventListener.h> #include <LibWeb/DOM/Node.h> +#include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/HTML/HTMLAnchorElement.h> #include <LibWeb/Layout/BlockBox.h> #include <LibWeb/Layout/InitialContainingBlockBox.h> @@ -124,12 +125,9 @@ bool Node::is_link() const return enclosing_link_element(); } -void Node::dispatch_event(NonnullRefPtr<Event> event) +bool Node::dispatch_event(NonnullRefPtr<Event> event) { - 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)); + return EventDispatcher::dispatch(*this, event); } String Node::child_text_content() const @@ -145,17 +143,25 @@ String Node::child_text_content() const return builder.build(); } -const Node* Node::root() const +Node* Node::root() { - const Node* root = this; + Node* root = this; while (root->parent()) root = root->parent(); 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 { - return root() && root()->is_document(); + return shadow_including_root() && shadow_including_root()->is_document(); } 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; } +EventTarget* Node::get_parent(const Event&) +{ + // FIXME: returns the node’s assigned slot, if node is assigned, and node’s parent otherwise. + return parent(); +} + } diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index a415b17d4b..677d823d99 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -60,7 +60,7 @@ public: // ^EventTarget virtual void ref_event_target() final { ref(); } 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 ~Node(); @@ -76,7 +76,11 @@ public: 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_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_shadow_root() const { return false; } + + virtual bool is_node() const final { return true; } virtual bool is_editable() const; @@ -102,7 +106,18 @@ public: virtual bool is_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; Node* parent_node() { return parent(); } @@ -134,6 +149,8 @@ public: void set_document(Badge<Document>, Document&); + virtual EventTarget* get_parent(const Event&) override; + protected: 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() diff --git a/Libraries/LibWeb/DOM/ShadowRoot.cpp b/Libraries/LibWeb/DOM/ShadowRoot.cpp new file mode 100644 index 0000000000..237216379b --- /dev/null +++ b/Libraries/LibWeb/DOM/ShadowRoot.cpp @@ -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(); +} + +} diff --git a/Libraries/LibWeb/DOM/ShadowRoot.h b/Libraries/LibWeb/DOM/ShadowRoot.h new file mode 100644 index 0000000000..878a2ab477 --- /dev/null +++ b/Libraries/LibWeb/DOM/ShadowRoot.h @@ -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() diff --git a/Libraries/LibWeb/DOM/ShadowRoot.idl b/Libraries/LibWeb/DOM/ShadowRoot.idl new file mode 100644 index 0000000000..d7507c4919 --- /dev/null +++ b/Libraries/LibWeb/DOM/ShadowRoot.idl @@ -0,0 +1,6 @@ +interface ShadowRoot : DocumentFragment { + + readonly attribute DOMString mode; + readonly attribute Element host; + +} diff --git a/Libraries/LibWeb/DOM/Window.cpp b/Libraries/LibWeb/DOM/Window.cpp index cdf88d6b3d..e651a524ee 100644 --- a/Libraries/LibWeb/DOM/Window.cpp +++ b/Libraries/LibWeb/DOM/Window.cpp @@ -159,9 +159,9 @@ void Window::did_call_location_reload(Badge<Bindings::LocationObject>) 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&) diff --git a/Libraries/LibWeb/DOM/Window.h b/Libraries/LibWeb/DOM/Window.h index 5cb4c01363..3602b1d5c8 100644 --- a/Libraries/LibWeb/DOM/Window.h +++ b/Libraries/LibWeb/DOM/Window.h @@ -32,6 +32,7 @@ #include <AK/RefPtr.h> #include <LibWeb/Bindings/WindowObject.h> #include <LibWeb/Bindings/Wrappable.h> +#include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/EventTarget.h> namespace Web::DOM { @@ -48,7 +49,7 @@ public: virtual void ref_event_target() override { RefCounted::ref(); } 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; const Document& document() const { return m_document; } @@ -77,6 +78,11 @@ public: 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: explicit Window(Document&); @@ -87,6 +93,11 @@ private: HashMap<int, NonnullRefPtr<Timer>> m_timers; 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() diff --git a/Libraries/LibWeb/DOM/XMLHttpRequest.cpp b/Libraries/LibWeb/DOM/XMLHttpRequest.cpp index eba45e458b..43e8ddeccb 100644 --- a/Libraries/LibWeb/DOM/XMLHttpRequest.cpp +++ b/Libraries/LibWeb/DOM/XMLHttpRequest.cpp @@ -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) diff --git a/Libraries/LibWeb/DOM/XMLHttpRequest.h b/Libraries/LibWeb/DOM/XMLHttpRequest.h index 346898153b..33a6038863 100644 --- a/Libraries/LibWeb/DOM/XMLHttpRequest.h +++ b/Libraries/LibWeb/DOM/XMLHttpRequest.h @@ -65,7 +65,7 @@ public: private: virtual void ref_event_target() override { ref(); } 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; void set_ready_state(ReadyState); diff --git a/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp b/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp index 520d062fef..d4dddffc0b 100644 --- a/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp +++ b/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp @@ -180,18 +180,22 @@ void HTMLDocumentParser::run(const URL& url) script.execute_script(); } - m_document->dispatch_event(DOM::Event::create("DOMContentLoaded")); - - // FIXME: These are not in the right place, they should only fire once subresources are ready. - m_document->dispatch_event(DOM::Event::create("load")); - m_document->window().dispatch_event(DOM::Event::create("load")); + auto content_loaded_event = DOM::Event::create("DOMContentLoaded"); + content_loaded_event->set_bubbles(true); + m_document->dispatch_event(content_loaded_event); 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) { 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->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) diff --git a/Libraries/LibWeb/HighResolutionTime/Performance.cpp b/Libraries/LibWeb/HighResolutionTime/Performance.cpp index 77a905e9fd..cdefa63eff 100644 --- a/Libraries/LibWeb/HighResolutionTime/Performance.cpp +++ b/Libraries/LibWeb/HighResolutionTime/Performance.cpp @@ -60,9 +60,9 @@ void Performance::unref_event_target() 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) diff --git a/Libraries/LibWeb/HighResolutionTime/Performance.h b/Libraries/LibWeb/HighResolutionTime/Performance.h index 6cb39f014e..d5a9effbf5 100644 --- a/Libraries/LibWeb/HighResolutionTime/Performance.h +++ b/Libraries/LibWeb/HighResolutionTime/Performance.h @@ -49,7 +49,7 @@ public: virtual void ref_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; private: diff --git a/Libraries/LibWeb/UIEvents/MouseEvent.cpp b/Libraries/LibWeb/UIEvents/MouseEvent.cpp new file mode 100644 index 0000000000..ba9993be80 --- /dev/null +++ b/Libraries/LibWeb/UIEvents/MouseEvent.cpp @@ -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); + } +} + +} diff --git a/Libraries/LibWeb/UIEvents/MouseEvent.h b/Libraries/LibWeb/UIEvents/MouseEvent.h index a84a88fe4e..ebc84ddecb 100644 --- a/Libraries/LibWeb/UIEvents/MouseEvent.h +++ b/Libraries/LibWeb/UIEvents/MouseEvent.h @@ -26,6 +26,7 @@ #pragma once +#include <AK/TypeCasts.h> #include <LibWeb/UIEvents/UIEvent.h> namespace Web::UIEvents { @@ -39,24 +40,25 @@ public: 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_y() const { return m_offset_y; } protected: - MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y) - : UIEvent(event_name) - , m_offset_x(offset_x) - , m_offset_y(offset_y) - { - } + MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y); private: virtual bool is_mouse_event() const override { return true; } + void set_event_characteristics(); + i32 m_offset_x { 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() |