/src/serenity/Userland/Libraries/LibWeb/Painting/Paintable.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/NonnullOwnPtr.h> |
10 | | #include <LibWeb/Layout/Box.h> |
11 | | #include <LibWeb/Layout/LineBox.h> |
12 | | #include <LibWeb/Layout/TextNode.h> |
13 | | |
14 | | namespace Web::Painting { |
15 | | |
16 | | enum class PaintPhase { |
17 | | Background, |
18 | | Border, |
19 | | TableCollapsedBorder, |
20 | | Foreground, |
21 | | Outline, |
22 | | Overlay, |
23 | | }; |
24 | | |
25 | | struct HitTestResult { |
26 | | JS::Handle<Paintable> paintable; |
27 | | int index_in_node { 0 }; |
28 | | Optional<CSSPixels> vertical_distance {}; |
29 | | Optional<CSSPixels> horizontal_distance {}; |
30 | | |
31 | | enum InternalPosition { |
32 | | None, |
33 | | Before, |
34 | | Inside, |
35 | | After, |
36 | | }; |
37 | | InternalPosition internal_position { None }; |
38 | | |
39 | | DOM::Node* dom_node(); |
40 | | DOM::Node const* dom_node() const; |
41 | | }; |
42 | | |
43 | | enum class HitTestType { |
44 | | Exact, // Exact matches only |
45 | | TextCursor, // Clicking past the right/bottom edge of text will still hit the text |
46 | | }; |
47 | | |
48 | | class Paintable |
49 | | : public JS::Cell |
50 | | , public TreeNode<Paintable> { |
51 | | JS_CELL(Paintable, JS::Cell); |
52 | | |
53 | | public: |
54 | | virtual ~Paintable(); |
55 | | |
56 | | [[nodiscard]] bool is_visible() const; |
57 | 0 | [[nodiscard]] bool is_positioned() const { return m_positioned; } |
58 | 0 | [[nodiscard]] bool is_fixed_position() const { return m_fixed_position; } |
59 | 0 | [[nodiscard]] bool is_absolutely_positioned() const { return m_absolutely_positioned; } |
60 | 0 | [[nodiscard]] bool is_floating() const { return m_floating; } |
61 | 0 | [[nodiscard]] bool is_inline() const { return m_inline; } |
62 | 0 | [[nodiscard]] bool is_selected() const { return m_selected; } |
63 | 0 | [[nodiscard]] CSS::Display display() const { return layout_node().display(); } |
64 | | |
65 | | template<typename U, typename Callback> |
66 | | TraversalDecision for_each_in_inclusive_subtree_of_type(Callback callback) const |
67 | 0 | { |
68 | 0 | if (is<U>(*this)) { |
69 | 0 | if (auto decision = callback(static_cast<U const&>(*this)); decision != TraversalDecision::Continue) |
70 | 0 | return decision; |
71 | 0 | } |
72 | 0 | for (auto* child = first_child(); child; child = child->next_sibling()) { |
73 | 0 | if (child->template for_each_in_inclusive_subtree_of_type<U>(callback) == TraversalDecision::Break) |
74 | 0 | return TraversalDecision::Break; |
75 | 0 | } |
76 | 0 | return TraversalDecision::Continue; |
77 | 0 | } Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree_of_type<Web::Painting::PaintableBox, Web::Painting::ViewportPaintable::assign_scroll_frames()::$_0>(Web::Painting::ViewportPaintable::assign_scroll_frames()::$_0) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree_of_type<Web::Painting::PaintableBox, Web::Painting::ViewportPaintable::assign_clip_frames()::$_0>(Web::Painting::ViewportPaintable::assign_clip_frames()::$_0) const |
78 | | |
79 | | template<typename U, typename Callback> |
80 | | TraversalDecision for_each_in_subtree_of_type(Callback callback) const |
81 | 0 | { |
82 | 0 | for (auto* child = first_child(); child; child = child->next_sibling()) { |
83 | 0 | if (child->template for_each_in_inclusive_subtree_of_type<U>(callback) == TraversalDecision::Break) |
84 | 0 | return TraversalDecision::Break; |
85 | 0 | } |
86 | 0 | return TraversalDecision::Continue; |
87 | 0 | } Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_subtree_of_type<Web::Painting::PaintableBox, Web::Painting::ViewportPaintable::assign_scroll_frames()::$_0>(Web::Painting::ViewportPaintable::assign_scroll_frames()::$_0) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_subtree_of_type<Web::Painting::PaintableBox, Web::Painting::ViewportPaintable::assign_clip_frames()::$_0>(Web::Painting::ViewportPaintable::assign_clip_frames()::$_0) const |
88 | | |
89 | | template<typename Callback> |
90 | | TraversalDecision for_each_in_inclusive_subtree(Callback callback) |
91 | 0 | { |
92 | 0 | if (auto decision = callback(*this); decision != TraversalDecision::Continue) |
93 | 0 | return decision; |
94 | 0 | for (auto* child = first_child(); child; child = child->next_sibling()) { |
95 | 0 | if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) |
96 | 0 | return TraversalDecision::Break; |
97 | 0 | } |
98 | 0 | return TraversalDecision::Continue; |
99 | 0 | } Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::ViewportPaintable::resolve_paint_only_properties()::$_0>(Web::Painting::ViewportPaintable::resolve_paint_only_properties()::$_0) Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::ViewportPaintable::update_selection()::$_0>(Web::Painting::ViewportPaintable::update_selection()::$_0) Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::ViewportPaintable::recompute_selection_states(Web::DOM::Range&)::$_0>(Web::Painting::ViewportPaintable::recompute_selection_states(Web::DOM::Range&)::$_0) |
100 | | |
101 | | template<typename Callback> |
102 | | TraversalDecision for_each_in_inclusive_subtree(Callback callback) const |
103 | 0 | { |
104 | 0 | if (auto decision = callback(*this); decision != TraversalDecision::Continue) |
105 | 0 | return decision; |
106 | 0 | for (auto* child = first_child(); child; child = child->next_sibling()) { |
107 | 0 | if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) |
108 | 0 | return TraversalDecision::Break; |
109 | 0 | } |
110 | 0 | return TraversalDecision::Continue; |
111 | 0 | } Unexecuted instantiation: BackgroundPainting.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::compute_text_clip_paths(Web::PaintContext&, Web::Painting::Paintable const&)::$_1>(Web::Painting::compute_text_clip_paths(Web::PaintContext&, Web::Painting::Paintable const&)::$_1) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::ViewportPaintable::build_stacking_context_tree()::$_0>(Web::Painting::ViewportPaintable::build_stacking_context_tree()::$_0) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::ViewportPaintable::assign_scroll_frames()::$_1>(Web::Painting::ViewportPaintable::assign_scroll_frames()::$_1) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_inclusive_subtree<Web::Painting::ViewportPaintable::assign_clip_frames()::$_1>(Web::Painting::ViewportPaintable::assign_clip_frames()::$_1) const |
112 | | |
113 | | template<typename Callback> |
114 | | TraversalDecision for_each_in_subtree(Callback callback) const |
115 | 0 | { |
116 | 0 | for (auto* child = first_child(); child; child = child->next_sibling()) { |
117 | 0 | if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) |
118 | 0 | return TraversalDecision::Break; |
119 | 0 | } |
120 | 0 | return TraversalDecision::Continue; |
121 | 0 | } Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_subtree<Web::Painting::ViewportPaintable::build_stacking_context_tree()::$_0>(Web::Painting::ViewportPaintable::build_stacking_context_tree()::$_0) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_subtree<Web::Painting::ViewportPaintable::assign_scroll_frames()::$_1>(Web::Painting::ViewportPaintable::assign_scroll_frames()::$_1) const Unexecuted instantiation: ViewportPaintable.cpp:Web::TraversalDecision Web::Painting::Paintable::for_each_in_subtree<Web::Painting::ViewportPaintable::assign_clip_frames()::$_1>(Web::Painting::ViewportPaintable::assign_clip_frames()::$_1) const |
122 | | |
123 | 0 | StackingContext* stacking_context() { return m_stacking_context; } |
124 | 0 | StackingContext const* stacking_context() const { return m_stacking_context; } |
125 | | void set_stacking_context(NonnullOwnPtr<StackingContext>); |
126 | | StackingContext* enclosing_stacking_context(); |
127 | | |
128 | | void invalidate_stacking_context(); |
129 | | |
130 | 0 | virtual void before_paint(PaintContext&, PaintPhase) const { } |
131 | 0 | virtual void after_paint(PaintContext&, PaintPhase) const { } |
132 | | |
133 | 0 | virtual void paint(PaintContext&, PaintPhase) const { } |
134 | | |
135 | 0 | virtual void before_children_paint(PaintContext&, PaintPhase) const { } |
136 | 0 | virtual void after_children_paint(PaintContext&, PaintPhase) const { } |
137 | | |
138 | 0 | virtual void apply_scroll_offset(PaintContext&, PaintPhase) const { } |
139 | 0 | virtual void reset_scroll_offset(PaintContext&, PaintPhase) const { } |
140 | | |
141 | 0 | virtual void apply_clip_overflow_rect(PaintContext&, PaintPhase) const { } |
142 | 0 | virtual void clear_clip_overflow_rect(PaintContext&, PaintPhase) const { } |
143 | | |
144 | | [[nodiscard]] virtual TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const; |
145 | | |
146 | 0 | virtual bool wants_mouse_events() const { return false; } |
147 | | |
148 | 0 | virtual bool forms_unconnected_subtree() const { return false; } |
149 | | |
150 | | enum class DispatchEventOfSameName { |
151 | | Yes, |
152 | | No, |
153 | | }; |
154 | | // When these methods return true, the DOM event with the same name will be |
155 | | // dispatch at the mouse_event_target if it returns a valid DOM::Node, or |
156 | | // the layout node's associated DOM node if it doesn't. |
157 | | virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers); |
158 | | virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers); |
159 | | virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers); |
160 | 0 | virtual DOM::Node* mouse_event_target() const { return nullptr; } |
161 | | |
162 | | virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y); |
163 | | |
164 | 0 | Layout::Node const& layout_node() const { return m_layout_node; } |
165 | 0 | Layout::Node& layout_node() { return const_cast<Layout::Node&>(*m_layout_node); } |
166 | | |
167 | | [[nodiscard]] JS::GCPtr<DOM::Node> dom_node(); |
168 | | [[nodiscard]] JS::GCPtr<DOM::Node const> dom_node() const; |
169 | | void set_dom_node(JS::GCPtr<DOM::Node>); |
170 | | |
171 | 0 | auto const& computed_values() const { return m_layout_node->computed_values(); } |
172 | | |
173 | 0 | bool visible_for_hit_testing() const { return computed_values().pointer_events() != CSS::PointerEvents::None; } |
174 | | |
175 | | [[nodiscard]] HTML::BrowsingContext const& browsing_context() const; |
176 | | [[nodiscard]] HTML::BrowsingContext& browsing_context(); |
177 | | |
178 | | JS::GCPtr<HTML::Navigable> navigable() const; |
179 | | |
180 | | virtual void set_needs_display() const; |
181 | | |
182 | | PaintableBox* containing_block() const |
183 | 0 | { |
184 | 0 | if (!m_containing_block.has_value()) { |
185 | 0 | auto containing_layout_box = m_layout_node->containing_block(); |
186 | 0 | if (containing_layout_box) |
187 | 0 | m_containing_block = const_cast<PaintableBox*>(containing_layout_box->paintable_box()); |
188 | 0 | else |
189 | 0 | m_containing_block = nullptr; |
190 | 0 | } |
191 | 0 | return *m_containing_block; |
192 | 0 | } |
193 | | |
194 | | template<typename T> |
195 | | bool fast_is() const = delete; |
196 | | |
197 | 0 | [[nodiscard]] virtual bool is_paintable_box() const { return false; } |
198 | 0 | [[nodiscard]] virtual bool is_paintable_with_lines() const { return false; } |
199 | 0 | [[nodiscard]] virtual bool is_inline_paintable() const { return false; } |
200 | 0 | [[nodiscard]] virtual bool is_svg_paintable() const { return false; } |
201 | 0 | [[nodiscard]] virtual bool is_text_paintable() const { return false; } |
202 | | |
203 | 0 | DOM::Document const& document() const { return layout_node().document(); } |
204 | 0 | DOM::Document& document() { return layout_node().document(); } |
205 | | |
206 | | CSSPixelPoint box_type_agnostic_position() const; |
207 | | |
208 | | enum class SelectionState : u8 { |
209 | | None, // No selection |
210 | | Start, // Selection starts in this Node |
211 | | End, // Selection ends in this Node |
212 | | StartAndEnd, // Selection starts and ends in this Node |
213 | | Full, // Selection starts before and ends after this Node |
214 | | }; |
215 | | |
216 | 0 | SelectionState selection_state() const { return m_selection_state; } |
217 | 0 | void set_selection_state(SelectionState state) { m_selection_state = state; } |
218 | 0 | void set_selected(bool selected) { m_selected = selected; } |
219 | | |
220 | | Gfx::AffineTransform compute_combined_css_transform() const; |
221 | | |
222 | 0 | virtual void resolve_paint_properties() {}; |
223 | | |
224 | | protected: |
225 | | explicit Paintable(Layout::Node const&); |
226 | | |
227 | | virtual void visit_edges(Cell::Visitor&) override; |
228 | | |
229 | | private: |
230 | | JS::GCPtr<DOM::Node> m_dom_node; |
231 | | JS::NonnullGCPtr<Layout::Node const> m_layout_node; |
232 | | JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context; |
233 | | Optional<JS::GCPtr<PaintableBox>> mutable m_containing_block; |
234 | | |
235 | | OwnPtr<StackingContext> m_stacking_context; |
236 | | |
237 | | SelectionState m_selection_state { SelectionState::None }; |
238 | | |
239 | | bool m_positioned : 1 { false }; |
240 | | bool m_fixed_position : 1 { false }; |
241 | | bool m_absolutely_positioned : 1 { false }; |
242 | | bool m_floating : 1 { false }; |
243 | | bool m_inline : 1 { false }; |
244 | | bool m_selected : 1 { false }; |
245 | | }; |
246 | | |
247 | | inline DOM::Node* HitTestResult::dom_node() |
248 | 0 | { |
249 | 0 | return paintable->dom_node(); |
250 | 0 | } |
251 | | |
252 | | inline DOM::Node const* HitTestResult::dom_node() const |
253 | 0 | { |
254 | 0 | return paintable->dom_node(); |
255 | 0 | } |
256 | | |
257 | | template<> |
258 | 0 | inline bool Paintable::fast_is<PaintableBox>() const { return is_paintable_box(); } |
259 | | |
260 | | template<> |
261 | 0 | inline bool Paintable::fast_is<PaintableWithLines>() const { return is_paintable_with_lines(); } |
262 | | |
263 | | template<> |
264 | 0 | inline bool Paintable::fast_is<TextPaintable>() const { return is_text_paintable(); } |
265 | | |
266 | | Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData const& top_left_radius, CSS::BorderRadiusData const& top_right_radius, CSS::BorderRadiusData const& bottom_right_radius, CSS::BorderRadiusData const& bottom_left_radius); |
267 | | |
268 | | } |