mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-06 03:02:55 +00:00
Since paintables have a default content size of 0x0, we were neglecting to notify the corresponding layout node about size changes, if the used content size came out to 0x0. This fixes an issue where resizing an iframe to 0x0 didn't take effect.
566 lines
25 KiB
C++
566 lines
25 KiB
C++
/*
|
||
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <LibWeb/DOM/Document.h>
|
||
#include <LibWeb/HTML/HTMLHtmlElement.h>
|
||
#include <LibWeb/Layout/BlockContainer.h>
|
||
#include <LibWeb/Painting/BackgroundPainting.h>
|
||
#include <LibWeb/Painting/PaintableBox.h>
|
||
#include <LibWeb/Painting/ShadowPainting.h>
|
||
#include <LibWeb/Painting/StackingContext.h>
|
||
|
||
namespace Web::Painting {
|
||
|
||
NonnullRefPtr<PaintableBox> PaintableBox::create(Layout::Box const& layout_box)
|
||
{
|
||
return adopt_ref(*new PaintableBox(layout_box));
|
||
}
|
||
|
||
PaintableBox::PaintableBox(Layout::Box const& layout_box)
|
||
: Paintable(layout_box)
|
||
{
|
||
}
|
||
|
||
PaintableBox::~PaintableBox()
|
||
{
|
||
}
|
||
|
||
PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box)
|
||
: PaintableBox(layout_box)
|
||
{
|
||
}
|
||
|
||
PaintableWithLines::~PaintableWithLines()
|
||
{
|
||
}
|
||
|
||
void PaintableBox::set_offset(const Gfx::FloatPoint& offset)
|
||
{
|
||
m_offset = offset;
|
||
// FIXME: This const_cast is gross.
|
||
const_cast<Layout::Box&>(layout_box()).did_set_rect();
|
||
}
|
||
|
||
void PaintableBox::set_content_size(Gfx::FloatSize const& size)
|
||
{
|
||
m_content_size = size;
|
||
// FIXME: This const_cast is gross.
|
||
const_cast<Layout::Box&>(layout_box()).did_set_rect();
|
||
}
|
||
|
||
Gfx::FloatPoint PaintableBox::effective_offset() const
|
||
{
|
||
if (m_containing_line_box_fragment.has_value()) {
|
||
auto const& fragment = containing_block()->paint_box()->line_boxes()[m_containing_line_box_fragment->line_box_index].fragments()[m_containing_line_box_fragment->fragment_index];
|
||
return fragment.offset();
|
||
}
|
||
return m_offset;
|
||
}
|
||
|
||
Gfx::FloatRect PaintableBox::absolute_rect() const
|
||
{
|
||
if (!m_absolute_rect.has_value()) {
|
||
Gfx::FloatRect rect { effective_offset(), content_size() };
|
||
for (auto const* block = containing_block(); block && block->paintable(); block = block->paintable()->containing_block())
|
||
rect.translate_by(block->paint_box()->effective_offset());
|
||
m_absolute_rect = rect;
|
||
}
|
||
return *m_absolute_rect;
|
||
}
|
||
|
||
void PaintableBox::set_containing_line_box_fragment(Optional<Layout::LineBoxFragmentCoordinate> fragment_coordinate)
|
||
{
|
||
m_containing_line_box_fragment = fragment_coordinate;
|
||
}
|
||
|
||
Painting::StackingContext* PaintableBox::enclosing_stacking_context()
|
||
{
|
||
for (auto* ancestor = layout_box().parent(); ancestor; ancestor = ancestor->parent()) {
|
||
if (!is<Layout::Box>(ancestor))
|
||
continue;
|
||
auto& ancestor_box = static_cast<Layout::Box&>(const_cast<Layout::NodeWithStyle&>(*ancestor));
|
||
if (auto* ancestor_paint_box = ancestor_box.paint_box(); ancestor_paint_box && ancestor_paint_box->stacking_context())
|
||
return const_cast<StackingContext*>(ancestor_paint_box->stacking_context());
|
||
}
|
||
// We should always reach the Layout::InitialContainingBlock stacking context.
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
|
||
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
|
||
{
|
||
if (!is_visible())
|
||
return;
|
||
|
||
if (phase == PaintPhase::Background) {
|
||
paint_background(context);
|
||
paint_box_shadow(context);
|
||
}
|
||
|
||
if (phase == PaintPhase::Border) {
|
||
paint_border(context);
|
||
}
|
||
|
||
if (phase == PaintPhase::Overlay && layout_box().dom_node() && layout_box().document().inspected_node() == layout_box().dom_node()) {
|
||
auto content_rect = absolute_rect();
|
||
|
||
auto margin_box = box_model().margin_box();
|
||
Gfx::FloatRect margin_rect;
|
||
margin_rect.set_x(absolute_x() - margin_box.left);
|
||
margin_rect.set_width(content_width() + margin_box.left + margin_box.right);
|
||
margin_rect.set_y(absolute_y() - margin_box.top);
|
||
margin_rect.set_height(content_height() + margin_box.top + margin_box.bottom);
|
||
|
||
auto border_rect = absolute_border_box_rect();
|
||
auto padding_rect = absolute_padding_box_rect();
|
||
|
||
auto paint_inspector_rect = [&](Gfx::FloatRect const& rect, Color color) {
|
||
context.painter().fill_rect(enclosing_int_rect(rect), Color(color).with_alpha(100));
|
||
context.painter().draw_rect(enclosing_int_rect(rect), Color(color));
|
||
};
|
||
|
||
paint_inspector_rect(margin_rect, Color::Yellow);
|
||
paint_inspector_rect(padding_rect, Color::Cyan);
|
||
paint_inspector_rect(border_rect, Color::Green);
|
||
paint_inspector_rect(content_rect, Color::Magenta);
|
||
|
||
StringBuilder builder;
|
||
if (layout_box().dom_node())
|
||
builder.append(layout_box().dom_node()->debug_description());
|
||
else
|
||
builder.append(layout_box().debug_description());
|
||
builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y());
|
||
auto size_text = builder.to_string();
|
||
auto size_text_rect = border_rect;
|
||
size_text_rect.set_y(border_rect.y() + border_rect.height());
|
||
size_text_rect.set_top(size_text_rect.top());
|
||
size_text_rect.set_width((float)context.painter().font().width(size_text) + 4);
|
||
size_text_rect.set_height(context.painter().font().glyph_height() + 4);
|
||
context.painter().fill_rect(enclosing_int_rect(size_text_rect), context.palette().color(Gfx::ColorRole::Tooltip));
|
||
context.painter().draw_rect(enclosing_int_rect(size_text_rect), context.palette().threed_shadow1());
|
||
context.painter().draw_text(enclosing_int_rect(size_text_rect), size_text, Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText));
|
||
}
|
||
|
||
if (phase == PaintPhase::FocusOutline && layout_box().dom_node() && layout_box().dom_node()->is_element() && verify_cast<DOM::Element>(*layout_box().dom_node()).is_focused()) {
|
||
context.painter().draw_rect(enclosing_int_rect(absolute_rect()), context.palette().focus_outline());
|
||
}
|
||
}
|
||
|
||
void PaintableBox::paint_border(PaintContext& context) const
|
||
{
|
||
auto borders_data = BordersData {
|
||
.top = computed_values().border_top(),
|
||
.right = computed_values().border_right(),
|
||
.bottom = computed_values().border_bottom(),
|
||
.left = computed_values().border_left(),
|
||
};
|
||
paint_all_borders(context, absolute_border_box_rect(), normalized_border_radius_data(), borders_data);
|
||
}
|
||
|
||
void PaintableBox::paint_background(PaintContext& context) const
|
||
{
|
||
// If the body's background properties were propagated to the root element, do no re-paint the body's background.
|
||
if (layout_box().is_body() && document().html_element()->should_use_body_background_properties())
|
||
return;
|
||
|
||
Gfx::IntRect background_rect;
|
||
Color background_color = computed_values().background_color();
|
||
auto* background_layers = &computed_values().background_layers();
|
||
|
||
if (layout_box().is_root_element()) {
|
||
// CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas.
|
||
background_rect = context.viewport_rect();
|
||
|
||
// Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent,
|
||
// user agents must instead propagate the computed values of the background properties from that element’s first HTML BODY child element.
|
||
if (document().html_element()->should_use_body_background_properties()) {
|
||
background_layers = document().background_layers();
|
||
background_color = document().background_color(context.palette());
|
||
}
|
||
} else {
|
||
background_rect = enclosing_int_rect(absolute_padding_box_rect());
|
||
}
|
||
|
||
// HACK: If the Box has a border, use the bordered_rect to paint the background.
|
||
// This way if we have a border-radius there will be no gap between the filling and actual border.
|
||
if (computed_values().border_top().width || computed_values().border_right().width || computed_values().border_bottom().width || computed_values().border_left().width)
|
||
background_rect = enclosing_int_rect(absolute_border_box_rect());
|
||
|
||
Painting::paint_background(context, layout_box(), background_rect, background_color, background_layers, normalized_border_radius_data());
|
||
}
|
||
|
||
void PaintableBox::paint_box_shadow(PaintContext& context) const
|
||
{
|
||
auto box_shadow_data = computed_values().box_shadow();
|
||
if (box_shadow_data.is_empty())
|
||
return;
|
||
|
||
Vector<BoxShadowData> resolved_box_shadow_data;
|
||
resolved_box_shadow_data.ensure_capacity(box_shadow_data.size());
|
||
for (auto const& layer : box_shadow_data) {
|
||
resolved_box_shadow_data.empend(
|
||
layer.color,
|
||
static_cast<int>(layer.offset_x.to_px(layout_box())),
|
||
static_cast<int>(layer.offset_y.to_px(layout_box())),
|
||
static_cast<int>(layer.blur_radius.to_px(layout_box())),
|
||
static_cast<int>(layer.spread_distance.to_px(layout_box())),
|
||
layer.placement == CSS::BoxShadowPlacement::Outer ? BoxShadowPlacement::Outer : BoxShadowPlacement::Inner);
|
||
}
|
||
Painting::paint_box_shadow(context, enclosing_int_rect(absolute_border_box_rect()), resolved_box_shadow_data);
|
||
}
|
||
|
||
BorderRadiusData PaintableBox::normalized_border_radius_data() const
|
||
{
|
||
return Painting::normalized_border_radius_data(layout_box(), absolute_border_box_rect(),
|
||
computed_values().border_top_left_radius(),
|
||
computed_values().border_top_right_radius(),
|
||
computed_values().border_bottom_right_radius(),
|
||
computed_values().border_bottom_left_radius());
|
||
}
|
||
|
||
void PaintableBox::before_children_paint(PaintContext& context, PaintPhase) const
|
||
{
|
||
// FIXME: Support more overflow variations.
|
||
if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden) {
|
||
context.painter().save();
|
||
context.painter().add_clip_rect(enclosing_int_rect(absolute_border_box_rect()));
|
||
}
|
||
}
|
||
|
||
void PaintableBox::after_children_paint(PaintContext& context, PaintPhase) const
|
||
{
|
||
// FIXME: Support more overflow variations.
|
||
if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden)
|
||
context.painter().restore();
|
||
}
|
||
|
||
static void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_node, Layout::LineBoxFragment const& fragment)
|
||
{
|
||
auto const& browsing_context = text_node.browsing_context();
|
||
|
||
if (!browsing_context.is_focused_context())
|
||
return;
|
||
|
||
if (!browsing_context.cursor_blink_state())
|
||
return;
|
||
|
||
if (browsing_context.cursor_position().node() != &text_node.dom_node())
|
||
return;
|
||
|
||
// NOTE: This checks if the cursor is before the start or after the end of the fragment. If it is at the end, after all text, it should still be painted.
|
||
if (browsing_context.cursor_position().offset() < (unsigned)fragment.start() || browsing_context.cursor_position().offset() > (unsigned)(fragment.start() + fragment.length()))
|
||
return;
|
||
|
||
if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable())
|
||
return;
|
||
|
||
auto fragment_rect = fragment.absolute_rect();
|
||
|
||
float cursor_x = fragment_rect.x() + text_node.font().width(fragment.text().substring_view(0, text_node.browsing_context().cursor_position().offset() - fragment.start()));
|
||
float cursor_top = fragment_rect.top();
|
||
float cursor_height = fragment_rect.height();
|
||
Gfx::IntRect cursor_rect(cursor_x, cursor_top, 1, cursor_height);
|
||
|
||
context.painter().draw_rect(cursor_rect, text_node.computed_values().color());
|
||
}
|
||
|
||
static void paint_text_decoration(Gfx::Painter& painter, Layout::Node const& text_node, Layout::LineBoxFragment const& fragment)
|
||
{
|
||
Gfx::IntPoint line_start_point {};
|
||
Gfx::IntPoint line_end_point {};
|
||
|
||
auto& font = fragment.layout_node().font();
|
||
auto fragment_box = enclosing_int_rect(fragment.absolute_rect());
|
||
auto glyph_height = font.glyph_height();
|
||
auto baseline = fragment_box.height() / 2 - (glyph_height + 4) / 2 + glyph_height;
|
||
|
||
switch (text_node.computed_values().text_decoration_line()) {
|
||
case CSS::TextDecorationLine::None:
|
||
return;
|
||
case CSS::TextDecorationLine::Underline:
|
||
line_start_point = fragment_box.top_left().translated(0, baseline + 2);
|
||
line_end_point = fragment_box.top_right().translated(0, baseline + 2);
|
||
break;
|
||
case CSS::TextDecorationLine::Overline:
|
||
line_start_point = fragment_box.top_left().translated(0, baseline - glyph_height);
|
||
line_end_point = fragment_box.top_right().translated(0, baseline - glyph_height);
|
||
break;
|
||
case CSS::TextDecorationLine::LineThrough: {
|
||
auto x_height = font.x_height();
|
||
line_start_point = fragment_box.top_left().translated(0, baseline - x_height / 2);
|
||
line_end_point = fragment_box.top_right().translated(0, baseline - x_height / 2);
|
||
break;
|
||
}
|
||
case CSS::TextDecorationLine::Blink:
|
||
// Conforming user agents may simply not blink the text
|
||
return;
|
||
}
|
||
|
||
auto line_color = text_node.computed_values().text_decoration_color();
|
||
|
||
int line_thickness = [&] {
|
||
CSS::Length computed_thickness = text_node.computed_values().text_decoration_thickness().resolved(text_node, CSS::Length(1, CSS::Length::Type::Em));
|
||
if (computed_thickness.is_auto())
|
||
return CSS::InitialValues::text_decoration_thickness().to_px(text_node);
|
||
|
||
return computed_thickness.to_px(text_node);
|
||
}();
|
||
|
||
switch (text_node.computed_values().text_decoration_style()) {
|
||
case CSS::TextDecorationStyle::Solid:
|
||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Solid);
|
||
break;
|
||
case CSS::TextDecorationStyle::Double:
|
||
switch (text_node.computed_values().text_decoration_line()) {
|
||
case CSS::TextDecorationLine::Underline:
|
||
break;
|
||
case CSS::TextDecorationLine::Overline:
|
||
line_start_point.translate_by(0, -line_thickness - 1);
|
||
line_end_point.translate_by(0, -line_thickness - 1);
|
||
break;
|
||
case CSS::TextDecorationLine::LineThrough:
|
||
line_start_point.translate_by(0, -line_thickness / 2);
|
||
line_end_point.translate_by(0, -line_thickness / 2);
|
||
break;
|
||
default:
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
|
||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness);
|
||
painter.draw_line(line_start_point.translated(0, line_thickness + 1), line_end_point.translated(0, line_thickness + 1), line_color, line_thickness);
|
||
break;
|
||
case CSS::TextDecorationStyle::Dashed:
|
||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Dashed);
|
||
break;
|
||
case CSS::TextDecorationStyle::Dotted:
|
||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Dotted);
|
||
break;
|
||
case CSS::TextDecorationStyle::Wavy:
|
||
painter.draw_triangle_wave(line_start_point, line_end_point, line_color, line_thickness + 1, line_thickness);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_node, Layout::LineBoxFragment const& fragment, Painting::PaintPhase phase)
|
||
{
|
||
auto& painter = context.painter();
|
||
|
||
if (phase == Painting::PaintPhase::Foreground) {
|
||
auto fragment_absolute_rect = fragment.absolute_rect();
|
||
|
||
painter.set_font(text_node.font());
|
||
|
||
if (text_node.document().inspected_node() == &text_node.dom_node())
|
||
context.painter().draw_rect(enclosing_int_rect(fragment_absolute_rect), Color::Magenta);
|
||
|
||
// FIXME: text-transform should be done already in layout, since uppercase glyphs may be wider than lowercase, etc.
|
||
auto text = text_node.text_for_rendering();
|
||
auto text_transform = text_node.computed_values().text_transform();
|
||
if (text_transform == CSS::TextTransform::Uppercase)
|
||
text = text_node.text_for_rendering().to_uppercase();
|
||
if (text_transform == CSS::TextTransform::Lowercase)
|
||
text = text_node.text_for_rendering().to_lowercase();
|
||
|
||
// FIXME: This is a hack to prevent text clipping when painting a bitmap font into a too-small box.
|
||
auto draw_rect = enclosing_int_rect(fragment_absolute_rect);
|
||
draw_rect.set_height(max(draw_rect.height(), text_node.font().glyph_height()));
|
||
painter.draw_text(draw_rect, text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::CenterLeft, text_node.computed_values().color());
|
||
|
||
auto selection_rect = fragment.selection_rect(text_node.font());
|
||
if (!selection_rect.is_empty()) {
|
||
painter.fill_rect(enclosing_int_rect(selection_rect), context.palette().selection());
|
||
Gfx::PainterStateSaver saver(painter);
|
||
painter.add_clip_rect(enclosing_int_rect(selection_rect));
|
||
painter.draw_text(enclosing_int_rect(fragment_absolute_rect), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::CenterLeft, context.palette().selection_text());
|
||
}
|
||
|
||
paint_text_decoration(painter, text_node, fragment);
|
||
paint_cursor_if_needed(context, text_node, fragment);
|
||
}
|
||
}
|
||
|
||
void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
|
||
{
|
||
if (!is_visible())
|
||
return;
|
||
|
||
PaintableBox::paint(context, phase);
|
||
|
||
if (m_line_boxes.is_empty())
|
||
return;
|
||
|
||
bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible;
|
||
|
||
if (should_clip_overflow) {
|
||
context.painter().save();
|
||
// FIXME: Handle overflow-x and overflow-y being different values.
|
||
context.painter().add_clip_rect(enclosing_int_rect(absolute_padding_box_rect()));
|
||
auto scroll_offset = static_cast<Layout::BlockContainer const&>(layout_box()).scroll_offset();
|
||
context.painter().translate(-scroll_offset.to_type<int>());
|
||
}
|
||
|
||
for (auto& line_box : m_line_boxes) {
|
||
for (auto& fragment : line_box.fragments()) {
|
||
if (context.should_show_line_box_borders())
|
||
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green);
|
||
if (is<Layout::TextNode>(fragment.layout_node()))
|
||
paint_text_fragment(context, static_cast<Layout::TextNode const&>(fragment.layout_node()), fragment, phase);
|
||
}
|
||
}
|
||
|
||
if (should_clip_overflow) {
|
||
context.painter().restore();
|
||
}
|
||
|
||
// FIXME: Merge this loop with the above somehow..
|
||
if (phase == PaintPhase::FocusOutline) {
|
||
for (auto& line_box : m_line_boxes) {
|
||
for (auto& fragment : line_box.fragments()) {
|
||
auto* node = fragment.layout_node().dom_node();
|
||
if (!node)
|
||
continue;
|
||
auto* parent = node->parent_element();
|
||
if (!parent)
|
||
continue;
|
||
if (parent->is_focused())
|
||
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), context.palette().focus_outline());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool PaintableWithLines::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
|
||
{
|
||
if (!layout_box().is_scrollable())
|
||
return false;
|
||
auto new_offset = layout_box().scroll_offset();
|
||
new_offset.translate_by(wheel_delta_x, wheel_delta_y);
|
||
const_cast<Layout::BlockContainer&>(layout_box()).set_scroll_offset(new_offset);
|
||
return true;
|
||
}
|
||
|
||
Layout::BlockContainer const& PaintableWithLines::layout_box() const
|
||
{
|
||
return static_cast<Layout::BlockContainer const&>(PaintableBox::layout_box());
|
||
}
|
||
|
||
Layout::BlockContainer& PaintableWithLines::layout_box()
|
||
{
|
||
return static_cast<Layout::BlockContainer&>(PaintableBox::layout_box());
|
||
}
|
||
|
||
void PaintableBox::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context)
|
||
{
|
||
m_stacking_context = move(stacking_context);
|
||
}
|
||
|
||
template<typename Callback>
|
||
void PaintableBox::for_each_child_in_paint_order(Callback callback) const
|
||
{
|
||
// Element traversal using the order defined in https://www.w3.org/TR/CSS2/zindex.html#painting-order.
|
||
// Note: Some steps are skipped because they are not relevant to node traversal.
|
||
|
||
// 3. Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order
|
||
// (most negative first) then tree order.
|
||
// FIXME: This does not retrieve elements in the z-index order.
|
||
layout_box().for_each_child([&](auto& child) {
|
||
if (!child.is_positioned() || !is<Layout::Box>(child))
|
||
return;
|
||
|
||
auto& box_child = verify_cast<Layout::Box>(child);
|
||
auto* stacking_context = box_child.paint_box()->stacking_context();
|
||
if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() < 0)
|
||
callback(child);
|
||
});
|
||
|
||
// 4. For all its in-flow, non-positioned, block-level descendants in tree order: If the element is a block, list-item,
|
||
// or other block equivalent:
|
||
layout_box().for_each_child([&](auto& child) {
|
||
if (is<Layout::Box>(child) && verify_cast<Layout::Box>(child).paint_box()->stacking_context())
|
||
return;
|
||
if (!child.is_positioned())
|
||
callback(child);
|
||
});
|
||
|
||
// 5. All non-positioned floating descendants, in tree order. For each one of these, treat the element as if it created
|
||
// a new stacking context, but any positioned descendants and descendants which actually create a new stacking context
|
||
// should be considered part of the parent stacking context, not this new one.
|
||
layout_box().for_each_child([&](auto& child) {
|
||
if (is<Layout::Box>(child) && verify_cast<Layout::Box>(child).paint_box()->stacking_context())
|
||
return;
|
||
if (child.is_positioned())
|
||
callback(child);
|
||
});
|
||
|
||
// 8. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order. For those with 'z-index: auto', treat
|
||
// the element as if it created a new stacking context, but any positioned descendants and descendants which actually
|
||
// create a new stacking context should be considered part of the parent stacking context, not this new one. For those
|
||
// with 'z-index: 0', treat the stacking context generated atomically.
|
||
layout_box().for_each_child([&](auto& child) {
|
||
if (!child.is_positioned() || !is<Layout::Box>(child))
|
||
return;
|
||
|
||
auto& box_child = verify_cast<Layout::Box>(child);
|
||
auto* stacking_context = box_child.paint_box()->stacking_context();
|
||
if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() == 0)
|
||
callback(child);
|
||
});
|
||
|
||
// 9. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order
|
||
// (smallest first) then tree order.
|
||
// FIXME: This does not retrieve elements in the z-index order.
|
||
layout_box().for_each_child([&](auto& child) {
|
||
if (!child.is_positioned() || !is<Layout::Box>(child))
|
||
return;
|
||
|
||
auto& box_child = verify_cast<Layout::Box>(child);
|
||
auto* stacking_context = box_child.paint_box()->stacking_context();
|
||
if (stacking_context && box_child.computed_values().z_index().has_value() && box_child.computed_values().z_index().value() > 0)
|
||
callback(child);
|
||
});
|
||
}
|
||
|
||
HitTestResult PaintableBox::hit_test(Gfx::FloatPoint const& position, HitTestType type) const
|
||
{
|
||
if (layout_box().is_initial_containing_block_box())
|
||
return stacking_context()->hit_test(position, type);
|
||
|
||
HitTestResult result { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
|
||
for_each_child_in_paint_order([&](auto& child) {
|
||
if (child.paintable()) {
|
||
auto child_result = child.paintable()->hit_test(position, type);
|
||
if (child_result.paintable)
|
||
result = child_result;
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
|
||
HitTestResult PaintableWithLines::hit_test(const Gfx::FloatPoint& position, HitTestType type) const
|
||
{
|
||
if (!layout_box().children_are_inline())
|
||
return PaintableBox::hit_test(position, type);
|
||
|
||
HitTestResult last_good_candidate;
|
||
for (auto& line_box : m_line_boxes) {
|
||
for (auto& fragment : line_box.fragments()) {
|
||
if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paint_box()->stacking_context())
|
||
continue;
|
||
if (fragment.absolute_rect().contains(position)) {
|
||
if (is<Layout::BlockContainer>(fragment.layout_node()) && fragment.layout_node().paintable())
|
||
return fragment.layout_node().paintable()->hit_test(position, type);
|
||
return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) };
|
||
}
|
||
if (fragment.absolute_rect().top() <= position.y())
|
||
last_good_candidate = { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) };
|
||
}
|
||
}
|
||
|
||
if (type == HitTestType::TextCursor && last_good_candidate.paintable)
|
||
return last_good_candidate;
|
||
return { absolute_border_box_rect().contains(position.x(), position.y()) ? this : nullptr };
|
||
}
|
||
|
||
}
|