LibWeb/CSS: Move property code from CSSStyleDeclaration to *Properties

CSSStyleDeclaration is a base class that's used by various collections
of style properties or descriptors. This commit moves all
style-property-related code into CSSStyleProperties, where it belongs.

As noted in the previous commit, we also apply the CSSStyleProperties
prototype now.
This commit is contained in:
Sam Atkins 2025-03-18 14:07:08 +00:00
parent 83bb92c4e0
commit a28197669a
Notes: github-actions[bot] 2025-03-19 13:54:14 +00:00
12 changed files with 276 additions and 295 deletions

View file

@ -60,4 +60,4 @@ bool Paintable::is_visible() const
Some properties have special rules for getting the computed value from JS. For these, you will need to add to
`CSSStyleProperties::style_value_for_computed_property()`. Shorthands that are constructed in an unusual way (as in, not
using `ShorthandStyleValue`) also need handling inside `CSSStyleDeclaration::get_property_internal()`.
using `ShorthandStyleValue`) also need handling inside `CSSStyleProperties::get_property_internal()`.

View file

@ -9,12 +9,7 @@
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
@ -59,230 +54,6 @@ void CSSStyleDeclaration::update_style_attribute()
set_is_updating(false);
}
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())
return {};
if (top->important != right->important || top->important != bottom->important || top->important != left->important)
return {};
ValueComparingNonnullRefPtr<CSSStyleValue> const top_value { top->value };
ValueComparingNonnullRefPtr<CSSStyleValue> const right_value { right->value };
ValueComparingNonnullRefPtr<CSSStyleValue> const bottom_value { bottom->value };
ValueComparingNonnullRefPtr<CSSStyleValue> const left_value { left->value };
bool const top_and_bottom_same = top_value == bottom_value;
bool const left_and_right_same = left_value == right_value;
RefPtr<CSSStyleValue const> value;
if (top_and_bottom_same && left_and_right_same && top_value == left_value) {
value = top_value;
} else if (top_and_bottom_same && left_and_right_same) {
value = StyleValueList::create(StyleValueVector { top_value, right_value }, StyleValueList::Separator::Space);
} else if (left_and_right_same) {
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value }, StyleValueList::Separator::Space);
} else {
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value, left_value }, StyleValueList::Separator::Space);
}
return StyleProperty {
.important = top->important,
.property_id = property_id,
.value = value.release_nonnull(),
};
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
Optional<StyleProperty> CSSStyleDeclaration::get_property_internal(PropertyID property_id) const
{
// 2. If property is a shorthand property, then follow these substeps:
if (property_is_shorthand(property_id)) {
// AD-HOC: Handle shorthands that require manual construction.
switch (property_id) {
case PropertyID::Border: {
auto width = get_property_internal(PropertyID::BorderWidth);
auto style = get_property_internal(PropertyID::BorderStyle);
auto color = get_property_internal(PropertyID::BorderColor);
// `border` only has a reasonable value if all four sides are the same.
if (!width.has_value() || width->value->is_value_list() || !style.has_value() || style->value->is_value_list() || !color.has_value() || color->value->is_value_list())
return {};
if (width->important != style->important || width->important != color->important)
return {};
return StyleProperty {
.important = width->important,
.property_id = property_id,
.value = ShorthandStyleValue::create(property_id,
{ PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor },
{ width->value, style->value, color->value })
};
}
case PropertyID::BorderColor: {
auto top = get_property_internal(PropertyID::BorderTopColor);
auto right = get_property_internal(PropertyID::BorderRightColor);
auto bottom = get_property_internal(PropertyID::BorderBottomColor);
auto left = get_property_internal(PropertyID::BorderLeftColor);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::BorderStyle: {
auto top = get_property_internal(PropertyID::BorderTopStyle);
auto right = get_property_internal(PropertyID::BorderRightStyle);
auto bottom = get_property_internal(PropertyID::BorderBottomStyle);
auto left = get_property_internal(PropertyID::BorderLeftStyle);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::BorderWidth: {
auto top = get_property_internal(PropertyID::BorderTopWidth);
auto right = get_property_internal(PropertyID::BorderRightWidth);
auto bottom = get_property_internal(PropertyID::BorderBottomWidth);
auto left = get_property_internal(PropertyID::BorderLeftWidth);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::FontVariant: {
auto ligatures = get_property_internal(PropertyID::FontVariantLigatures);
auto caps = get_property_internal(PropertyID::FontVariantCaps);
auto alternates = get_property_internal(PropertyID::FontVariantAlternates);
auto numeric = get_property_internal(PropertyID::FontVariantNumeric);
auto east_asian = get_property_internal(PropertyID::FontVariantEastAsian);
auto position = get_property_internal(PropertyID::FontVariantPosition);
auto emoji = get_property_internal(PropertyID::FontVariantEmoji);
if (!ligatures.has_value() || !caps.has_value() || !alternates.has_value() || !numeric.has_value() || !east_asian.has_value() || !position.has_value() || !emoji.has_value())
return {};
if (ligatures->important != caps->important || ligatures->important != alternates->important || ligatures->important != numeric->important || ligatures->important != east_asian->important || ligatures->important != position->important || ligatures->important != emoji->important)
return {};
// If ligatures is `none` and any other value isn't `normal`, that's invalid.
if (ligatures->value->to_keyword() == Keyword::None
&& (caps->value->to_keyword() != Keyword::Normal
|| alternates->value->to_keyword() != Keyword::Normal
|| numeric->value->to_keyword() != Keyword::Normal
|| east_asian->value->to_keyword() != Keyword::Normal
|| position->value->to_keyword() != Keyword::Normal
|| emoji->value->to_keyword() != Keyword::Normal)) {
return {};
}
return StyleProperty {
.important = ligatures->important,
.property_id = property_id,
.value = ShorthandStyleValue::create(property_id,
{ PropertyID::FontVariantLigatures, PropertyID::FontVariantCaps, PropertyID::FontVariantAlternates, PropertyID::FontVariantNumeric, PropertyID::FontVariantEastAsian, PropertyID::FontVariantPosition, PropertyID::FontVariantEmoji },
{ ligatures->value, caps->value, alternates->value, numeric->value, east_asian->value, position->value, emoji->value })
};
}
case PropertyID::Margin: {
auto top = get_property_internal(PropertyID::MarginTop);
auto right = get_property_internal(PropertyID::MarginRight);
auto bottom = get_property_internal(PropertyID::MarginBottom);
auto left = get_property_internal(PropertyID::MarginLeft);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::Padding: {
auto top = get_property_internal(PropertyID::PaddingTop);
auto right = get_property_internal(PropertyID::PaddingRight);
auto bottom = get_property_internal(PropertyID::PaddingBottom);
auto left = get_property_internal(PropertyID::PaddingLeft);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
default:
break;
}
// 1. Let list be a new empty array.
Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>> list;
Optional<Important> last_important_flag;
// 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps:
Vector<PropertyID> longhand_ids = longhands_for_shorthand(property_id);
for (auto longhand_property_id : longhand_ids) {
// 1. If longhand is a case-sensitive match for a property name of a CSS declaration in the declarations,
// let declaration be that CSS declaration, or null otherwise.
auto declaration = get_property_internal(longhand_property_id);
// 2. If declaration is null, then return the empty string.
if (!declaration.has_value())
return {};
// 3. Append the declaration to list.
list.append(declaration->value);
if (last_important_flag.has_value() && declaration->important != *last_important_flag)
return {};
last_important_flag = declaration->important;
}
// 3. If important flags of all declarations in list are same, then return the serialization of list.
// NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string().
return StyleProperty {
.important = last_important_flag.value(),
.property_id = property_id,
.value = ShorthandStyleValue::create(property_id, longhand_ids, list),
};
// 4. Return the empty string.
// NOTE: This is handled by the loop.
}
return property(property_id);
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
String CSSStyleDeclaration::get_property_value(StringView property_name) const
{
auto property_id = property_id_from_string(property_name);
if (!property_id.has_value())
return {};
if (property_id.value() == PropertyID::Custom) {
auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes()));
if (maybe_custom_property.has_value()) {
return maybe_custom_property.value().value->to_string(
is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue
: CSSStyleValue::SerializationMode::Normal);
}
return {};
}
auto maybe_property = get_property_internal(property_id.value());
if (!maybe_property.has_value())
return {};
return maybe_property->value->to_string(
is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue
: CSSStyleValue::SerializationMode::Normal);
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority
StringView CSSStyleDeclaration::get_property_priority(StringView property_name) const
{
auto property_id = property_id_from_string(property_name);
if (!property_id.has_value())
return {};
if (property_id.value() == PropertyID::Custom) {
auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes()));
if (!maybe_custom_property.has_value())
return {};
return maybe_custom_property.value().important == Important::Yes ? "important"sv : ""sv;
}
auto maybe_property = property(property_id.value());
if (!maybe_property.has_value())
return {};
return maybe_property->important == Important::Yes ? "important"sv : ""sv;
}
WebIDL::ExceptionOr<void> CSSStyleDeclaration::set_property(PropertyID property_id, StringView css_text, StringView priority)
{
return set_property(string_from_property_id(property_id), css_text, priority);
}
WebIDL::ExceptionOr<String> CSSStyleDeclaration::remove_property(PropertyID property_name)
{
return remove_property(string_from_property_id(property_name));
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext
String CSSStyleDeclaration::css_text() const
{
@ -294,20 +65,6 @@ String CSSStyleDeclaration::css_text() const
return serialized();
}
// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat
String CSSStyleDeclaration::css_float() const
{
// The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument.
return get_property_value("float"sv);
}
WebIDL::ExceptionOr<void> CSSStyleDeclaration::set_css_float(StringView value)
{
// On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value,
// and no third argument. Any exceptions thrown must be re-thrown.
return set_property("float"sv, value, ""sv);
}
Optional<JS::Value> CSSStyleDeclaration::item_value(size_t index) const
{
auto value = item(index);

View file

@ -8,10 +8,8 @@
#pragma once
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/GeneratedCSSStyleProperties.h>
#include <LibWeb/CSS/StyleProperty.h>
#include <LibWeb/DOM/ElementReference.h>
@ -19,8 +17,7 @@ namespace Web::CSS {
// https://drafts.csswg.org/cssom/#css-declaration-blocks
class CSSStyleDeclaration
: public Bindings::PlatformObject
, public Bindings::GeneratedCSSStyleProperties {
: public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(CSSStyleDeclaration, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(CSSStyleDeclaration);
@ -31,24 +28,15 @@ public:
virtual size_t length() const = 0;
virtual String item(size_t index) const = 0;
virtual Optional<StyleProperty> property(PropertyID) const = 0;
virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const = 0;
virtual WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority = ""sv);
virtual WebIDL::ExceptionOr<String> remove_property(PropertyID);
virtual WebIDL::ExceptionOr<void> set_property(StringView property_name, StringView css_text, StringView priority) = 0;
virtual WebIDL::ExceptionOr<String> remove_property(StringView property_name) = 0;
String get_property_value(StringView property) const;
StringView get_property_priority(StringView property) const;
virtual String get_property_value(StringView property_name) const = 0;
virtual StringView get_property_priority(StringView property_name) const = 0;
String css_text() const;
virtual WebIDL::ExceptionOr<void> set_css_text(StringView) = 0;
String css_float() const;
WebIDL::ExceptionOr<void> set_css_float(StringView);
virtual String serialized() const = 0;
// https://drafts.csswg.org/cssom/#cssstyledeclaration-computed-flag
@ -82,14 +70,11 @@ protected:
virtual void visit_edges(Visitor&) override;
virtual CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() override { return *this; }
void update_style_attribute();
private:
// ^PlatformObject
virtual Optional<JS::Value> item_value(size_t index) const override;
Optional<StyleProperty> get_property_internal(PropertyID) const;
// https://drafts.csswg.org/cssom/#cssstyledeclaration-parent-css-rule
GC::Ptr<CSSRule> m_parent_rule { nullptr };

View file

@ -1,9 +1,6 @@
#import <CSS/GeneratedCSSStyleProperties.idl>
// https://drafts.csswg.org/cssom/#cssstyledeclaration
[Exposed=Window]
interface CSSStyleDeclaration {
[CEReactions] attribute CSSOMString cssText;
readonly attribute unsigned long length;
@ -16,9 +13,4 @@ interface CSSStyleDeclaration {
[CEReactions] CSSOMString removeProperty(CSSOMString property);
readonly attribute CSSRule? parentRule;
[CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat;
};
CSSStyleDeclaration includes GeneratedCSSStyleProperties;

View file

@ -78,8 +78,7 @@ CSSStyleProperties::CSSStyleProperties(JS::Realm& realm, Computed computed, Read
void CSSStyleProperties::initialize(JS::Realm& realm)
{
Base::initialize(realm);
// Temporarily disabled for a single commit to make the changes cleaner.
// WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties);
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSStyleProperties);
}
void CSSStyleProperties::visit_edges(Visitor& visitor)
@ -278,6 +277,11 @@ WebIDL::ExceptionOr<void> CSSStyleProperties::set_property(StringView property_n
return {};
}
WebIDL::ExceptionOr<void> CSSStyleProperties::set_property(PropertyID property_id, StringView css_text, StringView priority)
{
return set_property(string_from_property_id(property_id), css_text, priority);
}
static NonnullRefPtr<CSSStyleValue const> style_value_for_length_percentage(LengthPercentage const& length_percentage)
{
if (length_percentage.is_auto())
@ -374,6 +378,220 @@ static RefPtr<CSSStyleValue const> style_value_for_shadow(Vector<ShadowData> con
return StyleValueList::create(move(style_values), StyleValueList::Separator::Comma);
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
String CSSStyleProperties::get_property_value(StringView property_name) const
{
auto property_id = property_id_from_string(property_name);
if (!property_id.has_value())
return {};
if (property_id.value() == PropertyID::Custom) {
auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes()));
if (maybe_custom_property.has_value()) {
return maybe_custom_property.value().value->to_string(
is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue
: CSSStyleValue::SerializationMode::Normal);
}
return {};
}
auto maybe_property = get_property_internal(property_id.value());
if (!maybe_property.has_value())
return {};
return maybe_property->value->to_string(
is_computed() ? CSSStyleValue::SerializationMode::ResolvedValue
: CSSStyleValue::SerializationMode::Normal);
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority
StringView CSSStyleProperties::get_property_priority(StringView property_name) const
{
auto property_id = property_id_from_string(property_name);
if (!property_id.has_value())
return {};
if (property_id.value() == PropertyID::Custom) {
auto maybe_custom_property = custom_property(FlyString::from_utf8_without_validation(property_name.bytes()));
if (!maybe_custom_property.has_value())
return {};
return maybe_custom_property.value().important == Important::Yes ? "important"sv : ""sv;
}
auto maybe_property = property(property_id.value());
if (!maybe_property.has_value())
return {};
return maybe_property->important == Important::Yes ? "important"sv : ""sv;
}
static Optional<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())
return {};
if (top->important != right->important || top->important != bottom->important || top->important != left->important)
return {};
ValueComparingNonnullRefPtr<CSSStyleValue> const top_value { top->value };
ValueComparingNonnullRefPtr<CSSStyleValue> const right_value { right->value };
ValueComparingNonnullRefPtr<CSSStyleValue> const bottom_value { bottom->value };
ValueComparingNonnullRefPtr<CSSStyleValue> const left_value { left->value };
bool const top_and_bottom_same = top_value == bottom_value;
bool const left_and_right_same = left_value == right_value;
RefPtr<CSSStyleValue const> value;
if (top_and_bottom_same && left_and_right_same && top_value == left_value) {
value = top_value;
} else if (top_and_bottom_same && left_and_right_same) {
value = StyleValueList::create(StyleValueVector { top_value, right_value }, StyleValueList::Separator::Space);
} else if (left_and_right_same) {
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value }, StyleValueList::Separator::Space);
} else {
value = StyleValueList::create(StyleValueVector { top_value, right_value, bottom_value, left_value }, StyleValueList::Separator::Space);
}
return StyleProperty {
.important = top->important,
.property_id = property_id,
.value = value.release_nonnull(),
};
}
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
Optional<StyleProperty> CSSStyleProperties::get_property_internal(PropertyID property_id) const
{
// 2. If property is a shorthand property, then follow these substeps:
if (property_is_shorthand(property_id)) {
// AD-HOC: Handle shorthands that require manual construction.
switch (property_id) {
case PropertyID::Border: {
auto width = get_property_internal(PropertyID::BorderWidth);
auto style = get_property_internal(PropertyID::BorderStyle);
auto color = get_property_internal(PropertyID::BorderColor);
// `border` only has a reasonable value if all four sides are the same.
if (!width.has_value() || width->value->is_value_list() || !style.has_value() || style->value->is_value_list() || !color.has_value() || color->value->is_value_list())
return {};
if (width->important != style->important || width->important != color->important)
return {};
return StyleProperty {
.important = width->important,
.property_id = property_id,
.value = ShorthandStyleValue::create(property_id,
{ PropertyID::BorderWidth, PropertyID::BorderStyle, PropertyID::BorderColor },
{ width->value, style->value, color->value })
};
}
case PropertyID::BorderColor: {
auto top = get_property_internal(PropertyID::BorderTopColor);
auto right = get_property_internal(PropertyID::BorderRightColor);
auto bottom = get_property_internal(PropertyID::BorderBottomColor);
auto left = get_property_internal(PropertyID::BorderLeftColor);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::BorderStyle: {
auto top = get_property_internal(PropertyID::BorderTopStyle);
auto right = get_property_internal(PropertyID::BorderRightStyle);
auto bottom = get_property_internal(PropertyID::BorderBottomStyle);
auto left = get_property_internal(PropertyID::BorderLeftStyle);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::BorderWidth: {
auto top = get_property_internal(PropertyID::BorderTopWidth);
auto right = get_property_internal(PropertyID::BorderRightWidth);
auto bottom = get_property_internal(PropertyID::BorderBottomWidth);
auto left = get_property_internal(PropertyID::BorderLeftWidth);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::FontVariant: {
auto ligatures = get_property_internal(PropertyID::FontVariantLigatures);
auto caps = get_property_internal(PropertyID::FontVariantCaps);
auto alternates = get_property_internal(PropertyID::FontVariantAlternates);
auto numeric = get_property_internal(PropertyID::FontVariantNumeric);
auto east_asian = get_property_internal(PropertyID::FontVariantEastAsian);
auto position = get_property_internal(PropertyID::FontVariantPosition);
auto emoji = get_property_internal(PropertyID::FontVariantEmoji);
if (!ligatures.has_value() || !caps.has_value() || !alternates.has_value() || !numeric.has_value() || !east_asian.has_value() || !position.has_value() || !emoji.has_value())
return {};
if (ligatures->important != caps->important || ligatures->important != alternates->important || ligatures->important != numeric->important || ligatures->important != east_asian->important || ligatures->important != position->important || ligatures->important != emoji->important)
return {};
// If ligatures is `none` and any other value isn't `normal`, that's invalid.
if (ligatures->value->to_keyword() == Keyword::None
&& (caps->value->to_keyword() != Keyword::Normal
|| alternates->value->to_keyword() != Keyword::Normal
|| numeric->value->to_keyword() != Keyword::Normal
|| east_asian->value->to_keyword() != Keyword::Normal
|| position->value->to_keyword() != Keyword::Normal
|| emoji->value->to_keyword() != Keyword::Normal)) {
return {};
}
return StyleProperty {
.important = ligatures->important,
.property_id = property_id,
.value = ShorthandStyleValue::create(property_id,
{ PropertyID::FontVariantLigatures, PropertyID::FontVariantCaps, PropertyID::FontVariantAlternates, PropertyID::FontVariantNumeric, PropertyID::FontVariantEastAsian, PropertyID::FontVariantPosition, PropertyID::FontVariantEmoji },
{ ligatures->value, caps->value, alternates->value, numeric->value, east_asian->value, position->value, emoji->value })
};
}
case PropertyID::Margin: {
auto top = get_property_internal(PropertyID::MarginTop);
auto right = get_property_internal(PropertyID::MarginRight);
auto bottom = get_property_internal(PropertyID::MarginBottom);
auto left = get_property_internal(PropertyID::MarginLeft);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
case PropertyID::Padding: {
auto top = get_property_internal(PropertyID::PaddingTop);
auto right = get_property_internal(PropertyID::PaddingRight);
auto bottom = get_property_internal(PropertyID::PaddingBottom);
auto left = get_property_internal(PropertyID::PaddingLeft);
return style_property_for_sided_shorthand(property_id, top, right, bottom, left);
}
default:
break;
}
// 1. Let list be a new empty array.
Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>> list;
Optional<Important> last_important_flag;
// 2. For each longhand property longhand that property maps to, in canonical order, follow these substeps:
Vector<PropertyID> longhand_ids = longhands_for_shorthand(property_id);
for (auto longhand_property_id : longhand_ids) {
// 1. If longhand is a case-sensitive match for a property name of a CSS declaration in the declarations,
// let declaration be that CSS declaration, or null otherwise.
auto declaration = get_property_internal(longhand_property_id);
// 2. If declaration is null, then return the empty string.
if (!declaration.has_value())
return {};
// 3. Append the declaration to list.
list.append(declaration->value);
if (last_important_flag.has_value() && declaration->important != *last_important_flag)
return {};
last_important_flag = declaration->important;
}
// 3. If important flags of all declarations in list are same, then return the serialization of list.
// NOTE: Currently we implement property-specific shorthand serialization in ShorthandStyleValue::to_string().
return StyleProperty {
.important = last_important_flag.value(),
.property_id = property_id,
.value = ShorthandStyleValue::create(property_id, longhand_ids, list),
};
// 4. Return the empty string.
// NOTE: This is handled by the loop.
}
return property(property_id);
}
RefPtr<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> {
@ -806,6 +1024,25 @@ WebIDL::ExceptionOr<String> CSSStyleProperties::remove_property(StringView prope
return value;
}
WebIDL::ExceptionOr<String> CSSStyleProperties::remove_property(PropertyID property_name)
{
return remove_property(string_from_property_id(property_name));
}
// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat
String CSSStyleProperties::css_float() const
{
// The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument.
return get_property_value("float"sv);
}
WebIDL::ExceptionOr<void> CSSStyleProperties::set_css_float(StringView value)
{
// On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value,
// and no third argument. Any exceptions thrown must be re-thrown.
return set_property("float"sv, value, ""sv);
}
// https://www.w3.org/TR/cssom/#serialize-a-css-declaration
static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important)
{

View file

@ -8,11 +8,14 @@
#pragma once
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/GeneratedCSSStyleProperties.h>
namespace Web::CSS {
// https://drafts.csswg.org/cssom/#cssstyleproperties
class CSSStyleProperties : public CSSStyleDeclaration {
class CSSStyleProperties
: public CSSStyleDeclaration
, public Bindings::GeneratedCSSStyleProperties {
WEB_PLATFORM_OBJECT(CSSStyleProperties, CSSStyleDeclaration);
GC_DECLARE_ALLOCATOR(CSSStyleProperties);
@ -28,30 +31,41 @@ public:
virtual size_t length() const override;
virtual String item(size_t index) const override;
virtual Optional<StyleProperty> property(PropertyID) const override;
virtual Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const override;
Optional<StyleProperty> property(PropertyID) const;
Optional<StyleProperty const&> custom_property(FlyString const& custom_property_name) const;
// Temporary for one commit.
using Base::remove_property, Base::set_property;
WebIDL::ExceptionOr<void> set_property(PropertyID, StringView css_text, StringView priority = ""sv);
WebIDL::ExceptionOr<String> remove_property(PropertyID);
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;
virtual String get_property_value(StringView property_name) const override;
virtual StringView get_property_priority(StringView property_name) const 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(); }
String css_float() const;
WebIDL::ExceptionOr<void> set_css_float(StringView);
virtual String serialized() const final override;
virtual WebIDL::ExceptionOr<void> set_css_text(StringView) override;
void set_declarations_from_text(StringView);
// ^Bindings::GeneratedCSSStyleProperties
virtual CSSStyleProperties& generated_style_properties_to_css_style_properties() override { return *this; }
private:
CSSStyleProperties(JS::Realm&, Computed, Readonly, Vector<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;
Optional<StyleProperty> get_property_internal(PropertyID) const;
bool set_a_css_declaration(PropertyID, NonnullRefPtr<CSSStyleValue const>, Important);
void empty_the_declarations();

View file

@ -1,6 +1,10 @@
#import <CSS/CSSStyleDeclaration.idl>
#import <CSS/GeneratedCSSStyleProperties.idl>
// https://drafts.csswg.org/cssom/#cssstyleproperties
[Exposed=Window]
interface CSSStyleProperties : CSSStyleDeclaration {
[CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat;
};
CSSStyleProperties includes GeneratedCSSStyleProperties;

View file

@ -97,8 +97,8 @@ protected:
GeneratedCSSStyleProperties() = default;
virtual ~GeneratedCSSStyleProperties() = default;
virtual CSS::CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() = 0;
CSS::CSSStyleDeclaration const& generated_style_properties_to_css_style_declaration() const { return const_cast<GeneratedCSSStyleProperties&>(*this).generated_style_properties_to_css_style_declaration(); }
virtual CSS::CSSStyleProperties& generated_style_properties_to_css_style_properties() = 0;
CSS::CSSStyleProperties const& generated_style_properties_to_css_style_properties() const { return const_cast<GeneratedCSSStyleProperties&>(*this).generated_style_properties_to_css_style_properties(); }
}; // class GeneratedCSSStyleProperties
} // namespace Web::Bindings
@ -114,7 +114,7 @@ ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& f
SourceGenerator generator { builder };
generator.append(R"~~~(
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/GeneratedCSSStyleProperties.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@ -131,12 +131,12 @@ namespace Web::Bindings {
definition_generator.append(R"~~~(
WebIDL::ExceptionOr<void> GeneratedCSSStyleProperties::set_@name:acceptable_cpp@(StringView value)
{
return generated_style_properties_to_css_style_declaration().set_property("@name@"sv, value, ""sv);
return generated_style_properties_to_css_style_properties().set_property("@name@"sv, value, ""sv);
}
String GeneratedCSSStyleProperties::@name:acceptable_cpp@() const
{
return generated_style_properties_to_css_style_declaration().get_property_value("@name@"sv);
return generated_style_properties_to_css_style_properties().get_property_value("@name@"sv);
}
)~~~");
});

View file

@ -1,3 +1,3 @@
[object CSSStyleDeclaration]
[object CSSStyleProperties]
[object DOMRectList]
PASS (didn't crash)

View file

@ -1,7 +1,4 @@
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
'cssText': ''
'length': '232'
'parentRule': 'null'
All supported properties and their default values exposed from CSSStyleProperties from getComputedStyle:
'cssFloat': 'none'
'WebkitAlignContent': 'normal'
'webkitAlignContent': 'normal'
@ -641,9 +638,4 @@ All supported properties and their default values exposed from CSSStyleDeclarati
'y': '0px'
'zIndex': 'auto'
'z-index': 'auto'
'getPropertyPriority': 'function getPropertyPriority() { [native code] }'
'getPropertyValue': 'function getPropertyValue() { [native code] }'
'removeProperty': 'function removeProperty() { [native code] }'
'item': 'function item() { [native code] }'
'setProperty': 'function setProperty() { [native code] }'
'constructor': 'function CSSStyleDeclaration() { [native code] }'
'constructor': 'function CSSStyleProperties() { [native code] }'

View file

@ -1,4 +1,4 @@
spanRule: [object CSSStyleRule] ~ span { color: purple; }
spanRule.style: [object CSSStyleDeclaration] ~ span { color: purple; }
spanRule.style: [object CSSStyleProperties] ~ span { color: purple; }
spanRule.style.parentRule: [object CSSStyleRule] ~ span { color: purple; }
spanRule.style.parentRule === spanRule: true

View file

@ -10,7 +10,7 @@
const stylePrototype = Object.getPrototypeOf(defaultStyle);
const supportedProperties = Object.getOwnPropertyNames(stylePrototype);
println("All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:");
println("All supported properties and their default values exposed from CSSStyleProperties from getComputedStyle:");
for (const supportedProperty of supportedProperties) {
println(`'${supportedProperty}': '${defaultStyle[supportedProperty]}'`);
}