mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-28 13:18:19 +00:00
LibWeb/CSS: Support nested shorthands in CSSStyleDeclaration
Without this, getting a property's value from `element.style.foo` would fail if `foo` is a shorthand property which has a longhand that is also a shorthand. For example, `border` expands to `border-width` which expands to `border-top-width`. This is because we used `property()` to get a longhand's value, but this returns nothing if the property is a shorthand. This commit solves that by moving most of get_property_value() into a separate method that returns a StyleProperty instead of a String, and which calls itself recursively for shorthands. Also move the manual shorthand construction out of ResolvedCSSStyleDeclaration so that all CSSStyleDeclarations can use it.
This commit is contained in:
parent
006c8ba2d4
commit
412b758107
Notes:
github-actions[bot]
2025-02-12 16:02:13 +00:00
Author: https://github.com/AtkinsSJ
Commit: 412b758107
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3482
Reviewed-by: https://github.com/jdahlin
7 changed files with 237 additions and 207 deletions
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023-2024, Sam Atkins <sam@ladybird.org>
|
||||
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@
|
|||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
|
@ -261,34 +262,115 @@ bool PropertyOwningCSSStyleDeclaration::set_a_css_declaration(PropertyID propert
|
|||
return true;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
|
||||
String CSSStyleDeclaration::get_property_value(StringView property_name) const
|
||||
static Optional<StyleProperty> style_property_for_sided_shorthand(PropertyID property_id, Optional<StyleProperty> const& top, Optional<StyleProperty> const& right, Optional<StyleProperty> const& bottom, Optional<StyleProperty> const& left)
|
||||
{
|
||||
auto property_id = property_id_from_string(property_name);
|
||||
if (!property_id.has_value())
|
||||
if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value())
|
||||
return {};
|
||||
|
||||
if (property_id.value() == PropertyID::Custom) {
|
||||
auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes()));
|
||||
if (maybe_custom_property.has_value()) {
|
||||
return maybe_custom_property.value().value->to_string(
|
||||
computed_flag() ? Web::CSS::CSSStyleValue::SerializationMode::ResolvedValue
|
||||
: Web::CSS::CSSStyleValue::SerializationMode::Normal);
|
||||
}
|
||||
if (top->important != right->important || top->important != bottom->important || top->important != left->important)
|
||||
return {};
|
||||
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> const top_value { top->value };
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> const right_value { right->value };
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> const bottom_value { bottom->value };
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> const left_value { left->value };
|
||||
|
||||
bool const top_and_bottom_same = top_value == bottom_value;
|
||||
bool const left_and_right_same = left_value == right_value;
|
||||
|
||||
RefPtr<CSSStyleValue const> value;
|
||||
|
||||
if (top_and_bottom_same && left_and_right_same && top_value == left_value) {
|
||||
value = top_value;
|
||||
} else if (top_and_bottom_same && left_and_right_same) {
|
||||
value = StyleValueList::create(StyleValueVector { top_value, right_value }, StyleValueList::Separator::Space);
|
||||
} else if (left_and_right_same) {
|
||||
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value }, StyleValueList::Separator::Space);
|
||||
} else {
|
||||
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value, left_value }, StyleValueList::Separator::Space);
|
||||
}
|
||||
|
||||
return StyleProperty {
|
||||
.important = top->important,
|
||||
.property_id = property_id,
|
||||
.value = value.release_nonnull(),
|
||||
};
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
|
||||
Optional<StyleProperty> CSSStyleDeclaration::get_property_internal(PropertyID property_id) const
|
||||
{
|
||||
// 2. If property is a shorthand property, then follow these substeps:
|
||||
if (property_is_shorthand(property_id.value())) {
|
||||
if (property_is_shorthand(property_id)) {
|
||||
|
||||
// AD-HOC: Handle shorthands that require manual construction.
|
||||
switch (property_id) {
|
||||
case PropertyID::Border: {
|
||||
auto width = get_property_internal(PropertyID::BorderWidth);
|
||||
auto style = get_property_internal(PropertyID::BorderStyle);
|
||||
auto color = get_property_internal(PropertyID::BorderColor);
|
||||
// `border` only has a reasonable value if all four sides are the same.
|
||||
if (!width.has_value() || width->value->is_value_list() || !style.has_value() || style->value->is_value_list() || !color.has_value() || color->value->is_value_list())
|
||||
return {};
|
||||
if (width->important != style->important || width->important != color->important)
|
||||
return {};
|
||||
return StyleProperty {
|
||||
.important = width->important,
|
||||
.property_id = property_id,
|
||||
.value = ShorthandStyleValue::create(property_id,
|
||||
{ PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor },
|
||||
{ width->value, style->value, color->value })
|
||||
};
|
||||
}
|
||||
case PropertyID::BorderColor: {
|
||||
auto top = get_property_internal(PropertyID::BorderTopColor);
|
||||
auto right = get_property_internal(PropertyID::BorderRightColor);
|
||||
auto bottom = get_property_internal(PropertyID::BorderBottomColor);
|
||||
auto left = get_property_internal(PropertyID::BorderLeftColor);
|
||||
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
|
||||
}
|
||||
case PropertyID::BorderStyle: {
|
||||
auto top = get_property_internal(PropertyID::BorderTopStyle);
|
||||
auto right = get_property_internal(PropertyID::BorderRightStyle);
|
||||
auto bottom = get_property_internal(PropertyID::BorderBottomStyle);
|
||||
auto left = get_property_internal(PropertyID::BorderLeftStyle);
|
||||
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
|
||||
}
|
||||
case PropertyID::BorderWidth: {
|
||||
auto top = get_property_internal(PropertyID::BorderTopWidth);
|
||||
auto right = get_property_internal(PropertyID::BorderRightWidth);
|
||||
auto bottom = get_property_internal(PropertyID::BorderBottomWidth);
|
||||
auto left = get_property_internal(PropertyID::BorderLeftWidth);
|
||||
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
|
||||
}
|
||||
case PropertyID::Margin: {
|
||||
auto top = get_property_internal(PropertyID::MarginTop);
|
||||
auto right = get_property_internal(PropertyID::MarginRight);
|
||||
auto bottom = get_property_internal(PropertyID::MarginBottom);
|
||||
auto left = get_property_internal(PropertyID::MarginLeft);
|
||||
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
|
||||
}
|
||||
case PropertyID::Padding: {
|
||||
auto top = get_property_internal(PropertyID::PaddingTop);
|
||||
auto right = get_property_internal(PropertyID::PaddingRight);
|
||||
auto bottom = get_property_internal(PropertyID::PaddingBottom);
|
||||
auto left = get_property_internal(PropertyID::PaddingLeft);
|
||||
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 1. Let list be a new empty array.
|
||||
Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>> list;
|
||||
Optional<Important> last_important_flag;
|
||||
|
||||
// 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps:
|
||||
Vector<PropertyID> longhand_ids = longhands_for_shorthand(property_id.value());
|
||||
Vector<PropertyID> longhand_ids = longhands_for_shorthand(property_id);
|
||||
for (auto longhand_property_id : longhand_ids) {
|
||||
// 1. If longhand is a case-sensitive match for a property name of a CSS declaration in the declarations, let declaration be that CSS declaration, or null otherwise.
|
||||
auto declaration = property(longhand_property_id);
|
||||
// 1. If longhand is a case-sensitive match for a property name of a CSS declaration in the declarations,
|
||||
// let declaration be that CSS declaration, or null otherwise.
|
||||
auto declaration = get_property_internal(longhand_property_id);
|
||||
|
||||
// 2. If declaration is null, then return the empty string.
|
||||
if (!declaration.has_value())
|
||||
|
@ -304,18 +386,42 @@ String CSSStyleDeclaration::get_property_value(StringView property_name) const
|
|||
|
||||
// 3. If important flags of all declarations in list are same, then return the serialization of list.
|
||||
// NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string().
|
||||
return ShorthandStyleValue::create(property_id.value(), longhand_ids, list)->to_string(computed_flag() ? CSSStyleValue::SerializationMode::ResolvedValue : CSSStyleValue::SerializationMode::Normal);
|
||||
return StyleProperty {
|
||||
.important = last_important_flag.value(),
|
||||
.property_id = property_id,
|
||||
.value = ShorthandStyleValue::create(property_id, longhand_ids, list),
|
||||
};
|
||||
|
||||
// 4. Return the empty string.
|
||||
// NOTE: This is handled by the loop.
|
||||
}
|
||||
|
||||
auto maybe_property = property(property_id.value());
|
||||
return property(property_id);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
|
||||
String CSSStyleDeclaration::get_property_value(StringView property_name) const
|
||||
{
|
||||
auto property_id = property_id_from_string(property_name);
|
||||
if (!property_id.has_value())
|
||||
return {};
|
||||
|
||||
if (property_id.value() == PropertyID::Custom) {
|
||||
auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes()));
|
||||
if (maybe_custom_property.has_value()) {
|
||||
return maybe_custom_property.value().value->to_string(
|
||||
computed_flag() ? CSSStyleValue::SerializationMode::ResolvedValue
|
||||
: CSSStyleValue::SerializationMode::Normal);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto maybe_property = get_property_internal(property_id.value());
|
||||
if (!maybe_property.has_value())
|
||||
return {};
|
||||
return maybe_property->value->to_string(
|
||||
computed_flag() ? Web::CSS::CSSStyleValue::SerializationMode::ResolvedValue
|
||||
: Web::CSS::CSSStyleValue::SerializationMode::Normal);
|
||||
computed_flag() ? CSSStyleValue::SerializationMode::ResolvedValue
|
||||
: CSSStyleValue::SerializationMode::Normal);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority
|
||||
|
|
|
@ -61,6 +61,7 @@ protected:
|
|||
private:
|
||||
// ^PlatformObject
|
||||
virtual Optional<JS::Value> item_value(size_t index) const override;
|
||||
Optional<StyleProperty> get_property_internal(PropertyID) const;
|
||||
};
|
||||
|
||||
class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, 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
|
||||
*/
|
||||
|
@ -9,37 +9,23 @@
|
|||
#include <AK/Debug.h>
|
||||
#include <AK/Format.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <LibWeb/CSS/Enums.h>
|
||||
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||||
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/StackingContext.h>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
|
@ -115,23 +101,6 @@ static NonnullRefPtr<CSSStyleValue const> style_value_for_size(Size const& size)
|
|||
TODO();
|
||||
}
|
||||
|
||||
static NonnullRefPtr<CSSStyleValue const> style_value_for_sided_shorthand(ValueComparingNonnullRefPtr<CSSStyleValue const> top, ValueComparingNonnullRefPtr<CSSStyleValue const> right, ValueComparingNonnullRefPtr<CSSStyleValue const> bottom, ValueComparingNonnullRefPtr<CSSStyleValue const> left)
|
||||
{
|
||||
bool top_and_bottom_same = top == bottom;
|
||||
bool left_and_right_same = left == right;
|
||||
|
||||
if (top_and_bottom_same && left_and_right_same && top == left)
|
||||
return top;
|
||||
|
||||
if (top_and_bottom_same && left_and_right_same)
|
||||
return StyleValueList::create(StyleValueVector { move(top), move(right) }, StyleValueList::Separator::Space);
|
||||
|
||||
if (left_and_right_same)
|
||||
return StyleValueList::create(StyleValueVector { move(top), move(right), move(bottom) }, StyleValueList::Separator::Space);
|
||||
|
||||
return StyleValueList::create(StyleValueVector { move(top), move(right), move(bottom), move(left) }, StyleValueList::Separator::Space);
|
||||
}
|
||||
|
||||
enum class LogicalSide {
|
||||
BlockStart,
|
||||
BlockEnd,
|
||||
|
@ -437,54 +406,6 @@ RefPtr<CSSStyleValue const> ResolvedCSSStyleDeclaration::style_value_for_propert
|
|||
// -> Any other property
|
||||
// The resolved value is the computed value.
|
||||
// NOTE: This is handled inside the `default` case.
|
||||
|
||||
// NOTE: Everything below is a shorthand that requires some manual construction.
|
||||
case PropertyID::Border: {
|
||||
auto width = style_value_for_property(layout_node, PropertyID::BorderWidth);
|
||||
auto style = style_value_for_property(layout_node, PropertyID::BorderStyle);
|
||||
auto color = style_value_for_property(layout_node, PropertyID::BorderColor);
|
||||
// `border` only has a reasonable value if all four sides are the same.
|
||||
if (width->is_value_list() || style->is_value_list() || color->is_value_list())
|
||||
return nullptr;
|
||||
return ShorthandStyleValue::create(property_id,
|
||||
{ PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor },
|
||||
{ width.release_nonnull(), style.release_nonnull(), color.release_nonnull() });
|
||||
}
|
||||
case PropertyID::BorderColor: {
|
||||
auto top = style_value_for_property(layout_node, PropertyID::BorderTopColor);
|
||||
auto right = style_value_for_property(layout_node, PropertyID::BorderRightColor);
|
||||
auto bottom = style_value_for_property(layout_node, PropertyID::BorderBottomColor);
|
||||
auto left = style_value_for_property(layout_node, PropertyID::BorderLeftColor);
|
||||
return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull());
|
||||
}
|
||||
case PropertyID::BorderStyle: {
|
||||
auto top = style_value_for_property(layout_node, PropertyID::BorderTopStyle);
|
||||
auto right = style_value_for_property(layout_node, PropertyID::BorderRightStyle);
|
||||
auto bottom = style_value_for_property(layout_node, PropertyID::BorderBottomStyle);
|
||||
auto left = style_value_for_property(layout_node, PropertyID::BorderLeftStyle);
|
||||
return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull());
|
||||
}
|
||||
case PropertyID::BorderWidth: {
|
||||
auto top = style_value_for_property(layout_node, PropertyID::BorderTopWidth);
|
||||
auto right = style_value_for_property(layout_node, PropertyID::BorderRightWidth);
|
||||
auto bottom = style_value_for_property(layout_node, PropertyID::BorderBottomWidth);
|
||||
auto left = style_value_for_property(layout_node, PropertyID::BorderLeftWidth);
|
||||
return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull());
|
||||
}
|
||||
case PropertyID::Margin: {
|
||||
auto top = style_value_for_property(layout_node, PropertyID::MarginTop);
|
||||
auto right = style_value_for_property(layout_node, PropertyID::MarginRight);
|
||||
auto bottom = style_value_for_property(layout_node, PropertyID::MarginBottom);
|
||||
auto left = style_value_for_property(layout_node, PropertyID::MarginLeft);
|
||||
return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull());
|
||||
}
|
||||
case PropertyID::Padding: {
|
||||
auto top = style_value_for_property(layout_node, PropertyID::PaddingTop);
|
||||
auto right = style_value_for_property(layout_node, PropertyID::PaddingRight);
|
||||
auto bottom = style_value_for_property(layout_node, PropertyID::PaddingBottom);
|
||||
auto left = style_value_for_property(layout_node, PropertyID::PaddingLeft);
|
||||
return style_value_for_sided_shorthand(top.release_nonnull(), right.release_nonnull(), bottom.release_nonnull(), left.release_nonnull());
|
||||
}
|
||||
case PropertyID::WebkitTextFillColor:
|
||||
return CSSColorValue::create_from_color(layout_node.computed_values().webkit_text_fill_color());
|
||||
case PropertyID::Invalid:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue