mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 05:55:13 +00:00
LibWeb: Make event dispatching spec-compliant
Specification: https://dom.spec.whatwg.org/#concept-event-dispatch This also introduces shadow roots due to it being a requirement of the event dispatcher. However, it does not introduce the full shadow DOM, that can be left for future work. This changes some event dispatches which require certain attributes to be initialised to a value.
This commit is contained in:
parent
819f099a8e
commit
e8b3a65581
Notes:
sideshowbarker
2024-07-19 01:18:46 +09:00
Author: https://github.com/Lubrsi Commit: https://github.com/SerenityOS/serenity/commit/e8b3a655819 Pull-request: https://github.com/SerenityOS/serenity/pull/4131 Reviewed-by: https://github.com/awesomekling
32 changed files with 858 additions and 54 deletions
|
@ -35,6 +35,7 @@
|
||||||
#include <LibJS/Runtime/Shape.h>
|
#include <LibJS/Runtime/Shape.h>
|
||||||
#include <LibTextCodec/Decoder.h>
|
#include <LibTextCodec/Decoder.h>
|
||||||
#include <LibWeb/Bindings/DocumentWrapper.h>
|
#include <LibWeb/Bindings/DocumentWrapper.h>
|
||||||
|
#include <LibWeb/Bindings/EventWrapper.h>
|
||||||
#include <LibWeb/Bindings/LocationObject.h>
|
#include <LibWeb/Bindings/LocationObject.h>
|
||||||
#include <LibWeb/Bindings/NavigatorObject.h>
|
#include <LibWeb/Bindings/NavigatorObject.h>
|
||||||
#include <LibWeb/Bindings/NodeWrapperFactory.h>
|
#include <LibWeb/Bindings/NodeWrapperFactory.h>
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
#include <LibWeb/Bindings/XMLHttpRequestConstructor.h>
|
#include <LibWeb/Bindings/XMLHttpRequestConstructor.h>
|
||||||
#include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
|
#include <LibWeb/Bindings/XMLHttpRequestPrototype.h>
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
|
#include <LibWeb/DOM/Event.h>
|
||||||
#include <LibWeb/DOM/Window.h>
|
#include <LibWeb/DOM/Window.h>
|
||||||
#include <LibWeb/Origin.h>
|
#include <LibWeb/Origin.h>
|
||||||
|
|
||||||
|
@ -75,6 +77,9 @@ void WindowObject::initialize()
|
||||||
define_native_function("atob", atob, 1);
|
define_native_function("atob", atob, 1);
|
||||||
define_native_function("btoa", btoa, 1);
|
define_native_function("btoa", btoa, 1);
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
define_native_property("event", event_getter, nullptr, JS::Attribute::Enumerable);
|
||||||
|
|
||||||
define_property("navigator", heap().allocate<NavigatorObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
|
define_property("navigator", heap().allocate<NavigatorObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
|
||||||
define_property("location", heap().allocate<LocationObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
|
define_property("location", heap().allocate<LocationObject>(*this, *this), JS::Attribute::Enumerable | JS::Attribute::Configurable);
|
||||||
|
|
||||||
|
@ -337,5 +342,15 @@ JS_DEFINE_NATIVE_GETTER(WindowObject::performance_getter)
|
||||||
return wrap(global_object, impl->performance());
|
return wrap(global_object, impl->performance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS_DEFINE_NATIVE_GETTER(WindowObject::event_getter)
|
||||||
|
{
|
||||||
|
auto* impl = impl_from(vm, global_object);
|
||||||
|
if (!impl)
|
||||||
|
return {};
|
||||||
|
if (!impl->current_event())
|
||||||
|
return JS::js_undefined();
|
||||||
|
return wrap(global_object, const_cast<DOM::Event&>(*impl->current_event()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/TypeCasts.h>
|
||||||
#include <AK/Weakable.h>
|
#include <AK/Weakable.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibWeb/Forward.h>
|
#include <LibWeb/Forward.h>
|
||||||
|
@ -58,6 +59,8 @@ private:
|
||||||
|
|
||||||
JS_DECLARE_NATIVE_GETTER(performance_getter);
|
JS_DECLARE_NATIVE_GETTER(performance_getter);
|
||||||
|
|
||||||
|
JS_DECLARE_NATIVE_GETTER(event_getter);
|
||||||
|
|
||||||
JS_DECLARE_NATIVE_FUNCTION(alert);
|
JS_DECLARE_NATIVE_FUNCTION(alert);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(confirm);
|
JS_DECLARE_NATIVE_FUNCTION(confirm);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(set_interval);
|
JS_DECLARE_NATIVE_FUNCTION(set_interval);
|
||||||
|
@ -77,3 +80,7 @@ private:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AK_BEGIN_TYPE_TRAITS(Web::Bindings::WindowObject)
|
||||||
|
static bool is_type(const JS::GlobalObject& global) { return String(global.class_name()) == "WindowObject"; }
|
||||||
|
AK_END_TYPE_TRAITS()
|
||||||
|
|
|
@ -35,12 +35,14 @@ set(SOURCES
|
||||||
DOM/DOMImplementation.cpp
|
DOM/DOMImplementation.cpp
|
||||||
DOM/Element.cpp
|
DOM/Element.cpp
|
||||||
DOM/ElementFactory.cpp
|
DOM/ElementFactory.cpp
|
||||||
|
DOM/Event.cpp
|
||||||
DOM/EventDispatcher.cpp
|
DOM/EventDispatcher.cpp
|
||||||
DOM/EventListener.cpp
|
DOM/EventListener.cpp
|
||||||
DOM/EventTarget.cpp
|
DOM/EventTarget.cpp
|
||||||
DOM/Node.cpp
|
DOM/Node.cpp
|
||||||
DOM/ParentNode.cpp
|
DOM/ParentNode.cpp
|
||||||
DOM/Position.cpp
|
DOM/Position.cpp
|
||||||
|
DOM/ShadowRoot.cpp
|
||||||
DOM/TagNames.cpp
|
DOM/TagNames.cpp
|
||||||
DOM/Text.cpp
|
DOM/Text.cpp
|
||||||
DOM/Text.idl
|
DOM/Text.idl
|
||||||
|
@ -185,6 +187,7 @@ set(SOURCES
|
||||||
SVG/SVGSVGElement.cpp
|
SVG/SVGSVGElement.cpp
|
||||||
SVG/TagNames.cpp
|
SVG/TagNames.cpp
|
||||||
StylePropertiesModel.cpp
|
StylePropertiesModel.cpp
|
||||||
|
UIEvents/MouseEvent.cpp
|
||||||
URLEncoder.cpp
|
URLEncoder.cpp
|
||||||
WebContentClient.cpp
|
WebContentClient.cpp
|
||||||
)
|
)
|
||||||
|
@ -237,6 +240,7 @@ libweb_js_wrapper(DOM/DOMImplementation)
|
||||||
libweb_js_wrapper(DOM/Element)
|
libweb_js_wrapper(DOM/Element)
|
||||||
libweb_js_wrapper(DOM/Event)
|
libweb_js_wrapper(DOM/Event)
|
||||||
libweb_js_wrapper(DOM/EventTarget)
|
libweb_js_wrapper(DOM/EventTarget)
|
||||||
|
libweb_js_wrapper(DOM/ShadowRoot)
|
||||||
libweb_js_wrapper(DOM/Node)
|
libweb_js_wrapper(DOM/Node)
|
||||||
libweb_js_wrapper(DOM/Text)
|
libweb_js_wrapper(DOM/Text)
|
||||||
libweb_js_wrapper(HTML/CanvasRenderingContext2D)
|
libweb_js_wrapper(HTML/CanvasRenderingContext2D)
|
||||||
|
|
|
@ -354,8 +354,6 @@ static bool should_emit_wrapper_factory(const IDL::Interface& interface)
|
||||||
return false;
|
return false;
|
||||||
if (interface.name.ends_with("Element"))
|
if (interface.name.ends_with("Element"))
|
||||||
return false;
|
return false;
|
||||||
if (interface.name.ends_with("Event"))
|
|
||||||
return false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,6 +508,7 @@ void generate_implementation(const IDL::Interface& interface)
|
||||||
#include <LibWeb/Bindings/DocumentFragmentWrapper.h>
|
#include <LibWeb/Bindings/DocumentFragmentWrapper.h>
|
||||||
#include <LibWeb/Bindings/DocumentTypeWrapper.h>
|
#include <LibWeb/Bindings/DocumentTypeWrapper.h>
|
||||||
#include <LibWeb/Bindings/DocumentWrapper.h>
|
#include <LibWeb/Bindings/DocumentWrapper.h>
|
||||||
|
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
|
||||||
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
|
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
|
||||||
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
|
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
|
||||||
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
|
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
|
||||||
|
@ -733,7 +732,7 @@ static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_ob
|
||||||
|
|
||||||
return new_array;
|
return new_array;
|
||||||
)~~~");
|
)~~~");
|
||||||
} else if (return_type.name == "long" || return_type.name == "double" || return_type.name == "boolean") {
|
} else if (return_type.name == "long" || return_type.name == "double" || return_type.name == "boolean" || return_type.name == "short") {
|
||||||
scoped_generator.append(R"~~~(
|
scoped_generator.append(R"~~~(
|
||||||
return JS::Value(retval);
|
return JS::Value(retval);
|
||||||
)~~~");
|
)~~~");
|
||||||
|
|
|
@ -44,6 +44,7 @@ const NonnullRefPtr<Document> DOMImplementation::create_htmldocument(const Strin
|
||||||
auto html_document = Document::create();
|
auto html_document = Document::create();
|
||||||
|
|
||||||
html_document->set_content_type("text/html");
|
html_document->set_content_type("text/html");
|
||||||
|
html_document->set_ready_for_post_load_tasks(true);
|
||||||
|
|
||||||
auto doctype = adopt(*new DocumentType(html_document));
|
auto doctype = adopt(*new DocumentType(html_document));
|
||||||
doctype->set_name("html");
|
doctype->set_name("html");
|
||||||
|
|
|
@ -621,4 +621,18 @@ const Page* Document::page() const
|
||||||
return m_frame ? m_frame->page() : nullptr;
|
return m_frame ? m_frame->page() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventTarget* Document::get_parent(const Event& event)
|
||||||
|
{
|
||||||
|
if (event.type() == "load")
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &window();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Document::completely_finish_loading()
|
||||||
|
{
|
||||||
|
// FIXME: This needs to handle iframes.
|
||||||
|
dispatch_event(DOM::Event::create("load"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,7 +207,14 @@ public:
|
||||||
const String& charset() const { return encoding(); }
|
const String& charset() const { return encoding(); }
|
||||||
const String& input_encoding() const { return encoding(); }
|
const String& input_encoding() const { return encoding(); }
|
||||||
|
|
||||||
const NonnullRefPtr<DOMImplementation> implementation() { return m_implementation; }
|
bool ready_for_post_load_tasks() const { return m_ready_for_post_load_tasks; }
|
||||||
|
void set_ready_for_post_load_tasks(bool ready) { m_ready_for_post_load_tasks = ready; }
|
||||||
|
|
||||||
|
void completely_finish_loading();
|
||||||
|
|
||||||
|
const NonnullRefPtr<DOMImplementation> implementation() const { return m_implementation; }
|
||||||
|
|
||||||
|
virtual EventTarget* get_parent(const Event&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Document(const URL&);
|
explicit Document(const URL&);
|
||||||
|
@ -272,6 +279,8 @@ private:
|
||||||
String m_content_type { "application/xml" };
|
String m_content_type { "application/xml" };
|
||||||
String m_encoding { "UTF-8" };
|
String m_encoding { "UTF-8" };
|
||||||
|
|
||||||
|
bool m_ready_for_post_load_tasks { false };
|
||||||
|
|
||||||
NonnullRefPtr<DOMImplementation> m_implementation;
|
NonnullRefPtr<DOMImplementation> m_implementation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,8 @@ public:
|
||||||
|
|
||||||
virtual FlyString node_name() const override { return "#document-fragment"; }
|
virtual FlyString node_name() const override { return "#document-fragment"; }
|
||||||
|
|
||||||
Element& host() { return *m_host; }
|
RefPtr<Element> host() { return m_host; }
|
||||||
const Element& host() const { return *m_host; }
|
const RefPtr<Element> host() const { return m_host; }
|
||||||
|
|
||||||
void set_host(Element& host) { m_host = host; }
|
void set_host(Element& host) { m_host = host; }
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ public:
|
||||||
const CSS::StyleProperties* resolved_style() const { return m_resolved_style.ptr(); }
|
const CSS::StyleProperties* resolved_style() const { return m_resolved_style.ptr(); }
|
||||||
NonnullRefPtr<CSS::StyleProperties> computed_style();
|
NonnullRefPtr<CSS::StyleProperties> computed_style();
|
||||||
|
|
||||||
|
// FIXME: innerHTML also appears on shadow roots. https://w3c.github.io/DOM-Parsing/#dom-innerhtml
|
||||||
String inner_html() const;
|
String inner_html() const;
|
||||||
void set_inner_html(StringView);
|
void set_inner_html(StringView);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface Element : Node {
|
||||||
Element? querySelector(DOMString selectors);
|
Element? querySelector(DOMString selectors);
|
||||||
ArrayFromVector querySelectorAll(DOMString selectors);
|
ArrayFromVector querySelectorAll(DOMString selectors);
|
||||||
|
|
||||||
attribute DOMString innerHTML;
|
[LegacyNullToEmptyString] attribute DOMString innerHTML;
|
||||||
[Reflect] attribute DOMString id;
|
[Reflect] attribute DOMString id;
|
||||||
[Reflect=class] attribute DOMString className;
|
[Reflect=class] attribute DOMString className;
|
||||||
|
|
||||||
|
|
59
Libraries/LibWeb/DOM/Event.cpp
Normal file
59
Libraries/LibWeb/DOM/Event.cpp
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, the SerenityOS developers.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/Assertions.h>
|
||||||
|
#include <AK/TypeCasts.h>
|
||||||
|
#include <LibWeb/DOM/Event.h>
|
||||||
|
#include <LibWeb/DOM/Node.h>
|
||||||
|
#include <LibWeb/DOM/ShadowRoot.h>
|
||||||
|
|
||||||
|
namespace Web::DOM {
|
||||||
|
|
||||||
|
void Event::append_to_path(EventTarget& invocation_target, RefPtr<EventTarget> shadow_adjusted_target, RefPtr<EventTarget> related_target, TouchTargetList& touch_targets, bool slot_in_closed_tree)
|
||||||
|
{
|
||||||
|
bool invocation_target_in_shadow_tree = false;
|
||||||
|
bool root_of_closed_tree = false;
|
||||||
|
|
||||||
|
if (is<Node>(invocation_target)) {
|
||||||
|
auto& invocation_target_node = downcast<Node>(invocation_target);
|
||||||
|
if (invocation_target_node.root()->is_shadow_root())
|
||||||
|
invocation_target_in_shadow_tree = true;
|
||||||
|
if (is<ShadowRoot>(invocation_target_node)) {
|
||||||
|
auto& invocation_target_shadow_root = downcast<ShadowRoot>(invocation_target_node);
|
||||||
|
root_of_closed_tree = invocation_target_shadow_root.closed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_path.append({ invocation_target, invocation_target_in_shadow_tree, shadow_adjusted_target, related_target, touch_targets, root_of_closed_tree, slot_in_closed_tree, m_path.size() });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Event::set_cancelled_flag()
|
||||||
|
{
|
||||||
|
if (m_cancelable && !m_in_passive_listener)
|
||||||
|
m_cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include <AK/FlyString.h>
|
#include <AK/FlyString.h>
|
||||||
#include <LibWeb/Bindings/Wrappable.h>
|
#include <LibWeb/Bindings/Wrappable.h>
|
||||||
|
#include <LibWeb/DOM/EventTarget.h>
|
||||||
|
|
||||||
namespace Web::DOM {
|
namespace Web::DOM {
|
||||||
|
|
||||||
|
@ -37,6 +38,28 @@ class Event
|
||||||
public:
|
public:
|
||||||
using WrapperType = Bindings::EventWrapper;
|
using WrapperType = Bindings::EventWrapper;
|
||||||
|
|
||||||
|
enum Phase : u16 {
|
||||||
|
None = 0,
|
||||||
|
CapturingPhase = 1,
|
||||||
|
AtTarget = 2,
|
||||||
|
BubblingPhase = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
using TouchTargetList = Vector<RefPtr<EventTarget>>;
|
||||||
|
|
||||||
|
struct PathEntry {
|
||||||
|
RefPtr<EventTarget> invocation_target;
|
||||||
|
bool invocation_target_in_shadow_tree { false };
|
||||||
|
RefPtr<EventTarget> shadow_adjusted_target;
|
||||||
|
RefPtr<EventTarget> related_target;
|
||||||
|
TouchTargetList touch_target_list;
|
||||||
|
bool root_of_closed_tree { false };
|
||||||
|
bool slot_in_closed_tree { false };
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Path = Vector<PathEntry>;
|
||||||
|
|
||||||
static NonnullRefPtr<Event> create(const FlyString& event_name)
|
static NonnullRefPtr<Event> create(const FlyString& event_name)
|
||||||
{
|
{
|
||||||
return adopt(*new Event(event_name));
|
return adopt(*new Event(event_name));
|
||||||
|
@ -45,6 +68,86 @@ public:
|
||||||
virtual ~Event() { }
|
virtual ~Event() { }
|
||||||
|
|
||||||
const FlyString& type() const { return m_type; }
|
const FlyString& type() const { return m_type; }
|
||||||
|
void set_type(const StringView& type) { m_type = type; }
|
||||||
|
|
||||||
|
RefPtr<EventTarget> target() const { return m_target; }
|
||||||
|
void set_target(EventTarget* target) { m_target = target; }
|
||||||
|
|
||||||
|
// NOTE: This is intended for the JS bindings.
|
||||||
|
RefPtr<EventTarget> src_target() const { return target(); }
|
||||||
|
|
||||||
|
RefPtr<EventTarget> related_target() const { return m_related_target; }
|
||||||
|
void set_related_target(EventTarget* related_target) { m_related_target = related_target; }
|
||||||
|
|
||||||
|
bool should_stop_propagation() const { return m_stop_propagation; }
|
||||||
|
void set_stop_propagation(bool stop_propagation) { m_stop_propagation = stop_propagation; }
|
||||||
|
|
||||||
|
bool should_stop_immediate_propagation() const { return m_stop_immediate_propagation; }
|
||||||
|
void set_stop_immediate_propagation(bool stop_immediate_propagation) { m_stop_immediate_propagation = stop_immediate_propagation; }
|
||||||
|
|
||||||
|
bool cancelled() const { return m_cancelled; }
|
||||||
|
void set_cancelled(bool cancelled) { m_cancelled = cancelled; }
|
||||||
|
|
||||||
|
bool in_passive_listener() const { return m_in_passive_listener; }
|
||||||
|
void set_in_passive_listener(bool in_passive_listener) { m_in_passive_listener = in_passive_listener; }
|
||||||
|
|
||||||
|
bool composed() const { return m_composed; }
|
||||||
|
void set_composed(bool composed) { m_composed = composed; }
|
||||||
|
|
||||||
|
bool initialized() const { return m_initialized; }
|
||||||
|
void set_initialized(bool initialized) { m_initialized = initialized; }
|
||||||
|
|
||||||
|
bool dispatched() const { return m_dispatch; }
|
||||||
|
void set_dispatched(bool dispatched) { m_dispatch = dispatched; }
|
||||||
|
|
||||||
|
void prevent_default() { set_cancelled_flag(); }
|
||||||
|
bool default_prevented() const { return cancelled(); }
|
||||||
|
|
||||||
|
u16 event_phase() const { return m_phase; }
|
||||||
|
void set_phase(Phase phase) { m_phase = phase; }
|
||||||
|
|
||||||
|
RefPtr<EventTarget> current_target() const { return m_current_target; }
|
||||||
|
void set_current_target(EventTarget* current_target) { m_current_target = current_target; }
|
||||||
|
|
||||||
|
bool return_value() const { return !m_cancelled; }
|
||||||
|
void set_return_value(bool return_value)
|
||||||
|
{
|
||||||
|
if (!return_value)
|
||||||
|
set_cancelled_flag();
|
||||||
|
}
|
||||||
|
|
||||||
|
void append_to_path(EventTarget&, RefPtr<EventTarget>, RefPtr<EventTarget>, TouchTargetList&, bool);
|
||||||
|
Path& path() { return m_path; }
|
||||||
|
const Path& path() const { return m_path; }
|
||||||
|
void clear_path() { m_path.clear(); }
|
||||||
|
|
||||||
|
void set_touch_target_list(TouchTargetList& touch_target_list) { m_touch_target_list = touch_target_list; }
|
||||||
|
TouchTargetList& touch_target_list() { return m_touch_target_list; };
|
||||||
|
void clear_touch_target_list() { m_touch_target_list.clear(); }
|
||||||
|
|
||||||
|
bool bubbles() const { return m_bubbles; }
|
||||||
|
void set_bubbles(bool bubbles) { m_bubbles = bubbles; }
|
||||||
|
|
||||||
|
bool cancelable() const { return m_cancelable; }
|
||||||
|
void set_cancelable(bool cancelable) { m_cancelable = cancelable; }
|
||||||
|
|
||||||
|
bool is_trusted() const { return m_is_trusted; }
|
||||||
|
void set_is_trusted(bool is_trusted) { m_is_trusted = is_trusted; }
|
||||||
|
|
||||||
|
void stop_propagation() { m_stop_propagation = true; }
|
||||||
|
|
||||||
|
bool cancel_bubble() const { return m_stop_propagation; }
|
||||||
|
void set_cancel_bubble(bool cancel_bubble)
|
||||||
|
{
|
||||||
|
if (cancel_bubble)
|
||||||
|
m_stop_propagation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_immediate_propagation()
|
||||||
|
{
|
||||||
|
m_stop_propagation = true;
|
||||||
|
m_stop_immediate_propagation = true;
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool is_ui_event() const { return false; }
|
virtual bool is_ui_event() const { return false; }
|
||||||
virtual bool is_mouse_event() const { return false; }
|
virtual bool is_mouse_event() const { return false; }
|
||||||
|
@ -52,11 +155,35 @@ public:
|
||||||
protected:
|
protected:
|
||||||
explicit Event(const FlyString& type)
|
explicit Event(const FlyString& type)
|
||||||
: m_type(type)
|
: m_type(type)
|
||||||
|
, m_initialized(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FlyString m_type;
|
FlyString m_type;
|
||||||
|
RefPtr<EventTarget> m_target;
|
||||||
|
RefPtr<EventTarget> m_related_target;
|
||||||
|
RefPtr<EventTarget> m_current_target;
|
||||||
|
|
||||||
|
Phase m_phase { None };
|
||||||
|
|
||||||
|
bool m_bubbles { false };
|
||||||
|
bool m_cancelable { false };
|
||||||
|
|
||||||
|
bool m_stop_propagation { false };
|
||||||
|
bool m_stop_immediate_propagation { false };
|
||||||
|
bool m_cancelled { false };
|
||||||
|
bool m_in_passive_listener { false };
|
||||||
|
bool m_composed { false };
|
||||||
|
bool m_initialized { false };
|
||||||
|
bool m_dispatch { false };
|
||||||
|
|
||||||
|
bool m_is_trusted { true };
|
||||||
|
|
||||||
|
Path m_path;
|
||||||
|
TouchTargetList m_touch_target_list;
|
||||||
|
|
||||||
|
void set_cancelled_flag();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
interface Event {
|
interface Event {
|
||||||
|
|
||||||
readonly attribute DOMString type;
|
readonly attribute DOMString type;
|
||||||
|
readonly attribute EventTarget? target;
|
||||||
|
readonly attribute EventTarget? srcTarget;
|
||||||
|
readonly attribute EventTarget? currentTarget;
|
||||||
|
|
||||||
|
readonly attribute unsigned short eventPhase;
|
||||||
|
|
||||||
|
void stopPropagation();
|
||||||
|
attribute boolean cancelBubble;
|
||||||
|
void stopImmediatePropagation();
|
||||||
|
|
||||||
|
readonly attribute boolean bubbles;
|
||||||
|
readonly attribute boolean cancelable;
|
||||||
|
attribute boolean returnValue;
|
||||||
|
void preventDefault();
|
||||||
|
readonly attribute boolean defaultPrevented;
|
||||||
|
readonly attribute boolean composed;
|
||||||
|
|
||||||
|
readonly attribute boolean isTrusted;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,35 +24,309 @@
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Assertions.h>
|
||||||
|
#include <AK/TypeCasts.h>
|
||||||
#include <LibJS/Runtime/Function.h>
|
#include <LibJS/Runtime/Function.h>
|
||||||
#include <LibWeb/Bindings/EventTargetWrapper.h>
|
#include <LibWeb/Bindings/EventTargetWrapper.h>
|
||||||
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
|
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
|
||||||
#include <LibWeb/Bindings/EventWrapper.h>
|
#include <LibWeb/Bindings/EventWrapper.h>
|
||||||
#include <LibWeb/Bindings/EventWrapperFactory.h>
|
#include <LibWeb/Bindings/EventWrapperFactory.h>
|
||||||
#include <LibWeb/Bindings/ScriptExecutionContext.h>
|
#include <LibWeb/Bindings/ScriptExecutionContext.h>
|
||||||
|
#include <LibWeb/Bindings/WindowObject.h>
|
||||||
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/DOM/Event.h>
|
#include <LibWeb/DOM/Event.h>
|
||||||
#include <LibWeb/DOM/EventDispatcher.h>
|
#include <LibWeb/DOM/EventDispatcher.h>
|
||||||
#include <LibWeb/DOM/EventListener.h>
|
#include <LibWeb/DOM/EventListener.h>
|
||||||
#include <LibWeb/DOM/EventTarget.h>
|
#include <LibWeb/DOM/EventTarget.h>
|
||||||
|
#include <LibWeb/DOM/Node.h>
|
||||||
|
#include <LibWeb/DOM/ShadowRoot.h>
|
||||||
|
#include <LibWeb/DOM/Window.h>
|
||||||
|
#include <LibWeb/UIEvents/MouseEvent.h>
|
||||||
|
|
||||||
namespace Web::DOM {
|
namespace Web::DOM {
|
||||||
|
|
||||||
void EventDispatcher::dispatch(EventTarget& target, NonnullRefPtr<Event> event)
|
// FIXME: This shouldn't be here, as retargeting is not only used by the event dispatcher.
|
||||||
|
// When moving this function, it needs to be generalized. https://dom.spec.whatwg.org/#retarget
|
||||||
|
static EventTarget* retarget(EventTarget* left, EventTarget* right)
|
||||||
{
|
{
|
||||||
auto listeners = target.listeners();
|
// FIXME
|
||||||
for (auto& listener : listeners) {
|
UNUSED_PARAM(right);
|
||||||
if (listener.event_name != event->type())
|
for (;;) {
|
||||||
continue;
|
if (!is<Node>(left))
|
||||||
auto& function = listener.listener->function();
|
return left;
|
||||||
auto& global_object = function.global_object();
|
|
||||||
auto* this_value = Bindings::wrap(global_object, target);
|
|
||||||
auto* wrapped_event = Bindings::wrap(global_object, *event);
|
|
||||||
|
|
||||||
auto& vm = global_object.vm();
|
auto* left_node = downcast<Node>(left);
|
||||||
(void)vm.call(function, this_value, wrapped_event);
|
auto* left_root = left_node->root();
|
||||||
if (vm.exception())
|
if (!left_root->is_shadow_root())
|
||||||
vm.clear_exception();
|
return left;
|
||||||
|
|
||||||
|
// FIXME: If right is a node and 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.listener->removed())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (event.type() != listener.listener->type())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
|
||||||
|
if (phase == Event::Phase::CapturingPhase && !listener.listener->capture())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (phase == Event::Phase::BubblingPhase && listener.listener->capture())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (listener.listener->once())
|
||||||
|
event.current_target()->remove_from_event_listener_list(listener.listener);
|
||||||
|
|
||||||
|
auto& function = listener.listener->function();
|
||||||
|
auto& global = function.global_object();
|
||||||
|
|
||||||
|
RefPtr<Event> current_event;
|
||||||
|
|
||||||
|
if (is<Bindings::WindowObject>(global)) {
|
||||||
|
auto& bindings_window_global = downcast<Bindings::WindowObject>(global);
|
||||||
|
auto& window_impl = bindings_window_global.impl();
|
||||||
|
current_event = window_impl.current_event();
|
||||||
|
if (!invocation_target_in_shadow_tree)
|
||||||
|
window_impl.set_current_event(&event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener.listener->passive())
|
||||||
|
event.set_in_passive_listener(true);
|
||||||
|
|
||||||
|
auto* this_value = Bindings::wrap(global, *event.current_target());
|
||||||
|
auto* wrapped_event = Bindings::wrap(global, event);
|
||||||
|
auto& vm = global.vm();
|
||||||
|
(void)vm.call(listener.listener->function(), this_value, wrapped_event);
|
||||||
|
if (vm.exception()) {
|
||||||
|
vm.clear_exception();
|
||||||
|
// FIXME: Set legacyOutputDidListenersThrowFlag if given. (Only used by IndexedDB currently)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.set_in_passive_listener(false);
|
||||||
|
if (is<Bindings::WindowObject>(global)) {
|
||||||
|
auto& bindings_window_global = downcast<Bindings::WindowObject>(global);
|
||||||
|
auto& window_impl = bindings_window_global.impl();
|
||||||
|
window_impl.set_current_event(current_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.should_stop_immediate_propagation())
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dom.spec.whatwg.org/#concept-event-listener-invoke
|
||||||
|
void EventDispatcher::invoke(Event::PathEntry& struct_, Event& event, Event::Phase phase)
|
||||||
|
{
|
||||||
|
auto last_valid_shadow_adjusted_target = event.path().last_matching([&struct_](auto& entry) {
|
||||||
|
return entry.index <= struct_.index && !entry.shadow_adjusted_target.is_null();
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT(last_valid_shadow_adjusted_target.has_value());
|
||||||
|
|
||||||
|
event.set_target(last_valid_shadow_adjusted_target.value().shadow_adjusted_target);
|
||||||
|
event.set_related_target(struct_.related_target);
|
||||||
|
event.set_touch_target_list(struct_.touch_target_list);
|
||||||
|
|
||||||
|
if (event.should_stop_propagation())
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.set_current_target(struct_.invocation_target);
|
||||||
|
|
||||||
|
// NOTE: This is an intentional copy. Any event listeners added after this point will not be invoked.
|
||||||
|
auto listeners = event.current_target()->listeners();
|
||||||
|
bool invocation_target_in_shadow_tree = struct_.invocation_target_in_shadow_tree;
|
||||||
|
|
||||||
|
bool found = inner_invoke(event, listeners, phase, invocation_target_in_shadow_tree);
|
||||||
|
|
||||||
|
if (!found && event.is_trusted()) {
|
||||||
|
auto original_event_type = event.type();
|
||||||
|
|
||||||
|
if (event.type() == "animationend")
|
||||||
|
event.set_type("webkitAnimationEnd");
|
||||||
|
else if (event.type() == "animationiteration")
|
||||||
|
event.set_type("webkitAnimationIteration");
|
||||||
|
else if (event.type() == "animationstart")
|
||||||
|
event.set_type("webkitAnimationStart");
|
||||||
|
else if (event.type() == "transitionend")
|
||||||
|
event.set_type("webkitTransitionEnd");
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
inner_invoke(event, listeners, phase, invocation_target_in_shadow_tree);
|
||||||
|
event.set_type(original_event_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://dom.spec.whatwg.org/#concept-event-dispatch
|
||||||
|
bool EventDispatcher::dispatch(NonnullRefPtr<EventTarget> target, NonnullRefPtr<Event> event, bool legacy_target_override)
|
||||||
|
{
|
||||||
|
event->set_dispatched(true);
|
||||||
|
RefPtr<EventTarget> target_override;
|
||||||
|
|
||||||
|
if (!legacy_target_override) {
|
||||||
|
target_override = target;
|
||||||
|
} else {
|
||||||
|
// NOTE: This can be done because legacy_target_override is only set for events targeted at Window.
|
||||||
|
target_override = downcast<Window>(*target).document();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<EventTarget> activation_target;
|
||||||
|
RefPtr<EventTarget> related_target = retarget(event->related_target(), target);
|
||||||
|
|
||||||
|
bool clear_targets = false;
|
||||||
|
|
||||||
|
if (related_target != target || event->related_target() == target) {
|
||||||
|
Event::TouchTargetList touch_targets;
|
||||||
|
|
||||||
|
for (auto& touch_target : event->touch_target_list()) {
|
||||||
|
touch_targets.append(retarget(touch_target, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
event->append_to_path(*target, target_override, related_target, touch_targets, false);
|
||||||
|
|
||||||
|
bool is_activation_event = is<UIEvents::MouseEvent>(*event) && event->type() == "click";
|
||||||
|
|
||||||
|
if (is_activation_event && target->activation_behaviour)
|
||||||
|
activation_target = target;
|
||||||
|
|
||||||
|
// FIXME: Let slottable be target, if target is a slottable and is assigned, and null otherwise.
|
||||||
|
|
||||||
|
bool slot_in_closed_tree = false;
|
||||||
|
auto* parent = target->get_parent(event);
|
||||||
|
|
||||||
|
while (parent) {
|
||||||
|
// FIXME: If slottable is non-null:
|
||||||
|
|
||||||
|
// FIXME: If parent is a slottable and is assigned, then set slottable to parent.
|
||||||
|
|
||||||
|
related_target = retarget(event->related_target(), parent);
|
||||||
|
touch_targets.clear();
|
||||||
|
|
||||||
|
for (auto& touch_target : event->touch_target_list()) {
|
||||||
|
touch_targets.append(retarget(touch_target, parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: or parent is a node and 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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,11 @@ namespace Web::DOM {
|
||||||
|
|
||||||
class EventDispatcher {
|
class EventDispatcher {
|
||||||
public:
|
public:
|
||||||
static void dispatch(EventTarget&, NonnullRefPtr<Event>);
|
static bool dispatch(NonnullRefPtr<EventTarget>, NonnullRefPtr<Event>, bool legacy_target_override = false);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void invoke(Event::PathEntry&, Event&, Event::Phase);
|
||||||
|
static bool inner_invoke(Event&, Vector<EventTarget::EventListenerRegistration>&, Event::Phase, bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,28 @@ public:
|
||||||
|
|
||||||
JS::Function& function();
|
JS::Function& function();
|
||||||
|
|
||||||
|
const FlyString& type() const { return m_type; }
|
||||||
|
void set_type(const FlyString& type) { m_type = type; }
|
||||||
|
|
||||||
|
bool capture() const { return m_capture; }
|
||||||
|
void set_capture(bool capture) { m_capture = capture; }
|
||||||
|
|
||||||
|
bool passive() const { return m_passive; }
|
||||||
|
void set_passive(bool passive) { m_capture = passive; }
|
||||||
|
|
||||||
|
bool once() const { return m_once; }
|
||||||
|
void set_once(bool once) { m_once = once; }
|
||||||
|
|
||||||
|
bool removed() const { return m_removed; }
|
||||||
|
void set_removed(bool removed) { m_removed = removed; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
FlyString m_type;
|
||||||
JS::Handle<JS::Function> m_function;
|
JS::Handle<JS::Function> m_function;
|
||||||
|
bool m_capture { false };
|
||||||
|
bool m_passive { false };
|
||||||
|
bool m_once { false };
|
||||||
|
bool m_removed { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,13 +41,29 @@ EventTarget::~EventTarget()
|
||||||
|
|
||||||
void EventTarget::add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
|
void EventTarget::add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
|
||||||
{
|
{
|
||||||
|
auto existing_listener = m_listeners.first_matching([&](auto& entry) {
|
||||||
|
return entry.listener->type() == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
|
||||||
|
});
|
||||||
|
if (existing_listener.has_value())
|
||||||
|
return;
|
||||||
|
listener->set_type(event_name);
|
||||||
m_listeners.append({ event_name, move(listener) });
|
m_listeners.append({ event_name, move(listener) });
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventTarget::remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
|
void EventTarget::remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener> listener)
|
||||||
{
|
{
|
||||||
m_listeners.remove_first_matching([&](auto& entry) {
|
m_listeners.remove_first_matching([&](auto& entry) {
|
||||||
return entry.event_name == event_name && &entry.listener->function() == &listener->function();
|
auto matches = entry.event_name == event_name && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
|
||||||
|
if (matches)
|
||||||
|
entry.listener->set_removed(true);
|
||||||
|
return matches;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventTarget::remove_from_event_listener_list(NonnullRefPtr<EventListener> listener)
|
||||||
|
{
|
||||||
|
m_listeners.remove_first_matching([&](auto& entry) {
|
||||||
|
return entry.listener->type() == listener->type() && &entry.listener->function() == &listener->function() && entry.listener->capture() == listener->capture();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/FlyString.h>
|
#include <AK/FlyString.h>
|
||||||
|
#include <AK/Function.h>
|
||||||
#include <AK/Noncopyable.h>
|
#include <AK/Noncopyable.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
|
@ -47,10 +48,14 @@ public:
|
||||||
void add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
|
void add_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
|
||||||
void remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
|
void remove_event_listener(const FlyString& event_name, NonnullRefPtr<EventListener>);
|
||||||
|
|
||||||
virtual void dispatch_event(NonnullRefPtr<Event>) = 0;
|
void remove_from_event_listener_list(NonnullRefPtr<EventListener>);
|
||||||
|
|
||||||
|
virtual bool dispatch_event(NonnullRefPtr<Event>) = 0;
|
||||||
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) = 0;
|
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) = 0;
|
||||||
Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; }
|
Bindings::ScriptExecutionContext* script_execution_context() { return m_script_execution_context; }
|
||||||
|
|
||||||
|
virtual EventTarget* get_parent(const Event&) { return nullptr; }
|
||||||
|
|
||||||
struct EventListenerRegistration {
|
struct EventListenerRegistration {
|
||||||
FlyString event_name;
|
FlyString event_name;
|
||||||
NonnullRefPtr<EventListener> listener;
|
NonnullRefPtr<EventListener> listener;
|
||||||
|
@ -58,6 +63,15 @@ public:
|
||||||
|
|
||||||
const Vector<EventListenerRegistration>& listeners() const { return m_listeners; }
|
const Vector<EventListenerRegistration>& listeners() const { return m_listeners; }
|
||||||
|
|
||||||
|
virtual bool is_node() const { return false; }
|
||||||
|
virtual bool is_window() const { return false; }
|
||||||
|
|
||||||
|
Function<void(const Event&)> activation_behaviour;
|
||||||
|
|
||||||
|
// NOTE: These only exist for checkbox and radio input elements.
|
||||||
|
Function<void()> legacy_pre_activation_behaviour;
|
||||||
|
Function<void()> legacy_cancelled_activation_behaviour;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit EventTarget(Bindings::ScriptExecutionContext&);
|
explicit EventTarget(Bindings::ScriptExecutionContext&);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include <LibWeb/DOM/EventDispatcher.h>
|
#include <LibWeb/DOM/EventDispatcher.h>
|
||||||
#include <LibWeb/DOM/EventListener.h>
|
#include <LibWeb/DOM/EventListener.h>
|
||||||
#include <LibWeb/DOM/Node.h>
|
#include <LibWeb/DOM/Node.h>
|
||||||
|
#include <LibWeb/DOM/ShadowRoot.h>
|
||||||
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
||||||
#include <LibWeb/Layout/BlockBox.h>
|
#include <LibWeb/Layout/BlockBox.h>
|
||||||
#include <LibWeb/Layout/InitialContainingBlockBox.h>
|
#include <LibWeb/Layout/InitialContainingBlockBox.h>
|
||||||
|
@ -124,12 +125,9 @@ bool Node::is_link() const
|
||||||
return enclosing_link_element();
|
return enclosing_link_element();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::dispatch_event(NonnullRefPtr<Event> event)
|
bool Node::dispatch_event(NonnullRefPtr<Event> event)
|
||||||
{
|
{
|
||||||
EventDispatcher::dispatch(*this, event);
|
return EventDispatcher::dispatch(*this, event);
|
||||||
// FIXME: This is a hack. We should follow the real rules of event bubbling.
|
|
||||||
if (parent())
|
|
||||||
parent()->dispatch_event(move(event));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String Node::child_text_content() const
|
String Node::child_text_content() const
|
||||||
|
@ -145,17 +143,25 @@ String Node::child_text_content() const
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Node* Node::root() const
|
Node* Node::root()
|
||||||
{
|
{
|
||||||
const Node* root = this;
|
Node* root = this;
|
||||||
while (root->parent())
|
while (root->parent())
|
||||||
root = root->parent();
|
root = root->parent();
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node* Node::shadow_including_root()
|
||||||
|
{
|
||||||
|
auto node_root = root();
|
||||||
|
if (is<ShadowRoot>(node_root))
|
||||||
|
return downcast<ShadowRoot>(node_root)->host()->shadow_including_root();
|
||||||
|
return node_root;
|
||||||
|
}
|
||||||
|
|
||||||
bool Node::is_connected() const
|
bool Node::is_connected() const
|
||||||
{
|
{
|
||||||
return root() && root()->is_document();
|
return shadow_including_root() && shadow_including_root()->is_document();
|
||||||
}
|
}
|
||||||
|
|
||||||
Element* Node::parent_element()
|
Element* Node::parent_element()
|
||||||
|
@ -238,4 +244,10 @@ void Node::set_layout_node(Badge<Layout::Node>, Layout::Node* layout_node) const
|
||||||
m_layout_node = nullptr;
|
m_layout_node = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventTarget* Node::get_parent(const Event&)
|
||||||
|
{
|
||||||
|
// FIXME: returns the node’s assigned slot, if node is assigned, and node’s parent otherwise.
|
||||||
|
return parent();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public:
|
||||||
// ^EventTarget
|
// ^EventTarget
|
||||||
virtual void ref_event_target() final { ref(); }
|
virtual void ref_event_target() final { ref(); }
|
||||||
virtual void unref_event_target() final { unref(); }
|
virtual void unref_event_target() final { unref(); }
|
||||||
virtual void dispatch_event(NonnullRefPtr<Event>) final;
|
virtual bool dispatch_event(NonnullRefPtr<Event>) final;
|
||||||
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
||||||
|
|
||||||
virtual ~Node();
|
virtual ~Node();
|
||||||
|
@ -76,7 +76,11 @@ public:
|
||||||
bool is_character_data() const { return type() == NodeType::TEXT_NODE || type() == NodeType::COMMENT_NODE; }
|
bool is_character_data() const { return type() == NodeType::TEXT_NODE || type() == NodeType::COMMENT_NODE; }
|
||||||
bool is_document_fragment() const { return type() == NodeType::DOCUMENT_FRAGMENT_NODE; }
|
bool is_document_fragment() const { return type() == NodeType::DOCUMENT_FRAGMENT_NODE; }
|
||||||
bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
|
bool is_parent_node() const { return is_element() || is_document() || is_document_fragment(); }
|
||||||
|
bool is_slottable() const { return is_element() || is_text(); }
|
||||||
virtual bool is_svg_element() const { return false; }
|
virtual bool is_svg_element() const { return false; }
|
||||||
|
virtual bool is_shadow_root() const { return false; }
|
||||||
|
|
||||||
|
virtual bool is_node() const final { return true; }
|
||||||
|
|
||||||
virtual bool is_editable() const;
|
virtual bool is_editable() const;
|
||||||
|
|
||||||
|
@ -102,7 +106,18 @@ public:
|
||||||
virtual bool is_html_element() const { return false; }
|
virtual bool is_html_element() const { return false; }
|
||||||
virtual bool is_unknown_html_element() const { return false; }
|
virtual bool is_unknown_html_element() const { return false; }
|
||||||
|
|
||||||
const Node* root() const;
|
Node* root();
|
||||||
|
const Node* root() const
|
||||||
|
{
|
||||||
|
return const_cast<Node*>(this)->root();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* shadow_including_root();
|
||||||
|
const Node* shadow_including_root() const
|
||||||
|
{
|
||||||
|
return const_cast<Node*>(this)->shadow_including_root();
|
||||||
|
}
|
||||||
|
|
||||||
bool is_connected() const;
|
bool is_connected() const;
|
||||||
|
|
||||||
Node* parent_node() { return parent(); }
|
Node* parent_node() { return parent(); }
|
||||||
|
@ -134,6 +149,8 @@ public:
|
||||||
|
|
||||||
void set_document(Badge<Document>, Document&);
|
void set_document(Badge<Document>, Document&);
|
||||||
|
|
||||||
|
virtual EventTarget* get_parent(const Event&) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Node(Document&, NodeType);
|
Node(Document&, NodeType);
|
||||||
|
|
||||||
|
@ -144,3 +161,7 @@ protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AK_BEGIN_TYPE_TRAITS(Web::DOM::Node)
|
||||||
|
static bool is_type(const Web::DOM::EventTarget& event_target) { return event_target.is_node(); }
|
||||||
|
AK_END_TYPE_TRAITS()
|
||||||
|
|
49
Libraries/LibWeb/DOM/ShadowRoot.cpp
Normal file
49
Libraries/LibWeb/DOM/ShadowRoot.cpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, the SerenityOS developers.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/DOM/Event.h>
|
||||||
|
#include <LibWeb/DOM/ShadowRoot.h>
|
||||||
|
|
||||||
|
namespace Web::DOM {
|
||||||
|
|
||||||
|
ShadowRoot::ShadowRoot(Document& document, Element& host)
|
||||||
|
: DocumentFragment(document)
|
||||||
|
{
|
||||||
|
set_host(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventTarget* ShadowRoot::get_parent(const Event& event)
|
||||||
|
{
|
||||||
|
if (!event.composed()) {
|
||||||
|
auto& events_first_invocation_target = downcast<Node>(*event.path().first().invocation_target);
|
||||||
|
if (events_first_invocation_target.root() == this)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return host();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
Libraries/LibWeb/DOM/ShadowRoot.h
Normal file
65
Libraries/LibWeb/DOM/ShadowRoot.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, the SerenityOS developers.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/DOM/DocumentFragment.h>
|
||||||
|
|
||||||
|
namespace Web::DOM {
|
||||||
|
|
||||||
|
class ShadowRoot final : public DocumentFragment {
|
||||||
|
public:
|
||||||
|
ShadowRoot(Document&, Element&);
|
||||||
|
|
||||||
|
bool closed() const { return m_closed; }
|
||||||
|
|
||||||
|
bool delegates_focus() const { return m_delegates_focus; }
|
||||||
|
void set_delegates_focus(bool delegates_focus) { m_delegates_focus = delegates_focus; }
|
||||||
|
|
||||||
|
bool available_to_element_internals() const { return m_available_to_element_internals; }
|
||||||
|
void set_available_to_element_internals(bool available_to_element_internals) { m_available_to_element_internals = available_to_element_internals; }
|
||||||
|
|
||||||
|
// ^Node
|
||||||
|
virtual bool is_shadow_root() const override { return true; }
|
||||||
|
|
||||||
|
// ^EventTarget
|
||||||
|
virtual EventTarget* get_parent(const Event&) override;
|
||||||
|
|
||||||
|
// NOTE: This is intended for the JS bindings.
|
||||||
|
String mode() const { return m_closed ? "closed" : "open"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// NOTE: The specification doesn't seem to specify a default value for closed. Assuming false for now.
|
||||||
|
bool m_closed { false };
|
||||||
|
bool m_delegates_focus { false };
|
||||||
|
bool m_available_to_element_internals { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AK_BEGIN_TYPE_TRAITS(Web::DOM::ShadowRoot)
|
||||||
|
static bool is_type(const Web::DOM::Node& node) { return node.is_shadow_root(); }
|
||||||
|
AK_END_TYPE_TRAITS()
|
6
Libraries/LibWeb/DOM/ShadowRoot.idl
Normal file
6
Libraries/LibWeb/DOM/ShadowRoot.idl
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
interface ShadowRoot : DocumentFragment {
|
||||||
|
|
||||||
|
readonly attribute DOMString mode;
|
||||||
|
readonly attribute Element host;
|
||||||
|
|
||||||
|
}
|
|
@ -159,9 +159,9 @@ void Window::did_call_location_reload(Badge<Bindings::LocationObject>)
|
||||||
frame->loader().load(document().url(), FrameLoader::Type::Reload);
|
frame->loader().load(document().url(), FrameLoader::Type::Reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::dispatch_event(NonnullRefPtr<Event> event)
|
bool Window::dispatch_event(NonnullRefPtr<Event> event)
|
||||||
{
|
{
|
||||||
EventDispatcher::dispatch(*this, event);
|
return EventDispatcher::dispatch(*this, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bindings::EventTargetWrapper* Window::create_wrapper(JS::GlobalObject&)
|
Bindings::EventTargetWrapper* Window::create_wrapper(JS::GlobalObject&)
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
#include <LibWeb/Bindings/WindowObject.h>
|
#include <LibWeb/Bindings/WindowObject.h>
|
||||||
#include <LibWeb/Bindings/Wrappable.h>
|
#include <LibWeb/Bindings/Wrappable.h>
|
||||||
|
#include <LibWeb/DOM/Event.h>
|
||||||
#include <LibWeb/DOM/EventTarget.h>
|
#include <LibWeb/DOM/EventTarget.h>
|
||||||
|
|
||||||
namespace Web::DOM {
|
namespace Web::DOM {
|
||||||
|
@ -48,7 +49,7 @@ public:
|
||||||
|
|
||||||
virtual void ref_event_target() override { RefCounted::ref(); }
|
virtual void ref_event_target() override { RefCounted::ref(); }
|
||||||
virtual void unref_event_target() override { RefCounted::unref(); }
|
virtual void unref_event_target() override { RefCounted::unref(); }
|
||||||
virtual void dispatch_event(NonnullRefPtr<Event>) override;
|
virtual bool dispatch_event(NonnullRefPtr<Event>) override;
|
||||||
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
||||||
|
|
||||||
const Document& document() const { return m_document; }
|
const Document& document() const { return m_document; }
|
||||||
|
@ -77,6 +78,11 @@ public:
|
||||||
|
|
||||||
HighResolutionTime::Performance& performance() { return *m_performance; }
|
HighResolutionTime::Performance& performance() { return *m_performance; }
|
||||||
|
|
||||||
|
virtual bool is_window() const override { return true; }
|
||||||
|
|
||||||
|
const Event* current_event() const { return m_current_event; }
|
||||||
|
void set_current_event(Event* event) { m_current_event = event; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Window(Document&);
|
explicit Window(Document&);
|
||||||
|
|
||||||
|
@ -87,6 +93,11 @@ private:
|
||||||
HashMap<int, NonnullRefPtr<Timer>> m_timers;
|
HashMap<int, NonnullRefPtr<Timer>> m_timers;
|
||||||
|
|
||||||
NonnullOwnPtr<HighResolutionTime::Performance> m_performance;
|
NonnullOwnPtr<HighResolutionTime::Performance> m_performance;
|
||||||
|
RefPtr<Event> m_current_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AK_BEGIN_TYPE_TRAITS(Web::DOM::Window)
|
||||||
|
static bool is_type(const Web::DOM::EventTarget& event_target) { return event_target.is_window(); }
|
||||||
|
AK_END_TYPE_TRAITS()
|
||||||
|
|
|
@ -107,9 +107,9 @@ void XMLHttpRequest::send()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLHttpRequest::dispatch_event(NonnullRefPtr<DOM::Event> event)
|
bool XMLHttpRequest::dispatch_event(NonnullRefPtr<DOM::Event> event)
|
||||||
{
|
{
|
||||||
DOM::EventDispatcher::dispatch(*this, move(event));
|
return DOM::EventDispatcher::dispatch(*this, move(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
Bindings::EventTargetWrapper* XMLHttpRequest::create_wrapper(JS::GlobalObject& global_object)
|
Bindings::EventTargetWrapper* XMLHttpRequest::create_wrapper(JS::GlobalObject& global_object)
|
||||||
|
|
|
@ -65,7 +65,7 @@ public:
|
||||||
private:
|
private:
|
||||||
virtual void ref_event_target() override { ref(); }
|
virtual void ref_event_target() override { ref(); }
|
||||||
virtual void unref_event_target() override { unref(); }
|
virtual void unref_event_target() override { unref(); }
|
||||||
virtual void dispatch_event(NonnullRefPtr<DOM::Event>) override;
|
virtual bool dispatch_event(NonnullRefPtr<DOM::Event>) override;
|
||||||
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
||||||
|
|
||||||
void set_ready_state(ReadyState);
|
void set_ready_state(ReadyState);
|
||||||
|
|
|
@ -180,18 +180,22 @@ void HTMLDocumentParser::run(const URL& url)
|
||||||
script.execute_script();
|
script.execute_script();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_document->dispatch_event(DOM::Event::create("DOMContentLoaded"));
|
auto content_loaded_event = DOM::Event::create("DOMContentLoaded");
|
||||||
|
content_loaded_event->set_bubbles(true);
|
||||||
// FIXME: These are not in the right place, they should only fire once subresources are ready.
|
m_document->dispatch_event(content_loaded_event);
|
||||||
m_document->dispatch_event(DOM::Event::create("load"));
|
|
||||||
m_document->window().dispatch_event(DOM::Event::create("load"));
|
|
||||||
|
|
||||||
auto scripts_to_execute_as_soon_as_possible = m_document->take_scripts_to_execute_as_soon_as_possible({});
|
auto scripts_to_execute_as_soon_as_possible = m_document->take_scripts_to_execute_as_soon_as_possible({});
|
||||||
for (auto& script : scripts_to_execute_as_soon_as_possible) {
|
for (auto& script : scripts_to_execute_as_soon_as_possible) {
|
||||||
script.execute_script();
|
script.execute_script();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Spin the event loop until there is nothing that delays the load event in the Document.
|
||||||
|
|
||||||
m_document->set_ready_state("complete");
|
m_document->set_ready_state("complete");
|
||||||
|
m_document->window().dispatch_event(DOM::Event::create("load"));
|
||||||
|
|
||||||
|
m_document->set_ready_for_post_load_tasks(true);
|
||||||
|
m_document->completely_finish_loading();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLDocumentParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& token)
|
void HTMLDocumentParser::process_using_the_rules_for(InsertionMode mode, HTMLToken& token)
|
||||||
|
|
|
@ -60,9 +60,9 @@ void Performance::unref_event_target()
|
||||||
m_window.unref();
|
m_window.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Performance::dispatch_event(NonnullRefPtr<DOM::Event> event)
|
bool Performance::dispatch_event(NonnullRefPtr<DOM::Event> event)
|
||||||
{
|
{
|
||||||
DOM::EventDispatcher::dispatch(*this, event);
|
return DOM::EventDispatcher::dispatch(*this, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bindings::EventTargetWrapper* Performance::create_wrapper(JS::GlobalObject& global_object)
|
Bindings::EventTargetWrapper* Performance::create_wrapper(JS::GlobalObject& global_object)
|
||||||
|
|
|
@ -49,7 +49,7 @@ public:
|
||||||
virtual void ref_event_target() override;
|
virtual void ref_event_target() override;
|
||||||
virtual void unref_event_target() override;
|
virtual void unref_event_target() override;
|
||||||
|
|
||||||
virtual void dispatch_event(NonnullRefPtr<DOM::Event>) override;
|
virtual bool dispatch_event(NonnullRefPtr<DOM::Event>) override;
|
||||||
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
virtual Bindings::EventTargetWrapper* create_wrapper(JS::GlobalObject&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
52
Libraries/LibWeb/UIEvents/MouseEvent.cpp
Normal file
52
Libraries/LibWeb/UIEvents/MouseEvent.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, the SerenityOS developers.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/UIEvents/MouseEvent.h>
|
||||||
|
|
||||||
|
namespace Web::UIEvents {
|
||||||
|
|
||||||
|
MouseEvent::MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y)
|
||||||
|
: UIEvent(event_name)
|
||||||
|
, m_offset_x(offset_x)
|
||||||
|
, m_offset_y(offset_y)
|
||||||
|
{
|
||||||
|
set_event_characteristics();
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseEvent::~MouseEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MouseEvent::set_event_characteristics()
|
||||||
|
{
|
||||||
|
if (type() == "mousedown" || type() == "mousemove" || type() == "mouseout" || type() == "mouseover" || type() == "mouseup" || type() == "click") {
|
||||||
|
set_bubbles(true);
|
||||||
|
set_cancelable(true);
|
||||||
|
set_composed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/TypeCasts.h>
|
||||||
#include <LibWeb/UIEvents/UIEvent.h>
|
#include <LibWeb/UIEvents/UIEvent.h>
|
||||||
|
|
||||||
namespace Web::UIEvents {
|
namespace Web::UIEvents {
|
||||||
|
@ -39,24 +40,25 @@ public:
|
||||||
return adopt(*new MouseEvent(event_name, offset_x, offset_y));
|
return adopt(*new MouseEvent(event_name, offset_x, offset_y));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~MouseEvent() override { }
|
virtual ~MouseEvent() override;
|
||||||
|
|
||||||
i32 offset_x() const { return m_offset_x; }
|
i32 offset_x() const { return m_offset_x; }
|
||||||
i32 offset_y() const { return m_offset_y; }
|
i32 offset_y() const { return m_offset_y; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y)
|
MouseEvent(const FlyString& event_name, i32 offset_x, i32 offset_y);
|
||||||
: UIEvent(event_name)
|
|
||||||
, m_offset_x(offset_x)
|
|
||||||
, m_offset_y(offset_y)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool is_mouse_event() const override { return true; }
|
virtual bool is_mouse_event() const override { return true; }
|
||||||
|
|
||||||
|
void set_event_characteristics();
|
||||||
|
|
||||||
i32 m_offset_x { 0 };
|
i32 m_offset_x { 0 };
|
||||||
i32 m_offset_y { 0 };
|
i32 m_offset_y { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AK_BEGIN_TYPE_TRAITS(Web::UIEvents::MouseEvent)
|
||||||
|
static bool is_type(const Web::DOM::Event& event) { return event.is_mouse_event(); }
|
||||||
|
AK_END_TYPE_TRAITS()
|
||||||
|
|
Loading…
Add table
Reference in a new issue