diff --git a/Documentation/CSSProperties.md b/Documentation/CSSProperties.md index ad60b2603f..d2c26def7c 100644 --- a/Documentation/CSSProperties.md +++ b/Documentation/CSSProperties.md @@ -59,5 +59,5 @@ bool Paintable::is_visible() const ## JavaScript Some properties have special rules for getting the computed value from JS. For these, you will need to add to -`ResolvedCSSStyleDeclaration::style_value_for_property()`. Shorthands that are constructed in an unusual way (as in, not +`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()`. diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 4e0a70eac0..75b29cce57 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -81,6 +81,7 @@ set(SOURCES CSS/CSSRule.cpp CSS/CSSRuleList.cpp CSS/CSSStyleDeclaration.cpp + CSS/CSSStyleProperties.cpp CSS/CSSStyleRule.cpp CSS/CSSStyleSheet.cpp CSS/CSSStyleValue.cpp @@ -124,7 +125,6 @@ set(SOURCES CSS/PreferredMotion.cpp CSS/Ratio.cpp CSS/Resolution.cpp - CSS/ResolvedCSSStyleDeclaration.cpp CSS/Screen.cpp CSS/ScreenOrientation.cpp CSS/Selector.cpp diff --git a/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp b/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp index ede8de3ce0..f057bf3840 100644 --- a/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp +++ b/Libraries/LibWeb/CSS/CSSKeyframeRule.cpp @@ -13,12 +13,12 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSKeyframeRule); -GC::Ref CSSKeyframeRule::create(JS::Realm& realm, CSS::Percentage key, Web::CSS::PropertyOwningCSSStyleDeclaration& declarations) +GC::Ref CSSKeyframeRule::create(JS::Realm& realm, Percentage key, CSSStyleProperties& declarations) { return realm.create(realm, key, declarations); } -CSSKeyframeRule::CSSKeyframeRule(JS::Realm& realm, CSS::Percentage key, PropertyOwningCSSStyleDeclaration& declarations) +CSSKeyframeRule::CSSKeyframeRule(JS::Realm& realm, Percentage key, CSSStyleProperties& declarations) : CSSRule(realm, Type::Keyframe) , m_key(key) , m_declarations(declarations) diff --git a/Libraries/LibWeb/CSS/CSSKeyframeRule.h b/Libraries/LibWeb/CSS/CSSKeyframeRule.h index 343066bda3..c6be31e54a 100644 --- a/Libraries/LibWeb/CSS/CSSKeyframeRule.h +++ b/Libraries/LibWeb/CSS/CSSKeyframeRule.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -21,13 +21,12 @@ class CSSKeyframeRule final : public CSSRule { GC_DECLARE_ALLOCATOR(CSSKeyframeRule); public: - static GC::Ref create(JS::Realm&, CSS::Percentage key, PropertyOwningCSSStyleDeclaration&); + static GC::Ref create(JS::Realm&, CSS::Percentage key, CSSStyleProperties&); virtual ~CSSKeyframeRule() = default; CSS::Percentage key() const { return m_key; } - GC::Ref style() const { return m_declarations; } - GC::Ref style_as_property_owning_style_declaration() const { return m_declarations; } + GC::Ref style() const { return m_declarations; } String key_text() const { @@ -40,14 +39,14 @@ public: } private: - CSSKeyframeRule(JS::Realm&, CSS::Percentage, PropertyOwningCSSStyleDeclaration&); + CSSKeyframeRule(JS::Realm&, CSS::Percentage, CSSStyleProperties&); virtual void visit_edges(Visitor&) override; virtual void initialize(JS::Realm&) override; virtual String serialized() const override; CSS::Percentage m_key; - GC::Ref m_declarations; + GC::Ref m_declarations; }; template<> diff --git a/Libraries/LibWeb/CSS/CSSKeyframeRule.idl b/Libraries/LibWeb/CSS/CSSKeyframeRule.idl index 1d6f21767e..9daf1560e1 100644 --- a/Libraries/LibWeb/CSS/CSSKeyframeRule.idl +++ b/Libraries/LibWeb/CSS/CSSKeyframeRule.idl @@ -1,9 +1,9 @@ #import -#import +#import // https://drafts.csswg.org/css-animations-1/#interface-csskeyframerule-idl [Exposed=Window] interface CSSKeyframeRule : CSSRule { attribute CSSOMString keyText; - [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; + [SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style; }; diff --git a/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp b/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp index 0463f86542..4bed40058d 100644 --- a/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp +++ b/Libraries/LibWeb/CSS/CSSNestedDeclarations.cpp @@ -13,12 +13,12 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSNestedDeclarations); -GC::Ref CSSNestedDeclarations::create(JS::Realm& realm, PropertyOwningCSSStyleDeclaration& declaration) +GC::Ref CSSNestedDeclarations::create(JS::Realm& realm, CSSStyleProperties& declaration) { return realm.create(realm, declaration); } -CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, PropertyOwningCSSStyleDeclaration& declaration) +CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, CSSStyleProperties& declaration) : CSSRule(realm, Type::NestedDeclarations) , m_declaration(declaration) { diff --git a/Libraries/LibWeb/CSS/CSSNestedDeclarations.h b/Libraries/LibWeb/CSS/CSSNestedDeclarations.h index 18206b5266..34b6db1427 100644 --- a/Libraries/LibWeb/CSS/CSSNestedDeclarations.h +++ b/Libraries/LibWeb/CSS/CSSNestedDeclarations.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Sam Atkins + * Copyright (c) 2024-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +7,7 @@ #pragma once #include +#include namespace Web::CSS { @@ -15,25 +16,25 @@ class CSSNestedDeclarations final : public CSSRule { GC_DECLARE_ALLOCATOR(CSSNestedDeclarations); public: - [[nodiscard]] static GC::Ref create(JS::Realm&, PropertyOwningCSSStyleDeclaration&); + [[nodiscard]] static GC::Ref create(JS::Realm&, CSSStyleProperties&); virtual ~CSSNestedDeclarations() override = default; - PropertyOwningCSSStyleDeclaration const& declaration() const { return m_declaration; } + CSSStyleProperties const& declaration() const { return m_declaration; } CSSStyleDeclaration* style(); CSSStyleRule const& parent_style_rule() const; private: - CSSNestedDeclarations(JS::Realm&, PropertyOwningCSSStyleDeclaration&); + CSSNestedDeclarations(JS::Realm&, CSSStyleProperties&); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; virtual String serialized() const override; virtual void clear_caches() override; - GC::Ref m_declaration; + GC::Ref m_declaration; GC::Ptr mutable m_parent_style_rule; }; diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 1bec03e8f1..4860098c68 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -17,12 +17,10 @@ #include #include #include -#include namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSStyleDeclaration); -GC_DEFINE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); CSSStyleDeclaration::CSSStyleDeclaration(JS::Realm& realm, Computed computed, Readonly readonly) : PlatformObject(realm) @@ -40,169 +38,6 @@ void CSSStyleDeclaration::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleDeclaration); } -GC::Ref PropertyOwningCSSStyleDeclaration::create(JS::Realm& realm, Vector properties, HashMap custom_properties) -{ - return realm.create(realm, nullptr, move(properties), move(custom_properties)); -} - -GC::Ref PropertyOwningCSSStyleDeclaration::create_element_inline_style(JS::Realm& realm, GC::Ref element, Vector properties, HashMap custom_properties) -{ - return realm.create(realm, element, move(properties), move(custom_properties)); -} - -PropertyOwningCSSStyleDeclaration::PropertyOwningCSSStyleDeclaration(JS::Realm& realm, GC::Ptr element, Vector properties, HashMap custom_properties) - : CSSStyleDeclaration(realm, Computed::No, Readonly::No) - , m_properties(move(properties)) - , m_custom_properties(move(custom_properties)) -{ - if (element) - set_owner_node(DOM::ElementReference { *element }); -} - -void PropertyOwningCSSStyleDeclaration::visit_edges(Visitor& visitor) -{ - Base::visit_edges(visitor); - for (auto& property : m_properties) { - if (property.value->is_image()) - property.value->as_image().visit_edges(visitor); - } -} - -String PropertyOwningCSSStyleDeclaration::item(size_t index) const -{ - if (index >= m_properties.size()) - return {}; - return CSS::string_from_property_id(m_properties[index].property_id).to_string(); -} - -size_t PropertyOwningCSSStyleDeclaration::length() const -{ - return m_properties.size(); -} - -Optional PropertyOwningCSSStyleDeclaration::property(PropertyID property_id) const -{ - for (auto& property : m_properties) { - if (property.property_id == property_id) - return property; - } - return {}; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(StringView property_name, StringView value, StringView priority) -{ - auto maybe_property_id = property_id_from_string(property_name); - if (!maybe_property_id.has_value()) - return {}; - auto property_id = maybe_property_id.value(); - - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - // NOTE: This is handled by the virtual override in ResolvedCSSStyleDeclaration. - - // FIXME: 2. If property is not a custom property, follow these substeps: - // FIXME: 1. Let property be property converted to ASCII lowercase. - // FIXME: 2. If property is not a case-sensitive match for a supported CSS property, then return. - // NOTE: This must be handled before we've turned the property string into a PropertyID. - - // 3. If value is the empty string, invoke removeProperty() with property as argument and return. - if (value.is_empty()) { - MUST(remove_property(property_name)); - return {}; - } - - // 4. If priority is not the empty string and is not an ASCII case-insensitive match for the string "important", then return. - if (!priority.is_empty() && !Infra::is_ascii_case_insensitive_match(priority, "important"sv)) - return {}; - - // 5. Let component value list be the result of parsing value for property property. - auto component_value_list = owner_node().has_value() - ? parse_css_value(CSS::Parser::ParsingParams { owner_node()->element().document() }, value, property_id) - : parse_css_value(CSS::Parser::ParsingParams {}, value, property_id); - - // 6. If component value list is null, then return. - if (!component_value_list) - return {}; - - // 7. Let updated be false. - bool updated = false; - - // 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 { - if (property_id == PropertyID::Custom) { - auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); - StyleProperty style_property { - .important = !priority.is_empty() ? Important::Yes : Important::No, - .property_id = property_id, - .value = component_value_list.release_nonnull(), - .custom_name = custom_name, - }; - m_custom_properties.set(custom_name, style_property); - updated = true; - } 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) - update_style_attribute(); - - return {}; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(StringView property_name) -{ - auto property_id = property_id_from_string(property_name); - if (!property_id.has_value()) - return String {}; - - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - // NOTE: This is handled by the virtual override in ResolvedCSSStyleDeclaration. - - // 2. If property is not a custom property, let property be property converted to ASCII lowercase. - // NOTE: We've already converted it to a PropertyID enum value. - - // 3. Let value be the return value of invoking getPropertyValue() with property as argument. - auto value = get_property_value(property_name); - - // 4. Let removed be false. - bool removed = false; - - // FIXME: 5. If property is a shorthand property, for each longhand property longhand that property maps to: - // 1. If longhand is not a property name of a CSS declaration in the declarations, continue. - // 2. Remove that CSS declaration and let removed be true. - - // 6. Otherwise, if property is a case-sensitive match for a property name of a CSS declaration in the declarations, remove that CSS declaration and let removed be true. - if (property_id == PropertyID::Custom) { - auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); - removed = m_custom_properties.remove(custom_name); - } else { - removed = m_properties.remove_first_matching([&](auto& entry) { return entry.property_id == property_id; }); - } - - // 7. If removed is true, Update style attribute for the CSS declaration block. - if (removed) - update_style_attribute(); - - // 8. Return value. - return value; -} - // https://drafts.csswg.org/cssom/#update-style-attribute-for void CSSStyleDeclaration::update_style_attribute() { @@ -224,29 +59,6 @@ void CSSStyleDeclaration::update_style_attribute() set_is_updating(false); } -// https://drafts.csswg.org/cssom/#set-a-css-declaration -bool PropertyOwningCSSStyleDeclaration::set_a_css_declaration(PropertyID property_id, NonnullRefPtr value, Important important) -{ - // FIXME: Handle logical property groups. - - for (auto& property : m_properties) { - if (property.property_id == property_id) { - if (property.important == important && *property.value == *value) - return false; - property.value = move(value); - property.important = important; - return true; - } - } - - m_properties.append(CSS::StyleProperty { - .important = important, - .property_id = property_id, - .value = move(value), - }); - return true; -} - 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()) @@ -475,7 +287,8 @@ WebIDL::ExceptionOr CSSStyleDeclaration::remove_property(PropertyID prop String CSSStyleDeclaration::css_text() const { // 1. If the computed flag is set, then return the empty string. - // NOTE: See ResolvedCSSStyleDeclaration::serialized() + if (is_computed()) + return {}; // 2. Return the result of serializing the declarations. return serialized(); @@ -512,166 +325,4 @@ void CSSStyleDeclaration::visit_edges(Visitor& visitor) m_owner_node->visit(visitor); } -// https://www.w3.org/TR/cssom/#serialize-a-css-declaration -static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) -{ - StringBuilder builder; - - // 1. Let s be the empty string. - // 2. Append property to s. - builder.append(string_from_property_id(property)); - - // 3. Append ": " (U+003A U+0020) to s. - builder.append(": "sv); - - // 4. Append value to s. - builder.append(value); - - // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. - if (important == Important::Yes) - builder.append(" !important"sv); - - // 6. Append ";" (U+003B) to s. - builder.append(';'); - - // 7. Return s. - return MUST(builder.to_string()); -} - -// https://www.w3.org/TR/cssom/#serialize-a-css-declaration-block -String PropertyOwningCSSStyleDeclaration::serialized() const -{ - // 1. Let list be an empty array. - Vector list; - - // 2. Let already serialized be an empty array. - HashTable already_serialized; - - // NOTE: The spec treats custom properties the same as any other property, and expects the above loop to handle them. - // However, our implementation separates them from regular properties, so we need to handle them separately here. - // FIXME: Is the relative order of custom properties and regular properties supposed to be preserved? - for (auto& declaration : m_custom_properties) { - // 1. Let property be declaration’s property name. - auto const& property = declaration.key; - - // 2. If property is in already serialized, continue with the steps labeled declaration loop. - // NOTE: It is never in already serialized, as there are no shorthands for custom properties. - - // 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. - // NOTE: There are no shorthands for custom properties. - - // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... - // NOTE: There are no shorthands for custom properties. - - // 5. Let value be the result of invoking serialize a CSS value of declaration. - auto value = declaration.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); - - // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, - // and the important flag set if declaration has its important flag set. - // NOTE: We have to inline this here as the actual implementation does not accept custom properties. - String serialized_declaration = [&] { - // https://www.w3.org/TR/cssom/#serialize-a-css-declaration - StringBuilder builder; - - // 1. Let s be the empty string. - // 2. Append property to s. - builder.append(property); - - // 3. Append ": " (U+003A U+0020) to s. - builder.append(": "sv); - - // 4. Append value to s. - builder.append(value); - - // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. - if (declaration.value.important == Important::Yes) - builder.append(" !important"sv); - - // 6. Append ";" (U+003B) to s. - builder.append(';'); - - // 7. Return s. - return MUST(builder.to_string()); - }(); - - // 7. Append serialized declaration to list. - list.append(move(serialized_declaration)); - - // 8. Append property to already serialized. - // NOTE: We don't need to do this, as we don't have shorthands for custom properties. - } - - // 3. Declaration loop: For each CSS declaration declaration in declaration block’s declarations, follow these substeps: - for (auto& declaration : m_properties) { - // 1. Let property be declaration’s property name. - auto property = declaration.property_id; - - // 2. If property is in already serialized, continue with the steps labeled declaration loop. - if (already_serialized.contains(property)) - continue; - - // FIXME: 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. - - // FIXME: 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... - - // 5. Let value be the result of invoking serialize a CSS value of declaration. - auto value = declaration.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); - - // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, - // and the important flag set if declaration has its important flag set. - auto serialized_declaration = serialize_a_css_declaration(property, move(value), declaration.important); - - // 7. Append serialized declaration to list. - list.append(move(serialized_declaration)); - - // 8. Append property to already serialized. - already_serialized.set(property); - } - - // 4. Return list joined with " " (U+0020). - StringBuilder builder; - builder.join(' ', list); - return MUST(builder.to_string()); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_css_text(StringView css_text) -{ - // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. - if (is_readonly()) { - return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties: CSSStyleDeclaration is read-only."_string); - } - - // 2. Empty the declarations. - // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the declarations, in specified order. - set_declarations_from_text(css_text); - - // 4. Update style attribute for the CSS declaration block. - update_style_attribute(); - - return {}; -} - -void PropertyOwningCSSStyleDeclaration::empty_the_declarations() -{ - m_properties.clear(); - m_custom_properties.clear(); -} - -void PropertyOwningCSSStyleDeclaration::set_the_declarations(Vector properties, HashMap custom_properties) -{ - m_properties = move(properties); - m_custom_properties = move(custom_properties); -} - -void PropertyOwningCSSStyleDeclaration::set_declarations_from_text(StringView css_text) -{ - empty_the_declarations(); - auto parsing_params = owner_node().has_value() - ? Parser::ParsingParams(owner_node()->element().document()) - : Parser::ParsingParams(); - auto style = parse_css_style_attribute(parsing_params, css_text); - set_the_declarations(style.properties, style.custom_properties); -} - } diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index 0092fa13cc..5e039e2610 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -107,50 +107,4 @@ private: bool m_updating { false }; }; -class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration { - WEB_PLATFORM_OBJECT(PropertyOwningCSSStyleDeclaration, CSSStyleDeclaration); - GC_DECLARE_ALLOCATOR(PropertyOwningCSSStyleDeclaration); - -public: - [[nodiscard]] static GC::Ref - create(JS::Realm&, Vector, HashMap custom_properties); - - [[nodiscard]] static GC::Ref - create_element_inline_style(JS::Realm&, GC::Ref, Vector, HashMap custom_properties); - - virtual ~PropertyOwningCSSStyleDeclaration() override = default; - - 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 { return m_custom_properties.get(custom_property_name); } - - virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr remove_property(StringView property_name) 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(); } - - virtual String serialized() const final override; - virtual WebIDL::ExceptionOr set_css_text(StringView) override; - - void set_declarations_from_text(StringView); - -protected: - PropertyOwningCSSStyleDeclaration(JS::Realm&, GC::Ptr owner_node, Vector, HashMap); - - void empty_the_declarations(); - void set_the_declarations(Vector properties, HashMap custom_properties); - -private: - bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); - - virtual void visit_edges(Cell::Visitor&) override; - - Vector m_properties; - HashMap m_custom_properties; -}; - } diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp similarity index 58% rename from Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp rename to Libraries/LibWeb/CSS/CSSStyleProperties.cpp index cba2e8bf13..aadb05a82d 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -1,66 +1,281 @@ /* - * Copyright (c) 2021-2025, Andreas Kling - * Copyright (c) 2021, Tobias Christiansen - * Copyright (c) 2022-2025, Sam Atkins + * Copyright (c) 2018-2024, Andreas Kling + * Copyright (c) 2023-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include +#include +#include +#include +#include #include -#include +#include #include -#include -#include -#include #include #include +#include #include #include #include -#include #include #include #include #include #include +#include +#include +#include #include -#include namespace Web::CSS { -GC_DEFINE_ALLOCATOR(ResolvedCSSStyleDeclaration); +GC_DEFINE_ALLOCATOR(CSSStyleProperties); -GC::Ref ResolvedCSSStyleDeclaration::create(DOM::Element& element, Optional pseudo_element) +GC::Ref CSSStyleProperties::create(JS::Realm& realm, Vector properties, HashMap custom_properties) { - return element.realm().create(element, move(pseudo_element)); + // https://drafts.csswg.org/cssom/#dom-cssstylerule-style + // The style attribute must return a CSSStyleProperties object for the style rule, with the following properties: + // computed flag: Unset. + // readonly flag: Unset. + // declarations: The declared declarations in the rule, in specified order. + // parent CSS rule: The context object. + // owner node: Null. + return realm.create(realm, Computed::No, Readonly::No, move(properties), move(custom_properties), OptionalNone {}); } -ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element, Optional pseudo_element) - : CSSStyleDeclaration(element.realm(), Computed::Yes, Readonly::Yes) +GC::Ref CSSStyleProperties::create_resolved_style(DOM::ElementReference element_reference) { - set_owner_node(DOM::ElementReference { element, pseudo_element }); + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle + // 6. Return a live CSSStyleProperties object with the following properties: + // computed flag: Set. + // readonly flag: Set. + // declarations: decls. + // parent CSS rule: Null. + // owner node: obj. + // AD-HOC: Rather than instantiate with a list of decls, they're generated on demand. + auto& realm = element_reference.element().realm(); + return realm.create(realm, Computed::Yes, Readonly::Yes, Vector {}, HashMap {}, move(element_reference)); +} + +GC::Ref CSSStyleProperties::create_element_inline_style(DOM::ElementReference element_reference, Vector properties, HashMap custom_properties) +{ + // https://drafts.csswg.org/cssom/#dom-elementcssinlinestyle-style + // The style attribute must return a CSS declaration block object whose readonly flag is unset, whose parent CSS + // rule is null, and whose owner node is the context object. + auto& realm = element_reference.element().realm(); + return realm.create(realm, Computed::No, Readonly::No, move(properties), move(custom_properties), move(element_reference)); +} + +CSSStyleProperties::CSSStyleProperties(JS::Realm& realm, Computed computed, Readonly readonly, Vector properties, HashMap custom_properties, Optional owner_node) + : CSSStyleDeclaration(realm, computed, readonly) + , m_properties(move(properties)) + , m_custom_properties(move(custom_properties)) +{ + set_owner_node(move(owner_node)); +} + +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); +} + +void CSSStyleProperties::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + for (auto& property : m_properties) { + if (property.value->is_image()) + property.value->as_image().visit_edges(visitor); + } } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length -size_t ResolvedCSSStyleDeclaration::length() const +size_t CSSStyleProperties::length() const { // The length attribute must return the number of CSS declarations in the declarations. // FIXME: Include the number of custom properties. - return to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1; + + if (is_computed()) + return to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1; + + return m_properties.size(); } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item -String ResolvedCSSStyleDeclaration::item(size_t index) const +String CSSStyleProperties::item(size_t index) const { // The item(index) method must return the property name of the CSS declaration at position index. - // FIXME: Return custom properties if index > last_longhand_property_id. + // FIXME: Include custom properties. + if (index >= length()) return {}; - auto property_id = static_cast(index + to_underlying(first_longhand_property_id)); - return string_from_property_id(property_id).to_string(); + + if (is_computed()) { + auto property_id = static_cast(index + to_underlying(first_longhand_property_id)); + return string_from_property_id(property_id).to_string(); + } + + return CSS::string_from_property_id(m_properties[index].property_id).to_string(); +} + +Optional CSSStyleProperties::property(PropertyID property_id) const +{ + if (is_computed()) { + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); + + // https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle + // NB: This is a partial enforcement of step 5 ("If elt is connected, ...") + if (!element.is_connected()) + return {}; + + auto get_layout_node = [&]() { + if (pseudo_element.has_value()) + return element.get_pseudo_element_node(pseudo_element.value()); + return element.layout_node(); + }; + + Layout::NodeWithStyle* layout_node = get_layout_node(); + + // FIXME: Be smarter about updating layout if there's no layout node. + // We may legitimately have no layout node if we're not visible, but this protects against situations + // where we're requesting the computed style before layout has happened. + if (!layout_node || property_affects_layout(property_id)) { + element.document().update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty); + layout_node = get_layout_node(); + } else { + // FIXME: If we had a way to update style for a single element, this would be a good place to use it. + element.document().update_style(); + } + + if (!layout_node) { + auto style = element.document().style_computer().compute_style(element, pseudo_element); + + // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. + auto const* value = style->maybe_null_property(property_id); + if (!value) { + dbgln("FIXME: CSSStyleProperties::property(property_id={:#x}) No value for property ID in newly computed style case.", to_underlying(property_id)); + return {}; + } + return StyleProperty { + .property_id = property_id, + .value = *value, + }; + } + + auto value = style_value_for_computed_property(*layout_node, property_id); + if (!value) + return {}; + return StyleProperty { + .property_id = property_id, + .value = *value, + }; + } + + for (auto& property : m_properties) { + if (property.property_id == property_id) + return property; + } + return {}; +} + +Optional CSSStyleProperties::custom_property(FlyString const& custom_property_name) const +{ + if (is_computed()) { + auto& element = owner_node()->element(); + auto pseudo_element = owner_node()->pseudo_element(); + + element.document().update_style(); + + auto const* element_to_check = &element; + while (element_to_check) { + if (auto property = element_to_check->custom_properties(pseudo_element).get(custom_property_name); property.has_value()) + return *property; + + element_to_check = element_to_check->parent_element(); + } + + return {}; + } + + return m_custom_properties.get(custom_property_name); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty +WebIDL::ExceptionOr CSSStyleProperties::set_property(StringView property_name, StringView value, StringView priority) +{ + // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. + if (is_computed()) + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()"_string); + + // FIXME: 2. If property is not a custom property, follow these substeps: + // FIXME: 1. Let property be property converted to ASCII lowercase. + // FIXME: 2. If property is not a case-sensitive match for a supported CSS property, then return. + // NB: This must be handled before we've turned the property string into a PropertyID. + + auto maybe_property_id = property_id_from_string(property_name); + if (!maybe_property_id.has_value()) + return {}; + auto property_id = maybe_property_id.value(); + + // 3. If value is the empty string, invoke removeProperty() with property as argument and return. + if (value.is_empty()) { + MUST(remove_property(property_name)); + return {}; + } + + // 4. If priority is not the empty string and is not an ASCII case-insensitive match for the string "important", then return. + if (!priority.is_empty() && !Infra::is_ascii_case_insensitive_match(priority, "important"sv)) + return {}; + + // 5. Let component value list be the result of parsing value for property property. + auto component_value_list = owner_node().has_value() + ? parse_css_value(CSS::Parser::ParsingParams { owner_node()->element().document() }, value, property_id) + : parse_css_value(CSS::Parser::ParsingParams {}, value, property_id); + + // 6. If component value list is null, then return. + if (!component_value_list) + return {}; + + // 7. Let updated be false. + bool updated = false; + + // 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 { + if (property_id == PropertyID::Custom) { + auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); + StyleProperty style_property { + .important = !priority.is_empty() ? Important::Yes : Important::No, + .property_id = property_id, + .value = component_value_list.release_nonnull(), + .custom_name = custom_name, + }; + m_custom_properties.set(custom_name, style_property); + updated = true; + } 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) + update_style_attribute(); + + return {}; } static NonnullRefPtr style_value_for_length_percentage(LengthPercentage const& length_percentage) @@ -159,7 +374,7 @@ static RefPtr style_value_for_shadow(Vector con return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma); } -RefPtr ResolvedCSSStyleDeclaration::style_value_for_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const +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 { auto const& display = layout_node.computed_values().display(); @@ -222,18 +437,18 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return CSSColorValue::create_from_color(layout_node.computed_values().background_color(), ColorSyntax::Modern); case PropertyID::BorderBlockEndColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderBottomColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderBottomColor); case PropertyID::BorderBlockStartColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderTopColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderTopColor); case PropertyID::BorderBottomColor: return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color, ColorSyntax::Modern); case PropertyID::BorderInlineEndColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderRightColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderRightColor); case PropertyID::BorderInlineStartColor: // FIXME: Honor writing-mode, direction and text-orientation. - return style_value_for_property(layout_node, PropertyID::BorderLeftColor); + return style_value_for_computed_property(layout_node, PropertyID::BorderLeftColor); case PropertyID::BorderLeftColor: return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color, ColorSyntax::Modern); case PropertyID::BorderRightColor: @@ -250,7 +465,7 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert return CSSColorValue::create_from_color(layout_node.computed_values().outline_color(), ColorSyntax::Modern); case PropertyID::TextDecorationColor: return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color(), ColorSyntax::Modern); - // NOTE: text-shadow isn't listed, but is computed the same as box-shadow. + // NB: text-shadow isn't listed, but is computed the same as box-shadow. case PropertyID::TextShadow: return style_value_for_shadow(layout_node.computed_values().text_shadow()); @@ -290,8 +505,8 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert auto writing_mode = layout_node.computed_values().writing_mode(); auto is_vertically_oriented = first_is_one_of(writing_mode, WritingMode::VerticalLr, WritingMode::VerticalRl); if (is_vertically_oriented) - return style_value_for_property(layout_node, PropertyID::Width); - return style_value_for_property(layout_node, PropertyID::Height); + return style_value_for_computed_property(layout_node, PropertyID::Width); + return style_value_for_computed_property(layout_node, PropertyID::Height); } case PropertyID::Height: { auto maybe_used_height = used_value_for_property([](auto const& paintable_box) { return paintable_box.content_height(); }); @@ -303,8 +518,8 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert auto writing_mode = layout_node.computed_values().writing_mode(); auto is_vertically_oriented = first_is_one_of(writing_mode, WritingMode::VerticalLr, WritingMode::VerticalRl); if (is_vertically_oriented) - return style_value_for_property(layout_node, PropertyID::Height); - return style_value_for_property(layout_node, PropertyID::Width); + return style_value_for_computed_property(layout_node, PropertyID::Height); + return style_value_for_computed_property(layout_node, PropertyID::Width); } case PropertyID::MarginBlockEnd: if (auto maybe_used_value = used_value_for_property([&](auto const& paintable_box) { return pixels_for_pixel_box_logical_side(layout_node, paintable_box.box_model().margin, LogicalSide::BlockEnd); }); maybe_used_value.has_value()) @@ -451,8 +666,8 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // A 3x2 transformation matrix, // or a 4x4 matrix where the items m31, m32, m13, m23, m43, m14, m24, m34 are equal to 0 // and m33, m44 are equal to 1. - // NOTE: We only care about 4x4 matrices here. - // NOTE: Our elements are 0-indexed not 1-indexed, and in the opposite order. + // NB: We only care about 4x4 matrices here. + // NB: Our elements are 0-indexed not 1-indexed, and in the opposite order. if (matrix.elements()[0][2] != 0 // m31 || matrix.elements()[1][2] != 0 // m32 || matrix.elements()[2][0] != 0 // m13 @@ -546,135 +761,236 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert StyleValueVector longhand_values; longhand_values.ensure_capacity(longhand_ids.size()); for (auto longhand_id : longhand_ids) - longhand_values.append(style_value_for_property(layout_node, longhand_id).release_nonnull()); + longhand_values.append(style_value_for_computed_property(layout_node, longhand_id).release_nonnull()); return ShorthandStyleValue::create(property_id, move(longhand_ids), move(longhand_values)); } } -Optional ResolvedCSSStyleDeclaration::property(PropertyID property_id) const +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty +WebIDL::ExceptionOr CSSStyleProperties::remove_property(StringView property_name) { - auto& element = owner_node()->element(); - auto pseudo_element = owner_node()->pseudo_element(); + // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. + if (is_readonly()) + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot remove property: CSSStyleProperties is read-only."_string); - // https://www.w3.org/TR/cssom-1/#dom-window-getcomputedstyle - // NOTE: This is a partial enforcement of step 5 ("If elt is connected, ...") - if (!element.is_connected()) - return {}; + auto property_id = property_id_from_string(property_name); + if (!property_id.has_value()) + return String {}; - auto get_layout_node = [&]() { - if (pseudo_element.has_value()) - return element.get_pseudo_element_node(pseudo_element.value()); - return element.layout_node(); - }; + // 2. If property is not a custom property, let property be property converted to ASCII lowercase. + // NB: We've already converted it to a PropertyID enum value. - Layout::NodeWithStyle* layout_node = get_layout_node(); + // 3. Let value be the return value of invoking getPropertyValue() with property as argument. + auto value = get_property_value(property_name); - // FIXME: Be smarter about updating layout if there's no layout node. - // We may legitimately have no layout node if we're not visible, but this protects against situations - // where we're requesting the computed style before layout has happened. - if (!layout_node || property_affects_layout(property_id)) { - element.document().update_layout(DOM::UpdateLayoutReason::ResolvedCSSStyleDeclarationProperty); - layout_node = get_layout_node(); + // 4. Let removed be false. + bool removed = false; + + // FIXME: 5. If property is a shorthand property, for each longhand property longhand that property maps to: + // 1. If longhand is not a property name of a CSS declaration in the declarations, continue. + // 2. Remove that CSS declaration and let removed be true. + + // 6. Otherwise, if property is a case-sensitive match for a property name of a CSS declaration in the declarations, remove that CSS declaration and let removed be true. + if (property_id == PropertyID::Custom) { + auto custom_name = FlyString::from_utf8_without_validation(property_name.bytes()); + removed = m_custom_properties.remove(custom_name); } else { - // FIXME: If we had a way to update style for a single element, this would be a good place to use it. - element.document().update_style(); + removed = m_properties.remove_first_matching([&](auto& entry) { return entry.property_id == property_id; }); } - if (!layout_node) { - auto style = element.document().style_computer().compute_style(element, pseudo_element); + // 7. If removed is true, Update style attribute for the CSS declaration block. + if (removed) + update_style_attribute(); - // FIXME: This is a stopgap until we implement shorthand -> longhand conversion. - auto const* value = style->maybe_null_property(property_id); - if (!value) { - dbgln("FIXME: ResolvedCSSStyleDeclaration::property(property_id={:#x}) No value for property ID in newly computed style case.", to_underlying(property_id)); - return {}; - } - return StyleProperty { - .property_id = property_id, - .value = *value, - }; - } - - auto value = style_value_for_property(*layout_node, property_id); - if (!value) - return {}; - return StyleProperty { - .property_id = property_id, - .value = *value, - }; + // 8. Return value. + return value; } -Optional ResolvedCSSStyleDeclaration::custom_property(FlyString const& name) const +// https://www.w3.org/TR/cssom/#serialize-a-css-declaration +static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) { - auto& element = owner_node()->element(); - auto pseudo_element = owner_node()->pseudo_element(); + StringBuilder builder; - element.document().update_style(); + // 1. Let s be the empty string. + // 2. Append property to s. + builder.append(string_from_property_id(property)); - auto const* element_to_check = &element; - while (element_to_check) { - if (auto property = element_to_check->custom_properties(pseudo_element).get(name); property.has_value()) - return *property; + // 3. Append ": " (U+003A U+0020) to s. + builder.append(": "sv); - element_to_check = element_to_check->parent_element(); + // 4. Append value to s. + builder.append(value); + + // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. + if (important == Important::Yes) + builder.append(" !important"sv); + + // 6. Append ";" (U+003B) to s. + builder.append(';'); + + // 7. Return s. + return MUST(builder.to_string()); +} + +// https://www.w3.org/TR/cssom/#serialize-a-css-declaration-block +String CSSStyleProperties::serialized() const +{ + // 1. Let list be an empty array. + Vector list; + + // 2. Let already serialized be an empty array. + HashTable already_serialized; + + // NB: The spec treats custom properties the same as any other property, and expects the above loop to handle them. + // However, our implementation separates them from regular properties, so we need to handle them separately here. + // FIXME: Is the relative order of custom properties and regular properties supposed to be preserved? + for (auto& declaration : m_custom_properties) { + // 1. Let property be declaration’s property name. + auto const& property = declaration.key; + + // 2. If property is in already serialized, continue with the steps labeled declaration loop. + // NB: It is never in already serialized, as there are no shorthands for custom properties. + + // 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. + // NB: There are no shorthands for custom properties. + + // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... + // NB: There are no shorthands for custom properties. + + // 5. Let value be the result of invoking serialize a CSS value of declaration. + auto value = declaration.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); + + // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, + // and the important flag set if declaration has its important flag set. + // NB: We have to inline this here as the actual implementation does not accept custom properties. + String serialized_declaration = [&] { + // https://www.w3.org/TR/cssom/#serialize-a-css-declaration + StringBuilder builder; + + // 1. Let s be the empty string. + // 2. Append property to s. + builder.append(property); + + // 3. Append ": " (U+003A U+0020) to s. + builder.append(": "sv); + + // 4. Append value to s. + builder.append(value); + + // 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s. + if (declaration.value.important == Important::Yes) + builder.append(" !important"sv); + + // 6. Append ";" (U+003B) to s. + builder.append(';'); + + // 7. Return s. + return MUST(builder.to_string()); + }(); + + // 7. Append serialized declaration to list. + list.append(move(serialized_declaration)); + + // 8. Append property to already serialized. + // NB: We don't need to do this, as we don't have shorthands for custom properties. } + // 3. Declaration loop: For each CSS declaration declaration in declaration block’s declarations, follow these substeps: + for (auto& declaration : m_properties) { + // 1. Let property be declaration’s property name. + auto property = declaration.property_id; + + // 2. If property is in already serialized, continue with the steps labeled declaration loop. + if (already_serialized.contains(property)) + continue; + + // FIXME: 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. + + // FIXME: 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... + + // 5. Let value be the result of invoking serialize a CSS value of declaration. + auto value = declaration.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal); + + // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, + // and the important flag set if declaration has its important flag set. + auto serialized_declaration = serialize_a_css_declaration(property, move(value), declaration.important); + + // 7. Append serialized declaration to list. + list.append(move(serialized_declaration)); + + // 8. Append property to already serialized. + already_serialized.set(property); + } + + // 4. Return list joined with " " (U+0020). + StringBuilder builder; + builder.join(' ', list); + return MUST(builder.to_string()); +} + +// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext +WebIDL::ExceptionOr CSSStyleProperties::set_css_text(StringView css_text) +{ + // 1. If the readonly flag is set, then throw a NoModificationAllowedError exception. + if (is_readonly()) { + return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties: CSSStyleProperties is read-only."_string); + } + + // 2. Empty the declarations. + // 3. Parse the given value and, if the return value is not the empty list, insert the items in the list into the declarations, in specified order. + set_declarations_from_text(css_text); + + // 4. Update style attribute for the CSS declaration block. + update_style_attribute(); + return {}; } -static WebIDL::ExceptionOr cannot_modify_computed_property_error(JS::Realm& realm) +// https://drafts.csswg.org/cssom/#set-a-css-declaration +bool CSSStyleProperties::set_a_css_declaration(PropertyID property_id, NonnullRefPtr value, Important important) { - return WebIDL::NoModificationAllowedError::create(realm, "Cannot modify properties in result of getComputedStyle()"_string); + VERIFY(!is_computed()); + + // FIXME: Handle logical property groups. + + for (auto& property : m_properties) { + if (property.property_id == property_id) { + if (property.important == important && *property.value == *value) + return false; + property.value = move(value); + property.important = important; + return true; + } + } + + m_properties.append(StyleProperty { + .important = important, + .property_id = property_id, + .value = move(value), + }); + return true; } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_property(PropertyID, StringView, StringView) +void CSSStyleProperties::empty_the_declarations() { - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_modify_computed_property_error(realm()); + m_properties.clear(); + m_custom_properties.clear(); } -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_property(StringView, StringView, StringView) +void CSSStyleProperties::set_the_declarations(Vector properties, HashMap custom_properties) { - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_modify_computed_property_error(realm()); + m_properties = move(properties); + m_custom_properties = move(custom_properties); } -static WebIDL::ExceptionOr cannot_remove_computed_property_error(JS::Realm& realm) +void CSSStyleProperties::set_declarations_from_text(StringView css_text) { - return WebIDL::NoModificationAllowedError::create(realm, "Cannot remove properties from result of getComputedStyle()"_string); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::remove_property(PropertyID) -{ - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_remove_computed_property_error(realm()); -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::remove_property(StringView) -{ - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return cannot_remove_computed_property_error(realm()); -} - -String ResolvedCSSStyleDeclaration::serialized() const -{ - // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-csstext - // If the computed flag is set, then return the empty string. - - // NOTE: ResolvedCSSStyleDeclaration is something you would only get from window.getComputedStyle(), - // which returns what the spec calls "resolved style". The "computed flag" is always set here. - return String {}; -} - -// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_css_text(StringView) -{ - // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. - return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties in result of getComputedStyle()"_string); + empty_the_declarations(); + auto parsing_params = owner_node().has_value() + ? Parser::ParsingParams(owner_node()->element().document()) + : Parser::ParsingParams(); + auto style = parse_css_style_attribute(parsing_params, css_text); + set_the_declarations(style.properties, style.custom_properties); } } diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.h b/Libraries/LibWeb/CSS/CSSStyleProperties.h new file mode 100644 index 0000000000..03fff5dbe6 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2023, Andreas Kling + * Copyright (c) 2024-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/cssom/#cssstyleproperties +class CSSStyleProperties : public CSSStyleDeclaration { + WEB_PLATFORM_OBJECT(CSSStyleProperties, CSSStyleDeclaration); + GC_DECLARE_ALLOCATOR(CSSStyleProperties); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, Vector, HashMap custom_properties); + + [[nodiscard]] static GC::Ref create_resolved_style(DOM::ElementReference); + [[nodiscard]] static GC::Ref create_element_inline_style(DOM::ElementReference, Vector, HashMap custom_properties); + + virtual ~CSSStyleProperties() override = default; + virtual void initialize(JS::Realm&) override; + + 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; + + // Temporary for one commit. + using Base::remove_property, Base::set_property; + + virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; + virtual WebIDL::ExceptionOr remove_property(StringView property_name) 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(); } + + virtual String serialized() const final override; + virtual WebIDL::ExceptionOr set_css_text(StringView) override; + + void set_declarations_from_text(StringView); + +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; + + bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); + void empty_the_declarations(); + void set_the_declarations(Vector properties, HashMap custom_properties); + + Vector m_properties; + HashMap m_custom_properties; +}; + +} diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.idl b/Libraries/LibWeb/CSS/CSSStyleProperties.idl new file mode 100644 index 0000000000..22db503641 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.idl @@ -0,0 +1,6 @@ +#import + +// https://drafts.csswg.org/cssom/#cssstyleproperties +[Exposed=Window] +interface CSSStyleProperties : CSSStyleDeclaration { +}; diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.cpp b/Libraries/LibWeb/CSS/CSSStyleRule.cpp index ee568cc70f..010953d791 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleRule.cpp @@ -17,12 +17,12 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(CSSStyleRule); -GC::Ref CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, PropertyOwningCSSStyleDeclaration& declaration, CSSRuleList& nested_rules) +GC::Ref CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules) { return realm.create(realm, move(selectors), declaration, nested_rules); } -CSSStyleRule::CSSStyleRule(JS::Realm& realm, SelectorList&& selectors, PropertyOwningCSSStyleDeclaration& declaration, CSSRuleList& nested_rules) +CSSStyleRule::CSSStyleRule(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules) : CSSGroupingRule(realm, nested_rules, Type::Style) , m_selectors(move(selectors)) , m_declaration(declaration) @@ -43,7 +43,7 @@ void CSSStyleRule::visit_edges(Cell::Visitor& visitor) } // https://drafts.csswg.org/cssom-1/#dom-cssstylerule-style -CSSStyleDeclaration* CSSStyleRule::style() +CSSStyleProperties* CSSStyleRule::style() { return m_declaration; } diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.h b/Libraries/LibWeb/CSS/CSSStyleRule.h index 1370600e5f..572be8bd49 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.h +++ b/Libraries/LibWeb/CSS/CSSStyleRule.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include namespace Web::CSS { @@ -19,23 +19,23 @@ class CSSStyleRule final : public CSSGroupingRule { GC_DECLARE_ALLOCATOR(CSSStyleRule); public: - [[nodiscard]] static GC::Ref create(JS::Realm&, SelectorList&&, PropertyOwningCSSStyleDeclaration&, CSSRuleList&); + [[nodiscard]] static GC::Ref create(JS::Realm&, SelectorList&&, CSSStyleProperties&, CSSRuleList&); virtual ~CSSStyleRule() override = default; SelectorList const& selectors() const { return m_selectors; } SelectorList const& absolutized_selectors() const; - PropertyOwningCSSStyleDeclaration const& declaration() const { return m_declaration; } + CSSStyleProperties const& declaration() const { return m_declaration; } String selector_text() const; void set_selector_text(StringView); - CSSStyleDeclaration* style(); + CSSStyleProperties* style(); [[nodiscard]] FlyString const& qualified_layer_name() const { return parent_layer_internal_qualified_name(); } private: - CSSStyleRule(JS::Realm&, SelectorList&&, PropertyOwningCSSStyleDeclaration&, CSSRuleList&); + CSSStyleRule(JS::Realm&, SelectorList&&, CSSStyleProperties&, CSSRuleList&); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -46,7 +46,7 @@ private: SelectorList m_selectors; mutable Optional m_cached_absolutized_selectors; - GC::Ref m_declaration; + GC::Ref m_declaration; }; template<> diff --git a/Libraries/LibWeb/CSS/CSSStyleRule.idl b/Libraries/LibWeb/CSS/CSSStyleRule.idl index 1b61834b5d..f7df36c982 100644 --- a/Libraries/LibWeb/CSS/CSSStyleRule.idl +++ b/Libraries/LibWeb/CSS/CSSStyleRule.idl @@ -1,9 +1,9 @@ #import -#import +#import // https://drafts.csswg.org/cssom/#the-cssstylerule-interface [Exposed=Window] interface CSSStyleRule : CSSGroupingRule { attribute CSSOMString selectorText; - [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style; + [SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style; }; diff --git a/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl b/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl index 15147bad4a..8a09f8f5e0 100644 --- a/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl +++ b/Libraries/LibWeb/CSS/ElementCSSInlineStyle.idl @@ -1,6 +1,6 @@ -#import +#import // https://w3c.github.io/csswg-drafts/cssom/#elementcssinlinestyle interface mixin ElementCSSInlineStyle { - [SameObject, PutForwards=cssText, ImplementedAs=style_for_bindings] readonly attribute CSSStyleDeclaration style; + [SameObject, PutForwards=cssText, ImplementedAs=style_for_bindings] readonly attribute CSSStyleProperties style; }; diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 731fbfcdfd..3e4d4c00a6 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -1492,14 +1493,14 @@ void Parser::extract_property(Declaration const& declaration, PropertiesAndCusto } } -PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector const& declarations) +CSSStyleProperties* Parser::convert_to_style_declaration(Vector const& declarations) { PropertiesAndCustomProperties properties; PropertiesAndCustomProperties& dest = properties; for (auto const& declaration : declarations) { extract_property(declaration, dest); } - return PropertyOwningCSSStyleDeclaration::create(realm(), move(properties.properties), move(properties.custom_properties)); + return CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); } Optional Parser::convert_to_style_property(Declaration const& declaration) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 50391d0343..7c74ac7a93 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -245,7 +245,7 @@ private: GC::Ptr convert_to_supports_rule(AtRule const&, Nested); GC::Ptr convert_to_property_rule(AtRule const& rule); - PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector const&); + CSSStyleProperties* convert_to_style_declaration(Vector const&); Optional convert_to_style_property(Declaration const&); Optional parse_dimension(ComponentValue const&); diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index 6a669113d0..883a1bde1d 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2020-2021, the SerenityOS developers. - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * Copyright (c) 2021, Tobias Christiansen * Copyright (c) 2022, MacDue * Copyright (c) 2024, Shannon Booth @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -390,7 +391,7 @@ GC::Ptr Parser::convert_to_keyframes_rule(AtRule const& rule) qualified_rule.for_each_as_declaration_list([&](auto const& declaration) { extract_property(declaration, properties); }); - auto style = PropertyOwningCSSStyleDeclaration::create(realm(), move(properties.properties), move(properties.custom_properties)); + auto style = CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); for (auto& selector : selectors) { auto keyframe_rule = CSSKeyframeRule::create(realm(), selector, *style); keyframes.append(keyframe_rule); diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h deleted file mode 100644 index 7743ab0275..0000000000 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021-2023, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -namespace Web::CSS { - -class ResolvedCSSStyleDeclaration final : public CSSStyleDeclaration { - WEB_PLATFORM_OBJECT(ResolvedCSSStyleDeclaration, CSSStyleDeclaration); - GC_DECLARE_ALLOCATOR(ResolvedCSSStyleDeclaration); - -public: - [[nodiscard]] static GC::Ref create(DOM::Element&, Optional = {}); - - virtual ~ResolvedCSSStyleDeclaration() override = default; - - 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; - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr remove_property(PropertyID) override; - virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; - - virtual String serialized() const override; - virtual WebIDL::ExceptionOr set_css_text(StringView) override; - -private: - explicit ResolvedCSSStyleDeclaration(DOM::Element&, Optional); - - RefPtr style_value_for_property(Layout::NodeWithStyle const&, PropertyID) const; -}; - -} diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index b1a8c8805a..75cd39d664 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2023, Andreas Kling * Copyright (c) 2021, the SerenityOS developers. - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * Copyright (c) 2024, Matthew Olsson * * SPDX-License-Identifier: BSD-2-Clause @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -122,7 +123,7 @@ struct Traits : public DefaultTraitstype() == CSSRule::Type::Style) return static_cast(*rule).declaration(); @@ -2868,7 +2869,7 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame resolved_keyframe; auto key = static_cast(keyframe.key().value() * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor); - auto const& keyframe_style = *keyframe.style_as_property_owning_style_declaration(); + auto const& keyframe_style = *keyframe.style(); for (auto const& it : keyframe_style.properties()) { // Unresolved properties will be resolved in collect_animation_into() for_each_property_expanding_shorthands(it.property_id, it.value, AllowUnresolved::Yes, [&](PropertyID shorthand_id, CSSStyleValue const& shorthand_value) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index 972114c664..605c5913b3 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -89,7 +89,7 @@ struct MatchingRule { bool must_be_hovered { false }; // Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations - PropertyOwningCSSStyleDeclaration const& declaration() const; + CSSStyleProperties const& declaration() const; SelectorList const& absolutized_selectors() const; FlyString const& qualified_layer_name() const; }; diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 10f137cd7f..07ed6c07b7 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -14,10 +14,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -649,7 +649,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style() GC::Ref Element::resolved_css_values(Optional type) { - auto element_computed_style = CSS::ResolvedCSSStyleDeclaration::create(*this, type); + auto element_computed_style = CSS::CSSStyleProperties::create_resolved_style({ *this, type }); auto properties = heap().allocate(); for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) { @@ -918,10 +918,10 @@ void Element::set_shadow_root(GC::Ptr shadow_root) invalidate_style(StyleInvalidationReason::ElementSetShadowRoot); } -CSS::CSSStyleDeclaration* Element::style_for_bindings() +CSS::CSSStyleProperties* Element::style_for_bindings() { if (!m_inline_style) - m_inline_style = CSS::PropertyOwningCSSStyleDeclaration::create_element_inline_style(realm(), *this, {}, {}); + m_inline_style = CSS::CSSStyleProperties::create_element_inline_style({ *this }, {}, {}); return m_inline_style; } @@ -3524,7 +3524,7 @@ void Element::attribute_changed(FlyString const& local_name, Optional co if (m_inline_style && m_inline_style->is_updating()) return; if (!m_inline_style) - m_inline_style = CSS::PropertyOwningCSSStyleDeclaration::create_element_inline_style(realm(), *this, {}, {}); + m_inline_style = CSS::CSSStyleProperties::create_element_inline_style({ *this }, {}, {}); m_inline_style->set_declarations_from_text(*value); set_needs_style_update(true); } diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index d2fba9fe53..da314eda2e 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -211,10 +211,10 @@ public: void reset_animated_css_properties(); - GC::Ptr inline_style() { return m_inline_style; } - GC::Ptr inline_style() const { return m_inline_style; } + GC::Ptr inline_style() { return m_inline_style; } + GC::Ptr inline_style() const { return m_inline_style; } - CSS::CSSStyleDeclaration* style_for_bindings(); + CSS::CSSStyleProperties* style_for_bindings(); CSS::StyleSheetList& document_or_shadow_root_style_sheets(); @@ -499,7 +499,7 @@ private: FlyString m_html_uppercased_qualified_name; GC::Ptr m_attributes; - GC::Ptr m_inline_style; + GC::Ptr m_inline_style; GC::Ptr m_class_list; GC::Ptr m_shadow_root; diff --git a/Libraries/LibWeb/Dump.cpp b/Libraries/LibWeb/Dump.cpp index 8a69e21ee9..6a870f1d8c 100644 --- a/Libraries/LibWeb/Dump.cpp +++ b/Libraries/LibWeb/Dump.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018-2023, Andreas Kling * Copyright (c) 2021, the SerenityOS developers. - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -834,7 +835,7 @@ void dump_property_rule(StringBuilder& builder, CSS::CSSPropertyRule const& prop } } -void dump_declaration(StringBuilder& builder, CSS::PropertyOwningCSSStyleDeclaration const& declaration, int indent_levels) +void dump_style_properties(StringBuilder& builder, CSS::CSSStyleProperties const& declaration, int indent_levels) { indent(builder, indent_levels); builder.appendff("Declarations ({}):\n", declaration.length()); @@ -859,7 +860,7 @@ void dump_style_rule(StringBuilder& builder, CSS::CSSStyleRule const& rule, int for (auto& selector : rule.selectors()) { dump_selector(builder, selector, indent_levels + 1); } - dump_declaration(builder, rule.declaration(), indent_levels + 1); + dump_style_properties(builder, rule.declaration(), indent_levels + 1); indent(builder, indent_levels); builder.appendff(" Child rules ({}):\n", rule.css_rules().length()); @@ -951,6 +952,6 @@ void dump_nested_declarations(StringBuilder& builder, CSS::CSSNestedDeclarations { indent(builder, indent_levels); builder.append(" Nested declarations:\n"sv); - dump_declaration(builder, declarations.declaration(), indent_levels + 1); + dump_style_properties(builder, declarations.declaration(), indent_levels + 1); } } diff --git a/Libraries/LibWeb/Dump.h b/Libraries/LibWeb/Dump.h index acb6d3ebae..9fd7746f88 100644 --- a/Libraries/LibWeb/Dump.h +++ b/Libraries/LibWeb/Dump.h @@ -24,7 +24,7 @@ void dump_sheet(StringBuilder&, CSS::StyleSheet const&); void dump_sheet(CSS::StyleSheet const&); void dump_rule(StringBuilder&, CSS::CSSRule const&, int indent_levels = 0); void dump_rule(CSS::CSSRule const&); -void dump_declaration(StringBuilder&, CSS::PropertyOwningCSSStyleDeclaration const&, int indent_levels = 0); +void dump_style_properties(StringBuilder&, CSS::CSSStyleProperties const&, int indent_levels = 0); void dump_font_face_rule(StringBuilder&, CSS::CSSFontFaceRule const&, int indent_levels = 0); void dump_import_rule(StringBuilder&, CSS::CSSImportRule const&, int indent_levels = 0); void dump_media_rule(StringBuilder&, CSS::CSSMediaRule const&, int indent_levels = 0); diff --git a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp index edf02ac027..f5680fbb69 100644 --- a/Libraries/LibWeb/Editing/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/Editing/Internal/Algorithms.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -4757,7 +4756,7 @@ Optional> resolved_value(GC::Ref(*element)); + auto resolved_css_style_declaration = CSS::CSSStyleProperties::create_resolved_style({ static_cast(*element) }); auto optional_style_property = resolved_css_style_declaration->property(property_id); if (!optional_style_property.has_value()) return {}; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index b3f539a108..a0b40c9e81 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -179,6 +179,7 @@ class CSSRGB; class CSSRule; class CSSRuleList; class CSSStyleDeclaration; +class CSSStyleProperties; class CSSStyleRule; class CSSStyleSheet; class CSSStyleValue; @@ -235,7 +236,6 @@ class Percentage; class PercentageOrCalculated; class PercentageStyleValue; class PositionStyleValue; -class PropertyOwningCSSStyleDeclaration; class RadialGradientStyleValue; class Ratio; class RatioStyleValue; diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp index 8be002b9b7..3fae3a9bee 100644 --- a/Libraries/LibWeb/HTML/Window.cpp +++ b/Libraries/LibWeb/HTML/Window.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -1208,7 +1207,7 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme // 1. Let doc be elt’s node document. // 2. Let obj be elt. - Optional obj_pseudo; + DOM::ElementReference object { element }; // 3. If pseudoElt is provided, is not the empty string, and starts with a colon, then: if (pseudo_element.has_value() && pseudo_element.value().starts_with(':')) { @@ -1216,22 +1215,18 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme auto type = parse_pseudo_element_selector(CSS::Parser::ParsingParams(associated_document()), pseudo_element.value()); // 2. If type is failure, or is an ::slotted() or ::part() pseudo-element, let obj be null. - // FIXME: We can't pass a null element to ResolvedCSSStyleDeclaration + // FIXME: We can't pass a null element to CSSStyleProperties::create_resolved_style() if (!type.has_value()) { } // 3. Otherwise let obj be the given pseudo-element of elt. else { // TODO: Keep the function arguments of the pseudo-element if there are any. - obj_pseudo = type.value().type(); + object = { element, type.value().type() }; } } - // AD-HOC: Just return a ResolvedCSSStyleDeclaration because that's what we have for now. - // FIXME: Implement CSSStyleProperties, and then follow the rest of these steps instead. - return realm().create(element, obj_pseudo); - + // FIXME: Implement steps 4 and 5 when we can. // 4. Let decls be an empty list of CSS declarations. - // 5. If obj is not null, and elt is connected, part of the flat tree, and its shadow-including root // has a browsing context which either doesn’t have a browsing context container, or whose browsing // context container is being rendered, set decls to a list of all longhand properties that are @@ -1250,6 +1245,7 @@ GC::Ref Window::get_computed_style(DOM::Element& eleme // Null. // owner node // obj. + return CSS::CSSStyleProperties::create_resolved_style(move(object)); } // https://w3c.github.io/csswg-drafts/cssom-view/#dom-window-matchmedia diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index d12820fbe1..1ca79bc859 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -38,6 +38,7 @@ libweb_js_bindings(CSS/CSSPropertyRule) libweb_js_bindings(CSS/CSSRule) libweb_js_bindings(CSS/CSSRuleList) libweb_js_bindings(CSS/CSSStyleDeclaration) +libweb_js_bindings(CSS/CSSStyleProperties) libweb_js_bindings(CSS/CSSStyleRule) libweb_js_bindings(CSS/CSSStyleSheet) libweb_js_bindings(CSS/CSSSupportsRule) diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn index b539f388a5..680c1e9e39 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/CSS/BUILD.gn @@ -26,6 +26,7 @@ source_set("CSS") { "CSSRule.cpp", "CSSRuleList.cpp", "CSSStyleDeclaration.cpp", + "CSSStyleProperties.cpp", "CSSStyleRule.cpp", "CSSStyleSheet.cpp", "CSSStyleValue.cpp", @@ -57,7 +58,6 @@ source_set("CSS") { "PreferredMotion.cpp", "Ratio.cpp", "Resolution.cpp", - "ResolvedCSSStyleDeclaration.cpp", "Screen.cpp", "ScreenOrientation.cpp", "Selector.cpp", diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 787ba16db7..df057954b1 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -51,6 +51,7 @@ CSSPropertyRule CSSRule CSSRuleList CSSStyleDeclaration +CSSStyleProperties CSSStyleRule CSSStyleSheet CSSSupportsRule