diff --git a/Tests/LibWeb/Ref/invalidate-css-transform-property.html b/Tests/LibWeb/Ref/invalidate-css-transform-property.html
new file mode 100644
index 0000000000..1233b26089
--- /dev/null
+++ b/Tests/LibWeb/Ref/invalidate-css-transform-property.html
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html b/Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html
new file mode 100644
index 0000000000..9bf9470f1c
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html
@@ -0,0 +1,12 @@
+
+
+
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
index f6605b55c8..366d257436 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -63,6 +63,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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 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(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;
diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp
index 8f8bec3af0..f8b0cf3325 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp
+++ b/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 scroll_frames;
if (is_traversable()) {
document->paintable()->assign_scroll_frame_ids(scroll_frames);
diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.h b/Userland/Libraries/LibWeb/HTML/Navigable.h
index 7b4e8b6456..c753d7e0f7 100644
--- a/Userland/Libraries/LibWeb/HTML/Navigable.h
+++ b/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& all_navigables();
diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp
index 886524f794..c37095d976 100644
--- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp
+++ b/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(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(*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(*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 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(*paintable)) {
- auto& paintable_box = static_cast(*paintable);
- paintable_box.set_box_shadow_data(move(resolved_box_shadow_data));
- } else if (paintable && is(*paintable)) {
- auto& inline_paintable = static_cast(*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(*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 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(fragment).set_shadows(move(resolved_shadow_data));
- }
- }
- }
-
- // Transform and transform origin
- if (is_paintable_box) {
- auto& paintable_box = static_cast(*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