/src/serenity/Userland/Libraries/LibWeb/DOM/Event.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020, the SerenityOS developers. |
3 | | * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> |
4 | | * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #include <AK/TypeCasts.h> |
10 | | #include <LibWeb/Bindings/EventPrototype.h> |
11 | | #include <LibWeb/Bindings/Intrinsics.h> |
12 | | #include <LibWeb/DOM/Event.h> |
13 | | #include <LibWeb/DOM/Node.h> |
14 | | #include <LibWeb/DOM/ShadowRoot.h> |
15 | | #include <LibWeb/HighResolutionTime/TimeOrigin.h> |
16 | | |
17 | | namespace Web::DOM { |
18 | | |
19 | | JS_DEFINE_ALLOCATOR(Event); |
20 | | |
21 | | // https://dom.spec.whatwg.org/#concept-event-create |
22 | | JS::NonnullGCPtr<Event> Event::create(JS::Realm& realm, FlyString const& event_name, EventInit const& event_init) |
23 | 0 | { |
24 | 0 | auto event = realm.heap().allocate<Event>(realm, realm, event_name, event_init); |
25 | | // 4. Initialize event’s isTrusted attribute to true. |
26 | 0 | event->m_is_trusted = true; |
27 | 0 | return event; |
28 | 0 | } |
29 | | |
30 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Event>> Event::construct_impl(JS::Realm& realm, FlyString const& event_name, EventInit const& event_init) |
31 | 0 | { |
32 | 0 | return realm.heap().allocate<Event>(realm, realm, event_name, event_init); |
33 | 0 | } |
34 | | |
35 | | // https://dom.spec.whatwg.org/#inner-event-creation-steps |
36 | | Event::Event(JS::Realm& realm, FlyString const& type) |
37 | 0 | : PlatformObject(realm) |
38 | 0 | , m_type(type) |
39 | 0 | , m_initialized(true) |
40 | 0 | , m_time_stamp(HighResolutionTime::current_high_resolution_time(HTML::relevant_global_object(*this))) |
41 | 0 | { |
42 | 0 | } |
43 | | |
44 | | // https://dom.spec.whatwg.org/#inner-event-creation-steps |
45 | | Event::Event(JS::Realm& realm, FlyString const& type, EventInit const& event_init) |
46 | 0 | : PlatformObject(realm) |
47 | 0 | , m_type(type) |
48 | 0 | , m_bubbles(event_init.bubbles) |
49 | 0 | , m_cancelable(event_init.cancelable) |
50 | 0 | , m_composed(event_init.composed) |
51 | 0 | , m_initialized(true) |
52 | 0 | , m_time_stamp(HighResolutionTime::current_high_resolution_time(HTML::relevant_global_object(*this))) |
53 | 0 | { |
54 | 0 | } |
55 | | |
56 | | void Event::initialize(JS::Realm& realm) |
57 | 0 | { |
58 | 0 | Base::initialize(realm); |
59 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(Event); |
60 | 0 | } |
61 | | |
62 | | void Event::visit_edges(Visitor& visitor) |
63 | 0 | { |
64 | 0 | Base::visit_edges(visitor); |
65 | 0 | visitor.visit(m_target); |
66 | 0 | visitor.visit(m_related_target); |
67 | 0 | visitor.visit(m_current_target); |
68 | 0 | for (auto& it : m_path) { |
69 | 0 | visitor.visit(it.invocation_target); |
70 | 0 | visitor.visit(it.shadow_adjusted_target); |
71 | 0 | visitor.visit(it.related_target); |
72 | 0 | visitor.visit(it.touch_target_list); |
73 | 0 | } |
74 | 0 | visitor.visit(m_touch_target_list); |
75 | 0 | } |
76 | | |
77 | | // https://dom.spec.whatwg.org/#concept-event-path-append |
78 | | void Event::append_to_path(EventTarget& invocation_target, JS::GCPtr<EventTarget> shadow_adjusted_target, JS::GCPtr<EventTarget> related_target, TouchTargetList& touch_targets, bool slot_in_closed_tree) |
79 | 0 | { |
80 | | // 1. Let invocationTargetInShadowTree be false. |
81 | 0 | bool invocation_target_in_shadow_tree = false; |
82 | | |
83 | | // 3. Let root-of-closed-tree be false. |
84 | 0 | bool root_of_closed_tree = false; |
85 | | |
86 | | // 2. If invocationTarget is a node and its root is a shadow root, then set invocationTargetInShadowTree to true. |
87 | 0 | if (is<Node>(invocation_target)) { |
88 | 0 | auto& invocation_target_node = verify_cast<Node>(invocation_target); |
89 | 0 | if (is<ShadowRoot>(invocation_target_node.root())) |
90 | 0 | invocation_target_in_shadow_tree = true; |
91 | 0 | if (is<ShadowRoot>(invocation_target_node)) { |
92 | 0 | auto& invocation_target_shadow_root = verify_cast<ShadowRoot>(invocation_target_node); |
93 | | // 4. If invocationTarget is a shadow root whose mode is "closed", then set root-of-closed-tree to true. |
94 | 0 | root_of_closed_tree = invocation_target_shadow_root.mode() == Bindings::ShadowRootMode::Closed; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | // 5. Append a new struct to event’s path whose invocation target is invocationTarget, invocation-target-in-shadow-tree is invocationTargetInShadowTree, |
99 | | // shadow-adjusted target is shadowAdjustedTarget, relatedTarget is relatedTarget, touch target list is touchTargets, root-of-closed-tree is root-of-closed-tree, |
100 | | // and slot-in-closed-tree is slot-in-closed-tree. |
101 | 0 | 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() }); |
102 | 0 | } |
103 | | |
104 | | void Event::set_cancelled_flag() |
105 | 0 | { |
106 | 0 | if (m_cancelable && !m_in_passive_listener) |
107 | 0 | m_cancelled = true; |
108 | 0 | } |
109 | | |
110 | | // https://dom.spec.whatwg.org/#concept-event-initialize |
111 | | void Event::initialize_event(String const& type, bool bubbles, bool cancelable) |
112 | 0 | { |
113 | | // 1. Set event’s initialized flag. |
114 | 0 | m_initialized = true; |
115 | | |
116 | | // 2. Unset event’s stop propagation flag, stop immediate propagation flag, and canceled flag. |
117 | 0 | m_stop_propagation = false; |
118 | 0 | m_stop_immediate_propagation = false; |
119 | 0 | m_cancelled = false; |
120 | | |
121 | | // 3. Set event’s isTrusted attribute to false. |
122 | 0 | m_is_trusted = false; |
123 | | |
124 | | // 4. Set event’s target to null. |
125 | 0 | m_target = nullptr; |
126 | | |
127 | | // 5. Set event’s type attribute to type. |
128 | 0 | m_type = type; |
129 | | |
130 | | // 6. Set event’s bubbles attribute to bubbles. |
131 | 0 | m_bubbles = bubbles; |
132 | | |
133 | | // 8. Set event’s cancelable attribute to cancelable. |
134 | 0 | m_cancelable = cancelable; |
135 | 0 | } |
136 | | |
137 | | // https://dom.spec.whatwg.org/#dom-event-initevent |
138 | | void Event::init_event(String const& type, bool bubbles, bool cancelable) |
139 | 0 | { |
140 | | // 1. If this’s dispatch flag is set, then return. |
141 | 0 | if (m_dispatch) |
142 | 0 | return; |
143 | | |
144 | | // 2. Initialize this with type, bubbles, and cancelable. |
145 | 0 | initialize_event(type, bubbles, cancelable); |
146 | 0 | } |
147 | | |
148 | | // https://dom.spec.whatwg.org/#dom-event-composedpath |
149 | | Vector<JS::Handle<EventTarget>> Event::composed_path() const |
150 | 0 | { |
151 | | // 1. Let composedPath be an empty list. |
152 | 0 | Vector<JS::Handle<EventTarget>> composed_path; |
153 | | |
154 | | // 2. Let path be this’s path. (NOTE: Not necessary) |
155 | | |
156 | | // 3. If path is empty, then return composedPath. |
157 | 0 | if (m_path.is_empty()) |
158 | 0 | return composed_path; |
159 | | |
160 | | // 4. Let currentTarget be this’s currentTarget attribute value. (NOTE: Not necessary) |
161 | | |
162 | | // 5. Append currentTarget to composedPath. |
163 | | // NOTE: If path is not empty, then the event is being dispatched and will have a currentTarget. |
164 | 0 | VERIFY(m_current_target); |
165 | 0 | composed_path.append(const_cast<EventTarget*>(m_current_target.ptr())); |
166 | | |
167 | | // 6. Let currentTargetIndex be 0. |
168 | 0 | size_t current_target_index = 0; |
169 | | |
170 | | // 7. Let currentTargetHiddenSubtreeLevel be 0. |
171 | 0 | size_t current_target_hidden_subtree_level = 0; |
172 | | |
173 | | // 8. Let index be path’s size − 1. |
174 | | // 9. While index is greater than or equal to 0: |
175 | 0 | for (ssize_t index = m_path.size() - 1; index >= 0; --index) { |
176 | 0 | auto& path_entry = m_path.at(index); |
177 | | |
178 | | // 1. If path[index]'s root-of-closed-tree is true, then increase currentTargetHiddenSubtreeLevel by 1. |
179 | 0 | if (path_entry.root_of_closed_tree) |
180 | 0 | ++current_target_hidden_subtree_level; |
181 | | |
182 | | // 2. If path[index]'s invocation target is currentTarget, then set currentTargetIndex to index and break. |
183 | 0 | if (path_entry.invocation_target == m_current_target) { |
184 | 0 | current_target_index = index; |
185 | 0 | break; |
186 | 0 | } |
187 | | |
188 | | // 3. If path[index]'s slot-in-closed-tree is true, then decrease currentTargetHiddenSubtreeLevel by 1. |
189 | 0 | if (path_entry.slot_in_closed_tree) |
190 | 0 | --current_target_hidden_subtree_level; |
191 | | |
192 | | // 4. Decrease index by 1. |
193 | 0 | } |
194 | | |
195 | | // 10. Let currentHiddenLevel and maxHiddenLevel be currentTargetHiddenSubtreeLevel. |
196 | 0 | size_t current_hidden_level = current_target_hidden_subtree_level; |
197 | 0 | size_t max_hidden_level = current_target_hidden_subtree_level; |
198 | | |
199 | | // 11. Set index to currentTargetIndex − 1. |
200 | | // 12. While index is greater than or equal to 0: |
201 | 0 | for (ssize_t index = current_target_index - 1; index >= 0; --index) { |
202 | 0 | auto& path_entry = m_path.at(index); |
203 | | |
204 | | // 1. If path[index]'s root-of-closed-tree is true, then increase currentHiddenLevel by 1. |
205 | 0 | if (path_entry.root_of_closed_tree) |
206 | 0 | ++current_hidden_level; |
207 | | |
208 | | // 2. If currentHiddenLevel is less than or equal to maxHiddenLevel, then prepend path[index]'s invocation target to composedPath. |
209 | 0 | if (current_hidden_level <= max_hidden_level) { |
210 | 0 | VERIFY(path_entry.invocation_target); |
211 | 0 | composed_path.prepend(const_cast<EventTarget*>(path_entry.invocation_target.ptr())); |
212 | 0 | } |
213 | | |
214 | | // 3. If path[index]'s slot-in-closed-tree is true, then: |
215 | 0 | if (path_entry.slot_in_closed_tree) { |
216 | | // 1. Decrease currentHiddenLevel by 1. |
217 | 0 | --current_hidden_level; |
218 | | |
219 | | // 2. If currentHiddenLevel is less than maxHiddenLevel, then set maxHiddenLevel to currentHiddenLevel. |
220 | 0 | if (current_hidden_level < max_hidden_level) |
221 | 0 | max_hidden_level = current_hidden_level; |
222 | 0 | } |
223 | | |
224 | | // 4. Decrease index by 1. |
225 | 0 | } |
226 | | |
227 | | // 13. Set currentHiddenLevel and maxHiddenLevel to currentTargetHiddenSubtreeLevel. |
228 | 0 | current_hidden_level = current_target_hidden_subtree_level; |
229 | 0 | max_hidden_level = current_target_hidden_subtree_level; |
230 | | |
231 | | // 14. Set index to currentTargetIndex + 1. |
232 | | // 15. While index is less than path’s size: |
233 | 0 | for (size_t index = current_target_index + 1; index < m_path.size(); ++index) { |
234 | 0 | auto& path_entry = m_path.at(index); |
235 | | |
236 | | // 1. If path[index]'s slot-in-closed-tree is true, then increase currentHiddenLevel by 1. |
237 | 0 | if (path_entry.slot_in_closed_tree) |
238 | 0 | ++current_hidden_level; |
239 | | |
240 | | // 2. If currentHiddenLevel is less than or equal to maxHiddenLevel, then append path[index]'s invocation target to composedPath. |
241 | 0 | if (current_hidden_level <= max_hidden_level) { |
242 | 0 | VERIFY(path_entry.invocation_target); |
243 | 0 | composed_path.append(const_cast<EventTarget*>(path_entry.invocation_target.ptr())); |
244 | 0 | } |
245 | | |
246 | | // 3. If path[index]'s root-of-closed-tree is true, then: |
247 | 0 | if (path_entry.root_of_closed_tree) { |
248 | | // 1. Decrease currentHiddenLevel by 1. |
249 | 0 | --current_hidden_level; |
250 | | |
251 | | // 2. If currentHiddenLevel is less than maxHiddenLevel, then set maxHiddenLevel to currentHiddenLevel. |
252 | 0 | if (current_hidden_level < max_hidden_level) |
253 | 0 | max_hidden_level = current_hidden_level; |
254 | 0 | } |
255 | | |
256 | | // 4. Increase index by 1. |
257 | 0 | } |
258 | | |
259 | | // 16. Return composedPath. |
260 | 0 | return composed_path; |
261 | 0 | } |
262 | | |
263 | | } |