diff --git a/Tests/LibWeb/Text/expected/css/set-style-declaration-shorthand.txt b/Tests/LibWeb/Text/expected/css/set-style-declaration-shorthand.txt
new file mode 100644
index 00000000000..b6c856ce170
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/css/set-style-declaration-shorthand.txt
@@ -0,0 +1,46 @@
+Setting flex: 'none'; becomes...
+ flex-basis: 'auto'
+ flex-grow: '0'
+ flex-shrink: '0'
+ flex: '0 0 auto'
+ e.style.length: 3
+ > [0] flex-grow
+ > [1] flex-shrink
+ > [2] flex-basis
+
+Setting flex: 'auto 1 2'; becomes...
+ flex-basis: 'auto'
+ flex-grow: '1'
+ flex-shrink: '2'
+ flex: '1 2 auto'
+ e.style.length: 3
+ > [0] flex-grow
+ > [1] flex-shrink
+ > [2] flex-basis
+
+Setting flex: ''; becomes...
+ flex-basis: ''
+ flex-grow: ''
+ flex-shrink: ''
+ flex: ''
+ e.style.length: 0
+
+Setting border: '1px solid red'; becomes...
+ border-width: '1px 1px 1px 1px'
+ border-style: 'solid solid solid solid'
+ border-color: 'rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)'
+ border: ''
+ e.style.length: 12
+ > [0] border-top-width
+ > [1] border-right-width
+ > [2] border-bottom-width
+ > [3] border-left-width
+ > [4] border-top-style
+ > [5] border-right-style
+ > [6] border-bottom-style
+ > [7] border-left-style
+ > [8] border-top-color
+ > [9] border-right-color
+ > [10] border-bottom-color
+ > [11] border-left-color
+
diff --git a/Tests/LibWeb/Text/input/css/set-style-declaration-shorthand.html b/Tests/LibWeb/Text/input/css/set-style-declaration-shorthand.html
new file mode 100644
index 00000000000..540d3c7a7fc
--- /dev/null
+++ b/Tests/LibWeb/Text/input/css/set-style-declaration-shorthand.html
@@ -0,0 +1,38 @@
+
+
diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp
index 6b314e3651b..cd81b5c0d54 100644
--- a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp
+++ b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2023, Andreas Kling
+ * Copyright (c) 2018-2024, Andreas Kling
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -130,15 +131,23 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(Proper
// 7. Let updated be false.
bool updated = false;
- // FIXME: 8. If property is a shorthand property, then for each longhand property longhand that property maps to, in canonical order, follow these substeps:
- // FIXME: 1. Let longhand result be the result of set the CSS declaration longhand with the appropriate value(s) from component value list,
- // with the important flag set if priority is not the empty string, and unset otherwise, and with the list of declarations being the declarations.
- // FIXME: 2. If longhand result is true, let updated be true.
-
- // 9. Otherwise, let updated be the result of set the CSS declaration property with value component value list,
- // with the important flag set if priority is not the empty string, and unset otherwise,
- // and with the list of declarations being the declarations.
- updated = set_a_css_declaration(property_id, component_value_list.release_nonnull(), !priority.is_empty() ? Important::Yes : Important::No);
+ // 8. If property is a shorthand property,
+ if (property_is_shorthand(property_id)) {
+ // then for each longhand property longhand that property maps to, in canonical order, follow these substeps:
+ StyleComputer::for_each_property_expanding_shorthands(property_id, *component_value_list, StyleComputer::AllowUnresolved::Yes, [this, &updated, priority](PropertyID longhand_property_id, CSSStyleValue const& longhand_value) {
+ // 1. Let longhand result be the result of set the CSS declaration longhand with the appropriate value(s) from component value list,
+ // with the important flag set if priority is not the empty string, and unset otherwise, and with the list of declarations being the declarations.
+ // 2. If longhand result is true, let updated be true.
+ updated |= set_a_css_declaration(longhand_property_id, longhand_value, !priority.is_empty() ? Important::Yes : Important::No);
+ });
+ }
+ // 9. Otherwise,
+ else {
+ // let updated be the result of set the CSS declaration property with value component value list,
+ // with the important flag set if priority is not the empty string, and unset otherwise,
+ // and with the list of declarations being the declarations.
+ updated = set_a_css_declaration(property_id, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No);
+ }
// 10. If updated is true, update style attribute for the CSS declaration block.
if (updated)
@@ -222,11 +231,45 @@ 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
{
auto property_id = property_id_from_string(property_name);
if (!property_id.has_value())
return {};
+
+ // 2. If property is a shorthand property, then follow these substeps:
+ if (property_is_shorthand(property_id.value())) {
+ // 1. Let list be a new empty array.
+ StringBuilder list;
+ Optional last_important_flag;
+
+ // 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps:
+ for (auto longhand_property_id : longhands_for_shorthand(property_id.value())) {
+ // 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);
+
+ // 2. If declaration is null, then return the empty string.
+ if (!declaration.has_value())
+ return {};
+
+ // 3. Append the declaration to list.
+ if (!list.is_empty())
+ list.append(' ');
+ list.append(declaration->value->to_string());
+
+ if (last_important_flag.has_value() && declaration->important != *last_important_flag)
+ return {};
+ last_important_flag = declaration->important;
+ }
+
+ // 3. If important flags of all declarations in list are same, then return the serialization of list.
+ return list.to_string_without_validation();
+
+ // 4. Return the empty string.
+ // NOTE: This is handled by the loop.
+ }
+
auto maybe_property = property(property_id.value());
if (!maybe_property.has_value())
return {};
@@ -424,9 +467,7 @@ JS::ThrowCompletionOr CSSStyleDeclaration::internal_get(JS::PropertyK
auto property_id = property_id_from_name(name.to_string());
if (property_id == CSS::PropertyID::Invalid)
return Base::internal_get(name, receiver, cacheable_metadata, phase);
- if (auto maybe_property = property(property_id); maybe_property.has_value())
- return { JS::PrimitiveString::create(vm(), maybe_property->value->to_string()) };
- return { JS::PrimitiveString::create(vm(), String {}) };
+ return JS::PrimitiveString::create(vm(), get_property_value(string_from_property_id(property_id)));
}
JS::ThrowCompletionOr CSSStyleDeclaration::internal_set(JS::PropertyKey const& name, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata)