mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-02 14:19:15 +00:00
LibWeb: Support relative lengths in calc
color values
Gains us ~40 WPT tests.
This commit is contained in:
parent
62d138ebf7
commit
9ab7c5d08d
Notes:
github-actions[bot]
2025-07-04 12:20:09 +00:00
Author: https://github.com/Calme1709
Commit: 9ab7c5d08d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5284
Reviewed-by: https://github.com/AtkinsSJ ✅
17 changed files with 169 additions and 74 deletions
|
@ -226,7 +226,7 @@ Color ComputedProperties::color_or_fallback(PropertyID id, Layout::NodeWithStyle
|
|||
auto const& value = property(id);
|
||||
if (!value.has_color())
|
||||
return fallback;
|
||||
return value.to_color(node, {});
|
||||
return value.to_color(node, { .length_resolution_context = Length::ResolutionContext::for_layout_node(node) });
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-color-adjust-1/#determine-the-used-color-scheme
|
||||
|
@ -670,7 +670,7 @@ Optional<Color> ComputedProperties::accent_color(Layout::NodeWithStyle const& no
|
|||
{
|
||||
auto const& value = property(PropertyID::AccentColor);
|
||||
if (value.has_color())
|
||||
return value.to_color(node, {});
|
||||
return value.to_color(node, { .length_resolution_context = Length::ResolutionContext::for_layout_node(node) });
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -924,7 +924,7 @@ Color ComputedProperties::caret_color(Layout::NodeWithStyle const& node) const
|
|||
return node.computed_values().color();
|
||||
|
||||
if (value.has_color())
|
||||
return value.to_color(node, {});
|
||||
return value.to_color(node, { .length_resolution_context = Length::ResolutionContext::for_layout_node(node) });
|
||||
|
||||
return InitialValues::caret_color();
|
||||
}
|
||||
|
@ -1187,7 +1187,7 @@ Vector<ShadowData> ComputedProperties::shadow(PropertyID property_id, Layout::No
|
|||
maybe_offset_y.release_value(),
|
||||
maybe_blur_radius.release_value(),
|
||||
maybe_spread_distance.release_value(),
|
||||
value.color()->to_color(as<Layout::NodeWithStyle>(layout_node), {}),
|
||||
value.color()->to_color(as<Layout::NodeWithStyle>(layout_node), { .length_resolution_context = Length::ResolutionContext::for_layout_node(layout_node) }),
|
||||
value.placement()
|
||||
};
|
||||
};
|
||||
|
@ -1807,8 +1807,9 @@ Color ComputedProperties::stop_color() const
|
|||
value = property(PropertyID::Color);
|
||||
}
|
||||
if (value->has_color()) {
|
||||
// FIXME: This is used by the SVGStopElement, which does not participate in layout,
|
||||
// so can't pass a layout node (so can't resolve some colors, e.g. palette ones)
|
||||
// FIXME: This is used by the SVGStopElement, which does not participate in layout, so we can't pass a layout
|
||||
// node or CalculationResolutionContext. This means we don't support all valid colors (e.g. palette
|
||||
// colors, calculated values which depend on length resolution, etc)
|
||||
return value->to_color({}, {});
|
||||
}
|
||||
return Color::Black;
|
||||
|
@ -1893,8 +1894,8 @@ ScrollbarColorData ComputedProperties::scrollbar_color(Layout::NodeWithStyle con
|
|||
|
||||
if (value.is_scrollbar_color()) {
|
||||
auto& scrollbar_color_value = value.as_scrollbar_color();
|
||||
auto thumb_color = scrollbar_color_value.thumb_color()->to_color(layout_node, {});
|
||||
auto track_color = scrollbar_color_value.track_color()->to_color(layout_node, {});
|
||||
auto thumb_color = scrollbar_color_value.thumb_color()->to_color(layout_node, { .length_resolution_context = Length::ResolutionContext::for_layout_node(layout_node) });
|
||||
auto track_color = scrollbar_color_value.track_color()->to_color(layout_node, { .length_resolution_context = Length::ResolutionContext::for_layout_node(layout_node) });
|
||||
return { thumb_color, track_color };
|
||||
}
|
||||
|
||||
|
|
|
@ -659,6 +659,13 @@ RefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, Calcul
|
|||
StyleValueVector result_shadows;
|
||||
result_shadows.ensure_capacity(from_shadows.size());
|
||||
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
CalculationResolutionContext resolution_context;
|
||||
if (auto node = element.layout_node()) {
|
||||
layout_node = *node;
|
||||
resolution_context.length_resolution_context = Length::ResolutionContext::for_layout_node(*node);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < from_shadows.size(); i++) {
|
||||
auto const& from_shadow = from_shadows[i]->as_shadow();
|
||||
auto const& to_shadow = to_shadows[i]->as_shadow();
|
||||
|
@ -669,7 +676,7 @@ RefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, Calcul
|
|||
if (!interpolated_offset_x || !interpolated_offset_y || !interpolated_blur_radius || !interpolated_spread_distance)
|
||||
return {};
|
||||
auto result_shadow = ShadowStyleValue::create(
|
||||
CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}, {}), to_shadow.color()->to_color({}, {}), delta), ColorSyntax::Modern),
|
||||
CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color(layout_node, resolution_context), to_shadow.color()->to_color(layout_node, resolution_context), delta), ColorSyntax::Modern),
|
||||
*interpolated_offset_x,
|
||||
*interpolated_offset_y,
|
||||
*interpolated_blur_radius,
|
||||
|
@ -781,9 +788,13 @@ static RefPtr<CSSStyleValue const> interpolate_value_impl(DOM::Element& element,
|
|||
}
|
||||
case CSSStyleValue::Type::Color: {
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
if (auto node = element.layout_node())
|
||||
CalculationResolutionContext resolution_context {};
|
||||
if (auto node = element.layout_node()) {
|
||||
layout_node = *node;
|
||||
return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node, {}), to.to_color(layout_node, {}), delta), ColorSyntax::Modern);
|
||||
resolution_context.length_resolution_context = Length::ResolutionContext::for_layout_node(*node);
|
||||
}
|
||||
|
||||
return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node, resolution_context), to.to_color(layout_node, resolution_context), delta), ColorSyntax::Modern);
|
||||
}
|
||||
case CSSStyleValue::Type::Edge: {
|
||||
auto resolved_from = from.as_edge().resolved_value(calculation_context);
|
||||
|
|
|
@ -5113,6 +5113,7 @@ RefPtr<CSSStyleValue const> Parser::parse_filter_value_list_value(TokenStream<Co
|
|||
}
|
||||
Optional<Color> color = {};
|
||||
if (maybe_color)
|
||||
// FIXME: We should support colors which require compute-time information (i.e. `em` and `vw` to `px` ratios).
|
||||
color = maybe_color->to_color({}, {});
|
||||
|
||||
return if_no_more_tokens_return(FilterOperation::DropShadow { x_offset.value(), y_offset.value(), maybe_radius, color });
|
||||
|
|
|
@ -1687,10 +1687,13 @@ void Document::obtain_theme_color()
|
|||
// 4. If color is not failure, then return color.
|
||||
if (!css_value.is_null() && css_value->has_color()) {
|
||||
Optional<Layout::NodeWithStyle const&> root_node;
|
||||
if (html_element() && html_element()->layout_node())
|
||||
CSS::CalculationResolutionContext resolution_context;
|
||||
if (html_element() && html_element()->layout_node()) {
|
||||
root_node = *html_element()->layout_node();
|
||||
resolution_context.length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*html_element()->layout_node());
|
||||
}
|
||||
|
||||
theme_color = css_value->to_color(root_node, {});
|
||||
theme_color = css_value->to_color(root_node, resolution_context);
|
||||
return TraversalDecision::Break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1174,7 +1174,8 @@ Optional<String> effective_command_value(GC::Ptr<DOM::Node> node, FlyString cons
|
|||
if (!background_color.has_value())
|
||||
return NumericLimits<u8>::max();
|
||||
VERIFY(is<Layout::NodeWithStyle>(node->layout_node()));
|
||||
return background_color.value()->to_color(*static_cast<Layout::NodeWithStyle*>(node->layout_node()), {}).alpha();
|
||||
auto& layout_node = *static_cast<Layout::NodeWithStyle*>(node->layout_node());
|
||||
return background_color.value()->to_color(layout_node, { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(layout_node) }).alpha();
|
||||
};
|
||||
while (resolved_background_alpha() == 0 && node->parent() && is<DOM::Element>(*node->parent()))
|
||||
node = node->parent();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -31,13 +32,27 @@ public:
|
|||
// 1. If the given value is a string, then:
|
||||
[&](String const& string) {
|
||||
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
|
||||
HTMLCanvasElement* context = my_canvas_element().visit(
|
||||
[&](HTMLCanvasElement* canvas_element) -> HTMLCanvasElement* {
|
||||
return canvas_element;
|
||||
},
|
||||
[&](OffscreenCanvas*) -> HTMLCanvasElement* {
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
|
||||
// FIXME: Parse a color value
|
||||
// https://drafts.csswg.org/css-color/#parse-a-css-color-value
|
||||
auto style_value = parse_css_value(CSS::Parser::ParsingParams(), string, CSS::PropertyID::Color);
|
||||
if (style_value && style_value->has_color()) {
|
||||
auto parsedValue = style_value->to_color(OptionalNone(), {});
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
CSS::CalculationResolutionContext resolution_context {};
|
||||
if (context && context->layout_node()) {
|
||||
layout_node = *context->layout_node();
|
||||
resolution_context.length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*context->layout_node());
|
||||
}
|
||||
|
||||
auto parsedValue = style_value->to_color(layout_node, resolution_context);
|
||||
|
||||
// 4. Set this's fill style to parsedValue.
|
||||
my_drawing_state().fill_style = parsedValue;
|
||||
|
@ -70,13 +85,27 @@ public:
|
|||
// 1. If the given value is a string, then:
|
||||
[&](String const& string) {
|
||||
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
|
||||
HTMLCanvasElement* context = my_canvas_element().visit(
|
||||
[&](HTMLCanvasElement* canvas_element) -> HTMLCanvasElement* {
|
||||
return canvas_element;
|
||||
},
|
||||
[&](OffscreenCanvas*) -> HTMLCanvasElement* {
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
|
||||
// FIXME: Parse a color value
|
||||
// https://drafts.csswg.org/css-color/#parse-a-css-color-value
|
||||
auto style_value = parse_css_value(CSS::Parser::ParsingParams(), string, CSS::PropertyID::Color);
|
||||
if (style_value && style_value->has_color()) {
|
||||
auto parsedValue = style_value->to_color(OptionalNone(), {});
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
CSS::CalculationResolutionContext resolution_context {};
|
||||
if (context && context->layout_node()) {
|
||||
layout_node = *context->layout_node();
|
||||
resolution_context.length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*context->layout_node());
|
||||
}
|
||||
|
||||
auto parsedValue = style_value->to_color(layout_node, resolution_context);
|
||||
|
||||
// 4. Set this's stroke style to parsedValue.
|
||||
my_drawing_state().stroke_style = parsedValue;
|
||||
|
@ -129,6 +158,7 @@ protected:
|
|||
CanvasFillStrokeStyles() = default;
|
||||
|
||||
private:
|
||||
Variant<HTMLCanvasElement*, OffscreenCanvas*> my_canvas_element() { return &reinterpret_cast<IncludingClass&>(*this).canvas_element(); }
|
||||
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
|
||||
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
|
||||
};
|
||||
|
|
|
@ -937,11 +937,20 @@ String CanvasRenderingContext2D::shadow_color() const
|
|||
void CanvasRenderingContext2D::set_shadow_color(String color)
|
||||
{
|
||||
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
|
||||
auto& context = canvas_element();
|
||||
|
||||
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
|
||||
auto style_value = parse_css_value(CSS::Parser::ParsingParams(), color, CSS::PropertyID::Color);
|
||||
if (style_value && style_value->has_color()) {
|
||||
auto parsedValue = style_value->to_color(OptionalNone(), {});
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
CSS::CalculationResolutionContext resolution_context;
|
||||
|
||||
if (auto node = context.layout_node()) {
|
||||
layout_node = *node;
|
||||
resolution_context.length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*node);
|
||||
}
|
||||
|
||||
auto parsedValue = style_value->to_color(layout_node, resolution_context);
|
||||
|
||||
// 4. Set this's shadow color to parsedValue.
|
||||
drawing_state().shadow_color = parsedValue;
|
||||
|
|
|
@ -378,9 +378,6 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
}
|
||||
computed_values.set_color_scheme(computed_style.color_scheme(preferred_color_scheme, document().supported_color_schemes()));
|
||||
|
||||
// NOTE: color must be set second to ensure currentColor can be resolved in other properties (e.g. background-color).
|
||||
computed_values.set_color(computed_style.color_or_fallback(CSS::PropertyID::Color, *this, CSS::InitialValues::color()));
|
||||
|
||||
// NOTE: We have to be careful that font-related properties get set in the right order.
|
||||
// m_font is used by Length::to_px() when resolving sizes against this layout node.
|
||||
// That's why it has to be set before everything else.
|
||||
|
@ -390,6 +387,10 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
computed_values.set_font_kerning(computed_style.font_kerning());
|
||||
computed_values.set_line_height(computed_style.line_height());
|
||||
|
||||
// NOTE: color must be set after color-scheme to ensure currentColor can be resolved in other properties (e.g. background-color).
|
||||
// NOTE: color must be set after font_size as `CalculatedStyleValue`s can rely on it being set for resolving lengths.
|
||||
computed_values.set_color(computed_style.color_or_fallback(CSS::PropertyID::Color, *this, CSS::InitialValues::color()));
|
||||
|
||||
computed_values.set_vertical_align(computed_style.vertical_align());
|
||||
|
||||
{
|
||||
|
@ -796,7 +797,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
do_border_style(computed_values.border_bottom(), CSS::PropertyID::BorderBottomWidth, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomStyle);
|
||||
|
||||
if (auto const& outline_color = computed_style.property(CSS::PropertyID::OutlineColor); outline_color.has_color())
|
||||
computed_values.set_outline_color(outline_color.to_color(*this, {}));
|
||||
computed_values.set_outline_color(outline_color.to_color(*this, { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*this) }));
|
||||
if (auto const& outline_offset = computed_style.property(CSS::PropertyID::OutlineOffset); outline_offset.is_length())
|
||||
computed_values.set_outline_offset(outline_offset.as_length().length());
|
||||
computed_values.set_outline_style(computed_style.outline_style());
|
||||
|
@ -836,16 +837,16 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
|
||||
auto const& fill = computed_style.property(CSS::PropertyID::Fill);
|
||||
if (fill.has_color())
|
||||
computed_values.set_fill(fill.to_color(*this, {}));
|
||||
computed_values.set_fill(fill.to_color(*this, { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*this) }));
|
||||
else if (fill.is_url())
|
||||
computed_values.set_fill(fill.as_url().url());
|
||||
auto const& stroke = computed_style.property(CSS::PropertyID::Stroke);
|
||||
if (stroke.has_color())
|
||||
computed_values.set_stroke(stroke.to_color(*this, {}));
|
||||
computed_values.set_stroke(stroke.to_color(*this, { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*this) }));
|
||||
else if (stroke.is_url())
|
||||
computed_values.set_stroke(stroke.as_url().url());
|
||||
if (auto const& stop_color = computed_style.property(CSS::PropertyID::StopColor); stop_color.has_color())
|
||||
computed_values.set_stop_color(stop_color.to_color(*this, {}));
|
||||
computed_values.set_stop_color(stop_color.to_color(*this, { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*this) }));
|
||||
auto const& stroke_width = computed_style.property(CSS::PropertyID::StrokeWidth);
|
||||
// FIXME: Converting to pixels isn't really correct - values should be in "user units"
|
||||
// https://svgwg.org/svg2-draft/coords.html#TermUserUnits
|
||||
|
|
|
@ -586,6 +586,7 @@ Optional<BordersData> borders_data_for_outline(Layout::Node const& layout_node,
|
|||
if (outline_style == CSS::OutlineStyle::Auto) {
|
||||
// `auto` lets us do whatever we want for the outline. 2px of the accent colour seems reasonable.
|
||||
line_style = CSS::LineStyle::Solid;
|
||||
// NOTE: CalculationResolutionContext is not required here as Accentcolor keyword value is guaranteed to not rely on it to resolve.
|
||||
outline_color = CSS::CSSKeywordValue::create(CSS::Keyword::Accentcolor)->to_color(*static_cast<Layout::NodeWithStyle const*>(&layout_node), {});
|
||||
outline_width = 2;
|
||||
} else {
|
||||
|
|
|
@ -30,7 +30,7 @@ static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& n
|
|||
|
||||
resolved_color_stops.ensure_capacity(expanded_size);
|
||||
for (auto& stop : color_stop_list) {
|
||||
auto resolved_stop = Gfx::ColorStop { .color = stop.color_stop.color->to_color(node, {}) };
|
||||
auto resolved_stop = Gfx::ColorStop { .color = stop.color_stop.color->to_color(node, { .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(node) }) };
|
||||
for (int i = 0; i < color_stop_length(stop); i++)
|
||||
resolved_color_stops.append(resolved_stop);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue