Quellcode durchsuchen

LibWeb: Fix invalidation of CSS properties that do not affect layout

Recently, we moved the resolution of CSS properties that do not affect
layout to occur within LayoutState::commit(). This decision was a
mistake as it breaks invalidation. With this change, we now re-resolve
all properties that do not affect layout before each repaint.
Aliaksandr Kalenik vor 1 Jahr
Ursprung
Commit
1af466babf

+ 16 - 0
Tests/LibWeb/Ref/invalidate-css-transform-property.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="match" href="reference/invalidate-css-transform-property-ref.html" />
+<style>
+    #box {
+        width: 100px;
+        height: 100px;
+        background-color: red;
+    }
+</style>
+<div id="box"></div>
+<script>
+    window.onload = function () {
+        const box = document.getElementById("box");
+        box.style.transform = "translate(50px, 50px)";
+    };
+</script>

+ 12 - 0
Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+    #box {
+        width: 100px;
+        height: 100px;
+        background-color: red;
+        position: relative;
+        top: 50px;
+        left: 50px;
+    }
+</style>
+<div id="box"></div>

+ 9 - 0
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -63,6 +63,7 @@
 #include <LibWeb/Page/Page.h>
 #include <LibWeb/Painting/InlinePaintable.h>
 #include <LibWeb/Painting/PaintableBox.h>
+#include <LibWeb/Painting/ViewportPaintable.h>
 #include <LibWeb/WebIDL/AbstractOperations.h>
 #include <LibWeb/WebIDL/DOMException.h>
 #include <LibWeb/WebIDL/ExceptionOr.h>
@@ -590,6 +591,9 @@ Element::RequiredInvalidationAfterStyleChange Element::recompute_style()
     m_computed_css_values = move(new_computed_css_values);
     computed_css_values_changed();
 
+    if (invalidation.repaint && document().navigable())
+        document().navigable()->set_needs_to_resolve_paint_only_properties();
+
     if (!invalidation.rebuild_layout_tree && layout_node()) {
         // If we're keeping the layout tree, we can just apply the new style to the existing layout tree.
         layout_node()->apply_style(*m_computed_css_values);
@@ -909,6 +913,11 @@ JS::NonnullGCPtr<Geometry::DOMRectList> Element::get_client_rects() const
     VERIFY(document().navigable());
     auto viewport_offset = document().navigable()->viewport_scroll_offset();
 
+    if (document().paintable()) {
+        // NOTE: Make sure CSS transforms are resolved before it is used to calculate the rect position.
+        const_cast<Painting::ViewportPaintable*>(document().paintable())->resolve_paint_only_properties();
+    }
+
     Gfx::AffineTransform transform;
     for (auto const* containing_block = this->layout_node(); containing_block; containing_block = containing_block->containing_block()) {
         Gfx::AffineTransform containing_block_transform;

+ 5 - 0
Userland/Libraries/LibWeb/HTML/Navigable.cpp

@@ -2082,6 +2082,11 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig
     context.set_should_paint_overlay(config.paint_overlay);
     context.set_has_focus(config.has_focus);
 
+    if (m_needs_to_resolve_paint_only_properties) {
+        document->paintable()->resolve_paint_only_properties();
+        m_needs_to_resolve_paint_only_properties = false;
+    }
+
     HashMap<Painting::PaintableBox const*, Painting::ViewportPaintable::ScrollFrame> scroll_frames;
     if (is_traversable()) {
         document->paintable()->assign_scroll_frame_ids(scroll_frames);

+ 4 - 0
Userland/Libraries/LibWeb/HTML/Navigable.h

@@ -179,6 +179,8 @@ public:
     };
     void paint(Painting::RecordingPainter&, PaintConfig);
 
+    void set_needs_to_resolve_paint_only_properties() { m_needs_to_resolve_paint_only_properties = true; }
+
 protected:
     Navigable();
 
@@ -220,6 +222,8 @@ private:
 
     CSSPixelSize m_size;
     CSSPixelPoint m_viewport_scroll_offset;
+
+    bool m_needs_to_resolve_paint_only_properties { true };
 };
 
 HashTable<Navigable*>& all_navigables();

+ 0 - 248
Userland/Libraries/LibWeb/Layout/LayoutState.cpp

@@ -197,252 +197,6 @@ static void build_paint_tree(Node& node, Painting::Paintable* parent_paintable =
     }
 }
 
-static Painting::BorderRadiiData normalized_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius)
-{
-    Painting::BorderRadiusData bottom_left_radius_px {};
-    Painting::BorderRadiusData bottom_right_radius_px {};
-    Painting::BorderRadiusData top_left_radius_px {};
-    Painting::BorderRadiusData top_right_radius_px {};
-
-    bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width());
-    bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width());
-    top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width());
-    top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width());
-
-    bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height());
-    bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height());
-    top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height());
-    top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height());
-
-    // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
-    // Let f = min(Li/Si), where i ∈ {top, right, bottom, left},
-    // Si is the sum of the two corresponding radii of the corners on side i,
-    // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box.
-    auto l_top = rect.width();
-    auto l_bottom = l_top;
-    auto l_left = rect.height();
-    auto l_right = l_left;
-    auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius);
-    auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius);
-    auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius);
-    auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius);
-    CSSPixelFraction f = 1;
-    f = (s_top != 0) ? min(f, l_top / s_top) : f;
-    f = (s_right != 0) ? min(f, l_right / s_right) : f;
-    f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f;
-    f = (s_left != 0) ? min(f, l_left / s_left) : f;
-
-    // If f < 1, then all corner radii are reduced by multiplying them by f.
-    if (f < 1) {
-        top_left_radius_px.horizontal_radius *= f;
-        top_left_radius_px.vertical_radius *= f;
-        top_right_radius_px.horizontal_radius *= f;
-        top_right_radius_px.vertical_radius *= f;
-        bottom_right_radius_px.horizontal_radius *= f;
-        bottom_right_radius_px.vertical_radius *= f;
-        bottom_left_radius_px.horizontal_radius *= f;
-        bottom_left_radius_px.vertical_radius *= f;
-    }
-
-    return Painting::BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px };
-}
-
-void LayoutState::resolve_layout_dependent_properties()
-{
-    // Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
-    // Properties resolved include:
-    // - Border radii
-    // - Box shadows
-    // - Text shadows
-    // - Transforms
-    // - Transform origins
-
-    for (auto& it : used_values_per_layout_node) {
-        auto& used_values = *it.value;
-        auto& node = const_cast<NodeWithStyle&>(used_values.node());
-
-        auto* paintable = node.paintable();
-        auto const is_inline_paintable = paintable && paintable->is_inline_paintable();
-        auto const is_paintable_box = paintable && paintable->is_paintable_box();
-        auto const is_paintable_with_lines = paintable && paintable->is_paintable_with_lines();
-
-        // Border radii
-        if (is_inline_paintable) {
-            auto& inline_paintable = static_cast<Painting::InlinePaintable&>(*paintable);
-            auto& fragments = inline_paintable.fragments();
-
-            auto const& top_left_border_radius = inline_paintable.computed_values().border_top_left_radius();
-            auto const& top_right_border_radius = inline_paintable.computed_values().border_top_right_radius();
-            auto const& bottom_right_border_radius = inline_paintable.computed_values().border_bottom_right_radius();
-            auto const& bottom_left_border_radius = inline_paintable.computed_values().border_bottom_left_radius();
-
-            auto containing_block_position_in_absolute_coordinates = inline_paintable.containing_block()->paintable_box()->absolute_position();
-            for (size_t i = 0; i < fragments.size(); ++i) {
-                auto is_first_fragment = i == 0;
-                auto is_last_fragment = i == fragments.size() - 1;
-                auto& fragment = fragments[i];
-                CSSPixelRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
-                if (is_first_fragment) {
-                    auto extra_start_width = inline_paintable.box_model().padding.left;
-                    absolute_fragment_rect.translate_by(-extra_start_width, 0);
-                    absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
-                }
-                if (is_last_fragment) {
-                    auto extra_end_width = inline_paintable.box_model().padding.right;
-                    absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
-                }
-                auto border_radii_data = normalized_border_radii_data(inline_paintable.layout_node(), absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius);
-                fragment.set_border_radii_data(border_radii_data);
-            }
-        }
-
-        // Border radii
-        if (is_paintable_box) {
-            auto& paintable_box = static_cast<Painting::PaintableBox&>(*paintable);
-
-            CSSPixelRect const border_rect { 0, 0, used_values.border_box_width(), used_values.border_box_height() };
-
-            auto const& border_top_left_radius = node.computed_values().border_top_left_radius();
-            auto const& border_top_right_radius = node.computed_values().border_top_right_radius();
-            auto const& border_bottom_right_radius = node.computed_values().border_bottom_right_radius();
-            auto const& border_bottom_left_radius = node.computed_values().border_bottom_left_radius();
-
-            auto radii_data = normalized_border_radii_data(node, border_rect, border_top_left_radius, border_top_right_radius, border_bottom_right_radius, border_bottom_left_radius);
-            paintable_box.set_border_radii_data(radii_data);
-        }
-
-        // Box shadows
-        auto const& box_shadow_data = node.computed_values().box_shadow();
-        if (!box_shadow_data.is_empty()) {
-            Vector<Painting::ShadowData> 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,
-                    layer.offset_x.to_px(node),
-                    layer.offset_y.to_px(node),
-                    layer.blur_radius.to_px(node),
-                    layer.spread_distance.to_px(node),
-                    layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer : Painting::ShadowPlacement::Inner);
-            }
-
-            if (paintable && is<Painting::PaintableBox>(*paintable)) {
-                auto& paintable_box = static_cast<Painting::PaintableBox&>(*paintable);
-                paintable_box.set_box_shadow_data(move(resolved_box_shadow_data));
-            } else if (paintable && is<Painting::InlinePaintable>(*paintable)) {
-                auto& inline_paintable = static_cast<Painting::InlinePaintable&>(*paintable);
-                inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data));
-            } else {
-                VERIFY_NOT_REACHED();
-            }
-        }
-
-        // Text shadows
-        if (is_paintable_with_lines) {
-            auto const& paintable_with_lines = static_cast<Painting::PaintableWithLines const&>(*paintable);
-            for (auto const& fragment : paintable_with_lines.fragments()) {
-                auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow();
-                if (!text_shadow.is_empty()) {
-                    Vector<Painting::ShadowData> resolved_shadow_data;
-                    resolved_shadow_data.ensure_capacity(text_shadow.size());
-                    for (auto const& layer : text_shadow) {
-                        resolved_shadow_data.empend(
-                            layer.color,
-                            layer.offset_x.to_px(paintable_with_lines.layout_node()),
-                            layer.offset_y.to_px(paintable_with_lines.layout_node()),
-                            layer.blur_radius.to_px(paintable_with_lines.layout_node()),
-                            layer.spread_distance.to_px(paintable_with_lines.layout_node()),
-                            Painting::ShadowPlacement::Outer);
-                    }
-                    const_cast<Painting::PaintableFragment&>(fragment).set_shadows(move(resolved_shadow_data));
-                }
-            }
-        }
-
-        // Transform and transform origin
-        if (is_paintable_box) {
-            auto& paintable_box = static_cast<Painting::PaintableBox&>(*paintable);
-            auto const& transformations = paintable_box.computed_values().transformations();
-            if (!transformations.is_empty()) {
-                auto matrix = Gfx::FloatMatrix4x4::identity();
-                for (auto const& transform : transformations)
-                    matrix = matrix * transform.to_matrix(paintable_box).release_value();
-                paintable_box.set_transform(matrix);
-            }
-
-            auto const& transform_origin = paintable_box.computed_values().transform_origin();
-            // https://www.w3.org/TR/css-transforms-1/#transform-box
-            auto transform_box = paintable_box.computed_values().transform_box();
-            // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for
-            // border-box is stroke-box.
-            // FIXME: This currently detects any SVG element except the <svg> one. Is that correct?
-            //        And is it correct to use `else` below?
-            if (is<Painting::SVGPaintable>(paintable_box)) {
-                switch (transform_box) {
-                case CSS::TransformBox::ContentBox:
-                    transform_box = CSS::TransformBox::FillBox;
-                    break;
-                case CSS::TransformBox::BorderBox:
-                    transform_box = CSS::TransformBox::StrokeBox;
-                    break;
-                default:
-                    break;
-                }
-            }
-            // For elements with associated CSS layout box, the used value for fill-box is content-box and for
-            // stroke-box and view-box is border-box.
-            else {
-                switch (transform_box) {
-                case CSS::TransformBox::FillBox:
-                    transform_box = CSS::TransformBox::ContentBox;
-                    break;
-                case CSS::TransformBox::StrokeBox:
-                case CSS::TransformBox::ViewBox:
-                    transform_box = CSS::TransformBox::BorderBox;
-                    break;
-                default:
-                    break;
-                }
-            }
-
-            CSSPixelRect reference_box = [&]() {
-                switch (transform_box) {
-                case CSS::TransformBox::ContentBox:
-                    // Uses the content box as reference box.
-                    // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
-                    return paintable_box.absolute_rect();
-                case CSS::TransformBox::BorderBox:
-                    // Uses the border box as reference box.
-                    // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
-                    return paintable_box.absolute_border_box_rect();
-                case CSS::TransformBox::FillBox:
-                    // Uses the object bounding box as reference box.
-                    // FIXME: For now we're using the content rect as an approximation.
-                    return paintable_box.absolute_rect();
-                case CSS::TransformBox::StrokeBox:
-                    // Uses the stroke bounding box as reference box.
-                    // FIXME: For now we're using the border rect as an approximation.
-                    return paintable_box.absolute_border_box_rect();
-                case CSS::TransformBox::ViewBox:
-                    // Uses the nearest SVG viewport as reference box.
-                    // FIXME: If a viewBox attribute is specified for the SVG viewport creating element:
-                    //  - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute.
-                    //  - The dimension of the reference box is set to the width and height values of the viewBox attribute.
-                    auto* svg_paintable = paintable_box.first_ancestor_of_type<Painting::SVGSVGPaintable>();
-                    if (!svg_paintable)
-                        return paintable_box.absolute_border_box_rect();
-                    return svg_paintable->absolute_rect();
-                }
-                VERIFY_NOT_REACHED();
-            }();
-            auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width());
-            auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height());
-            paintable_box.set_transform_origin({ x, y });
-            paintable_box.set_transform_origin({ x, y });
-        }
-    }
-}
-
 void LayoutState::commit(Box& root)
 {
     // Only the top-level LayoutState should ever be committed.
@@ -599,8 +353,6 @@ void LayoutState::commit(Box& root)
         auto const& box = static_cast<Layout::Box const&>(used_values.node());
         measure_scrollable_overflow(box);
     }
