mirror of
https://github.com/RGBCube/serenity
synced 2025-05-23 17:35:10 +00:00
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.
This commit is contained in:
parent
955d73657e
commit
1af466babf
11 changed files with 314 additions and 250 deletions
16
Tests/LibWeb/Ref/invalidate-css-transform-property.html
Normal file
16
Tests/LibWeb/Ref/invalidate-css-transform-property.html
Normal file
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -189,7 +189,6 @@ struct LayoutState {
|
|||
|
||||
private:
|
||||
void resolve_relative_positions();
|
||||
void resolve_layout_dependent_properties();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
namespace Web::Painting {
|
||||
|
||||
class PaintableFragment {
|
||||
friend struct Layout::LayoutState;
|
||||
friend class ViewportPaintable;
|
||||
|
||||
public:
|
||||
explicit PaintableFragment(Layout::LineBoxFragment const&);
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue