LibWeb: Don't serialize shorthand with non-uniform CSS-wide keywords

This commit is contained in:
Callum Law 2025-06-11 15:25:28 +12:00 committed by Sam Atkins
commit f8f4da3b90
Notes: github-actions[bot] 2025-06-16 11:39:06 +00:00
5 changed files with 253 additions and 9 deletions

View file

@ -61,9 +61,13 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const
} }
}); });
if (all_same_keyword && built_in_keyword.has_value()) if (built_in_keyword.has_value()) {
if (all_same_keyword)
return MUST(String::from_utf8(string_from_keyword(built_in_keyword.value()))); return MUST(String::from_utf8(string_from_keyword(built_in_keyword.value())));
return ""_string;
}
auto default_to_string = [&]() { auto default_to_string = [&]() {
auto all_properties_same_value = true; auto all_properties_same_value = true;
auto first_property_value = m_properties.values.first(); auto first_property_value = m_properties.values.first();

View file

@ -0,0 +1,17 @@
Harness status: OK
Found 11 tests
5 Pass
6 Fail
Fail All properties can serialize 'initial'
Fail All properties (except 'all') can serialize their initial value (computed)
Fail All properties (except 'all') can serialize their initial value (specified)
Fail All shorthands can serialize their longhands set to 'initial'
Fail All shorthands (except 'all') can serialize their longhands set to their initial value
Pass All aliases can serialize target property set to 'initial'
Pass All aliases can serialize target property set to its initial value
Fail Can't serialize shorthand when longhands are set to different css-wide keywords
Pass Can't serialize shorthand when longhands have different priority
Pass Can't serialize shorthand set to 'initial' when some longhand is missing
Pass Can't serialize shorthand set to initial value when some longhand is missing

View file

@ -2,10 +2,10 @@ Harness status: OK
Found 5 tests Found 5 tests
2 Pass 4 Pass
3 Fail 1 Fail
Pass Single value flex with CSS-wide keyword should serialize correctly. Pass Single value flex with CSS-wide keyword should serialize correctly.
Fail Single value flex with non-CSS-wide value should serialize correctly. Fail Single value flex with non-CSS-wide value should serialize correctly.
Pass Multiple values flex with CSS-wide keyword should serialize correctly. Pass Multiple values flex with CSS-wide keyword should serialize correctly.
Fail Multiple values flex with CSS-wide keywords and non-CSS-wide value should serialize correctly. Pass Multiple values flex with CSS-wide keywords and non-CSS-wide value should serialize correctly.
Fail Multiple values flex with CSS-wide and two non-CSS-wide-keyword values should serialize correctly. Pass Multiple values flex with CSS-wide and two non-CSS-wide-keyword values should serialize correctly.

View file

@ -2,12 +2,12 @@ Harness status: OK
Found 7 tests Found 7 tests
5 Pass 6 Pass
2 Fail 1 Fail
Pass font-variant: normal serialization Pass font-variant: normal serialization
Pass font-variant: none serialization Pass font-variant: none serialization
Pass font-variant-ligatures: none serialization with non-default value for another longhand Pass font-variant-ligatures: none serialization with non-default value for another longhand
Pass font-variant: normal with non-default longhands Pass font-variant: normal with non-default longhands
Fail CSS-wide keyword in one longhand Pass CSS-wide keyword in one longhand
Pass CSS-wide keyword in shorthand Pass CSS-wide keyword in shorthand
Fail font: menu serialization Fail font: menu serialization

View file

@ -0,0 +1,223 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Common serialization checks for all properties</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com" />
<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue">
<div id="element"></div>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
const element = document.getElementById("element");
const { style } = element;
const computedStyle = getComputedStyle(element);
const cssProperties = new Set();
const cssShorthands = new Map();
const cssShorthandsForLonghand = new Map();
const cssLonghands = new Set();
const cssAliases = new Map();
const initialValues = new Map();
setup(function() {
for (let obj = style; obj; obj = Reflect.getPrototypeOf(obj)) {
for (let name of Object.getOwnPropertyNames(obj)) {
const property = name.replace(/[A-Z]/g, c => "-" + c.toLowerCase());
if (CSS.supports(property, "initial")) {
cssProperties.add(property);
}
}
}
for (let property of cssProperties) {
style.cssText = "";
style.setProperty(property, "initial");
if (style.length > 1) {
cssShorthands.set(property, [...style]);
for (let longhand of style) {
if (cssShorthandsForLonghand.has(longhand)) {
cssShorthandsForLonghand.get(longhand).add(property);
} else {
cssShorthandsForLonghand.set(longhand, new Set([property]));
}
}
} else if (style.length === 1) {
if (property === style[0]) {
cssLonghands.add(property);
} else {
cssAliases.set(property, style[0]);
}
}
}
});
test(function() {
const bad = [];
for (let property of cssProperties) {
style.cssText = "";
style.setProperty(property, "initial");
const result = style.getPropertyValue(property);
if (result !== "initial") {
bad.push([property, result]);
}
}
assert_array_equals(bad, []);
}, "All properties can serialize 'initial'");
test(function() {
for (let longhand of cssLonghands) {
element.style.setProperty(longhand, "initial");
}
const bad = [];
for (let property of cssProperties) {
const result = computedStyle.getPropertyValue(property);
if (CSS.supports(property, result)) {
initialValues.set(property, result);
} else if (property !== "all") {
bad.push([property, result]);
}
}
assert_array_equals(bad, []);
}, "All properties (except 'all') can serialize their initial value (computed)");
test(function() {
const bad = [];
for (let [property, value] of initialValues) {
style.cssText = "";
style.setProperty(property, value);
const result = style.getPropertyValue(property);
if (!CSS.supports(property, result) && property !== "all") {
bad.push([property, value, result]);
}
}
assert_array_equals(bad, []);
}, "All properties (except 'all') can serialize their initial value (specified)");
test(function() {
const bad = [];
for (let [shorthand, longhands] of cssShorthands) {
style.cssText = "";
for (let longhand of longhands) {
style.setProperty(longhand, "initial");
}
const result = style.getPropertyValue(shorthand);
if (result !== "initial") {
bad.push([shorthand, result]);
}
}
assert_array_equals(bad, []);
}, "All shorthands can serialize their longhands set to 'initial'");
test(function() {
const bad = [];
outerloop:
for (let [shorthand, longhands] of cssShorthands) {
style.cssText = "";
for (let longhand of longhands) {
if (!initialValues.has(longhand)) {
continue outerloop;
}
style.setProperty(longhand, initialValues.get(longhand));
}
const result = style.getPropertyValue(shorthand);
if (!CSS.supports(shorthand, result) && shorthand !== "all") {
bad.push([shorthand, result]);
}
}
assert_array_equals(bad, []);
}, "All shorthands (except 'all') can serialize their longhands set to their initial value");
test(function() {
const bad = [];
for (let [alias, target] of cssAliases) {
style.cssText = "";
style.setProperty(target, "initial");
const result = style.getPropertyValue(alias);
if (result !== "initial") {
bad.push([alias, result]);
}
}
assert_array_equals(bad, []);
}, "All aliases can serialize target property set to 'initial'");
test(function() {
const bad = [];
for (let [alias, target] of cssAliases) {
if (!initialValues.has(target)) {
continue;
}
style.cssText = "";
style.setProperty(target, initialValues.get(target));
const result = style.getPropertyValue(alias);
if (!CSS.supports(alias, result)) {
bad.push([alias, result]);
}
}
assert_array_equals(bad, []);
}, "All aliases can serialize target property set to its initial value");
test(function() {
const bad = [];
for (let [shorthand, longhands] of cssShorthands) {
for (let longhand of longhands) {
style.cssText = "";
style.setProperty(shorthand, "initial");
style.setProperty(longhand, "inherit");
const result = style.getPropertyValue(shorthand);
if (result !== "") {
bad.push([shorthand, longhand, result]);
}
}
}
assert_array_equals(bad, []);
}, "Can't serialize shorthand when longhands are set to different css-wide keywords");
test(function() {
const bad = [];
for (let [shorthand, longhands] of cssShorthands) {
for (let longhand of longhands) {
style.cssText = "";
style.setProperty(shorthand, "initial");
style.setProperty(longhand, "initial", "important");
const result = style.getPropertyValue(shorthand);
if (result !== "") {
bad.push([shorthand, longhand, result]);
}
}
}
assert_array_equals(bad, []);
}, "Can't serialize shorthand when longhands have different priority");
test(function() {
const bad = [];
for (let [shorthand, longhands] of cssShorthands) {
for (let longhand of longhands) {
style.cssText = "";
style.setProperty(shorthand, "initial");
style.removeProperty(longhand);
const result = style.getPropertyValue(shorthand);
if (result !== "") {
bad.push([shorthand, longhand, result]);
}
}
}
assert_array_equals(bad, []);
}, "Can't serialize shorthand set to 'initial' when some longhand is missing");
test(function() {
const bad = [];
for (let [shorthand, longhands] of cssShorthands) {
if (initialValues.has(shorthand)) {
for (let longhand of longhands) {
style.cssText = "";
style.setProperty(shorthand, initialValues.get(shorthand));
style.removeProperty(longhand);
const result = style.getPropertyValue(shorthand);
if (result !== "") {
bad.push([shorthand, longhand, result]);
}
}
}
}
assert_array_equals(bad, []);
}, "Can't serialize shorthand set to initial value when some longhand is missing");
</script>