LibWeb/CSS: Make dimension types serialize in resolved form

Some dimensions would always serialize in a canonical unit, others never
did, and others we manually would do so in their StyleValue. This
commit moves all of that into the dimension types, which means for
example that Length can apply its special rounding.

Our local serialization test now produces the same output as other
browsers. :^)
This commit is contained in:
Sam Atkins 2025-05-16 20:02:16 +01:00 committed by Tim Ledbetter
commit 443f9e5afb
Notes: github-actions[bot] 2025-05-17 06:54:42 +00:00
20 changed files with 70 additions and 58 deletions

View file

@ -1,11 +1,11 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Angle.h"
#include <AK/Math.h>
#include <LibWeb/CSS/Angle.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
@ -27,8 +27,10 @@ Angle Angle::percentage_of(Percentage const& percentage) const
return Angle { percentage.as_fraction() * m_value, m_type };
}
String Angle::to_string() const
String Angle::to_string(SerializationMode serialization_mode) const
{
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}deg", to_degrees()));
return MUST(String::formatted("{}{}", raw_value(), unit_name()));
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,13 +7,14 @@
#pragma once
#include <AK/String.h>
#include <LibWeb/CSS/SerializationMode.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
class Angle {
public:
enum class Type {
enum class Type : u8 {
Deg,
Grad,
Rad,
@ -26,7 +27,7 @@ public:
static Angle make_degrees(double);
Angle percentage_of(Percentage const&) const;
String to_string() const;
String to_string(SerializationMode = SerializationMode::Normal) const;
double to_degrees() const;
double to_radians() const;

View file

@ -1,10 +1,10 @@
/*
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Flex.h"
#include <LibWeb/CSS/Flex.h>
#include <LibWeb/CSS/Percentage.h>
namespace Web::CSS {
@ -25,9 +25,11 @@ Flex Flex::percentage_of(Percentage const& percentage) const
return Flex { percentage.as_fraction() * m_value, m_type };
}
String Flex::to_string() const
String Flex::to_string(SerializationMode serialization_mode) const
{
return MUST(String::formatted("{}fr", to_fr()));
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}fr", to_fr()));
return MUST(String::formatted("{}{}", raw_value(), unit_name()));
}
double Flex::to_fr() const

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,6 +8,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <LibWeb/CSS/SerializationMode.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
@ -15,7 +16,7 @@ namespace Web::CSS {
// https://drafts.csswg.org/css-grid-2/#typedef-flex
class Flex {
public:
enum class Type {
enum class Type : u8 {
Fr,
};
@ -25,7 +26,7 @@ public:
static Flex make_fr(double);
Flex percentage_of(Percentage const&) const;
String to_string() const;
String to_string(SerializationMode = SerializationMode::Normal) const;
double to_fr() const;
Type type() const { return m_type; }

View file

@ -1,10 +1,10 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Frequency.h"
#include <LibWeb/CSS/Frequency.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
@ -26,9 +26,11 @@ Frequency Frequency::percentage_of(Percentage const& percentage) const
return Frequency { percentage.as_fraction() * m_value, m_type };
}
String Frequency::to_string() const
String Frequency::to_string(SerializationMode serialization_mode) const
{
return MUST(String::formatted("{}hz", to_hertz()));
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}hz", to_hertz()));
return MUST(String::formatted("{}{}", raw_value(), unit_name()));
}
double Frequency::to_hertz() const

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,13 +7,14 @@
#pragma once
#include <AK/String.h>
#include <LibWeb/CSS/SerializationMode.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
class Frequency {
public:
enum class Type {
enum class Type : u8 {
Hz,
kHz
};
@ -24,7 +25,7 @@ public:
static Frequency make_hertz(double);
Frequency percentage_of(Percentage const&) const;
String to_string() const;
String to_string(SerializationMode = SerializationMode::Normal) const;
double to_hertz() const;
Type type() const { return m_type; }

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -198,10 +198,14 @@ CSSPixels Length::to_px_slow_case(Layout::Node const& layout_node) const
return viewport_relative_length_to_px(viewport_rect);
}
String Length::to_string() const
String Length::to_string(SerializationMode serialization_mode) const
{
if (is_auto())
return "auto"_string;
// FIXME: Manually skip this for px so we avoid rounding errors in absolute_length_to_px.
// Maybe provide alternative functions that don't produce CSSPixels?
if (serialization_mode == SerializationMode::ResolvedValue && is_absolute() && m_type != Type::Px)
return MUST(String::formatted("{:.5}px", absolute_length_to_px()));
return MUST(String::formatted("{:.5}{}", m_value, unit_name()));
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -10,6 +10,7 @@
#include <AK/String.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibWeb/CSS/SerializationMode.h>
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>
@ -17,7 +18,7 @@ namespace Web::CSS {
class Length {
public:
enum class Type {
enum class Type : u8 {
// Font-relative
Em,
Rem,
@ -218,7 +219,7 @@ public:
}
}
String to_string() const;
String to_string(SerializationMode = SerializationMode::Normal) const;
bool operator==(Length const& other) const
{

View file

@ -1,11 +1,11 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Resolution.h"
#include <LibWeb/CSS/Resolution.h>
namespace Web::CSS {
@ -20,9 +20,11 @@ Resolution Resolution::make_dots_per_pixel(double value)
return { value, Type::Dppx };
}
String Resolution::to_string() const
String Resolution::to_string(SerializationMode serialization_mode) const
{
return MUST(String::formatted("{}dppx", to_dots_per_pixel()));
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}dppx", to_dots_per_pixel()));
return MUST(String::formatted("{}{}", raw_value(), unit_name()));
}
double Resolution::to_dots_per_pixel() const

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,13 +7,13 @@
#pragma once
#include <AK/String.h>
#include <LibWeb/Forward.h>
#include <LibWeb/CSS/SerializationMode.h>
namespace Web::CSS {
class Resolution {
public:
enum class Type {
enum class Type : u8 {
Dpi,
Dpcm,
Dppx,
@ -24,7 +24,7 @@ public:
Resolution(double value, Type type);
static Resolution make_dots_per_pixel(double);
String to_string() const;
String to_string(SerializationMode = SerializationMode::Normal) const;
double to_dots_per_pixel() const;
Type type() const { return m_type; }

View file

@ -21,9 +21,7 @@ AngleStyleValue::~AngleStyleValue() = default;
String AngleStyleValue::to_string(SerializationMode serialization_mode) const
{
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}deg", m_angle.to_degrees()));
return m_angle.to_string();
return m_angle.to_string(serialization_mode);
}
bool AngleStyleValue::equals(CSSStyleValue const& other) const

View file

@ -23,7 +23,7 @@ public:
virtual double value() const override { return m_flex.raw_value(); }
virtual StringView unit() const override { return m_flex.unit_name(); }
virtual String to_string(SerializationMode) const override { return m_flex.to_string(); }
virtual String to_string(SerializationMode serialization_mode) const override { return m_flex.to_string(serialization_mode); }
bool equals(CSSStyleValue const& other) const override
{

View file

@ -26,7 +26,7 @@ public:
virtual double value() const override { return m_frequency.raw_value(); }
virtual StringView unit() const override { return m_frequency.unit_name(); }
virtual String to_string(SerializationMode) const override { return m_frequency.to_string(); }
virtual String to_string(SerializationMode serialization_mode) const override { return m_frequency.to_string(serialization_mode); }
bool equals(CSSStyleValue const& other) const override
{

View file

@ -23,7 +23,7 @@ public:
virtual double value() const override { return m_length.raw_value(); }
virtual StringView unit() const override { return m_length.unit_name(); }
virtual String to_string(SerializationMode) const override { return m_length.to_string(); }
virtual String to_string(SerializationMode serialization_mode) const override { return m_length.to_string(serialization_mode); }
virtual ValueComparingNonnullRefPtr<CSSStyleValue const> absolutized(CSSPixelRect const& viewport_rect, Length::FontMetrics const& font_metrics, Length::FontMetrics const& root_font_metrics) const override;
bool equals(CSSStyleValue const& other) const override;

View file

@ -23,7 +23,7 @@ public:
virtual double value() const override { return m_resolution.raw_value(); }
virtual StringView unit() const override { return m_resolution.unit_name(); }
virtual String to_string(SerializationMode) const override { return m_resolution.to_string(); }
virtual String to_string(SerializationMode serialization_mode) const override { return m_resolution.to_string(serialization_mode); }
bool equals(CSSStyleValue const& other) const override
{

View file

@ -26,12 +26,7 @@ public:
virtual double value() const override { return m_time.raw_value(); }
virtual StringView unit() const override { return m_time.unit_name(); }
virtual String to_string(SerializationMode serialization_mode) const override
{
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}s", m_time.to_seconds()));
return m_time.to_string();
}
virtual String to_string(SerializationMode serialization_mode) const override { return m_time.to_string(serialization_mode); }
bool equals(CSSStyleValue const& other) const override
{

View file

@ -1,12 +1,12 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Time.h"
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/Time.h>
namespace Web::CSS {
@ -26,8 +26,10 @@ Time Time::percentage_of(Percentage const& percentage) const
return Time { percentage.as_fraction() * m_value, m_type };
}
String Time::to_string() const
String Time::to_string(SerializationMode serialization_mode) const
{
if (serialization_mode == SerializationMode::ResolvedValue)
return MUST(String::formatted("{}s", to_seconds()));
return MUST(String::formatted("{}{}", raw_value(), unit_name()));
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,13 +7,14 @@
#pragma once
#include <AK/String.h>
#include <LibWeb/CSS/SerializationMode.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
class Time {
public:
enum class Type {
enum class Type : u8 {
S,
Ms,
};
@ -24,7 +25,7 @@ public:
static Time make_seconds(double);
Time percentage_of(Percentage const&) const;
String to_string() const;
String to_string(SerializationMode = SerializationMode::Normal) const;
double to_milliseconds() const;
double to_seconds() const;

View file

@ -2,9 +2,9 @@
}
@media screen and (min-width: 20px) and (max-width: 40px) {
}
@media screen and (min-resolution: 1dppx) {
@media screen and (min-resolution: 96dpi) {
}
@media screen and (min-resolution: 2dppx) {
}
@media screen and (min-resolution: 2.54dppx) {
@media screen and (min-resolution: 96dpcm) {
}

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 34 tests
25 Pass
9 Fail
27 Pass
7 Fail
Pass Test parsing '' with matchMedia
Pass Test parsing ' ' with matchMedia
Pass Test parsing 'all' with matchMedia
@ -32,8 +32,8 @@ Pass Test parsing '(resolution: calc(2x))' with matchMedia
Fail Test parsing '(max-resolution: 7x)' with matchMedia
Pass Test parsing '(max-resolution: calc(7x))' with matchMedia
Pass Test parsing '(resolution: 2dppx)' with matchMedia
Fail Test parsing '(resolution: 600dpi)' with matchMedia
Fail Test parsing '(resolution: 77dpcm)' with matchMedia
Pass Test parsing '(resolution: 600dpi)' with matchMedia
Pass Test parsing '(resolution: 77dpcm)' with matchMedia
Pass Test parsing '(resolution: calc(1x + 2x))' with matchMedia
Pass Test parsing '(resolution: calc(5x - 2x))' with matchMedia
Pass Test parsing '(resolution: calc(1x * 3))' with matchMedia