LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties

We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)

In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.

So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.

There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
This commit is contained in:
Sam Atkins 2025-03-17 17:50:49 +00:00
parent 687d32b712
commit 83bb92c4e0
Notes: github-actions[bot] 2025-03-19 13:54:21 +00:00
32 changed files with 589 additions and 639 deletions

View file

@ -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()`.

View file

@ -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

View file

@ -13,12 +13,12 @@ namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSKeyframeRule);
GC::Ref<CSSKeyframeRule> CSSKeyframeRule::create(JS::Realm& realm, CSS::Percentage key, Web::CSS::PropertyOwningCSSStyleDeclaration& declarations)
GC::Ref<CSSKeyframeRule> CSSKeyframeRule::create(JS::Realm& realm, Percentage key, CSSStyleProperties& declarations)
{
return realm.create<CSSKeyframeRule>(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)

View file

@ -8,7 +8,7 @@
#include <AK/NonnullRefPtr.h>
#include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/Forward.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@ -21,13 +21,12 @@ class CSSKeyframeRule final : public CSSRule {
GC_DECLARE_ALLOCATOR(CSSKeyframeRule);
public:
static GC::Ref<CSSKeyframeRule> create(JS::Realm&, CSS::Percentage key, PropertyOwningCSSStyleDeclaration&);
static GC::Ref<CSSKeyframeRule> create(JS::Realm&, CSS::Percentage key, CSSStyleProperties&);
virtual ~CSSKeyframeRule() = default;
CSS::Percentage key() const { return m_key; }
GC::Ref<CSSStyleDeclaration> style() const { return m_declarations; }
GC::Ref<PropertyOwningCSSStyleDeclaration> style_as_property_owning_style_declaration() const { return m_declarations; }
GC::Ref<CSSStyleProperties> 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<PropertyOwningCSSStyleDeclaration> m_declarations;
GC::Ref<CSSStyleProperties> m_declarations;
};
template<>

View file

@ -1,9 +1,9 @@
#import <CSS/CSSRule.idl>
#import <CSS/CSSStyleDeclaration.idl>
#import <CSS/CSSStyleProperties.idl>
// 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;
};

View file

@ -13,12 +13,12 @@ namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSNestedDeclarations);
GC::Ref<CSSNestedDeclarations> CSSNestedDeclarations::create(JS::Realm& realm, PropertyOwningCSSStyleDeclaration& declaration)
GC::Ref<CSSNestedDeclarations> CSSNestedDeclarations::create(JS::Realm& realm, CSSStyleProperties& declaration)
{
return realm.create<CSSNestedDeclarations>(realm, declaration);
}
CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, PropertyOwningCSSStyleDeclaration& declaration)
CSSNestedDeclarations::CSSNestedDeclarations(JS::Realm& realm, CSSStyleProperties& declaration)
: CSSRule(realm, Type::NestedDeclarations)
, m_declaration(declaration)
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2024-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,6 +7,7 @@
#pragma once
#include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
namespace Web::CSS {
@ -15,25 +16,25 @@ class CSSNestedDeclarations final : public CSSRule {
GC_DECLARE_ALLOCATOR(CSSNestedDeclarations);
public:
[[nodiscard]] static GC::Ref<CSSNestedDeclarations> create(JS::Realm&, PropertyOwningCSSStyleDeclaration&);
[[nodiscard]] static GC::Ref<CSSNestedDeclarations> 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<PropertyOwningCSSStyleDeclaration> m_declaration;
GC::Ref<CSSStyleProperties> m_declaration;
GC::Ptr<CSSStyleRule const> mutable m_parent_style_rule;
};

View file

@ -17,12 +17,10 @@
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Infra/Strings.h>
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> PropertyOwningCSSStyleDeclaration::create(JS::Realm& realm, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties)
{
return realm.create<PropertyOwningCSSStyleDeclaration>(realm, nullptr, move(properties), move(custom_properties));
}
GC::Ref<PropertyOwningCSSStyleDeclaration> PropertyOwningCSSStyleDeclaration::create_element_inline_style(JS::Realm& realm, GC::Ref<DOM::Element> element, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties)
{
return realm.create<PropertyOwningCSSStyleDeclaration>(realm, element, move(properties), move(custom_properties));
}
PropertyOwningCSSStyleDeclaration::PropertyOwningCSSStyleDeclaration(JS::Realm& realm, GC::Ptr<DOM::Element> element, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> 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<StyleProperty> 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<void> 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<String> 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<CSSStyleValue const> 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<StyleProperty> style_property_for_sided_shorthand(PropertyID property_id, Optional<StyleProperty> const& top, Optional<StyleProperty> const& right, Optional<StyleProperty> const& bottom, Optional<StyleProperty> const& left)
{
if (!top.has_value() || !right.has_value() || !bottom.has_value() || !left.has_value())
@ -475,7 +287,8 @@ WebIDL::ExceptionOr<String> 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<String> list;
// 2. Let already serialized be an empty array.
HashTable<PropertyID> 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 declarations 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 blocks declarations, follow these substeps:
for (auto& declaration : m_properties) {
// 1. Let property be declarations 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<void> 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<StyleProperty> properties, HashMap<FlyString, StyleProperty> 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);
}
}

View file

@ -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<PropertyOwningCSSStyleDeclaration>
create(JS::Realm&, Vector<StyleProperty>, HashMap<FlyString, StyleProperty> custom_properties);
[[nodiscard]] static GC::Ref<PropertyOwningCSSStyleDeclaration>
create_element_inline_style(JS::Realm&, GC::Ref<DOM::Element>, Vector<StyleProperty>, HashMap<FlyString, StyleProperty> custom_properties);
virtual ~PropertyOwningCSSStyleDeclaration() override = default;
virtual size_t length() const override;
virtual String item(size_t index) const override;
virtual Optional<StyleProperty> property(PropertyID) const override;
virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const override { return m_custom_properties.get(custom_property_name); }
virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<String> remove_property(StringView property_name) override;
Vector<StyleProperty> const& properties() const { return m_properties; }
HashMap<FlyString, StyleProperty> 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<void> set_css_text(StringView) override;
void set_declarations_from_text(StringView);
protected:
PropertyOwningCSSStyleDeclaration(JS::Realm&, GC::Ptr<DOM::Element> owner_node, Vector<StyleProperty>, HashMap<FlyString, StyleProperty>);
void empty_the_declarations();
void set_the_declarations(Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties);
private:
bool set_a_css_declaration(PropertyID, NonnullRefPtr<CSSStyleValue const>, Important);
virtual void visit_edges(Cell::Visitor&) override;
Vector<StyleProperty> m_properties;
HashMap<FlyString, StyleProperty> m_custom_properties;
};
}

View file

@ -1,66 +1,281 @@
/*
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Format.h>
#include <AK/NonnullRefPtr.h>
#include <LibWeb/Bindings/CSSStylePropertiesPrototype.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/ComputedProperties.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/FitContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Painting/BoxModelMetrics.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/StackingContext.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(ResolvedCSSStyleDeclaration);
GC_DEFINE_ALLOCATOR(CSSStyleProperties);
GC::Ref<ResolvedCSSStyleDeclaration> ResolvedCSSStyleDeclaration::create(DOM::Element& element, Optional<Selector::PseudoElement::Type> pseudo_element)
GC::Ref<CSSStyleProperties> CSSStyleProperties::create(JS::Realm& realm, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties)
{
return element.realm().create<ResolvedCSSStyleDeclaration>(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<CSSStyleProperties>(realm, Computed::No, Readonly::No, move(properties), move(custom_properties), OptionalNone {});
}
ResolvedCSSStyleDeclaration::ResolvedCSSStyleDeclaration(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element)
: CSSStyleDeclaration(element.realm(), Computed::Yes, Readonly::Yes)
GC::Ref<CSSStyleProperties> 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<CSSStyleProperties>(realm, Computed::Yes, Readonly::Yes, Vector<StyleProperty> {}, HashMap<FlyString, StyleProperty> {}, move(element_reference));
}
GC::Ref<CSSStyleProperties> CSSStyleProperties::create_element_inline_style(DOM::ElementReference element_reference, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> 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<CSSStyleProperties>(realm, Computed::No, Readonly::No, move(properties), move(custom_properties), move(element_reference));
}
CSSStyleProperties::CSSStyleProperties(JS::Realm& realm, Computed computed, Readonly readonly, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties, Optional<DOM::ElementReference> 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<PropertyID>(index + to_underlying(first_longhand_property_id));
return string_from_property_id(property_id).to_string();
if (is_computed()) {
auto property_id = static_cast<PropertyID>(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<StyleProperty> 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<StyleProperty const&> 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<void> 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<CSSStyleValue const> style_value_for_length_percentage(LengthPercentage const& length_percentage)
@ -159,7 +374,7 @@ static RefPtr<CSSStyleValue const> style_value_for_shadow(Vector<ShadowData> con
return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma);
}
RefPtr<CSSStyleValue const> ResolvedCSSStyleDeclaration::style_value_for_property(Layout::NodeWithStyle const& layout_node, PropertyID property_id) const
RefPtr<CSSStyleValue const> 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<CSSPixels(Painting::PaintableBox const&)>&& used_value_getter) -> Optional<CSSPixels> {
auto const& display = layout_node.computed_values().display();
@ -222,18 +437,18 @@ RefPtr<CSSStyleValue const> 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<CSSStyleValue const> 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<CSSStyleValue const> 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<CSSStyleValue const> 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<CSSStyleValue const> 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<CSSStyleValue const> 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<StyleProperty> ResolvedCSSStyleDeclaration::property(PropertyID property_id) const
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty
WebIDL::ExceptionOr<String> 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<StyleProperty const&> 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<String> list;
// 2. Let already serialized be an empty array.
HashTable<PropertyID> 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 declarations 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 blocks declarations, follow these substeps:
for (auto& declaration : m_properties) {
// 1. Let property be declarations 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<void> 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<void> 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<CSSStyleValue const> 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<void> 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<void> ResolvedCSSStyleDeclaration::set_property(StringView, StringView, StringView)
void CSSStyleProperties::set_the_declarations(Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> 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<String> 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<String> 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<String> 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<void> 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);
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSStyleDeclaration.h>
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<CSSStyleProperties> create(JS::Realm&, Vector<StyleProperty>, HashMap<FlyString, StyleProperty> custom_properties);
[[nodiscard]] static GC::Ref<CSSStyleProperties> create_resolved_style(DOM::ElementReference);
[[nodiscard]] static GC::Ref<CSSStyleProperties> create_element_inline_style(DOM::ElementReference, Vector<StyleProperty>, HashMap<FlyString, StyleProperty> 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<StyleProperty> property(PropertyID) const override;
virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const override;
// Temporary for one commit.
using Base::remove_property, Base::set_property;
virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<String> remove_property(StringView property_name) override;
Vector<StyleProperty> const& properties() const { return m_properties; }
HashMap<FlyString, StyleProperty> 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<void> set_css_text(StringView) override;
void set_declarations_from_text(StringView);
private:
CSSStyleProperties(JS::Realm&, Computed, Readonly, Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties, Optional<DOM::ElementReference>);
virtual void visit_edges(Cell::Visitor&) override;
RefPtr<CSSStyleValue const> style_value_for_computed_property(Layout::NodeWithStyle const&, PropertyID) const;
bool set_a_css_declaration(PropertyID, NonnullRefPtr<CSSStyleValue const>, Important);
void empty_the_declarations();
void set_the_declarations(Vector<StyleProperty> properties, HashMap<FlyString, StyleProperty> custom_properties);
Vector<StyleProperty> m_properties;
HashMap<FlyString, StyleProperty> m_custom_properties;
};
}

View file

@ -0,0 +1,6 @@
#import <CSS/CSSStyleDeclaration.idl>
// https://drafts.csswg.org/cssom/#cssstyleproperties
[Exposed=Window]
interface CSSStyleProperties : CSSStyleDeclaration {
};

View file

@ -17,12 +17,12 @@ namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSStyleRule);
GC::Ref<CSSStyleRule> CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, PropertyOwningCSSStyleDeclaration& declaration, CSSRuleList& nested_rules)
GC::Ref<CSSStyleRule> CSSStyleRule::create(JS::Realm& realm, SelectorList&& selectors, CSSStyleProperties& declaration, CSSRuleList& nested_rules)
{
return realm.create<CSSStyleRule>(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;
}

View file

@ -9,7 +9,7 @@
#include <AK/NonnullRefPtr.h>
#include <LibWeb/CSS/CSSGroupingRule.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/Selector.h>
namespace Web::CSS {
@ -19,23 +19,23 @@ class CSSStyleRule final : public CSSGroupingRule {
GC_DECLARE_ALLOCATOR(CSSStyleRule);
public:
[[nodiscard]] static GC::Ref<CSSStyleRule> create(JS::Realm&, SelectorList&&, PropertyOwningCSSStyleDeclaration&, CSSRuleList&);
[[nodiscard]] static GC::Ref<CSSStyleRule> 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<SelectorList> m_cached_absolutized_selectors;
GC::Ref<PropertyOwningCSSStyleDeclaration> m_declaration;
GC::Ref<CSSStyleProperties> m_declaration;
};
template<>

View file

@ -1,9 +1,9 @@
#import <CSS/CSSGroupingRule.idl>
#import <CSS/CSSStyleDeclaration.idl>
#import <CSS/CSSStyleProperties.idl>
// 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;
};

View file

@ -1,6 +1,6 @@
#import <CSS/CSSStyleDeclaration.idl>
#import <CSS/CSSStyleProperties.idl>
// 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;
};

View file

@ -14,6 +14,7 @@
#include <AK/Debug.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/MediaList.h>
@ -1492,14 +1493,14 @@ void Parser::extract_property(Declaration const& declaration, PropertiesAndCusto
}
}
PropertyOwningCSSStyleDeclaration* Parser::convert_to_style_declaration(Vector<Declaration> const& declarations)
CSSStyleProperties* Parser::convert_to_style_declaration(Vector<Declaration> 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<StyleProperty> Parser::convert_to_style_property(Declaration const& declaration)

View file

@ -245,7 +245,7 @@ private:
GC::Ptr<CSSSupportsRule> convert_to_supports_rule(AtRule const&, Nested);
GC::Ptr<CSSPropertyRule> convert_to_property_rule(AtRule const& rule);
PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector<Declaration> const&);
CSSStyleProperties* convert_to_style_declaration(Vector<Declaration> const&);
Optional<StyleProperty> convert_to_style_property(Declaration const&);
Optional<Dimension> parse_dimension(ComponentValue const&);

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
@ -22,6 +22,7 @@
#include <LibWeb/CSS/CSSNamespaceRule.h>
#include <LibWeb/CSS/CSSNestedDeclarations.h>
#include <LibWeb/CSS/CSSPropertyRule.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/CSSSupportsRule.h>
#include <LibWeb/CSS/Parser/Parser.h>
@ -390,7 +391,7 @@ GC::Ptr<CSSKeyframesRule> 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);

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2021-2023, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/Selector.h>
namespace Web::CSS {
class ResolvedCSSStyleDeclaration final : public CSSStyleDeclaration {
WEB_PLATFORM_OBJECT(ResolvedCSSStyleDeclaration, CSSStyleDeclaration);
GC_DECLARE_ALLOCATOR(ResolvedCSSStyleDeclaration);
public:
[[nodiscard]] static GC::Ref<ResolvedCSSStyleDeclaration> create(DOM::Element&, Optional<Selector::PseudoElement::Type> = {});
virtual ~ResolvedCSSStyleDeclaration() override = default;
virtual size_t length() const override;
virtual String item(size_t index) const override;
virtual Optional<StyleProperty> property(PropertyID) const override;
virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const override;
virtual WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) override;
virtual WebIDL::ExceptionOr<String> remove_property(PropertyID) override;
virtual WebIDL::ExceptionOr<String> remove_property(StringView property_name) override;
virtual String serialized() const override;
virtual WebIDL::ExceptionOr<void> set_css_text(StringView) override;
private:
explicit ResolvedCSSStyleDeclaration(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>);
RefPtr<CSSStyleValue const> style_value_for_property(Layout::NodeWithStyle const&, PropertyID) const;
};
}

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -35,6 +35,7 @@
#include <LibWeb/CSS/CSSLayerBlockRule.h>
#include <LibWeb/CSS/CSSLayerStatementRule.h>
#include <LibWeb/CSS/CSSNestedDeclarations.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/CSSTransition.h>
#include <LibWeb/CSS/ComputedProperties.h>
@ -122,7 +123,7 @@ struct Traits<Web::CSS::OwnFontFaceKey> : public DefaultTraits<Web::CSS::OwnFont
namespace Web::CSS {
PropertyOwningCSSStyleDeclaration const& MatchingRule::declaration() const
CSSStyleProperties const& MatchingRule::declaration() const
{
if (rule->type() == CSSRule::Type::Style)
return static_cast<CSSStyleRule const&>(*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<u64>(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) {

View file

@ -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;
};

View file

@ -14,10 +14,10 @@
#include <LibWeb/Bindings/ElementPrototype.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/ComputedProperties.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
#include <LibWeb/CSS/SelectorEngine.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
@ -649,7 +649,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style()
GC::Ref<CSS::ComputedProperties> Element::resolved_css_values(Optional<CSS::Selector::PseudoElement::Type> 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<CSS::ComputedProperties>();
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<ShadowRoot> 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<String> 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);
}

View file

@ -211,10 +211,10 @@ public:
void reset_animated_css_properties();
GC::Ptr<CSS::PropertyOwningCSSStyleDeclaration> inline_style() { return m_inline_style; }
GC::Ptr<CSS::PropertyOwningCSSStyleDeclaration const> inline_style() const { return m_inline_style; }
GC::Ptr<CSS::CSSStyleProperties> inline_style() { return m_inline_style; }
GC::Ptr<CSS::CSSStyleProperties const> 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<NamedNodeMap> m_attributes;
GC::Ptr<CSS::PropertyOwningCSSStyleDeclaration> m_inline_style;
GC::Ptr<CSS::CSSStyleProperties> m_inline_style;
GC::Ptr<DOMTokenList> m_class_list;
GC::Ptr<ShadowRoot> m_shadow_root;

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -17,6 +17,7 @@
#include <LibWeb/CSS/CSSNestedDeclarations.h>
#include <LibWeb/CSS/CSSPropertyRule.h>
#include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleRule.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/CSSSupportsRule.h>
@ -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);
}
}

View file

@ -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);

View file

@ -6,7 +6,6 @@
#include <LibGfx/Color.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
@ -4757,7 +4756,7 @@ Optional<NonnullRefPtr<CSS::CSSStyleValue const>> resolved_value(GC::Ref<DOM::No
return {};
// Retrieve resolved style value
auto resolved_css_style_declaration = CSS::ResolvedCSSStyleDeclaration::create(static_cast<DOM::Element&>(*element));
auto resolved_css_style_declaration = CSS::CSSStyleProperties::create_resolved_style({ static_cast<DOM::Element&>(*element) });
auto optional_style_property = resolved_css_style_declaration->property(property_id);
if (!optional_style_property.has_value())
return {};

View file

@ -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;

View file

@ -23,7 +23,6 @@
#include <LibWeb/Bindings/WindowPrototype.h>
#include <LibWeb/CSS/MediaQueryList.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
#include <LibWeb/CSS/Screen.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
@ -1208,7 +1207,7 @@ GC::Ref<CSS::CSSStyleDeclaration> Window::get_computed_style(DOM::Element& eleme
// 1. Let doc be elts node document.
// 2. Let obj be elt.
Optional<CSS::Selector::PseudoElement::Type> 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<CSS::CSSStyleDeclaration> 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<CSS::ResolvedCSSStyleDeclaration>(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 doesnt 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<CSS::CSSStyleDeclaration> 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

View file

@ -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)

View file

@ -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",

View file

@ -51,6 +51,7 @@ CSSPropertyRule
CSSRule
CSSRuleList
CSSStyleDeclaration
CSSStyleProperties
CSSStyleRule
CSSStyleSheet
CSSSupportsRule