From 47332055fe6ad035267a7d6bd880a4ed7495e6fd Mon Sep 17 00:00:00 2001 From: Ankush Chatterjee Date: Fri, 15 Nov 2024 00:51:50 +0530 Subject: [PATCH] LibWeb/CSS: Add support for custom elements in CSSStyleDeclaration --- Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp | 58 +++++++++++++++---- Libraries/LibWeb/CSS/CSSStyleDeclaration.h | 14 +++-- .../CSS/ResolvedCSSStyleDeclaration.cpp | 10 +++- .../LibWeb/CSS/ResolvedCSSStyleDeclaration.h | 5 +- Libraries/LibWeb/HTML/HTMLInputElement.cpp | 16 ++--- Libraries/LibWeb/HTML/HTMLMeterElement.cpp | 2 +- Libraries/LibWeb/HTML/HTMLProgressElement.cpp | 4 +- Libraries/LibWeb/HTML/HTMLSelectElement.cpp | 4 +- Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp | 8 +-- .../CSSStyleDeclaration-custom-properties.txt | 3 + ...CSSStyleDeclaration-custom-properties.html | 19 ++++++ 11 files changed, 104 insertions(+), 39 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-custom-properties.txt create mode 100644 Tests/LibWeb/Text/input/css/CSSStyleDeclaration-custom-properties.html diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp index 98480f66de0..f4ea795ef95 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -102,7 +103,7 @@ Optional PropertyOwningCSSStyleDeclaration::property(PropertyID p } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(PropertyID property_id, StringView value, StringView priority) +WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(PropertyID property_id, Optional property_name, StringView value, StringView priority) { // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. // NOTE: This is handled by the virtual override in ResolvedCSSStyleDeclaration. @@ -114,7 +115,10 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(Proper // 3. If value is the empty string, invoke removeProperty() with property as argument and return. if (value.is_empty()) { - MUST(remove_property(property_id)); + if (property_name.has_value()) + MUST(remove_property(property_id, property_name.value())); + else + MUST(remove_property(property_id, string_from_property_id(property_id))); return {}; } @@ -137,11 +141,11 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(Proper // 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) { + StyleComputer::for_each_property_expanding_shorthands(property_id, *component_value_list, StyleComputer::AllowUnresolved::Yes, [this, &updated, property_name, 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); + updated |= set_a_css_declaration(longhand_property_id, property_name, longhand_value, !priority.is_empty() ? Important::Yes : Important::No); }); } // 9. Otherwise, @@ -149,7 +153,7 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(Proper // 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); + updated = set_a_css_declaration(property_id, property_name, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No); } // 10. If updated is true, update style attribute for the CSS declaration block. @@ -160,7 +164,7 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::set_property(Proper } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(PropertyID property_id) +WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(PropertyID property_id, StringView property_name) { // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. // NOTE: This is handled by the virtual override in ResolvedCSSStyleDeclaration. @@ -169,8 +173,7 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(P // 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. - // FIXME: The trip through string_from_property_id() here is silly. - auto value = get_property_value(string_from_property_id(property_id)); + auto value = get_property_value(property_name); // 4. Let removed be false. bool removed = false; @@ -180,7 +183,11 @@ WebIDL::ExceptionOr PropertyOwningCSSStyleDeclaration::remove_property(P // 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. - removed = m_properties.remove_first_matching([&](auto& entry) { return entry.property_id == property_id; }); + if (property_id == PropertyID::Custom) { + removed = m_custom_properties.remove(MUST(FlyString::from_utf8(property_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) @@ -212,10 +219,16 @@ void ElementInlineCSSStyleDeclaration::update_style_attribute() } // https://drafts.csswg.org/cssom/#set-a-css-declaration -bool PropertyOwningCSSStyleDeclaration::set_a_css_declaration(PropertyID property_id, NonnullRefPtr value, Important important) +bool PropertyOwningCSSStyleDeclaration::set_a_css_declaration(PropertyID property_id, Optional property_name, NonnullRefPtr value, Important important) { // FIXME: Handle logical property groups. + if (property_id == PropertyID::Custom && property_name.has_value()) { + auto fly_string_property = MUST(FlyString::from_utf8(property_name.release_value())); + m_custom_properties.set(fly_string_property, StyleProperty { .important = important, .property_id = property_id, .value = move(value), .custom_name = fly_string_property }); + return true; + } + for (auto& property : m_properties) { if (property.property_id == property_id) { if (property.important == important && *property.value == *value) @@ -237,6 +250,15 @@ bool PropertyOwningCSSStyleDeclaration::set_a_css_declaration(PropertyID propert // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue String CSSStyleDeclaration::get_property_value(StringView property_name) const { + // check for a custom property and return early + if (is_a_custom_property_name_string(property_name)) { + auto maybe_custom_property = custom_property(MUST(FlyString::from_utf8(property_name))); + if (maybe_custom_property.has_value()) { + return maybe_custom_property.value().value->to_string(); + } + return {}; + } + auto property_id = property_id_from_string(property_name); if (!property_id.has_value()) return {}; @@ -282,6 +304,13 @@ String CSSStyleDeclaration::get_property_value(StringView property_name) const // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority StringView CSSStyleDeclaration::get_property_priority(StringView property_name) const { + if (is_a_custom_property_name_string(property_name)) { + auto maybe_property = custom_property(MUST(FlyString::from_utf8(property_name))); + if (maybe_property.has_value()) { + return maybe_property->important == Important::Yes ? "important"sv : ""sv; + } + } + auto property_id = property_id_from_string(property_name); if (!property_id.has_value()) return {}; @@ -293,18 +322,23 @@ StringView CSSStyleDeclaration::get_property_priority(StringView property_name) WebIDL::ExceptionOr CSSStyleDeclaration::set_property(StringView property_name, StringView css_text, StringView priority) { + if (is_a_custom_property_name_string(property_name)) { + return set_property(PropertyID::Custom, property_name, css_text, priority); + } auto property_id = property_id_from_string(property_name); if (!property_id.has_value()) return {}; - return set_property(property_id.value(), css_text, priority); + return set_property(property_id.value(), property_name, css_text, priority); } WebIDL::ExceptionOr CSSStyleDeclaration::remove_property(StringView property_name) { auto property_id = property_id_from_string(property_name); + if (is_a_custom_property_name_string(property_name)) + property_id = PropertyID::Custom; if (!property_id.has_value()) return String {}; - return remove_property(property_id.value()); + return remove_property(property_id.value(), property_name); } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext diff --git a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h index 6f7e41f79c0..889046d9a3e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/CSSStyleDeclaration.h @@ -29,9 +29,11 @@ public: virtual String item(size_t index) const = 0; virtual Optional property(PropertyID) const = 0; + virtual Optional custom_property(FlyString const& custom_property_name) const = 0; - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority = ""sv) = 0; - virtual WebIDL::ExceptionOr remove_property(PropertyID) = 0; + // property_name only needs to be passed for custom properties. + virtual WebIDL::ExceptionOr set_property(PropertyID, Optional property_name, StringView css_text, StringView priority = ""sv) = 0; + virtual WebIDL::ExceptionOr remove_property(PropertyID, StringView property_name) = 0; virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority); virtual WebIDL::ExceptionOr remove_property(StringView property_name); @@ -76,12 +78,12 @@ public: virtual Optional property(PropertyID) const override; - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr remove_property(PropertyID) override; + virtual WebIDL::ExceptionOr set_property(PropertyID, Optional property_name, StringView css_text, StringView priority) override; + virtual WebIDL::ExceptionOr remove_property(PropertyID, StringView) override; Vector const& properties() const { return m_properties; } HashMap const& custom_properties() const { return m_custom_properties; } - Optional custom_property(FlyString const& custom_property_name) const { return m_custom_properties.get(custom_property_name); } + virtual Optional custom_property(FlyString const& custom_property_name) const override { return m_custom_properties.get(custom_property_name); } size_t custom_property_count() const { return m_custom_properties.size(); } virtual String serialized() const final override; @@ -99,7 +101,7 @@ protected: void set_the_declarations(Vector properties, HashMap custom_properties); private: - bool set_a_css_declaration(PropertyID, NonnullRefPtr, Important); + bool set_a_css_declaration(PropertyID, Optional, NonnullRefPtr, Important); virtual void visit_edges(Cell::Visitor&) override; diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index 0a47b40702d..9c426b7efe9 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -601,13 +601,19 @@ Optional ResolvedCSSStyleDeclaration::property(PropertyID propert }; } +Optional ResolvedCSSStyleDeclaration::custom_property(FlyString const&) const +{ + dbgln("FIXME: ResolvedCSSStyleDeclaration should support custom property"); + return {}; +} + static WebIDL::ExceptionOr cannot_modify_computed_property_error(JS::Realm& realm) { return WebIDL::NoModificationAllowedError::create(realm, "Cannot modify properties in result of getComputedStyle()"_string); } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_property(PropertyID, StringView, StringView) +WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::set_property(PropertyID, Optional, StringView, StringView) { // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. return cannot_modify_computed_property_error(realm()); @@ -626,7 +632,7 @@ static WebIDL::ExceptionOr cannot_remove_computed_property_error(JS::Rea } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty -WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::remove_property(PropertyID) +WebIDL::ExceptionOr ResolvedCSSStyleDeclaration::remove_property(PropertyID, StringView) { // 1. If the computed flag is set, then throw a NoModificationAllowedError exception. return cannot_remove_computed_property_error(realm()); diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h index 89bba8f05e3..05915e18fc0 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.h @@ -24,9 +24,10 @@ public: virtual String item(size_t index) const override; virtual Optional property(PropertyID) const override; - virtual WebIDL::ExceptionOr set_property(PropertyID, StringView css_text, StringView priority) override; + virtual Optional custom_property(FlyString const& custom_property_name) const override; + virtual WebIDL::ExceptionOr set_property(PropertyID, Optional property_name, StringView css_text, StringView priority) override; virtual WebIDL::ExceptionOr set_property(StringView property_name, StringView css_text, StringView priority) override; - virtual WebIDL::ExceptionOr remove_property(PropertyID) override; + virtual WebIDL::ExceptionOr remove_property(PropertyID, StringView) override; virtual WebIDL::ExceptionOr remove_property(StringView property_name) override; virtual String serialized() const override; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index fffa5f151f0..4a11cc3ee50 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -656,9 +656,9 @@ void HTMLInputElement::update_placeholder_visibility() if (!m_placeholder_element) return; if (this->placeholder_value().has_value()) { - MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv)); + MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "block"sv)); } else { - MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv)); + MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "none"sv)); } } @@ -961,7 +961,7 @@ void HTMLInputElement::create_color_input_shadow_tree() border: 1px solid ButtonBorder; box-sizing: border-box; )~~~"_string)); - MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, color)); + MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, {}, color)); MUST(border->append_child(*m_color_well_element)); MUST(shadow_root->append_child(border)); @@ -973,7 +973,7 @@ void HTMLInputElement::update_color_well_element() if (!m_color_well_element) return; - MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value)); + MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, {}, m_value)); } void HTMLInputElement::create_file_input_shadow_tree() @@ -1140,9 +1140,9 @@ void HTMLInputElement::computed_css_values_changed() accent_color = accent_color_property.to_string(); if (m_slider_progress_element) - MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color)); + MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, {}, accent_color)); if (m_slider_thumb) - MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color)); + MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, {}, accent_color)); } void HTMLInputElement::user_interaction_did_change_input_value() @@ -1170,10 +1170,10 @@ void HTMLInputElement::update_slider_shadow_tree_elements() double position = (value - minimum) / (maximum - minimum) * 100; if (m_slider_progress_element) - MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position)))); + MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::Width, {}, MUST(String::formatted("{}%", position)))); if (m_slider_thumb) - MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::MarginLeft, MUST(String::formatted("{}%", position)))); + MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::MarginLeft, {}, MUST(String::formatted("{}%", position)))); } void HTMLInputElement::did_receive_focus() diff --git a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp index 501b3c1b24f..2deac2b2cd9 100644 --- a/Libraries/LibWeb/HTML/HTMLMeterElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMeterElement.cpp @@ -240,6 +240,6 @@ void HTMLMeterElement::update_meter_value_element() } double position = (value - min) / (max - min) * 100; - MUST(m_meter_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position)))); + MUST(m_meter_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, {}, MUST(String::formatted("{}%", position)))); } } diff --git a/Libraries/LibWeb/HTML/HTMLProgressElement.cpp b/Libraries/LibWeb/HTML/HTMLProgressElement.cpp index 837a7272029..54aa159f978 100644 --- a/Libraries/LibWeb/HTML/HTMLProgressElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLProgressElement.cpp @@ -127,7 +127,7 @@ void HTMLProgressElement::create_shadow_tree_if_needed() void HTMLProgressElement::update_progress_value_element() { if (m_progress_value_element) - MUST(m_progress_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position() * 100)))); + MUST(m_progress_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, {}, MUST(String::formatted("{}%", position() * 100)))); } void HTMLProgressElement::computed_css_values_changed() @@ -140,7 +140,7 @@ void HTMLProgressElement::computed_css_values_changed() accent_color = accent_color_property.to_string(); if (m_progress_value_element) - MUST(m_progress_value_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color)); + MUST(m_progress_value_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, {}, accent_color)); } } diff --git a/Libraries/LibWeb/HTML/HTMLSelectElement.cpp b/Libraries/LibWeb/HTML/HTMLSelectElement.cpp index bc48c4b551e..44dc8a461eb 100644 --- a/Libraries/LibWeb/HTML/HTMLSelectElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLSelectElement.cpp @@ -502,9 +502,9 @@ void HTMLSelectElement::computed_css_values_changed() if (m_chevron_icon_element) { auto appearance = computed_css_values()->appearance(); if (appearance.has_value() && *appearance == CSS::Appearance::None) { - MUST(m_chevron_icon_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"_string)); + MUST(m_chevron_icon_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "none"_string)); } else { - MUST(m_chevron_icon_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"_string)); + MUST(m_chevron_icon_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "block"_string)); } } } diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index 7786093de72..6e0e6894a72 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -415,11 +415,11 @@ void HTMLTextAreaElement::update_placeholder_visibility() return; auto placeholder_text = get_attribute(AttributeNames::placeholder); if (placeholder_text.has_value() && m_text_node->data().is_empty()) { - MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv)); - MUST(m_inner_text_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv)); + MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "block"sv)); + MUST(m_inner_text_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "none"sv)); } else { - MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv)); - MUST(m_inner_text_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv)); + MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "none"sv)); + MUST(m_inner_text_element->style_for_bindings()->set_property(CSS::PropertyID::Display, {}, "block"sv)); } } diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-custom-properties.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-custom-properties.txt new file mode 100644 index 00000000000..d07d9f84857 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-custom-properties.txt @@ -0,0 +1,3 @@ +--foo= bar +--foo= baz important +--foo= blank diff --git a/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-custom-properties.html b/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-custom-properties.html new file mode 100644 index 00000000000..b0e9274b1e1 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/CSSStyleDeclaration-custom-properties.html @@ -0,0 +1,19 @@ + +
+