mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-16 07:11:52 +00:00
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:
parent
687d32b712
commit
83bb92c4e0
Notes:
github-actions[bot]
2025-03-19 13:54:21 +00:00
Author: https://github.com/AtkinsSJ
Commit: 83bb92c4e0
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3983
32 changed files with 589 additions and 639 deletions
|
@ -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 declaration’s property name.
|
||||
auto const& property = declaration.key;
|
||||
|
||||
// 2. If property is in already serialized, continue with the steps labeled declaration loop.
|
||||
// NOTE: It is never in already serialized, as there are no shorthands for custom properties.
|
||||
|
||||
// 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order.
|
||||
// NOTE: There are no shorthands for custom properties.
|
||||
|
||||
// 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ...
|
||||
// NOTE: There are no shorthands for custom properties.
|
||||
|
||||
// 5. Let value be the result of invoking serialize a CSS value of declaration.
|
||||
auto value = declaration.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal);
|
||||
|
||||
// 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value,
|
||||
// and the important flag set if declaration has its important flag set.
|
||||
// NOTE: We have to inline this here as the actual implementation does not accept custom properties.
|
||||
String serialized_declaration = [&] {
|
||||
// https://www.w3.org/TR/cssom/#serialize-a-css-declaration
|
||||
StringBuilder builder;
|
||||
|
||||
// 1. Let s be the empty string.
|
||||
// 2. Append property to s.
|
||||
builder.append(property);
|
||||
|
||||
// 3. Append ": " (U+003A U+0020) to s.
|
||||
builder.append(": "sv);
|
||||
|
||||
// 4. Append value to s.
|
||||
builder.append(value);
|
||||
|
||||
// 5. If the important flag is set, append " !important" (U+0020 U+0021 U+0069 U+006D U+0070 U+006F U+0072 U+0074 U+0061 U+006E U+0074) to s.
|
||||
if (declaration.value.important == Important::Yes)
|
||||
builder.append(" !important"sv);
|
||||
|
||||
// 6. Append ";" (U+003B) to s.
|
||||
builder.append(';');
|
||||
|
||||
// 7. Return s.
|
||||
return MUST(builder.to_string());
|
||||
}();
|
||||
|
||||
// 7. Append serialized declaration to list.
|
||||
list.append(move(serialized_declaration));
|
||||
|
||||
// 8. Append property to already serialized.
|
||||
// NOTE: We don't need to do this, as we don't have shorthands for custom properties.
|
||||
}
|
||||
|
||||
// 3. Declaration loop: For each CSS declaration declaration in declaration block’s declarations, follow these substeps:
|
||||
for (auto& declaration : m_properties) {
|
||||
// 1. Let property be declaration’s property name.
|
||||
auto property = declaration.property_id;
|
||||
|
||||
// 2. If property is in already serialized, continue with the steps labeled declaration loop.
|
||||
if (already_serialized.contains(property))
|
||||
continue;
|
||||
|
||||
// FIXME: 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order.
|
||||
|
||||
// FIXME: 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ...
|
||||
|
||||
// 5. Let value be the result of invoking serialize a CSS value of declaration.
|
||||
auto value = declaration.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal);
|
||||
|
||||
// 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value,
|
||||
// and the important flag set if declaration has its important flag set.
|
||||
auto serialized_declaration = serialize_a_css_declaration(property, move(value), declaration.important);
|
||||
|
||||
// 7. Append serialized declaration to list.
|
||||
list.append(move(serialized_declaration));
|
||||
|
||||
// 8. Append property to already serialized.
|
||||
already_serialized.set(property);
|
||||
}
|
||||
|
||||
// 4. Return list joined with " " (U+0020).
|
||||
StringBuilder builder;
|
||||
builder.join(' ', list);
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext
|
||||
WebIDL::ExceptionOr<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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue