LibWeb: Allow inline nodes to establish a stacking context

With this change, a stacking context can be established by any
paintable, including inline paintables. The stacking context traversal
is updated to remove the assumption that the stacking context root is
paintable box.
This commit is contained in:
Aliaksandr Kalenik 2024-01-03 02:40:31 +01:00 committed by Andreas Kling
parent 6c645f3a9f
commit 3cf5ad002a
12 changed files with 253 additions and 151 deletions

View file

@ -0,0 +1,16 @@
<link rel="match" href="reference/inline-stacking-context-ref.html" /><style>
span {
z-index: 10;
background: orange;
position: relative;
opacity: 0.5;
}
div {
z-index: 5;
width: 50px;
height: 50px;
background: green;
position: relative;
top: -10px;
}
</style><span>hello</span><div></div>

View file

@ -0,0 +1,17 @@
<style>
#text {
z-index: 10;
background: orange;
position: relative;
opacity: 0.5;
width: fit-content;
}
#box {
z-index: 5;
width: 50px;
height: 50px;
background: green;
position: relative;
top: -10px;
}
</style><div id="text">hello</div><div id="box"></div>

View file

@ -0,0 +1 @@
hello true

View file

@ -0,0 +1,23 @@
<style>
span {
z-index: 10;
background: orange;
position: relative;
opacity: 0.5;
font-size: 100px;
}
div {
z-index: 5;
width: 100px;
height: 100px;
background: green;
position: relative;
top: -10px;
}
</style><span id="inline-stacking-context">hello</span><div></div>
<script src="../include.js"></script>
<script>
test(() => {
println(internals.hitTest(50, 50).node.parentNode === document.getElementById("inline-stacking-context"));
});
</script>

View file

@ -24,6 +24,8 @@ public:
CSSPixelRect bounding_rect() const;
virtual bool is_inline_paintable() const override { return true; }
void mark_contained_fragments();
private:

View file

@ -8,6 +8,7 @@
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Painting/Paintable.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/StackingContext.h>
namespace Web::Painting {
@ -17,6 +18,10 @@ Paintable::Paintable(Layout::Node const& layout_node)
{
}
Paintable::~Paintable()
{
}
void Paintable::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
@ -28,6 +33,11 @@ void Paintable::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_containing_block.value());
}
bool Paintable::is_visible() const
{
return computed_values().visibility() == CSS::Visibility::Visible && computed_values().opacity() != 0;
}
bool Paintable::is_positioned() const
{
if (layout_node().is_grid_item() && computed_values().z_index().has_value()) {
@ -88,11 +98,37 @@ Optional<HitTestResult> Paintable::hit_test(CSSPixelPoint, HitTestType) const
return {};
}
StackingContext const* Paintable::stacking_context_rooted_here() const
StackingContext* Paintable::enclosing_stacking_context()
{
if (!is<PaintableBox>(*this))
return nullptr;
return static_cast<PaintableBox const&>(*this).stacking_context();
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
if (auto* stacking_context = ancestor->stacking_context())
return const_cast<StackingContext*>(stacking_context);
}
// We should always reach the viewport's stacking context.
VERIFY_NOT_REACHED();
}
void Paintable::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context)
{
m_stacking_context = move(stacking_context);
}
void Paintable::invalidate_stacking_context()
{
m_stacking_context = nullptr;
}
PaintableBox const* Paintable::nearest_scrollable_ancestor_within_stacking_context() const
{
auto* ancestor = parent();
while (ancestor) {
if (ancestor->stacking_context())
return nullptr;
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
return static_cast<PaintableBox const*>(ancestor);
ancestor = ancestor->parent();
}
return nullptr;
}
}

View file

