mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-23 01:12:45 +00:00
LibWeb: Allow <svg>
to establish a stacking context
83b6bc4
went too far by forbidding SVGSVGElement from establishing a
stacking context. This element type does follow the behavior of CSS
boxes, unlike inner SVG elements like `<rect>`, `<circle>`, etc., which
are not supposed to be aware of concepts like stacking contexts,
overflow clipping, scroll offsets, etc.
This change allows us to delete overrides of `before_paint()` and
`after_paint()` in SVGPaintable and SVGSVGPaintable, because display
list recording code has been rearranged to take care of clipping and
scrolling before recursing into SVGSVGPaintable descendants.
`Screenshot/images/css-transform-box-ref.png` expectation is updated and
fixes a bug where a rectangle at the very bottom of the page was not
clipped correctly.
`Screenshot/images/svg-filters-lb-website-ref.png` has a more subtle
difference, but if you look closely, you’ll see it matches other
browsers more closely now.
This commit is contained in:
parent
79293a3cbc
commit
910fd426a2
Notes:
github-actions[bot]
2025-07-12 09:02:17 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 910fd426a2
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5413
Reviewed-by: https://github.com/gmta ✅
10 changed files with 87 additions and 60 deletions
|
@ -174,7 +174,7 @@ bool Node::establishes_stacking_context() const
|
||||||
if (!has_style())
|
if (!has_style())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (is_svg_box() || is_svg_svg_box())
|
if (is_svg_box())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// We make a stacking context for the viewport. Painting and hit testing starts from here.
|
// We make a stacking context for the viewport. Painting and hit testing starts from here.
|
||||||
|
|
|
@ -17,26 +17,6 @@ SVGPaintable::SVGPaintable(Layout::SVGBox const& layout_box)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SVGPaintable::before_paint(PaintContext& context, PaintPhase phase) const
|
|
||||||
{
|
|
||||||
if (!is_visible())
|
|
||||||
return;
|
|
||||||
if (!has_css_transform()) {
|
|
||||||
apply_clip_overflow_rect(context, phase);
|
|
||||||
}
|
|
||||||
apply_scroll_offset(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SVGPaintable::after_paint(PaintContext& context, PaintPhase phase) const
|
|
||||||
{
|
|
||||||
if (!is_visible())
|
|
||||||
return;
|
|
||||||
reset_scroll_offset(context);
|
|
||||||
if (!has_css_transform()) {
|
|
||||||
clear_clip_overflow_rect(context, phase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout::SVGBox const& SVGPaintable::layout_box() const
|
Layout::SVGBox const& SVGPaintable::layout_box() const
|
||||||
{
|
{
|
||||||
return static_cast<Layout::SVGBox const&>(layout_node());
|
return static_cast<Layout::SVGBox const&>(layout_node());
|
||||||
|
|
|
@ -22,9 +22,6 @@ protected:
|
||||||
|
|
||||||
SVGPaintable(Layout::SVGBox const&);
|
SVGPaintable(Layout::SVGBox const&);
|
||||||
|
|
||||||
virtual void before_paint(PaintContext&, PaintPhase) const override;
|
|
||||||
virtual void after_paint(PaintContext&, PaintPhase) const override;
|
|
||||||
|
|
||||||
virtual CSSPixelRect compute_absolute_rect() const override;
|
virtual CSSPixelRect compute_absolute_rect() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,26 +24,6 @@ SVGSVGPaintable::SVGSVGPaintable(Layout::SVGSVGBox const& layout_box)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SVGSVGPaintable::before_paint(PaintContext& context, PaintPhase phase) const
|
|
||||||
{
|
|
||||||
if (!is_visible())
|
|
||||||
return;
|
|
||||||
if (!has_css_transform()) {
|
|
||||||
apply_clip_overflow_rect(context, phase);
|
|
||||||
}
|
|
||||||
apply_scroll_offset(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SVGSVGPaintable::after_paint(PaintContext& context, PaintPhase phase) const
|
|
||||||
{
|
|
||||||
if (!is_visible())
|
|
||||||
return;
|
|
||||||
reset_scroll_offset(context);
|
|
||||||
if (!has_css_transform()) {
|
|
||||||
clear_clip_overflow_rect(context, phase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout::SVGSVGBox const& SVGSVGPaintable::layout_box() const
|
Layout::SVGSVGBox const& SVGSVGPaintable::layout_box() const
|
||||||
{
|
{
|
||||||
return static_cast<Layout::SVGSVGBox const&>(layout_node());
|
return static_cast<Layout::SVGSVGBox const&>(layout_node());
|
||||||
|
@ -107,10 +87,7 @@ void SVGSVGPaintable::paint_svg_box(PaintContext& context, PaintableBox const& s
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skip_painting) {
|
if (!skip_painting) {
|
||||||
svg_box.before_paint(context, PaintPhase::Foreground);
|
|
||||||
svg_box.paint(context, PaintPhase::Foreground);
|
svg_box.paint(context, PaintPhase::Foreground);
|
||||||
svg_box.after_paint(context, PaintPhase::Foreground);
|
|
||||||
|
|
||||||
paint_descendants(context, svg_box, phase);
|
paint_descendants(context, svg_box, phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,6 @@ public:
|
||||||
static void paint_svg_box(PaintContext& context, PaintableBox const& svg_box, PaintPhase phase);
|
static void paint_svg_box(PaintContext& context, PaintableBox const& svg_box, PaintPhase phase);
|
||||||
static void paint_descendants(PaintContext& context, PaintableBox const& paintable, PaintPhase phase);
|
static void paint_descendants(PaintContext& context, PaintableBox const& paintable, PaintPhase phase);
|
||||||
|
|
||||||
virtual void before_paint(PaintContext&, PaintPhase) const override;
|
|
||||||
virtual void after_paint(PaintContext&, PaintPhase) const override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SVGSVGPaintable(Layout::SVGSVGBox const&);
|
SVGSVGPaintable(Layout::SVGSVGBox const&);
|
||||||
|
|
||||||
|
|
|
@ -108,9 +108,9 @@ void StackingContext::paint_svg(PaintContext& context, PaintableBox const& paint
|
||||||
if (phase != PaintPhase::Foreground)
|
if (phase != PaintPhase::Foreground)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
paintable.before_paint(context, PaintPhase::Foreground);
|
|
||||||
paint_node(paintable, context, PaintPhase::Background);
|
paint_node(paintable, context, PaintPhase::Background);
|
||||||
paint_node(paintable, context, PaintPhase::Border);
|
paint_node(paintable, context, PaintPhase::Border);
|
||||||
|
paintable.before_paint(context, PaintPhase::Foreground);
|
||||||
SVGSVGPaintable::paint_svg_box(context, paintable, phase);
|
SVGSVGPaintable::paint_svg_box(context, paintable, phase);
|
||||||
paintable.after_paint(context, PaintPhase::Foreground);
|
paintable.after_paint(context, PaintPhase::Foreground);
|
||||||
}
|
}
|
||||||
|
@ -118,14 +118,14 @@ void StackingContext::paint_svg(PaintContext& context, PaintableBox const& paint
|
||||||
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
|
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
|
||||||
{
|
{
|
||||||
paintable.for_each_child([&context, phase](auto& child) {
|
paintable.for_each_child([&context, phase](auto& child) {
|
||||||
|
if (child.has_stacking_context())
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
|
||||||
if (child.layout_node().is_svg_svg_box()) {
|
if (child.layout_node().is_svg_svg_box()) {
|
||||||
paint_svg(context, static_cast<PaintableBox const&>(child), to_paint_phase(phase));
|
paint_svg(context, static_cast<PaintableBox const&>(child), to_paint_phase(phase));
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.has_stacking_context())
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
|
|
||||||
// NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated
|
// NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated
|
||||||
// the same way as CSS2 defines for inline-blocks:
|
// the same way as CSS2 defines for inline-blocks:
|
||||||
// "For each one of these, treat the element as if it created a new stacking context, but any positioned
|
// "For each one of these, treat the element as if it created a new stacking context, but any positioned
|
||||||
|
@ -199,10 +199,7 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
|
||||||
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
|
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
|
||||||
{
|
{
|
||||||
VERIFY(!child.paintable_box().layout_node().is_svg_box());
|
VERIFY(!child.paintable_box().layout_node().is_svg_box());
|
||||||
VERIFY(!child.paintable_box().layout_node().is_svg_svg_box());
|
|
||||||
|
|
||||||
const_cast<StackingContext&>(child).set_last_paint_generation_id(context.paint_generation_id());
|
const_cast<StackingContext&>(child).set_last_paint_generation_id(context.paint_generation_id());
|
||||||
|
|
||||||
child.paint(context);
|
child.paint(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,10 +207,27 @@ void StackingContext::paint_internal(PaintContext& context) const
|
||||||
{
|
{
|
||||||
VERIFY(!paintable_box().layout_node().is_svg_box());
|
VERIFY(!paintable_box().layout_node().is_svg_box());
|
||||||
if (paintable_box().layout_node().is_svg_svg_box()) {
|
if (paintable_box().layout_node().is_svg_svg_box()) {
|
||||||
paint_svg(context, paintable_box(), PaintPhase::Foreground);
|
auto const& svg_svg_paintable = static_cast<SVGSVGPaintable const&>(paintable_box());
|
||||||
|
paint_node(svg_svg_paintable, context, PaintPhase::Background);
|
||||||
|
paint_node(svg_svg_paintable, context, PaintPhase::Border);
|
||||||
|
|
||||||
|
svg_svg_paintable.before_paint(context, PaintPhase::Foreground);
|
||||||
|
SVGSVGPaintable::paint_descendants(context, svg_svg_paintable, PaintPhase::Foreground);
|
||||||
|
svg_svg_paintable.after_paint(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
|
paint_node(svg_svg_paintable, context, PaintPhase::Outline);
|
||||||
|
if (context.should_paint_overlay()) {
|
||||||
|
paint_node(svg_svg_paintable, context, PaintPhase::Overlay);
|
||||||
|
paint_descendants(context, svg_svg_paintable, StackingContextPaintPhase::FocusAndOverlay);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.display_list_recorder().push_scroll_frame_id({});
|
||||||
|
ScopeGuard restore_scroll_frame_id([&] {
|
||||||
|
context.display_list_recorder().pop_scroll_frame_id();
|
||||||
|
});
|
||||||
|
|
||||||
// For a more elaborate description of the algorithm, see CSS 2.1 Appendix E
|
// 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)
|
// 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::Background);
|
||||||
|
@ -357,9 +371,7 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.display_list_recorder().push_scroll_frame_id({});
|
|
||||||
paint_internal(context);
|
paint_internal(context);
|
||||||
context.display_list_recorder().pop_scroll_frame_id();
|
|
||||||
|
|
||||||
if (filter.has_value()) {
|
if (filter.has_value()) {
|
||||||
context.display_list_recorder().restore();
|
context.display_list_recorder().restore();
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
display: flow-root;
|
||||||
|
position: relative;
|
||||||
|
width: 200px;
|
||||||
|
background: #ffffe0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 200px;
|
||||||
|
height: 40px;
|
||||||
|
background: crimson;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sc {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-top: 30px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background: lightgreen;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="container">
|
||||||
|
<div id="overlay"></div>
|
||||||
|
<div id="sc"></div>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="match" href="../../expected/css/svg-element-should-be-allowed-to-establish-stacking-context.html" />
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
position: relative;
|
||||||
|
width: 200px;
|
||||||
|
background: #ffffe0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 200px;
|
||||||
|
height: 40px;
|
||||||
|
background: crimson;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-top: 30px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background: lightgreen;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="container">
|
||||||
|
<div id="overlay"></div>
|
||||||
|
<svg></svg>
|
||||||
|
</div>
|
Binary file not shown.
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 129 KiB |
Loading…
Add table
Add a link
Reference in a new issue