ladybird/Libraries/LibWeb/Painting/Paintable.h
Jelle Raaijmakers d819152b4e LibGfx+LibWeb: Preserve text color for selections
Selecting text without custom ::selection styling changed the
foreground color of the selected content. This was especially visible
for links, where the text changed color but the underline did not.

The default selection style supplied both a selection background and a
foreground color from the palette or HighlightText system color. That
made ordinary selections behave as if the page had explicitly styled
::selection color.

Only provide a default selection background, so selected content keeps
its own foreground color unless CSS overrides it. Remove the now-unused
SelectionText palette role.
2026-06-18 22:43:26 +02:00

187 lines
6.6 KiB
C++

/*
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <AK/WeakPtr.h>
#include <AK/Weakable.h>
#include <AK/kmalloc.h>
#include <LibGC/Ptr.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/CSS/Display.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/InvalidateDisplayList.h>
#include <LibWeb/Painting/ChromeWidget.h>
#include <LibWeb/Painting/HitTestResult.h>
#include <LibWeb/Painting/ShadowData.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/RefCountedTreeNode.h>
namespace Web::Painting {
enum class PaintPhase {
Background,
Border,
TableCollapsedBorder,
Foreground,
Outline,
Overlay,
};
class WEB_API Paintable
: public RefCounted<Paintable>
, public Weakable<Paintable>
, public RefCountedTreeNode<Paintable> {
public:
AK_ALLOC_WITH_KMALLOC_PARTITION(HeapPartition::Painting);
virtual ~Paintable();
virtual StringView class_name() const { return "Paintable"sv; }
[[nodiscard]] bool is_visible() const
{
auto const& cv = computed_values();
return cv.visibility() == CSS::Visibility::Visible && cv.opacity() != 0;
}
[[nodiscard]] bool is_positioned() const { return m_positioned; }
[[nodiscard]] bool is_fixed_position() const { return m_fixed_position; }
[[nodiscard]] bool is_sticky_position() const { return m_sticky_position; }
[[nodiscard]] bool is_absolutely_positioned() const { return m_absolutely_positioned; }
[[nodiscard]] bool is_floating() const { return m_floating; }
[[nodiscard]] bool is_inline() const { return m_inline; }
[[nodiscard]] CSS::Display display() const { return m_display; }
bool has_stacking_context() const;
RefPtr<StackingContext> enclosing_stacking_context();
virtual void paint(DisplayListRecordingContext&, PaintPhase) const { }
void paint_inspector_overlay(DisplayListRecordingContext&) const;
virtual bool forms_unconnected_subtree() const { return false; }
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y);
Layout::Node const& layout_node() const
{
VERIFY(m_layout_node);
return *m_layout_node;
}
Layout::Node& layout_node() { return const_cast<Layout::Node&>(const_cast<Paintable const&>(*this).layout_node()); }
[[nodiscard]] GC::Ptr<DOM::Node> dom_node();
[[nodiscard]] GC::Ptr<DOM::Node const> dom_node() const;
void set_dom_node(GC::Ptr<DOM::Node>);
CSS::ImmutableComputedValues const& computed_values() const;
bool visible_for_hit_testing() const;
GC::Ptr<HTML::Navigable> navigable() const;
virtual void set_needs_repaint(InvalidateDisplayList = InvalidateDisplayList::Yes);
RefPtr<PaintableBox> containing_block() const;
template<typename T>
bool fast_is() const = delete;
[[nodiscard]] virtual bool is_navigable_container_viewport_paintable() const { return false; }
[[nodiscard]] virtual bool is_viewport_paintable() const { return false; }
[[nodiscard]] virtual bool is_paintable_box() const { return false; }
[[nodiscard]] virtual bool is_paintable_with_lines() const { return false; }
[[nodiscard]] virtual bool is_svg_paintable() const { return false; }
[[nodiscard]] virtual bool is_svg_svg_paintable() const { return false; }
[[nodiscard]] virtual bool is_svg_path_paintable() const { return false; }
[[nodiscard]] virtual bool is_svg_graphics_paintable() const { return false; }
[[nodiscard]] virtual bool is_text_paintable() const { return false; }
DOM::Document const& document() const;
DOM::Document& document();
CSSPixelPoint box_type_agnostic_position() const;
void scroll_ancestor_to_offset_into_view(size_t offset);
enum class SelectionState : u8 {
None, // No selection
Start, // Selection starts in this Node
End, // Selection ends in this Node
StartAndEnd, // Selection starts and ends in this Node
Full, // Selection starts before and ends after this Node
};
SelectionState selection_state() const { return m_selection_state; }
void set_selection_state(SelectionState state);
// https://drafts.csswg.org/css-pseudo-4/#highlight-styling
struct TextDecorationStyle {
Vector<CSS::TextDecorationLine> line;
CSS::TextDecorationStyle style;
Color color;
};
struct SelectionStyle {
Color background_color;
Optional<Color> text_color {};
Optional<Vector<ShadowData>> text_shadow {};
Optional<TextDecorationStyle> text_decoration {};
bool has_styling() const
{
return background_color.alpha() > 0 || text_color.has_value() || text_shadow.has_value() || text_decoration.has_value();
}
};
[[nodiscard]] SelectionStyle selection_style() const;
[[nodiscard]] String debug_description() const;
friend class Layout::Node;
protected:
explicit Paintable(Layout::Node const&);
void paint_with_inspector_overlay_context(DisplayListRecordingContext&, Function<void()> const&) const;
bool has_layout_node() const { return m_layout_node; }
virtual void paint_inspector_overlay_internal(DisplayListRecordingContext&) const { }
Optional<WeakPtr<PaintableBox>> mutable m_containing_block;
private:
void detach_from_layout_node(Badge<Layout::Node>)
{
m_containing_block.clear();
m_layout_node.clear();
}
GC::Weak<DOM::Node> m_dom_node;
WeakPtr<Layout::Node const> m_layout_node;
SelectionState m_selection_state { SelectionState::None };
bool m_positioned : 1 { false };
bool m_fixed_position : 1 { false };
bool m_sticky_position : 1 { false };
bool m_absolutely_positioned : 1 { false };
bool m_floating : 1 { false };
bool m_inline : 1 { false };
CSS::Display m_display;
};
template<>
inline bool Paintable::fast_is<PaintableBox>() const { return is_paintable_box(); }
template<>
inline bool Paintable::fast_is<PaintableWithLines>() const { return is_paintable_with_lines(); }
template<>
inline bool Paintable::fast_is<TextPaintable>() const { return is_text_paintable(); }
WEB_API Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& border_rect, CSSPixelRect const& reference_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);
}