@ -54,8 +54,9 @@ class Paintable
JS_CELL(Paintable, Cell);
public:
virtual ~Paintable() = default;
virtual ~Paintable();
[[nodiscard]] bool is_visible() const;
[[nodiscard]] bool is_positioned() const;
[[nodiscard]] bool is_fixed_position() const { return layout_node().is_fixed_position(); }
[[nodiscard]] bool is_absolutely_positioned() const { return layout_node().is_absolutely_positioned(); }
@ -109,6 +110,13 @@ public:
return TraversalDecision::Continue;
}
StackingContext* stacking_context() { return m_stacking_context; }
StackingContext const* stacking_context() const { return m_stacking_context; }
void set_stacking_context(NonnullOwnPtr<StackingContext>);
StackingContext* enclosing_stacking_context();
void invalidate_stacking_context();
virtual void before_paint(PaintContext&, PaintPhase) const { }
virtual void after_paint(PaintContext&, PaintPhase) const { }
@ -171,8 +179,12 @@ public:
[[nodiscard]] virtual bool is_paintable_box() const { return false; }
[[nodiscard]] virtual bool is_paintable_with_lines() const { return false; }
[[nodiscard]] virtual bool is_inline_paintable() const { return false; }
StackingContext const* stacking_context_rooted_here() const;
DOM::Document const& document() const { return layout_node().document(); }
DOM::Document& document() { return layout_node().document(); }
PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const;
protected:
explicit Paintable(Layout::Node const&);
@ -184,6 +196,8 @@ private:
JS::NonnullGCPtr<Layout::Node const> m_layout_node;
JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
Optional<JS::GCPtr<Layout::Box const>> mutable m_containing_block;
OwnPtr<StackingContext> m_stacking_context;
};
inline DOM::Node* HitTestResult::dom_node()

View file

@ -40,16 +40,6 @@ PaintableBox::~PaintableBox()
{
}
bool PaintableBox::is_visible() const
{
return computed_values().visibility() == CSS::Visibility::Visible && computed_values().opacity() != 0;
}
void PaintableBox::invalidate_stacking_context()
{
m_stacking_context = nullptr;
}
PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box)
: PaintableBox(layout_box)
{
@ -165,16 +155,6 @@ CSSPixelRect PaintableBox::absolute_paint_rect() const
return *m_absolute_paint_rect;
}
StackingContext* PaintableBox::enclosing_stacking_context()
{
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
if (auto* stacking_context = ancestor->stacking_context_rooted_here())
return const_cast<StackingContext*>(stacking_context);
}
// We should always reach the viewport's stacking context.
VERIFY_NOT_REACHED();
}
Optional<CSSPixelRect> PaintableBox::get_clip_rect() const
{
auto clip = computed_values().clip();
@ -748,11 +728,6 @@ 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);
}
Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestType type) const
{
if (!is_visible())
@ -828,17 +803,4 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
return {};
}
PaintableBox const* PaintableBox::nearest_scrollable_ancestor_within_stacking_context() const
{
auto* ancestor = parent();
while (ancestor) {
if (ancestor->stacking_context_rooted_here())
return nullptr;
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
return static_cast<PaintableBox const*>(ancestor);
ancestor = ancestor->parent();
}
return nullptr;
}
}

View file

