LibWeb/CSS: Serialize mask shorthand-property properly

This commit is contained in:
InvalidUsernameException 2025-07-18 20:36:15 +02:00 committed by Sam Atkins
commit 6c4483fe0e
Notes: github-actions[bot] 2025-08-06 22:10:14 +00:00
4 changed files with 147 additions and 38 deletions

View file

@ -489,6 +489,116 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
return start->to_string(mode);
return MUST(String::formatted("{} / {}", start->to_string(mode), end->to_string(mode)));
}
case PropertyID::Mask: {
StringBuilder builder;
auto serialize_layer = [mode, &builder](String image_value_string, String position_value_string, String size_value_string, String repeat_value_string, String origin_value_string, String clip_value_string, String composite_value_string, String mode_value_string) {
PropertyID canonical_property_order[] = {
PropertyID::MaskImage,
PropertyID::MaskPosition,
// Intentionally skipping MaskSize here, it is handled together with MaskPosition.
PropertyID::MaskRepeat,
PropertyID::MaskOrigin,
PropertyID::MaskClip,
PropertyID::MaskComposite,
PropertyID::MaskMode,
};
auto property_value = [&](PropertyID property) -> String const& {
switch (property) {
case PropertyID::MaskImage:
return image_value_string;
case PropertyID::MaskPosition:
return position_value_string;
case PropertyID::MaskSize:
return size_value_string;
case PropertyID::MaskRepeat:
return repeat_value_string;
case PropertyID::MaskOrigin:
return origin_value_string;
case PropertyID::MaskClip:
return clip_value_string;
case PropertyID::MaskComposite:
return composite_value_string;
case PropertyID::MaskMode:
return mode_value_string;
default:
VERIFY_NOT_REACHED();
}
};
auto is_initial_value = [mode, property_value](PropertyID property) -> bool {
return property_value(property) == property_initial_value(property)->to_string(mode);
};
auto can_skip_serializing_initial_value = [is_initial_value, property_value](PropertyID property) -> bool {
switch (property) {
case PropertyID::MaskPosition:
return is_initial_value(PropertyID::MaskSize);
case PropertyID::MaskOrigin:
return is_initial_value(PropertyID::MaskClip) || property_value(PropertyID::MaskClip) == string_from_keyword(Keyword::NoClip);
default:
return true;
}
};
bool layer_is_empty = true;
for (size_t i = 0; i < array_size(canonical_property_order); i++) {
auto property = canonical_property_order[i];
auto const& value = property_value(property);
if (is_initial_value(property) && can_skip_serializing_initial_value(property))
continue;
if (property == PropertyID::MaskClip && value == property_value(PropertyID::MaskOrigin))
continue;
if (!layer_is_empty)
builder.append(" "sv);
builder.append(value);
if (property == PropertyID::MaskPosition && !is_initial_value(PropertyID::MaskSize)) {
builder.append(" / "sv);
builder.append(property_value(PropertyID::MaskSize));
}
layer_is_empty = false;
}
if (layer_is_empty)
builder.append("none"sv);
};
auto get_layer_count = [](auto const& style_value) -> size_t {
return style_value->is_value_list() ? style_value->as_value_list().size() : 1;
};
auto mask_image = longhand(PropertyID::MaskImage);
auto mask_position = longhand(PropertyID::MaskPosition);
auto mask_size = longhand(PropertyID::MaskSize);
auto mask_repeat = longhand(PropertyID::MaskRepeat);
auto mask_origin = longhand(PropertyID::MaskOrigin);
auto mask_clip = longhand(PropertyID::MaskClip);
auto mask_composite = longhand(PropertyID::MaskComposite);
auto mask_mode = longhand(PropertyID::MaskMode);
auto layer_count = max(get_layer_count(mask_image), max(get_layer_count(mask_position), max(get_layer_count(mask_size), max(get_layer_count(mask_repeat), max(get_layer_count(mask_origin), max(get_layer_count(mask_clip), max(get_layer_count(mask_composite), get_layer_count(mask_mode))))))));
if (layer_count == 1) {
serialize_layer(mask_image->to_string(mode), mask_position->to_string(mode), mask_size->to_string(mode), mask_repeat->to_string(mode), mask_origin->to_string(mode), mask_clip->to_string(mode), mask_composite->to_string(mode), mask_mode->to_string(mode));
} else {
auto get_layer_value_string = [mode](ValueComparingRefPtr<CSSStyleValue const> const& style_value, size_t index) {
if (style_value->is_value_list())
return style_value->as_value_list().value_at(index, true)->to_string(mode);
return style_value->to_string(mode);
};
for (size_t i = 0; i < layer_count; i++) {
if (i)
builder.append(", "sv);
serialize_layer(get_layer_value_string(mask_image, i), get_layer_value_string(mask_position, i), get_layer_value_string(mask_size, i), get_layer_value_string(mask_repeat, i), get_layer_value_string(mask_origin, i), get_layer_value_string(mask_clip, i), get_layer_value_string(mask_composite, i), get_layer_value_string(mask_mode, i));
}
}
return builder.to_string_without_validation();
}
case PropertyID::PlaceContent:
case PropertyID::PlaceItems:
case PropertyID::PlaceSelf:

View file

@ -111,9 +111,9 @@ All supported properties and their default values exposed from CSSStylePropertie
'WebkitJustifyContent': 'normal'
'webkitJustifyContent': 'normal'
'-webkit-justify-content': 'normal'
'WebkitMask': 'border-box'
'webkitMask': 'border-box'
'-webkit-mask': 'border-box'
'WebkitMask': 'none'
'webkitMask': 'none'
'-webkit-mask': 'none'
'WebkitMaskImage': 'none'
'webkitMaskImage': 'none'
'-webkit-mask-image': 'none'
@ -524,7 +524,7 @@ All supported properties and their default values exposed from CSSStylePropertie
'margin-right': '8px'
'marginTop': '8px'
'margin-top': '8px'
'mask': 'border-box'
'mask': 'none'
'maskClip': 'border-box'
'mask-clip': 'border-box'
'maskComposite': 'add'

View file

@ -2,32 +2,31 @@ Harness status: OK
Found 27 tests
12 Pass
15 Fail
Fail Property mask value 'none'
27 Pass
Pass Property mask value 'none'
Pass Property mask value 'linear-gradient(to left bottom, red, blue)'
Pass Property mask value 'linear-gradient(to left bottom, red, blue) luminance'
Pass Property mask value 'url("https://example.com/")'
Pass Property mask value 'linear-gradient(to left bottom, red, blue) 1px 2px'
Fail Property mask value 'url("https://example.com/") 1px 2px / contain'
Pass Property mask value 'url("https://example.com/") 1px 2px / contain'
Pass Property mask value 'repeat-y'
Fail Property mask value 'border-box'
Fail Property mask value 'linear-gradient(to left bottom, red, blue) padding-box'
Fail Property mask value 'content-box'
Fail Property mask value 'url("https://example.com/") fill-box'
Fail Property mask value 'linear-gradient(to left bottom, red, blue) stroke-box'
Fail Property mask value 'view-box'
Pass Property mask value 'border-box'
Pass Property mask value 'linear-gradient(to left bottom, red, blue) padding-box'
Pass Property mask value 'content-box'
Pass Property mask value 'url("https://example.com/") fill-box'
Pass Property mask value 'linear-gradient(to left bottom, red, blue) stroke-box'
Pass Property mask value 'view-box'
Pass Property mask value 'no-clip'
Pass Property mask value 'url("https://example.com/") add'
Pass Property mask value 'subtract'
Fail Property mask value 'url("https://example.com/") intersect'
Fail Property mask value 'linear-gradient(to left bottom, red, blue) exclude'
Pass Property mask value 'url("https://example.com/") intersect'
Pass Property mask value 'linear-gradient(to left bottom, red, blue) exclude'
Pass Property mask value 'alpha'
Pass Property mask value 'url("https://example.com/") alpha'
Fail Property mask value 'border-box border-box'
Fail Property mask value 'content-box content-box'
Fail Property mask value 'border-box content-box'
Pass Property mask value 'border-box border-box'
Pass Property mask value 'content-box content-box'
Pass Property mask value 'border-box content-box'
Pass Property mask value 'border-box no-clip'
Fail Property mask value 'intersect no-clip space round 1px 2px / contain stroke-box linear-gradient(to left bottom, red, blue) luminance'
Fail Property mask value 'intersect no-clip space round 1px 2px / contain view-box, stroke-box linear-gradient(to left bottom, red, blue) luminance'
Pass Property mask value 'intersect no-clip space round 1px 2px / contain stroke-box linear-gradient(to left bottom, red, blue) luminance'
Pass Property mask value 'intersect no-clip space round 1px 2px / contain view-box, stroke-box linear-gradient(to left bottom, red, blue) luminance'
Pass Property mask value 'none alpha'

View file

@ -2,34 +2,34 @@ Harness status: OK
Found 55 tests
25 Pass
30 Fail
Fail e.style['mask'] = "none" should set the property value
40 Pass
15 Fail
Pass e.style['mask'] = "none" should set the property value
Pass e.style['mask'] = "linear-gradient(to left bottom, red, blue)" should set the property value
Pass e.style['mask'] = "linear-gradient(to left bottom, red, blue) luminance" should set the property value
Pass e.style['mask'] = "url(\"https://wpt.live/\")" should set the property value
Pass e.style['mask'] = "linear-gradient(to left bottom, red, blue) 1px 2px" should set the property value
Fail e.style['mask'] = "url(\"https://wpt.live/\") 1px 2px / contain" should set the property value
Pass e.style['mask'] = "url(\"https://wpt.live/\") 1px 2px / contain" should set the property value
Pass e.style['mask'] = "repeat-y" should set the property value
Fail e.style['mask'] = "border-box" should set the property value
Fail e.style['mask'] = "linear-gradient(to left bottom, red, blue) padding-box" should set the property value
Fail e.style['mask'] = "content-box" should set the property value
Fail e.style['mask'] = "url(\"https://wpt.live/\") fill-box" should set the property value
Fail e.style['mask'] = "linear-gradient(to left bottom, red, blue) stroke-box" should set the property value
Fail e.style['mask'] = "view-box" should set the property value
Pass e.style['mask'] = "border-box" should set the property value
Pass e.style['mask'] = "linear-gradient(to left bottom, red, blue) padding-box" should set the property value
Pass e.style['mask'] = "content-box" should set the property value
Pass e.style['mask'] = "url(\"https://wpt.live/\") fill-box" should set the property value
Pass e.style['mask'] = "linear-gradient(to left bottom, red, blue) stroke-box" should set the property value
Pass e.style['mask'] = "view-box" should set the property value
Pass e.style['mask'] = "no-clip" should set the property value
Pass e.style['mask'] = "url(\"https://wpt.live/\") add" should set the property value
Pass e.style['mask'] = "subtract" should set the property value
Fail e.style['mask'] = "url(\"https://wpt.live/\") intersect" should set the property value
Fail e.style['mask'] = "linear-gradient(to left bottom, red, blue) exclude" should set the property value
Pass e.style['mask'] = "url(\"https://wpt.live/\") intersect" should set the property value
Pass e.style['mask'] = "linear-gradient(to left bottom, red, blue) exclude" should set the property value
Pass e.style['mask'] = "alpha" should set the property value
Pass e.style['mask'] = "url(\"https://wpt.live/\") alpha" should set the property value
Fail e.style['mask'] = "border-box border-box" should set the property value
Fail e.style['mask'] = "content-box content-box" should set the property value
Fail e.style['mask'] = "border-box content-box" should set the property value
Pass e.style['mask'] = "border-box border-box" should set the property value
Pass e.style['mask'] = "content-box content-box" should set the property value
Pass e.style['mask'] = "border-box content-box" should set the property value
Pass e.style['mask'] = "border-box no-clip" should set the property value
Fail e.style['mask'] = "intersect no-clip space round 1px 2px / contain stroke-box linear-gradient(to left bottom, red, blue) luminance" should set the property value
Fail e.style['mask'] = "intersect no-clip space round 1px 2px / contain view-box, stroke-box linear-gradient(to left bottom, red, blue) luminance" should set the property value
Pass e.style['mask'] = "intersect no-clip space round 1px 2px / contain stroke-box linear-gradient(to left bottom, red, blue) luminance" should set the property value
Pass e.style['mask'] = "intersect no-clip space round 1px 2px / contain view-box, stroke-box linear-gradient(to left bottom, red, blue) luminance" should set the property value
Pass e.style['mask'] = "none alpha" should set the property value
Fail e.style['mask'] = "none" should set mask-border-outset
Fail e.style['mask'] = "none" should set mask-border-repeat