LibWeb/CSS: Treat 'mask' as a longhand property

Before this change, an element masked with 'mask-image: url(...)' would
show the mask, but 'mask: url(...)' would not. On e.g. dialogic.nl it
would show white boxes instead of the actual images in the top
navigation bar. We still do not support many of the other mask
properties, but with this change at least the masks show up in both
cases.
This commit is contained in:
Tommy van der Vorst 2025-03-01 11:27:55 +01:00 committed by Sam Atkins
parent 4bf197872b
commit 056205aa76
Notes: github-actions[bot] 2025-03-05 12:11:03 +00:00
9 changed files with 127 additions and 86 deletions

View file

@ -15,6 +15,7 @@
#include <AK/Debug.h>
#include <AK/GenericLexer.h>
#include <AK/TemporaryChange.h>
#include <LibURL/URL.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyName.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
@ -1925,8 +1926,19 @@ RefPtr<StringStyleValue> Parser::parse_string_value(TokenStream<ComponentValue>&
RefPtr<AbstractImageStyleValue> Parser::parse_image_value(TokenStream<ComponentValue>& tokens)
{
if (auto url = parse_url_function(tokens); url.has_value())
return ImageStyleValue::create(url.value());
tokens.mark();
auto url = parse_url_function(tokens);
if (url.has_value()) {
// If the value is a 'url(..)' parse as image, but if it is just a reference 'url(#xx)', leave it alone,
// so we can parse as URL further on. These URLs are used as references inside SVG documents for masks.
if (!url.value().equals(m_url, URL::ExcludeFragment::Yes)) {
tokens.discard_a_mark();
return ImageStyleValue::create(url.value());
}
tokens.restore_a_mark();
return nullptr;
}
tokens.discard_a_mark();
if (auto linear_gradient = parse_linear_gradient_function(tokens))
return linear_gradient;

View file

@ -1958,25 +1958,21 @@
]
},
"mask": {
"animation-type": "none",
"affects-layout": false,
"affects-stacking-context": true,
"inherited": false,
"valid-identifiers": [
"none"
],
"__comment": "FIXME: This should be a <mask-reference> and/or <mask-layer>#",
"valid-types": [
"url"
],
"initial": "none"
"__comment": "FIXME: add longhands mask-clip, mask-composite, mask-mode, mask-origin, mask-position, mask-repeat, mask-size; also reset mask-border",
"initial": "none",
"longhands": [
"mask-image"
]
},
"mask-image": {
"animation-type": "discrete",
"inherited": false,
"affects-layout": false,
"affects-stacking-context": true,
"valid-types": [
"image"
"image",
"url"
],
"valid-identifiers": [
"none"

View file

@ -827,7 +827,10 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
else if (stroke_width.is_percentage())
computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width.as_percentage().percentage() });
if (auto const& mask_image = computed_style.property(CSS::PropertyID::MaskImage); mask_image.is_abstract_image()) {
auto const& mask_image = computed_style.property(CSS::PropertyID::MaskImage);
if (mask_image.is_url()) {
computed_values.set_mask(mask_image.as_url().url());
} else if (mask_image.is_abstract_image()) {
auto const& abstract_image = mask_image.as_abstract_image();
computed_values.set_mask_image(abstract_image);
const_cast<CSS::AbstractImageStyleValue&>(abstract_image).load_any_resources(document());
@ -835,9 +838,6 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_mask_type(computed_style.mask_type());
if (auto const& mask = computed_style.property(CSS::PropertyID::Mask); mask.is_url())
computed_values.set_mask(mask.as_url().url());
auto const& clip_path = computed_style.property(CSS::PropertyID::ClipPath);
if (clip_path.is_url())
computed_values.set_clip_path(clip_path.as_url().url());

View file

@ -182,8 +182,17 @@ void SVGGraphicsElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropert
for (auto property : attribute_style_properties) {
if (!name.equals_ignoring_ascii_case(property.name))
continue;
if (auto style_value = parse_css_value(parsing_context, value, property.id))
cascaded_properties->set_property_from_presentational_hint(property.id, style_value.release_nonnull());
if (property.id == CSS::PropertyID::Mask) {
// Mask is a shorthand property in CSS, but parse_css_value does not take that into account. For now,
// just parse as 'mask-image' as anything else is currently not supported.
// FIXME: properly parse longhand 'mask' property
if (auto style_value = parse_css_value(parsing_context, value, CSS::PropertyID::MaskImage)) {
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::MaskImage, style_value.release_nonnull());
}
} else {
if (auto style_value = parse_css_value(parsing_context, value, property.id))
cascaded_properties->set_property_from_presentational_hint(property.id, style_value.release_nonnull());
}
break;
}
});

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<link rel="match" href="../expected/css-mask-longhand.html" />
<div class="masked"></div>
<style>
.masked {
display: block;
width: 200px;
height: 200px;
mask-image: url("data:image/png;base64,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");
background-color: red;
}
</style>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<link rel="match" href="../expected/css-mask-longhand.html" />
<div class="masked"></div>
<style>
.masked {
display: block;
width: 200px;
height: 200px;
mask: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIsAAACLCAYAAABRGWr/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAUoAAAABAAABSgAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAi6ADAAQAAAABAAAAiwAAAADPP8OeAAAACXBIWXMAADLAAAAywAEoZFrbAAACzGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj4zMzA8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjMzMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjUxMzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NTEzPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+ClWQD4sAAAOFSURBVHgB7ZixTQNBAATvjRMCEmdAimiAAjClURkUQAWIlAqM5AT0PPljEewmw1hycvdaaXZHetnTGGNevn5WGji7vh3T+cXKzc/R9PD59vL8yyXyeIOkEqrSgLJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCpWyVGplhioLc9cKlbJUamWGKgtz1wqVslRqZYYqC3PXCtW02V3NlWRA6GZ3eYJiejxxibyatjd3yoKcNg/layjfKTZRWbDT5sGUJd8pNlFZsNPmwZQl3yk2cTvG//sJ+Pc15/vl2f3a8/PxMObjx9oV9mzCkgXAlr8VnpaY/VrU1/vrIsth7Qp75msIO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhOVBTttHkxZ8p1iE5UFO20eTFnynWITlQU7bR5MWfKdYhO/AcH9GB0tjOd3AAAAAElFTkSuQmCC");
background-color: red;
}
</style>

View file

@ -1,6 +1,6 @@
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
'cssText': ''
'length': '220'
'length': '219'
'parentRule': 'null'
'cssFloat': 'none'
'WebkitAlignContent': 'normal'

View file

@ -156,70 +156,69 @@ All properties associated with getComputedStyle(document.body):
"153": "margin-left",
"154": "margin-right",
"155": "margin-top",
"156": "mask",
"157": "mask-image",
"158": "mask-type",
"159": "max-block-size",
"160": "max-height",
"161": "max-inline-size",
"162": "max-width",
"163": "min-block-size",
"164": "min-height",
"165": "min-inline-size",
"166": "min-width",
"167": "mix-blend-mode",
"168": "object-fit",
"169": "object-position",
"170": "opacity",
"171": "order",
"172": "outline-color",
"173": "outline-offset",
"174": "outline-style",
"175": "outline-width",
"176": "overflow-x",
"177": "overflow-y",
"178": "padding-block-end",
"179": "padding-block-start",
"180": "padding-bottom",
"181": "padding-inline-end",
"182": "padding-inline-start",
"183": "padding-left",
"184": "padding-right",
"185": "padding-top",
"186": "position",
"187": "r",
"188": "right",
"189": "rotate",
"190": "row-gap",
"191": "rx",
"192": "ry",
"193": "scale",
"194": "scrollbar-gutter",
"195": "scrollbar-width",
"196": "stop-color",
"197": "stop-opacity",
"198": "table-layout",
"199": "text-decoration-color",
"200": "text-decoration-style",
"201": "text-decoration-thickness",
"202": "text-overflow",
"203": "top",
"204": "transform",
"205": "transform-box",
"206": "transform-origin",
"207": "transition-delay",
"208": "transition-duration",
"209": "transition-property",
"210": "transition-timing-function",
"211": "translate",
"212": "unicode-bidi",
"213": "user-select",
"214": "vertical-align",
"215": "view-transition-name",
"216": "width",
"217": "x",
"218": "y",
"219": "z-index"
"156": "mask-image",
"157": "mask-type",
"158": "max-block-size",
"159": "max-height",
"160": "max-inline-size",
"161": "max-width",
"162": "min-block-size",
"163": "min-height",
"164": "min-inline-size",
"165": "min-width",
"166": "mix-blend-mode",
"167": "object-fit",
"168": "object-position",
"169": "opacity",
"170": "order",
"171": "outline-color",
"172": "outline-offset",
"173": "outline-style",
"174": "outline-width",
"175": "overflow-x",
"176": "overflow-y",
"177": "padding-block-end",
"178": "padding-block-start",
"179": "padding-bottom",
"180": "padding-inline-end",
"181": "padding-inline-start",
"182": "padding-left",
"183": "padding-right",
"184": "padding-top",
"185": "position",
"186": "r",
"187": "right",
"188": "rotate",
"189": "row-gap",
"190": "rx",
"191": "ry",
"192": "scale",
"193": "scrollbar-gutter",
"194": "scrollbar-width",
"195": "stop-color",
"196": "stop-opacity",
"197": "table-layout",
"198": "text-decoration-color",
"199": "text-decoration-style",
"200": "text-decoration-thickness",
"201": "text-overflow",
"202": "top",
"203": "transform",
"204": "transform-box",
"205": "transform-origin",
"206": "transition-delay",
"207": "transition-duration",
"208": "transition-property",
"209": "transition-timing-function",
"210": "translate",
"211": "unicode-bidi",
"212": "user-select",
"213": "vertical-align",
"214": "view-transition-name",
"215": "width",
"216": "x",
"217": "y",
"218": "z-index"
}
All properties associated with document.body.style by default:
{}

View file

@ -154,7 +154,6 @@ margin-inline-start: 8px
margin-left: 8px
margin-right: 8px
margin-top: 8px
mask: none
mask-image: none
mask-type: luminance
max-block-size: none