-
-    resolve_layout_dependent_properties();
 }
 
 void LayoutState::UsedValues::set_node(NodeWithStyle& node, UsedValues const* containing_block_used_values)

+ 0 - 1
Userland/Libraries/LibWeb/Layout/LayoutState.h

@@ -189,7 +189,6 @@ struct LayoutState {
 
 private:
     void resolve_relative_positions();
-    void resolve_layout_dependent_properties();
 };
 
 }

+ 12 - 0
Userland/Libraries/LibWeb/Painting/Paintable.h

@@ -88,6 +88,18 @@ public:
         return TraversalDecision::Continue;
     }
 
+    template<typename Callback>
+    TraversalDecision for_each_in_inclusive_subtree(Callback callback)
+    {
+        if (auto decision = callback(*this); decision != TraversalDecision::Continue)
+            return decision;
+        for (auto* child = first_child(); child; child = child->next_sibling()) {
+            if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break)
+                return TraversalDecision::Break;
+        }
+        return TraversalDecision::Continue;
+    }
+
     template<typename Callback>
     TraversalDecision for_each_in_inclusive_subtree(Callback callback) const
     {

+ 1 - 1
Userland/Libraries/LibWeb/Painting/PaintableFragment.h

@@ -14,7 +14,7 @@
 namespace Web::Painting {
 
 class PaintableFragment {
-    friend struct Layout::LayoutState;
+    friend class ViewportPaintable;
 
 public:
     explicit PaintableFragment(Layout::LineBoxFragment const&);

+ 254 - 0
Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp

@@ -5,6 +5,8 @@
  */
 
 #include <LibWeb/Layout/Viewport.h>
+#include <LibWeb/Painting/SVGPaintable.h>
+#include <LibWeb/Painting/SVGSVGPaintable.h>
 #include <LibWeb/Painting/StackingContext.h>
 #include <LibWeb/Painting/ViewportPaintable.h>
 
@@ -153,4 +155,256 @@ void ViewportPaintable::assign_clip_rectangles()
     });
 }
 
+static Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius)
+{
+    Painting::BorderRadiusData bottom_left_radius_px {};
+    Painting::BorderRadiusData bottom_right_radius_px {};
+    Painting::BorderRadiusData top_left_radius_px {};
+    Painting::BorderRadiusData top_right_radius_px {};
+
+    bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width());
+    bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width());
+    top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width());
+    top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width());
+
+    bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height());
+    bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height());
+    top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height());
+    top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height());
+
+    // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
+    // Let f = min(Li/Si), where i ∈ {top, right, bottom, left},
+    // Si is the sum of the two corresponding radii of the corners on side i,
+    // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box.
+    auto l_top = rect.width();
+    auto l_bottom = l_top;
+    auto l_left = rect.height();
+    auto l_right = l_left;
+    auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius);
+    auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius);
+    auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius);
+    auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius);
+    CSSPixelFraction f = 1;
+    f = (s_top != 0) ? min(f, l_top / s_top) : f;
+    f = (s_right != 0) ? min(f, l_right / s_right) : f;
+    f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f;
+    f = (s_left != 0) ? min(f, l_left / s_left) : f;
+
+    // If f < 1, then all corner radii are reduced by multiplying them by f.
+    if (f < 1) {
+        top_left_radius_px.horizontal_radius *= f;
+        top_left_radius_px.vertical_radius *= f;
+        top_right_radius_px.horizontal_radius *= f;
+        top_right_radius_px.vertical_radius *= f;
+        bottom_right_radius_px.horizontal_radius *= f;
+        bottom_right_radius_px.vertical_radius *= f;
+        bottom_left_radius_px.horizontal_radius *= f;
+        bottom_left_radius_px.vertical_radius *= f;
+    }
+
+    return Painting::BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px };
+}
+
+void ViewportPaintable::resolve_paint_only_properties()
+{
+    // Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
+    // Properties resolved include:
+    // - Border radii
+    // - Box shadows
+    // - Text shadows
+    // - Transforms
+    // - Transform origins
+    for_each_in_inclusive_subtree([&](Paintable& paintable) {
+        auto& node = paintable.layout_node();
+
+        auto const is_inline_paintable = paintable.is_inline_paintable();
+        auto const is_paintable_box = paintable.is_paintable_box();
+        auto const is_paintable_with_lines = paintable.is_paintable_with_lines();
+
+        // Border radii
+        if (is_inline_paintable) {
+            auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
+            auto& fragments = inline_paintable.fragments();
+
+            auto const& top_left_border_radius = inline_paintable.computed_values().border_top_left_radius();
+            auto const& top_right_border_radius = inline_paintable.computed_values().border_top_right_radius();
+            auto const& bottom_right_border_radius = inline_paintable.computed_values().border_bottom_right_radius();
+            auto const& bottom_left_border_radius = inline_paintable.computed_values().border_bottom_left_radius();
+
+            auto containing_block_position_in_absolute_coordinates = inline_paintable.containing_block()->paintable_box()->absolute_position();
+            for (size_t i = 0; i < fragments.size(); ++i) {
+                auto is_first_fragment = i == 0;
+                auto is_last_fragment = i == fragments.size() - 1;
+                auto& fragment = fragments[i];
+                CSSPixelRect absolute_fragment_rect {
+                    containing_block_position_in_absolute_coordinates.translated(fragment.offset()),
+                    fragment.size()
+                };
+                if (is_first_fragment) {
+                    auto extra_start_width = inline_paintable.box_model().padding.left;
+                    absolute_fragment_rect.translate_by(-extra_start_width, 0);
+                    absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
+                }
+                if (is_last_fragment) {
+                    auto extra_end_width = inline_paintable.box_model().padding.right;
+                    absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
+                }
+                auto border_radii_data = normalize_border_radii_data(inline_paintable.layout_node(),
+                    absolute_fragment_rect, top_left_border_radius,
+                    top_right_border_radius,
+                    bottom_right_border_radius,
+                    bottom_left_border_radius);
+                fragment.set_border_radii_data(border_radii_data);
+            }
+        }
+
+        // Border radii
+        if (is_paintable_box) {
+            auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
+
+            CSSPixelRect const border_rect { 0, 0, paintable_box.border_box_width(), paintable_box.border_box_height() };
+            auto const& border_top_left_radius = node.computed_values().border_top_left_radius();
+            auto const& border_top_right_radius = node.computed_values().border_top_right_radius();
+            auto const& border_bottom_right_radius = node.computed_values().border_bottom_right_radius();
+            auto const& border_bottom_left_radius = node.computed_values().border_bottom_left_radius();
+
+            auto radii_data = normalize_border_radii_data(node, border_rect, border_top_left_radius,
+                border_top_right_radius, border_bottom_right_radius,
+                border_bottom_left_radius);
+            paintable_box.set_border_radii_data(radii_data);
+        }
+
+        // Box shadows
+        auto const& box_shadow_data = node.computed_values().box_shadow();
+        if (!box_shadow_data.is_empty()) {
+            Vector<Painting::ShadowData> 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,
+                    layer.offset_x.to_px(node),
+                    layer.offset_y.to_px(node),
+                    layer.blur_radius.to_px(node),
+                    layer.spread_distance.to_px(node),
+                    layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer
+                                                                   : Painting::ShadowPlacement::Inner);
+            }
+
+            if (is<Painting::PaintableBox>(paintable)) {
+                auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
+                paintable_box.set_box_shadow_data(move(resolved_box_shadow_data));
+            } else if (is<Painting::InlinePaintable>(paintable)) {
+                auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
+                inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data));
+            }
+        }
+
+        // Text shadows
+        if (is_paintable_with_lines) {
+            auto const& paintable_with_lines = static_cast<Painting::PaintableWithLines const&>(paintable);
+            for (auto const& fragment : paintable_with_lines.fragments()) {
+                auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow();
+                if (!text_shadow.is_empty()) {
+                    Vector<Painting::ShadowData> resolved_shadow_data;
+                    resolved_shadow_data.ensure_capacity(text_shadow.size());
+                    for (auto const& layer : text_shadow) {
+                        resolved_shadow_data.empend(
+                            layer.color,
+                            layer.offset_x.to_px(paintable_with_lines.layout_node()),
+                            layer.offset_y.to_px(paintable_with_lines.layout_node()),
+                            layer.blur_radius.to_px(paintable_with_lines.layout_node()),
+                            layer.spread_distance.to_px(paintable_with_lines.layout_node()),
+                            Painting::ShadowPlacement::Outer);
+                    }
+                    const_cast<Painting::PaintableFragment&>(fragment).set_shadows(move(resolved_shadow_data));
+                }
+            }
+        }
+
+        // Transform and transform origin
+        if (is_paintable_box) {
+            auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
+            auto const& transformations = paintable_box.computed_values().transformations();
+            if (!transformations.is_empty()) {
+                auto matrix = Gfx::FloatMatrix4x4::identity();
+                for (auto const& transform : transformations)
+                    matrix = matrix * transform.to_matrix(paintable_box).release_value();
+                paintable_box.set_transform(matrix);
+            }
+
+            auto const& transform_origin = paintable_box.computed_values().transform_origin();
+            // https://www.w3.org/TR/css-transforms-1/#transform-box
+            auto transform_box = paintable_box.computed_values().transform_box();
+            // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for
+            // border-box is stroke-box.
+            // FIXME: This currently detects any SVG element except the <svg> one. Is that correct?
+            //        And is it correct to use `else` below?
+            if (is<Painting::SVGPaintable>(paintable_box)) {
+                switch (transform_box) {
+                case CSS::TransformBox::ContentBox:
+                    transform_box = CSS::TransformBox::FillBox;
+                    break;
+                case CSS::TransformBox::BorderBox:
+                    transform_box = CSS::TransformBox::StrokeBox;
+                    break;
+                default:
+                    break;
+                }
+            }
+            // For elements with associated CSS layout box, the used value for fill-box is content-box and for
+            // stroke-box and view-box is border-box.
+            else {
+                switch (transform_box) {
+                case CSS::TransformBox::FillBox:
+                    transform_box = CSS::TransformBox::ContentBox;
+                    break;
+                case CSS::TransformBox::StrokeBox:
+                case CSS::TransformBox::ViewBox:
+                    transform_box = CSS::TransformBox::BorderBox;
+                    break;
+                default:
+                    break;
+                }
+            }
+
+            CSSPixelRect reference_box = [&]() {
+                switch (transform_box) {
+                case CSS::TransformBox::ContentBox:
+                    // Uses the content box as reference box.
+                    // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
+                    return paintable_box.absolute_rect();
+                case CSS::TransformBox::BorderBox:
+                    // Uses the border box as reference box.
+                    // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
+                    return paintable_box.absolute_border_box_rect();
+                case CSS::TransformBox::FillBox:
+                    // Uses the object bounding box as reference box.
+                    // FIXME: For now we're using the content rect as an approximation.
+                    return paintable_box.absolute_rect();
+                case CSS::TransformBox::StrokeBox:
+                    // Uses the stroke bounding box as reference box.
+                    // FIXME: For now we're using the border rect as an approximation.
+                    return paintable_box.absolute_border_box_rect();
+                case CSS::TransformBox::ViewBox:
+                    // Uses the nearest SVG viewport as reference box.
+                    // FIXME: If a viewBox attribute is specified for the SVG viewport creating element:
+                    //  - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute.
+                    //  - The dimension of the reference box is set to the width and height values of the viewBox attribute.
+                    auto* svg_paintable = paintable_box.first_ancestor_of_type<Painting::SVGSVGPaintable>();
+                    if (!svg_paintable)
+                        return paintable_box.absolute_border_box_rect();
+                    return svg_paintable->absolute_rect();
+                }
+                VERIFY_NOT_REACHED();
+            }();
+            auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width());
+            auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height());
+            paintable_box.set_transform_origin({ x, y });
+            paintable_box.set_transform_origin({ x, y });
+        }
+
+        return TraversalDecision::Continue;
+    });
+}
+
 }

+ 1 - 0
Userland/Libraries/LibWeb/Painting/ViewportPaintable.h

@@ -26,6 +26,7 @@ public:
     };
     void assign_scroll_frame_ids(HashMap<Painting::PaintableBox const*, ScrollFrame>&) const;
     void assign_clip_rectangles();
+    void resolve_paint_only_properties();
 
 private:
     void build_stacking_context_tree();