@ -25,8 +25,6 @@ public:
virtual void paint(PaintContext&, PaintPhase) const override;
[[nodiscard]] bool is_visible() const;
virtual Optional<CSSPixelRect> get_masking_area() const { return {}; }
virtual Optional<Gfx::Bitmap::MaskKind> get_mask_type() const { return {}; }
virtual RefPtr<Gfx::Bitmap> calculate_mask(PaintContext&, CSSPixelRect const&) const { return {}; }
@ -122,17 +120,9 @@ public:
void set_overflow_data(OverflowData data) { m_overflow_data = move(data); }
StackingContext* stacking_context() { return m_stacking_context; }
StackingContext const* stacking_context() const { return m_stacking_context; }
void set_stacking_context(NonnullOwnPtr<StackingContext>);
StackingContext* enclosing_stacking_context();
DOM::Node const* dom_node() const { return layout_box().dom_node(); }
DOM::Node* dom_node() { return layout_box().dom_node(); }
DOM::Document const& document() const { return layout_box().document(); }
DOM::Document& document() { return layout_box().document(); }
virtual void apply_scroll_offset(PaintContext&, PaintPhase) const override;
virtual void reset_scroll_offset(PaintContext&, PaintPhase) const override;
@ -143,8 +133,6 @@ public:
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override;
void invalidate_stacking_context();
enum class ConflictingElementKind {
Cell,
Row,
@ -194,8 +182,6 @@ public:
void set_box_shadow_data(Vector<ShadowData> box_shadow_data) { m_box_shadow_data = move(box_shadow_data); }
Vector<ShadowData> const& box_shadow_data() const { return m_box_shadow_data; }
PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const;
protected:
explicit PaintableBox(Layout::Box const&);
@ -217,8 +203,6 @@ private:
CSSPixelPoint m_offset;
CSSPixelSize m_content_size;
OwnPtr<StackingContext> m_stacking_context;
Optional<CSSPixelRect> mutable m_absolute_rect;
Optional<CSSPixelRect> mutable m_absolute_paint_rect;

View file

@ -31,9 +31,9 @@ static void paint_node(Paintable const& paintable, PaintContext& context, PaintP
paintable.after_paint(context, phase);
}
StackingContext::StackingContext(PaintableBox& paintable_box, StackingContext* parent, size_t index_in_tree_order)
: m_paintable_box(paintable_box)
, m_transform(combine_transformations(paintable_box.computed_values().transformations()))
StackingContext::StackingContext(Paintable& paintable, StackingContext* parent, size_t index_in_tree_order)
: m_paintable(paintable)
, m_transform(combine_transformations(paintable.computed_values().transformations()))
, m_transform_origin(compute_transform_origin())
, m_parent(parent)
, m_index_in_tree_order(index_in_tree_order)
@ -46,8 +46,8 @@ StackingContext::StackingContext(PaintableBox& paintable_box, StackingContext* p
void StackingContext::sort()
{
quick_sort(m_children, [](auto& a, auto& b) {
auto a_z_index = a->paintable_box().computed_values().z_index().value_or(0);
auto b_z_index = b->paintable_box().computed_values().z_index().value_or(0);
auto a_z_index = a->paintable().computed_values().z_index().value_or(0);
auto b_z_index = b->paintable().computed_values().z_index().value_or(0);
if (a_z_index == b_z_index)
return a->m_index_in_tree_order < b->m_index_in_tree_order;
return a_z_index < b_z_index;
@ -95,7 +95,7 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
paintable.apply_clip_overflow_rect(context, to_paint_phase(phase));
paintable.for_each_child([&context, phase](auto& child) {
auto* stacking_context = child.stacking_context_rooted_here();
auto* stacking_context = child.stacking_context();
auto const& z_index = child.computed_values().z_index();
// NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated
@ -176,11 +176,11 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
{
auto parent_paintable = child.paintable_box().parent();
auto parent_paintable = child.paintable().parent();
if (parent_paintable)
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
PaintableBox const* nearest_scrollable_ancestor = child.paintable_box().nearest_scrollable_ancestor_within_stacking_context();
PaintableBox const* nearest_scrollable_ancestor = child.paintable().nearest_scrollable_ancestor_within_stacking_context();
if (nearest_scrollable_ancestor)
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
@ -198,41 +198,39 @@ void StackingContext::paint_internal(PaintContext& context) const
{
// For a more elaborate description of the algorithm, see CSS 2.1 Appendix E
// Draw the background and borders for the context root (steps 1, 2)
paint_node(paintable_box(), context, PaintPhase::Background);
paint_node(paintable_box(), context, PaintPhase::Border);
paint_node(paintable(), context, PaintPhase::Background);
paint_node(paintable(), context, PaintPhase::Border);
// Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order
// (most negative first) then tree order. (step 3)
// NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts.
for (auto* child : m_children) {
if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() < 0)
if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() < 0)
paint_child(context, *child);
}
// Draw the background and borders for block-level children (step 4)
paint_descendants(context, paintable_box(), StackingContextPaintPhase::BackgroundAndBorders);
paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBorders);
// Draw the non-positioned floats (step 5)
paint_descendants(context, paintable_box(), StackingContextPaintPhase::Floats);
paint_descendants(context, paintable(), StackingContextPaintPhase::Floats);
// Draw inline content, replaced content, etc. (steps 6, 7)
paint_descendants(context, paintable_box(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
paint_node(paintable_box(), context, PaintPhase::Foreground);
paint_descendants(context, paintable_box(), StackingContextPaintPhase::Foreground);
paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced);
paint_node(paintable(), context, PaintPhase::Foreground);
paint_descendants(context, paintable(), StackingContextPaintPhase::Foreground);
// Draw positioned descendants with z-index `0` or `auto` in tree order. (step 8)
// FIXME: There's more to this step that we have yet to understand and implement.
paintable_box().for_each_in_subtree([&context](Paintable const& paintable) {
paintable().for_each_in_subtree([&context](Paintable const& paintable) {
auto const& z_index = paintable.computed_values().z_index();
if (!paintable.is_positioned() || (z_index.has_value() && z_index.value() != 0)) {
return paintable.stacking_context_rooted_here()
return paintable.stacking_context()
? TraversalDecision::SkipChildrenAndContinue
: TraversalDecision::Continue;
}
// Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant.
PaintableBox const* nearest_scrollable_ancestor = nullptr;
if (paintable.is_paintable_box())
nearest_scrollable_ancestor = static_cast<PaintableBox const&>(paintable).nearest_scrollable_ancestor_within_stacking_context();
PaintableBox const* nearest_scrollable_ancestor = paintable.nearest_scrollable_ancestor_within_stacking_context();
if (nearest_scrollable_ancestor)
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
@ -246,7 +244,7 @@ void StackingContext::paint_internal(PaintContext& context) const
auto* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
if (containing_block_paintable)
containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground);
if (auto* child = paintable.stacking_context_rooted_here()) {
if (auto* child = paintable.stacking_context()) {
paint_child(context, *child);
exit_decision = TraversalDecision::SkipChildrenAndContinue;
} else {
@ -267,16 +265,16 @@ void StackingContext::paint_internal(PaintContext& context) const
// (smallest first) then tree order. (Step 9)
// NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts.
for (auto* child : m_children) {
PaintableBox const* nearest_scrollable_ancestor = child->paintable_box().nearest_scrollable_ancestor_within_stacking_context();
PaintableBox const* nearest_scrollable_ancestor = child->paintable().nearest_scrollable_ancestor_within_stacking_context();
if (nearest_scrollable_ancestor)
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
auto containing_block = child->paintable_box().containing_block();
auto containing_block = child->paintable().containing_block();
auto const* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
if (containing_block_paintable)
containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground);
if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() >= 1)
if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() >= 1)
paint_child(context, *child);
if (containing_block_paintable)
containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground);
@ -285,20 +283,27 @@ void StackingContext::paint_internal(PaintContext& context) const
nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
}
paint_node(paintable_box(), context, PaintPhase::Outline);
paint_node(paintable(), context, PaintPhase::Outline);
if (context.should_paint_overlay()) {
paint_node(paintable_box(), context, PaintPhase::Overlay);
paint_descendants(context, paintable_box(), StackingContextPaintPhase::FocusAndOverlay);
paint_node(paintable(), context, PaintPhase::Overlay);
paint_descendants(context, paintable(), StackingContextPaintPhase::FocusAndOverlay);
}
}
Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector<CSS::Transformation> const& transformations) const
{
auto matrix = Gfx::FloatMatrix4x4::identity();
// https://drafts.csswg.org/css-transforms-1/#WD20171130 says:
// "No transform on non-replaced inline boxes, table-column boxes, and table-column-group boxes."
// and https://www.w3.org/TR/css-transforms-2/ does not say anything about what to do with inline boxes.
for (auto const& transform : transformations)
matrix = matrix * transform.to_matrix(paintable_box());
auto matrix = Gfx::FloatMatrix4x4::identity();
if (paintable().is_paintable_box()) {
for (auto const& transform : transformations)
matrix = matrix * transform.to_matrix(paintable_box());
return matrix;
}
return matrix;
}
@ -321,35 +326,46 @@ static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 ma
void StackingContext::paint(PaintContext& context) const
{
auto opacity = paintable_box().computed_values().opacity();
auto opacity = paintable().computed_values().opacity();
if (opacity == 0.0f)
return;
RecordingPainterStateSaver saver(context.recording_painter());
auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel());
Gfx::IntRect source_paintable_rect;
if (paintable().is_paintable_box()) {
source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>();
} else if (paintable().is_inline()) {
source_paintable_rect = context.enclosing_device_rect(inline_paintable().bounding_rect()).to_type<int>();
} else {
VERIFY_NOT_REACHED();
}
RecordingPainter::PushStackingContextParams push_stacking_context_params {
.opacity = opacity,
.is_fixed_position = paintable_box().is_fixed_position(),
.source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>(),
.image_rendering = paintable_box().computed_values().image_rendering(),
.is_fixed_position = paintable().is_fixed_position(),
.source_paintable_rect = source_paintable_rect,
.image_rendering = paintable().computed_values().image_rendering(),
.transform = {
.origin = transform_origin().scaled(to_device_pixels_scale),
.matrix = matrix_with_scaled_translation(transform_matrix(), to_device_pixels_scale),
},
};
if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
if (masking_area->is_empty())
return;
auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area);
if (mask_bitmap) {
auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type<int>();
push_stacking_context_params.source_paintable_rect = source_paintable_rect;
push_stacking_context_params.mask = StackingContextMask {
.mask_bitmap = mask_bitmap.release_nonnull(),
.mask_kind = *paintable_box().get_mask_type()
};
if (paintable().is_paintable_box()) {
if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
if (masking_area->is_empty())
return;
auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area);
if (mask_bitmap) {
auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type<int>();
push_stacking_context_params.source_paintable_rect = source_paintable_rect;
push_stacking_context_params.mask = StackingContextMask {
.mask_bitmap = mask_bitmap.release_nonnull(),
.mask_kind = *paintable_box().get_mask_type()
};
}
}
}
@ -360,39 +376,40 @@ void StackingContext::paint(PaintContext& context) const
Gfx::FloatPoint StackingContext::compute_transform_origin() const
{
auto style_value = paintable_box().computed_values().transform_origin();
if (!paintable().is_paintable_box())
return {};
auto style_value = paintable().computed_values().transform_origin();
// FIXME: respect transform-box property
auto reference_box = paintable_box().absolute_border_box_rect();
auto x = reference_box.left() + style_value.x.to_px(paintable_box().layout_node(), reference_box.width());
auto y = reference_box.top() + style_value.y.to_px(paintable_box().layout_node(), reference_box.height());
auto x = reference_box.left() + style_value.x.to_px(paintable().layout_node(), reference_box.width());
auto y = reference_box.top() + style_value.y.to_px(paintable().layout_node(), reference_box.height());
return { x.to_float(), y.to_float() };
}
template<typename U, typename Callback>
static TraversalDecision for_each_in_inclusive_subtree_of_type_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback)
template<typename Callback>
static TraversalDecision for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback)
{
if (paintable.stacking_context_rooted_here()) {
if (paintable.stacking_context()) {
// Note: Include the stacking context (so we can hit test it), but don't recurse into it.
if (auto decision = callback(static_cast<U const&>(paintable)); decision != TraversalDecision::Continue)
if (auto decision = callback(paintable); decision != TraversalDecision::Continue)
return decision;
return TraversalDecision::SkipChildrenAndContinue;
}
for (auto* child = paintable.last_child(); child; child = child->previous_sibling()) {
if (for_each_in_inclusive_subtree_of_type_within_same_stacking_context_in_reverse<U>(*child, callback) == TraversalDecision::Break)
if (for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break)
return TraversalDecision::Break;
}
if (is<U>(paintable)) {
if (auto decision = callback(static_cast<U const&>(paintable)); decision != TraversalDecision::Continue)
return decision;
}
if (auto decision = callback(paintable); decision != TraversalDecision::Continue)
return decision;
return TraversalDecision::Continue;
}
template<typename U, typename Callback>
static TraversalDecision for_each_in_subtree_of_type_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback)
template<typename Callback>
static TraversalDecision for_each_in_subtree_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback)
{
for (auto* child = paintable.last_child(); child; child = child->previous_sibling()) {
if (for_each_in_inclusive_subtree_of_type_within_same_stacking_context_in_reverse<U>(*child, callback) == TraversalDecision::Break)
if (for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break)
return TraversalDecision::Break;
}
return TraversalDecision::Continue;
@ -400,7 +417,7 @@ static TraversalDecision for_each_in_subtree_of_type_within_same_stacking_contex
Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTestType type) const
{
if (!paintable_box().is_visible())
if (!paintable().is_visible())
return {};
auto transform_origin = this->transform_origin().to_type<CSSPixels>();
@ -411,15 +428,17 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
};
auto transformed_position = affine_transform_matrix().inverse().value_or({}).map(offset_position).to_type<CSSPixels>() + transform_origin;
if (paintable_box().is_fixed_position()) {
auto scroll_offset = paintable_box().document().navigable()->viewport_scroll_offset();
if (paintable().is_fixed_position()) {
auto scroll_offset = paintable().document().navigable()->viewport_scroll_offset();
transformed_position.translate_by(-scroll_offset);
}
// FIXME: Support more overflow variations.
if (paintable_box().computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box().computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
return {};
if (paintable().computed_values().overflow_x() == CSS::Overflow::Hidden && paintable().computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (paintable().is_paintable_box()) {
if (!paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
return {};
}
}
// NOTE: Hit testing basically happens in reverse painting order.
@ -429,7 +448,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
// NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
auto const& child = *m_children[i];
if (child.paintable_box().computed_values().z_index().value_or(0) <= 0)
if (child.paintable().computed_values().z_index().value_or(0) <= 0)
break;
auto result = child.hit_test(transformed_position, type);
if (result.has_value() && result->paintable->visible_for_hit_testing())
@ -438,7 +457,12 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
// 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
Optional<HitTestResult> result;
for_each_in_subtree_of_type_within_same_stacking_context_in_reverse<PaintableBox>(paintable_box(), [&](PaintableBox const& paintable_box) {
for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) {
if (!paintable.is_paintable_box())
return TraversalDecision::Continue;
auto const& paintable_box = verify_cast<PaintableBox>(paintable);
// FIXME: Support more overflow variations.
if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paintable_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
@ -470,14 +494,19 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
return result;
// 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
if (paintable_box().layout_box().children_are_inline() && is<Layout::BlockContainer>(paintable_box().layout_box())) {
if (paintable().layout_node().children_are_inline() && is<Layout::BlockContainer>(paintable().layout_node())) {
auto result = paintable_box().hit_test(transformed_position, type);
if (result.has_value() && result->paintable->visible_for_hit_testing())
return result;
}
// 4. the non-positioned floats.
for_each_in_subtree_of_type_within_same_stacking_context_in_reverse<PaintableBox>(paintable_box(), [&](PaintableBox const& paintable_box) {
for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) {
if (!paintable.is_paintable_box())
return TraversalDecision::Continue;
auto const& paintable_box = verify_cast<PaintableBox>(paintable);
// FIXME: Support more overflow variations.
if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paintable_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
@ -496,8 +525,13 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
return result;
// 3. the in-flow, non-inline-level, non-positioned descendants.
if (!paintable_box().layout_box().children_are_inline()) {
for_each_in_subtree_of_type_within_same_stacking_context_in_reverse<PaintableBox>(paintable_box(), [&](PaintableBox const& paintable_box) {
if (!paintable().layout_node().children_are_inline()) {
for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) {
if (!paintable.is_paintable_box())
return TraversalDecision::Continue;
auto const& paintable_box = verify_cast<PaintableBox>(paintable);
// FIXME: Support more overflow variations.
if (paintable_box.computed_values().overflow_x() == CSS::Overflow::Hidden && paintable_box.computed_values().overflow_y() == CSS::Overflow::Hidden) {
if (!paintable_box.absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y()))
@ -520,7 +554,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
// NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
for (ssize_t i = m_children.size() - 1; i >= 0; --i) {
auto const& child = *m_children[i];
if (child.paintable_box().computed_values().z_index().value_or(0) >= 0)
if (child.paintable().computed_values().z_index().value_or(0) >= 0)
break;
auto result = child.hit_test(transformed_position, type);
if (result.has_value() && result->paintable->visible_for_hit_testing())
@ -528,10 +562,12 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
}
// 1. the background and borders of the element forming the stacking context.
if (paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) {
return HitTestResult {
.paintable = const_cast<PaintableBox&>(paintable_box()),
};
if (paintable().is_paintable_box()) {
if (paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) {
return HitTestResult {
.paintable = const_cast<PaintableBox&>(paintable_box()),
};
}
}
return {};
@ -542,9 +578,18 @@ void StackingContext::dump(int indent) const
StringBuilder builder;
for (int i = 0; i < indent; ++i)
builder.append(' ');
builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable_box().layout_box().debug_description(), paintable_box().absolute_rect(), m_children.size());
if (paintable_box().computed_values().z_index().has_value())
builder.appendff("{}", paintable_box().computed_values().z_index().value());
CSSPixelRect rect;
if (paintable().is_paintable_box()) {
rect = paintable_box().absolute_rect();
} else if (paintable().is_inline_paintable()) {
rect = inline_paintable().bounding_rect();
} else {
VERIFY_NOT_REACHED();
}
builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable().layout_node().debug_description(), rect, m_children.size());
if (paintable().computed_values().z_index().has_value())
builder.appendff("{}", paintable().computed_values().z_index().value());
else
builder.append("auto"sv);
builder.append(')');

View file

@ -8,18 +8,21 @@
#include <AK/Vector.h>
#include <LibGfx/Matrix4x4.h>
#include <LibWeb/Painting/InlinePaintable.h>
#include <LibWeb/Painting/Paintable.h>
namespace Web::Painting {
class StackingContext {
public:
StackingContext(PaintableBox&, StackingContext* parent, size_t index_in_tree_order);
StackingContext(Paintable&, StackingContext* parent, size_t index_in_tree_order);
StackingContext* parent() { return m_parent; }
StackingContext const* parent() const { return m_parent; }
PaintableBox const& paintable_box() const { return *m_paintable_box; }
Paintable const& paintable() const { return *m_paintable; }
PaintableBox const& paintable_box() const { return verify_cast<PaintableBox>(*m_paintable); }
InlinePaintable const& inline_paintable() const { return verify_cast<InlinePaintable>(*m_paintable); }
enum class StackingContextPaintPhase {
BackgroundAndBorders,
@ -42,7 +45,7 @@ public:
void sort();
private:
JS::NonnullGCPtr<PaintableBox> m_paintable_box;
JS::NonnullGCPtr<Paintable> m_paintable;
Gfx::FloatMatrix4x4 m_transform;
Gfx::FloatPoint m_transform_origin;
StackingContext* const m_parent { nullptr };

View file

@ -34,16 +34,15 @@ void ViewportPaintable::build_stacking_context_tree()
set_stacking_context(make<StackingContext>(*this, nullptr, 0));
size_t index_in_tree_order = 1;
for_each_in_subtree_of_type<PaintableBox>([&](PaintableBox const& paintable) {
auto& paintable_box = const_cast<PaintableBox&>(paintable);
paintable_box.invalidate_stacking_context();
if (!paintable_box.layout_box().establishes_stacking_context()) {
VERIFY(!paintable_box.stacking_context());
for_each_in_subtree([&](Paintable const& paintable) {
const_cast<Paintable&>(paintable).invalidate_stacking_context();
if (!paintable.layout_node().establishes_stacking_context()) {
VERIFY(!paintable.stacking_context());
return TraversalDecision::Continue;
}
auto* parent_context = paintable_box.enclosing_stacking_context();
auto* parent_context = const_cast<Paintable&>(paintable).enclosing_stacking_context();
VERIFY(parent_context);
paintable_box.set_stacking_context(make<Painting::StackingContext>(paintable_box, parent_context, index_in_tree_order++));
const_cast<Paintable&>(paintable).set_stacking_context(make<Painting::StackingContext>(const_cast<Paintable&>(paintable), parent_context, index_in_tree_order++));
return TraversalDecision::Continue;
});