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 #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include @@ -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(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable); define_property("location", heap().allocate(*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(*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 #include #include #include @@ -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 #include #include +#include #include #include #include @@ -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 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 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 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 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 host() { return m_host; } + const RefPtr 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 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 +#include +#include +#include +#include + +namespace Web::DOM { + +void Event::append_to_path(EventTarget& invocation_target, RefPtr shadow_adjusted_target, RefPtr 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(invocation_target)) { + auto& invocation_target_node = downcast(invocation_target); + if (invocation_target_node.root()->is_shadow_root()) + invocation_target_in_shadow_tree = true; + if (is(invocation_target_node)) { + auto& invocation_target_shadow_root = downcast(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 #include +#include 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>; + + struct PathEntry { + RefPtr invocation_target; + bool invocation_target_in_shadow_tree { false }; + RefPtr shadow_adjusted_target; + RefPtr related_target; + TouchTargetList touch_target_list; + bool root_of_closed_tree { false }; + bool slot_in_closed_tree { false }; + size_t index; + }; + + using Path = Vector; + static NonnullRefPtr 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 target() const { return m_target; } + void set_target(EventTarget* target) { m_target = target; } + + // NOTE: This is intended for the JS bindings. + RefPtr src_target() const { return target(); } + + RefPtr 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 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, RefPtr, 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 m_target; + RefPtr m_related_target; + RefPtr 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 +#include #include #include #include #include #include #include +#include +#include #include #include #include #include +#include +#include +#include +#include namespace Web::DOM { -void EventDispatcher::dispatch(EventTarget& target, NonnullRefPtr 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(); - for (auto& listener : listeners) { - if (listener.event_name != event->type()) - continue; - 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); + // FIXME + UNUSED_PARAM(right); + for (;;) { + if (!is(left)) + return left; - auto& vm = global_object.vm(); - (void)vm.call(function, this_value, wrapped_event); - if (vm.exception()) - vm.clear_exception(); + auto* left_node = downcast(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(left_root); + left = left_shadow_root->host(); } } +// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke +bool EventDispatcher::inner_invoke(Event& event, Vector& 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 current_event; + + if (is(global)) { + auto& bindings_window_global = downcast(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(global)) { + auto& bindings_window_global = downcast(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 target, NonnullRefPtr event, bool legacy_target_override) +{ + event->set_dispatched(true); + RefPtr 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(*target).document(); + } + + RefPtr activation_target; + RefPtr 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(*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(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(clear_targets_struct.value().shadow_adjusted_target.ptr())) { + auto& shadow_adjusted_target_node = downcast(*clear_targets_struct.value().shadow_adjusted_target); + if (is(shadow_adjusted_target_node.root())) + clear_targets = true; + } + + if (!clear_targets && is(clear_targets_struct.value().related_target.ptr())) { + auto& related_target_node = downcast(*clear_targets_struct.value().related_target); + if (is(related_target_node.root())) + clear_targets = true; + } + + if (!clear_targets) { + for (auto touch_target : clear_targets_struct.value().touch_target_list) { + if (is(*touch_target.ptr())) { + auto& touch_target_node = downcast(*touch_target.ptr()); + if (is(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); + static bool dispatch(NonnullRefPtr, NonnullRefPtr, bool legacy_target_override = false); + +private: + static void invoke(Event::PathEntry&, Event&, Event::Phase); + static bool inner_invoke(Event&, Vector&, 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 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 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 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 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 +#include #include #include #include @@ -47,10 +48,14 @@ public: void add_event_listener(const FlyString& event_name, NonnullRefPtr); void remove_event_listener(const FlyString& event_name, NonnullRefPtr); - virtual void dispatch_event(NonnullRefPtr) = 0; + void remove_from_event_listener_list(NonnullRefPtr); + + virtual bool dispatch_event(NonnullRefPtr) = 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 listener; @@ -58,6 +63,15 @@ public: const Vector& listeners() const { return m_listeners; } + virtual bool is_node() const { return false; } + virtual bool is_window() const { return false; } + + Function activation_behaviour; + + // NOTE: These only exist for checkbox and radio input elements. + Function legacy_pre_activation_behaviour; + Function 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 #include #include +#include #include #include #include @@ -124,12 +125,9 @@ bool Node::is_link() const return enclosing_link_element(); } -void Node::dispatch_event(NonnullRefPtr event) +bool Node::dispatch_event(NonnullRefPtr 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(node_root)) + return downcast(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) 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) final; + virtual bool dispatch_event(NonnullRefPtr) 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(this)->root(); + } + + Node* shadow_including_root(); + const Node* shadow_including_root() const + { + return const_cast(this)->shadow_including_root(); + } + bool is_connected() const; Node* parent_node() { return parent(); } @@ -134,6 +149,8 @@ public: void set_document(Badge, 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 +#include + +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(*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 + +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) frame->loader().load(document().url(), FrameLoader::Type::Reload); } -void Window::dispatch_event(NonnullRefPtr event) +bool Window::dispatch_event(NonnullRefPtr 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 #include #include +#include #include 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) override; + virtual bool dispatch_event(NonnullRefPtr) 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> m_timers; NonnullOwnPtr m_performance; + RefPtr 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 event) +bool XMLHttpRequest::dispatch_event(NonnullRefPtr 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) override; + virtual bool dispatch_event(NonnullRefPtr) 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 event) +bool Performance::dispatch_event(NonnullRefPtr 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) override; + virtual bool dispatch_event(NonnullRefPtr) 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 + +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 #include 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()