From a28197669a9899d41ea63c06c39c9aa028ad091a Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 18 Mar 2025 14:07:08 +0000 Subject: [PATCH] LibWeb/CSS: Move property code from CSSStyleDeclaration to *Properties CSSStyleDeclaration is a base class that's used by various collections of style properties or descriptors. This commit moves all style-property-related code into CSSStyleProperties, where it belongs. As noted in the previous commit, we also apply the CSSStyleProperties prototype now. --- Documentation/CSSProperties.md | 2 +- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 243 ------------------ Libraries/LibWeb/CSS/CSSStyleDeclaration.h | 21 +- Libraries/LibWeb/CSS/CSSStyleDeclaration.idl | 8 - Libraries/LibWeb/CSS/CSSStyleProperties.cpp | 241 ++++++++++++++++- Libraries/LibWeb/CSS/CSSStyleProperties.h | 24 +- Libraries/LibWeb/CSS/CSSStyleProperties.idl | 4 + .../LibWeb/GenerateCSSStyleProperties.cpp | 10 +- .../getClientRects-in-detached-document.txt | 2 +- ...pported-properties-and-default-values.txt} | 12 +- .../css/style-declaration-parent-rule.txt | 2 +- ...ported-properties-and-default-values.html} | 2 +- 12 files changed, 276 insertions(+), 295 deletions(-) rename Tests/LibWeb/Text/expected/css/{CSSStyleDeclaration-all-supported-properties-and-default-values.txt => CSSStyleProperties-all-supported-properties-and-default-values.txt} (97%) rename Tests/LibWeb/Text/input/css/{CSSStyleDeclaration-all-supported-properties-and-default-values.html => CSSStyleProperties-all-supported-properties-and-default-values.html} (91%) diff --git a/Documentation/CSSProperties.md b/Documentation/CSSProperties.md index d2c26def7c3..9c105aadef0 100644 --- a/Documentation/CSSProperties.md +++ b/Documentation/CSSProperties.md @@ -60,4 +60,4 @@ bool Paintable::is_visible() const Some properties have special rules for getting the computed value from JS. For these, you will need to add to `CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are constructed in an unusual way (as in, not -using `ShorthandStyleValue`) also need handling inside `CSSStyleDeclaration::get_property_internal()`. +using `ShorthandStyleValue`) also need handling inside `CSSStyleProperties::get_property_internal()`. diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 4860098c687..c562ae3f90e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -9,12 +9,7 @@ #include #include #include -#include #include -#include -#include -#include -#include #include #include @@ -59,230 +54,6 @@ void CSSStyleDeclaration::update_style_attribute() set_is_updating(false); } -static Optional style_property_for_sided_shorthand(PropertyID property_id, Optional const& top, Optional const& right, Optional const& bottom, Optional const& left) -{ - if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value()) - return {}; - - if (top->important != right->important || top->important != bottom->important || top->important != left->important) - return {}; - - ValueComparingNonnullRefPtr const top_value { top->value }; - ValueComparingNonnullRefPtr const right_value { right->value }; - ValueComparingNonnullRefPtr const bottom_value { bottom->value }; - ValueComparingNonnullRefPtr 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 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 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)) { - - // 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::FontVariant: { - auto ligatures = get_property_internal(PropertyID::FontVariantLigatures); - auto caps = get_property_internal(PropertyID::FontVariantCaps); - auto alternates = get_property_internal(PropertyID::FontVariantAlternates); - auto numeric = get_property_internal(PropertyID::FontVariantNumeric); - auto east_asian = get_property_internal(PropertyID::FontVariantEastAsian); - auto position = get_property_internal(PropertyID::FontVariantPosition); - auto emoji = get_property_internal(PropertyID::FontVariantEmoji); - - if (!ligatures.has_value() || !caps.has_value() || !alternates.has_value() || !numeric.has_value() || !east_asian.has_value() || !position.has_value() || !emoji.has_value()) - return {}; - - if (ligatures->important != caps->important || ligatures->important != alternates->important || ligatures->important != numeric->important || ligatures->important != east_asian->important || ligatures->important != position->important || ligatures->important != emoji->important) - return {}; - - // If ligatures is `none` and any other value isn't `normal`, that's invalid. - if (ligatures->value->to_keyword() == Keyword::None - && (caps->value->to_keyword() != Keyword::Normal - || alternates->value->to_keyword() != Keyword::Normal - || numeric->value->to_keyword() != Keyword::Normal - || east_asian->value->to_keyword() != Keyword::Normal - || position->value->to_keyword() != Keyword::Normal - || emoji->value->to_keyword() != Keyword::Normal)) { - return {}; - } - - return StyleProperty { - .important = ligatures->important, - .property_id = property_id, - .value = ShorthandStyleValue::create(property_id, - { PropertyID::FontVariantLigatures, PropertyID::FontVariantCaps, PropertyID::FontVariantAlternates, PropertyID::FontVariantNumeric, PropertyID::FontVariantEastAsian, PropertyID::FontVariantPosition, PropertyID::FontVariantEmoji }, - { ligatures->value, caps->value, alternates->value, numeric->value, east_asian->value, position->value, emoji->value }) - }; - } - 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> list; - Optional last_important_flag; - - // 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps: - Vector 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 = get_property_internal(longhand_property_id); - - // 2. If declaration is null, then return the empty string. - if (!declaration.has_value()) - return {}; - - // 3. Append the declaration to list. - list.append(declaration->value); - - 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. - // NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string(). - 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. - } - - 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( - is_computed() ? 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( - is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue - : CSSStyleValue::SerializationMode::Normal); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority -StringView CSSStyleDeclaration::get_property_priority(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 {}; - return maybe_custom_property.value().important == Important::Yes ? "important"sv : ""sv; - } - auto maybe_property = property(property_id.value()); - if (!maybe_property.has_value()) - return {}; - return maybe_property->important == Important::Yes ? "important"sv : ""sv; -} - -WebIDL::ExceptionOr CSSStyleDeclaration::set_property(PropertyID property_id, StringView css_text, StringView priority) -{ - return set_property(string_from_property_id(property_id), css_text, priority); -} - -WebIDL::ExceptionOr CSSStyleDeclaration::remove_property(PropertyID property_name) -{ - return remove_property(string_from_property_id(property_name)); -} - // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext String CSSStyleDeclaration::css_text() const { @@ -294,20 +65,6 @@ String CSSStyleDeclaration::css_text() const return serialized(); } -// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat -String CSSStyleDeclaration::css_float() const -{ - // The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument. - return get_property_value("float"sv); -} - -WebIDL::ExceptionOr CSSStyleDeclaration::set_css_float(StringView value) -{ - // On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value, - // and no third argument. Any exceptions thrown must be re-thrown. - return set_property("float"sv, value, ""sv); -} - Optional CSSStyleDeclaration::item_value(size_t index) const { auto value = item(index); diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index 5e039e26107..7b4f63eddab 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -8,10 +8,8 @@ #pragma once #include -#include #include #include -#include #include #include @@ -19,8 +17,7 @@ namespace Web::CSS { // https://drafts.csswg.org/cssom/#css-declaration-blocks class CSSStyleDeclaration - : public Bindings::PlatformObject - , public Bindings::GeneratedCSSStyleProperties { + : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(CSSStyleDeclaration, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(CSSStyleDeclaration); @@ -31,24 +28,15 @@ public: virtual size_t length() const = 0; virtual String item(size_t index) const = 0; - virtual Optional property(PropertyID) const = 0; - virtual Optional custom_property(FlyString const& custom_property_name) const = 0; - - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority = ""sv); - virtual WebIDL::ExceptionOr remove_property(PropertyID); - virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) = 0; virtual WebIDL::ExceptionOr remove_property(StringView property_name) = 0; - String get_property_value(StringView property) const; - StringView get_property_priority(StringView property) const; + virtual String get_property_value(StringView property_name) const = 0; + virtual StringView get_property_priority(StringView property_name) const = 0; String css_text() const; virtual WebIDL::ExceptionOr set_css_text(StringView) = 0; - String css_float() const; - WebIDL::ExceptionOr set_css_float(StringView); - virtual String serialized() const = 0; // https://drafts.csswg.org/cssom/#cssstyledeclaration-computed-flag @@ -82,14 +70,11 @@ protected: virtual void visit_edges(Visitor&) override; - virtual CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() override { return *this; } - void update_style_attribute(); private: // ^PlatformObject virtual Optional item_value(size_t index) const override; - Optional get_property_internal(PropertyID) const; // https://drafts.csswg.org/cssom/#cssstyledeclaration-parent-css-rule GC::Ptr m_parent_rule { nullptr }; diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl b/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl index 8918d8fe51e..95c87cbd7db 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl @@ -1,9 +1,6 @@ -#import - // https://drafts.csswg.org/cssom/#cssstyledeclaration [Exposed=Window] interface CSSStyleDeclaration { - [CEReactions] attribute CSSOMString cssText; readonly attribute unsigned long length; @@ -16,9 +13,4 @@ interface CSSStyleDeclaration { [CEReactions] CSSOMString removeProperty(CSSOMString property); readonly attribute CSSRule? parentRule; - - [CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat; - }; - -CSSStyleDeclaration includes GeneratedCSSStyleProperties; diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp index aadb05a82d3..93f6a1dee41 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -78,8 +78,7 @@ CSSStyleProperties::CSSStyleProperties(JS::Realm& realm, Computed computed, Read void CSSStyleProperties::initialize(JS::Realm& realm) { Base::initialize(realm); - // Temporarily disabled for a single commit to make the changes cleaner. - // WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties); + WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties); } void CSSStyleProperties::visit_edges(Visitor& visitor) @@ -278,6 +277,11 @@ WebIDL::ExceptionOr CSSStyleProperties::set_property(StringView property_n return {}; } +WebIDL::ExceptionOr CSSStyleProperties::set_property(PropertyID property_id, StringView css_text, StringView priority) +{ + return set_property(string_from_property_id(property_id), css_text, priority); +} + static NonnullRefPtr style_value_for_length_percentage(LengthPercentage const& length_percentage) { if (length_percentage.is_auto()) @@ -374,6 +378,220 @@ static RefPtr style_value_for_shadow(Vector con return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma); } +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue +String CSSStyleProperties::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( + is_computed() ? 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( + is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue + : CSSStyleValue::SerializationMode::Normal); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority +StringView CSSStyleProperties::get_property_priority(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 {}; + return maybe_custom_property.value().important == Important::Yes ? "important"sv : ""sv; + } + auto maybe_property = property(property_id.value()); + if (!maybe_property.has_value()) + return {}; + return maybe_property->important == Important::Yes ? "important"sv : ""sv; +} + +static Optional style_property_for_sided_shorthand(PropertyID property_id, Optional const& top, Optional const& right, Optional const& bottom, Optional const& left) +{ + if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value()) + return {}; + + if (top->important != right->important || top->important != bottom->important || top->important != left->important) + return {}; + + ValueComparingNonnullRefPtr const top_value { top->value }; + ValueComparingNonnullRefPtr const right_value { right->value }; + ValueComparingNonnullRefPtr const bottom_value { bottom->value }; + ValueComparingNonnullRefPtr 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 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 CSSStyleProperties::get_property_internal(PropertyID property_id) const +{ + // 2. If property is a shorthand property, then follow these substeps: + 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::FontVariant: { + auto ligatures = get_property_internal(PropertyID::FontVariantLigatures); + auto caps = get_property_internal(PropertyID::FontVariantCaps); + auto alternates = get_property_internal(PropertyID::FontVariantAlternates); + auto numeric = get_property_internal(PropertyID::FontVariantNumeric); + auto east_asian = get_property_internal(PropertyID::FontVariantEastAsian); + auto position = get_property_internal(PropertyID::FontVariantPosition); + auto emoji = get_property_internal(PropertyID::FontVariantEmoji); + + if (!ligatures.has_value() || !caps.has_value() || !alternates.has_value() || !numeric.has_value() || !east_asian.has_value() || !position.has_value() || !emoji.has_value()) + return {}; + + if (ligatures->important != caps->important || ligatures->important != alternates->important || ligatures->important != numeric->important || ligatures->important != east_asian->important || ligatures->important != position->important || ligatures->important != emoji->important) + return {}; + + // If ligatures is `none` and any other value isn't `normal`, that's invalid. + if (ligatures->value->to_keyword() == Keyword::None + && (caps->value->to_keyword() != Keyword::Normal + || alternates->value->to_keyword() != Keyword::Normal + || numeric->value->to_keyword() != Keyword::Normal + || east_asian->value->to_keyword() != Keyword::Normal + || position->value->to_keyword() != Keyword::Normal + || emoji->value->to_keyword() != Keyword::Normal)) { + return {}; + } + + return StyleProperty { + .important = ligatures->important, + .property_id = property_id, + .value = ShorthandStyleValue::create(property_id, + { PropertyID::FontVariantLigatures, PropertyID::FontVariantCaps, PropertyID::FontVariantAlternates, PropertyID::FontVariantNumeric, PropertyID::FontVariantEastAsian, PropertyID::FontVariantPosition, PropertyID::FontVariantEmoji }, + { ligatures->value, caps->value, alternates->value, numeric->value, east_asian->value, position->value, emoji->value }) + }; + } + 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> list; + Optional last_important_flag; + + // 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps: + Vector 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 = get_property_internal(longhand_property_id); + + // 2. If declaration is null, then return the empty string. + if (!declaration.has_value()) + return {}; + + // 3. Append the declaration to list. + list.append(declaration->value); + + 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. + // NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string(). + 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. + } + + return property(property_id); +} + RefPtr CSSStyleProperties::style_value_for_computed_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const { auto used_value_for_property = [&layout_node, property_id](Function&& used_value_getter) -> Optional { @@ -806,6 +1024,25 @@ WebIDL::ExceptionOr CSSStyleProperties::remove_property(StringView prope return value; } +WebIDL::ExceptionOr CSSStyleProperties::remove_property(PropertyID property_name) +{ + return remove_property(string_from_property_id(property_name)); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat +String CSSStyleProperties::css_float() const +{ + // The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument. + return get_property_value("float"sv); +} + +WebIDL::ExceptionOr CSSStyleProperties::set_css_float(StringView value) +{ + // On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value, + // and no third argument. Any exceptions thrown must be re-thrown. + return set_property("float"sv, value, ""sv); +} + // https://www.w3.org/TR/cssom/#serialize-a-css-declaration static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) { diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.h b/Libraries/LibWeb/CSS/CSSStyleProperties.h index 03fff5dbe64..a6405248f4a 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.h +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.h @@ -8,11 +8,14 @@ #pragma once #include +#include namespace Web::CSS { // https://drafts.csswg.org/cssom/#cssstyleproperties -class CSSStyleProperties : public CSSStyleDeclaration { +class CSSStyleProperties + : public CSSStyleDeclaration + , public Bindings::GeneratedCSSStyleProperties { WEB_PLATFORM_OBJECT(CSSStyleProperties, CSSStyleDeclaration); GC_DECLARE_ALLOCATOR(CSSStyleProperties); @@ -28,30 +31,41 @@ public: virtual size_t length() const override; virtual String item(size_t index) const override; - virtual Optional property(PropertyID) const override; - virtual Optional custom_property(FlyString const& custom_property_name) const override; + Optional property(PropertyID) const; + Optional custom_property(FlyString const& custom_property_name) const; - // Temporary for one commit. - using Base::remove_property, Base::set_property; + WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority = ""sv); + WebIDL::ExceptionOr remove_property(PropertyID); virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; + + virtual String get_property_value(StringView property_name) const override; + virtual StringView get_property_priority(StringView property_name) const override; + Vector const& properties() const { return m_properties; } HashMap const& custom_properties() const { return m_custom_properties; } size_t custom_property_count() const { return m_custom_properties.size(); } + String css_float() const; + WebIDL::ExceptionOr set_css_float(StringView); + virtual String serialized() const final override; virtual WebIDL::ExceptionOr set_css_text(StringView) override; void set_declarations_from_text(StringView); + // ^Bindings::GeneratedCSSStyleProperties + virtual CSSStyleProperties& generated_style_properties_to_css_style_properties() override { return *this; } + private: CSSStyleProperties(JS::Realm&, Computed, Readonly, Vector properties, HashMap custom_properties, Optional); virtual void visit_edges(Cell::Visitor&) override; RefPtr style_value_for_computed_property(Layout::NodeWithStyle const&, PropertyID) const; + Optional get_property_internal(PropertyID) const; bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); void empty_the_declarations(); diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.idl b/Libraries/LibWeb/CSS/CSSStyleProperties.idl index 22db5036419..bec81f01b60 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.idl +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.idl @@ -1,6 +1,10 @@ #import +#import // https://drafts.csswg.org/cssom/#cssstyleproperties [Exposed=Window] interface CSSStyleProperties : CSSStyleDeclaration { + [CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat; }; + +CSSStyleProperties includes GeneratedCSSStyleProperties; diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp index ed138120da4..1d162d9f6ef 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSStyleProperties.cpp @@ -97,8 +97,8 @@ protected: GeneratedCSSStyleProperties() = default; virtual ~GeneratedCSSStyleProperties() = default; - virtual CSS::CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() = 0; - CSS::CSSStyleDeclaration const& generated_style_properties_to_css_style_declaration() const { return const_cast(*this).generated_style_properties_to_css_style_declaration(); } + virtual CSS::CSSStyleProperties& generated_style_properties_to_css_style_properties() = 0; + CSS::CSSStyleProperties const& generated_style_properties_to_css_style_properties() const { return const_cast(*this).generated_style_properties_to_css_style_properties(); } }; // class GeneratedCSSStyleProperties } // namespace Web::Bindings @@ -114,7 +114,7 @@ ErrorOr generate_implementation_file(JsonObject& properties, Core::File& f SourceGenerator generator { builder }; generator.append(R"~~~( -#include +#include #include #include @@ -131,12 +131,12 @@ namespace Web::Bindings { definition_generator.append(R"~~~( WebIDL::ExceptionOr GeneratedCSSStyleProperties::set_@name:acceptable_cpp@(StringView value) { - return generated_style_properties_to_css_style_declaration().set_property("@name@"sv, value, ""sv); + return generated_style_properties_to_css_style_properties().set_property("@name@"sv, value, ""sv); } String GeneratedCSSStyleProperties::@name:acceptable_cpp@() const { - return generated_style_properties_to_css_style_declaration().get_property_value("@name@"sv); + return generated_style_properties_to_css_style_properties().get_property_value("@name@"sv); } )~~~"); }); diff --git a/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt b/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt index cbd504d0973..fc942978953 100644 --- a/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt +++ b/Tests/LibWeb/Text/expected/CSSOMView/getClientRects-in-detached-document.txt @@ -1,3 +1,3 @@ -[object CSSStyleDeclaration] +[object CSSStyleProperties] [object DOMRectList] PASS (didn't crash) diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt similarity index 97% rename from Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt rename to Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt index aea43775afe..3245c3c0d4a 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt @@ -1,7 +1,4 @@ -All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle: -'cssText': '' -'length': '232' -'parentRule': 'null' +All supported properties and their default values exposed from CSSStyleProperties from getComputedStyle: 'cssFloat': 'none' 'WebkitAlignContent': 'normal' 'webkitAlignContent': 'normal' @@ -641,9 +638,4 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'y': '0px' 'zIndex': 'auto' 'z-index': 'auto' -'getPropertyPriority': 'function getPropertyPriority() { [native code] }' -'getPropertyValue': 'function getPropertyValue() { [native code] }' -'removeProperty': 'function removeProperty() { [native code] }' -'item': 'function item() { [native code] }' -'setProperty': 'function setProperty() { [native code] }' -'constructor': 'function CSSStyleDeclaration() { [native code] }' +'constructor': 'function CSSStyleProperties() { [native code] }' diff --git a/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt b/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt index 1d118bd8862..5b22506b0f2 100644 --- a/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt +++ b/Tests/LibWeb/Text/expected/css/style-declaration-parent-rule.txt @@ -1,4 +1,4 @@ spanRule: [object CSSStyleRule] ~ span { color: purple; } -spanRule.style: [object CSSStyleDeclaration] ~ span { color: purple; } +spanRule.style: [object CSSStyleProperties] ~ span { color: purple; } spanRule.style.parentRule: [object CSSStyleRule] ~ span { color: purple; } spanRule.style.parentRule === spanRule: true diff --git a/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-all-supported-properties-and-default-values.html b/Tests/LibWeb/Text/input/css/CSSStyleProperties-all-supported-properties-and-default-values.html similarity index 91% rename from Tests/LibWeb/Text/input/css/CSSStyleDeclaration-all-supported-properties-and-default-values.html rename to Tests/LibWeb/Text/input/css/CSSStyleProperties-all-supported-properties-and-default-values.html index a499a8c48ea..69af7250837 100644 --- a/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-all-supported-properties-and-default-values.html +++ b/Tests/LibWeb/Text/input/css/CSSStyleProperties-all-supported-properties-and-default-values.html @@ -10,7 +10,7 @@ const stylePrototype = Object.getPrototypeOf(defaultStyle); const supportedProperties = Object.getOwnPropertyNames(stylePrototype); - println("All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:"); + println("All supported properties and their default values exposed from CSSStyleProperties from getComputedStyle:"); for (const supportedProperty of supportedProperties) { println(`'${supportedProperty}': '${defaultStyle[supportedProperty]}'`); }