Procházet zdrojové kódy

LibWeb: Make CSS::StyleProperties copy-on-write internally

This makes the way we've implemented the CSS `revert` keyword a lot less
expensive.

Until now, we were making a deep copy of all property values at the
start of each cascade origin. (Those are the values that `revert` would
bring us back to if encountered.)

With this patch, the revert property set becomes a shallow copy, and we
only clone the property set if the cascade ends up writing something.

This knocks a 5% profile item down to 1.3% on https://tailwindcss.com
Andreas Kling před 11 měsíci
rodič
revize
e399b472e9

+ 6 - 6
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -887,7 +887,7 @@ void StyleComputer::set_property_expanding_shorthands(StyleProperties& style, CS
 {
     for_each_property_expanding_shorthands(property_id, value, AllowUnresolved::No, [&](PropertyID shorthand_id, CSSStyleValue const& shorthand_value) {
         if (shorthand_value.is_revert()) {
-            auto const& property_in_previous_cascade_origin = style_for_revert.m_property_values[to_underlying(shorthand_id)];
+            auto const& property_in_previous_cascade_origin = style_for_revert.m_data->m_property_values[to_underlying(shorthand_id)];
             if (property_in_previous_cascade_origin) {
                 style.set_property(shorthand_id, *property_in_previous_cascade_origin, StyleProperties::Inherited::No, important);
                 if (shorthand_id == CSS::PropertyID::AnimationName)
@@ -1826,7 +1826,7 @@ void StyleComputer::compute_cascaded_values(StyleProperties& style, DOM::Element
             // FIXME: This is not very efficient, we should only resolve the custom properties that are actually used.
             for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
                 auto property_id = (CSS::PropertyID)i;
-                auto& property = style.m_property_values[i];
+                auto& property = style.m_data->m_property_values[i];
                 if (property && property->is_unresolved())
                     property = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingContext { document() }, element, pseudo_element, property_id, property->as_unresolved());
             }
@@ -1939,7 +1939,7 @@ void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM
 {
     // FIXME: If we don't know the correct initial value for a property, we fall back to `initial`.
 
-    auto& value_slot = style.m_property_values[to_underlying(property_id)];
+    auto& value_slot = style.m_data->m_property_values[to_underlying(property_id)];
     if (!value_slot) {
         if (is_inherited_property(property_id)) {
             style.set_property(
@@ -2410,7 +2410,7 @@ void StyleComputer::absolutize_values(StyleProperties& style) const
     //       We have to resolve them right away, so that the *computed* line-height is ready for inheritance.
     //       We can't simply absolutize *all* percentage values against the font size,
     //       because most percentages are relative to containing block metrics.
-    auto& line_height_value_slot = style.m_property_values[to_underlying(CSS::PropertyID::LineHeight)];
+    auto& line_height_value_slot = style.m_data->m_property_values[to_underlying(CSS::PropertyID::LineHeight)];
     if (line_height_value_slot && line_height_value_slot->is_percentage()) {
         line_height_value_slot = LengthStyleValue::create(
             Length::make_px(CSSPixels::nearest_value_for(font_size * static_cast<double>(line_height_value_slot->as_percentage().percentage().as_fraction()))));
@@ -2423,8 +2423,8 @@ void StyleComputer::absolutize_values(StyleProperties& style) const
     if (line_height_value_slot && line_height_value_slot->is_length())
         line_height_value_slot = LengthStyleValue::create(Length::make_px(line_height));
 
-    for (size_t i = 0; i < style.m_property_values.size(); ++i) {
-        auto& value_slot = style.m_property_values[i];
+    for (size_t i = 0; i < style.m_data->m_property_values.size(); ++i) {
+        auto& value_slot = style.m_data->m_property_values[i];
         if (!value_slot)
             continue;
         value_slot = value_slot->absolutized(viewport_rect(), font_metrics, m_root_element_font_metrics);

+ 30 - 23
Userland/Libraries/LibWeb/CSS/StyleProperties.cpp

@@ -37,9 +37,9 @@
 
 namespace Web::CSS {
 
-NonnullRefPtr<StyleProperties> StyleProperties::clone() const
+NonnullRefPtr<StyleProperties::Data> StyleProperties::Data::clone() const
 {
-    auto clone = adopt_ref(*new StyleProperties);
+    auto clone = adopt_ref(*new StyleProperties::Data);
     clone->m_property_values = m_property_values;
     clone->m_animated_property_values = m_animated_property_values;
     clone->m_property_important = m_property_important;
@@ -51,74 +51,81 @@ NonnullRefPtr<StyleProperties> StyleProperties::clone() const
     return clone;
 }
 
+NonnullRefPtr<StyleProperties> StyleProperties::clone() const
+{
+    auto cloned = adopt_ref(*new StyleProperties);
+    cloned->m_data = m_data;
+    return cloned;
+}
+
 bool StyleProperties::is_property_important(CSS::PropertyID property_id) const
 {
     size_t n = to_underlying(property_id);
-    return m_property_important[n / 8] & (1 << (n % 8));
+    return m_data->m_property_important[n / 8] & (1 << (n % 8));
 }
 
 void StyleProperties::set_property_important(CSS::PropertyID property_id, Important important)
 {
     size_t n = to_underlying(property_id);
     if (important == Important::Yes)
-        m_property_important[n / 8] |= (1 << (n % 8));
+        m_data->m_property_important[n / 8] |= (1 << (n % 8));
     else
-        m_property_important[n / 8] &= ~(1 << (n % 8));
+        m_data->m_property_important[n / 8] &= ~(1 << (n % 8));
 }
 
 bool StyleProperties::is_property_inherited(CSS::PropertyID property_id) const
 {
     size_t n = to_underlying(property_id);
-    return m_property_inherited[n / 8] & (1 << (n % 8));
+    return m_data->m_property_inherited[n / 8] & (1 << (n % 8));
 }
 
 void StyleProperties::set_property_inherited(CSS::PropertyID property_id, Inherited inherited)
 {
     size_t n = to_underlying(property_id);
     if (inherited == Inherited::Yes)
-        m_property_inherited[n / 8] |= (1 << (n % 8));
+        m_data->m_property_inherited[n / 8] |= (1 << (n % 8));
     else
-        m_property_inherited[n / 8] &= ~(1 << (n % 8));
+        m_data->m_property_inherited[n / 8] &= ~(1 << (n % 8));
 }
 
 void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<CSSStyleValue const> value, Inherited inherited, Important important)
 {
-    m_property_values[to_underlying(id)] = move(value);
+    m_data->m_property_values[to_underlying(id)] = move(value);
     set_property_important(id, important);
     set_property_inherited(id, inherited);
 }
 
 void StyleProperties::revert_property(CSS::PropertyID id, StyleProperties const& style_for_revert)
 {
-    m_property_values[to_underlying(id)] = style_for_revert.m_property_values[to_underlying(id)];
+    m_data->m_property_values[to_underlying(id)] = style_for_revert.m_data->m_property_values[to_underlying(id)];
     set_property_important(id, style_for_revert.is_property_important(id) ? Important::Yes : Important::No);
     set_property_inherited(id, style_for_revert.is_property_inherited(id) ? Inherited::Yes : Inherited::No);
 }
 
 void StyleProperties::set_animated_property(CSS::PropertyID id, NonnullRefPtr<CSSStyleValue const> value)
 {
-    m_animated_property_values.set(id, move(value));
+    m_data->m_animated_property_values.set(id, move(value));
 }
 
 void StyleProperties::reset_animated_properties()
 {
-    m_animated_property_values.clear();
+    m_data->m_animated_property_values.clear();
 }
 
 NonnullRefPtr<CSSStyleValue const> StyleProperties::property(CSS::PropertyID property_id) const
 {
-    if (auto animated_value = m_animated_property_values.get(property_id).value_or(nullptr))
+    if (auto animated_value = m_data->m_animated_property_values.get(property_id).value_or(nullptr))
         return *animated_value;
 
     // By the time we call this method, all properties have values assigned.
-    return *m_property_values[to_underlying(property_id)];
+    return *m_data->m_property_values[to_underlying(property_id)];
 }
 
 RefPtr<CSSStyleValue const> StyleProperties::maybe_null_property(CSS::PropertyID property_id) const
 {
-    if (auto animated_value = m_animated_property_values.get(property_id).value_or(nullptr))
+    if (auto animated_value = m_data->m_animated_property_values.get(property_id).value_or(nullptr))
         return *animated_value;
-    return m_property_values[to_underlying(property_id)];
+    return m_data->m_property_values[to_underlying(property_id)];
 }
 
 CSS::Size StyleProperties::size_value(CSS::PropertyID id) const
@@ -242,7 +249,7 @@ CSSPixels StyleProperties::compute_line_height(CSSPixelRect const& viewport_rect
             auto resolved = line_height->as_calculated().resolve_number();
             if (!resolved.has_value()) {
                 dbgln("FIXME: Failed to resolve calc() line-height (number): {}", line_height->as_calculated().to_string());
-                return CSSPixels::nearest_value_for(m_font_list->first().pixel_metrics().line_spacing());
+                return CSSPixels::nearest_value_for(m_data->m_font_list->first().pixel_metrics().line_spacing());
             }
             return Length(resolved.value(), Length::Type::Em).to_px(viewport_rect, font_metrics, root_font_metrics);
         }
@@ -250,7 +257,7 @@ CSSPixels StyleProperties::compute_line_height(CSSPixelRect const& viewport_rect
         auto resolved = line_height->as_calculated().resolve_length(Length::ResolutionContext { viewport_rect, font_metrics, root_font_metrics });
         if (!resolved.has_value()) {
             dbgln("FIXME: Failed to resolve calc() line-height: {}", line_height->as_calculated().to_string());
-            return CSSPixels::nearest_value_for(m_font_list->first().pixel_metrics().line_spacing());
+            return CSSPixels::nearest_value_for(m_data->m_font_list->first().pixel_metrics().line_spacing());
         }
         return resolved->to_px(viewport_rect, font_metrics, root_font_metrics);
     }
@@ -608,12 +615,12 @@ Optional<CSS::Positioning> StyleProperties::position() const
 
 bool StyleProperties::operator==(StyleProperties const& other) const
 {
-    if (m_property_values.size() != other.m_property_values.size())
+    if (m_data->m_property_values.size() != other.m_data->m_property_values.size())
         return false;
 
-    for (size_t i = 0; i < m_property_values.size(); ++i) {
-        auto const& my_style = m_property_values[i];
-        auto const& other_style = other.m_property_values[i];
+    for (size_t i = 0; i < m_data->m_property_values.size(); ++i) {
+        auto const& my_style = m_data->m_property_values[i];
+        auto const& other_style = other.m_data->m_property_values[i];
         if (!my_style) {
             if (other_style)
                 return false;
@@ -1122,7 +1129,7 @@ Color StyleProperties::stop_color() const
 
 void StyleProperties::set_math_depth(int math_depth)
 {
-    m_math_depth = math_depth;
+    m_data->m_math_depth = math_depth;
     // Make our children inherit our computed value, not our specified value.
     set_property(PropertyID::MathDepth, MathDepthStyleValue::create_integer(IntegerStyleValue::create(math_depth)));
 }

+ 38 - 28
Userland/Libraries/LibWeb/CSS/StyleProperties.h

@@ -20,6 +20,30 @@
 namespace Web::CSS {
 
 class StyleProperties : public RefCounted<StyleProperties> {
+public:
+    static constexpr size_t number_of_properties = to_underlying(CSS::last_property_id) + 1;
+
+private:
+    struct Data : public RefCounted<Data> {
+        friend class StyleComputer;
+
+        NonnullRefPtr<Data> clone() const;
+
+        // FIXME: This needs protection from GC!
+        JS::GCPtr<CSS::CSSStyleDeclaration const> m_animation_name_source;
+
+        Array<RefPtr<CSSStyleValue const>, number_of_properties> m_property_values;
+        Array<u8, ceil_div(number_of_properties, 8uz)> m_property_important {};
+        Array<u8, ceil_div(number_of_properties, 8uz)> m_property_inherited {};
+
+        HashMap<CSS::PropertyID, NonnullRefPtr<CSSStyleValue const>> m_animated_property_values;
+
+        int m_math_depth { InitialValues::math_depth() };
+        mutable RefPtr<Gfx::FontCascadeList> m_font_list;
+
+        Optional<CSSPixels> m_line_height;
+    };
+
 public:
     StyleProperties() = default;
 
@@ -29,9 +53,9 @@ public:
     template<typename Callback>
     inline void for_each_property(Callback callback) const
     {
-        for (size_t i = 0; i < m_property_values.size(); ++i) {
-            if (m_property_values[i])
-                callback((CSS::PropertyID)i, *m_property_values[i]);
+        for (size_t i = 0; i < m_data->m_property_values.size(); ++i) {
+            if (m_data->m_property_values[i])
+                callback((CSS::PropertyID)i, *m_data->m_property_values[i]);
         }
     }
 
@@ -40,9 +64,7 @@ public:
         Yes
     };
 
-    static constexpr size_t number_of_properties = to_underlying(CSS::last_property_id) + 1;
-
-    HashMap<CSS::PropertyID, NonnullRefPtr<CSSStyleValue const>> const& animated_property_values() const { return m_animated_property_values; }
+    HashMap<CSS::PropertyID, NonnullRefPtr<CSSStyleValue const>> const& animated_property_values() const { return m_data->m_animated_property_values; }
     void reset_animated_properties();
 
     bool is_property_important(CSS::PropertyID property_id) const;
@@ -56,8 +78,8 @@ public:
     RefPtr<CSSStyleValue const> maybe_null_property(CSS::PropertyID) const;
     void revert_property(CSS::PropertyID, StyleProperties const& style_for_revert);
 
-    JS::GCPtr<CSS::CSSStyleDeclaration const> animation_name_source() const { return m_animation_name_source; }
-    void set_animation_name_source(JS::GCPtr<CSS::CSSStyleDeclaration const> declaration) { m_animation_name_source = declaration; }
+    JS::GCPtr<CSS::CSSStyleDeclaration const> animation_name_source() const { return m_data->m_animation_name_source; }
+    void set_animation_name_source(JS::GCPtr<CSS::CSSStyleDeclaration const> declaration) { m_data->m_animation_name_source = declaration; }
 
     CSS::Size size_value(CSS::PropertyID) const;
     LengthPercentage length_percentage_or_fallback(CSS::PropertyID, LengthPercentage const& fallback) const;
@@ -146,23 +168,23 @@ public:
     Optional<CSS::FillRule> fill_rule() const;
     Optional<CSS::ClipRule> clip_rule() const;
 
-    Gfx::Font const& first_available_computed_font() const { return m_font_list->first(); }
+    Gfx::Font const& first_available_computed_font() const { return m_data->m_font_list->first(); }
 
     Gfx::FontCascadeList const& computed_font_list() const
     {
-        VERIFY(m_font_list);
-        return *m_font_list;
+        VERIFY(m_data->m_font_list);
+        return *m_data->m_font_list;
     }
 
     void set_computed_font_list(NonnullRefPtr<Gfx::FontCascadeList> font_list) const
     {
-        m_font_list = move(font_list);
+        m_data->m_font_list = move(font_list);
     }
 
     [[nodiscard]] CSSPixels compute_line_height(CSSPixelRect const& viewport_rect, Length::FontMetrics const& font_metrics, Length::FontMetrics const& root_font_metrics) const;
 
-    [[nodiscard]] CSSPixels line_height() const { return *m_line_height; }
-    void set_line_height(Badge<StyleComputer> const&, CSSPixels line_height) { m_line_height = line_height; }
+    [[nodiscard]] CSSPixels line_height() const { return *m_data->m_line_height; }
+    void set_line_height(Badge<StyleComputer> const&, CSSPixels line_height) { m_data->m_line_height = line_height; }
 
     bool operator==(StyleProperties const&) const;
 
@@ -170,7 +192,7 @@ public:
     Optional<int> z_index() const;
 
     void set_math_depth(int math_depth);
-    int math_depth() const { return m_math_depth; }
+    int math_depth() const { return m_data->m_math_depth; }
 
     QuotesData quotes() const;
     Vector<CounterData> counter_data(PropertyID) const;
@@ -184,22 +206,10 @@ public:
 private:
     friend class StyleComputer;
 
-    // FIXME: This needs protection from GC!
-    JS::GCPtr<CSS::CSSStyleDeclaration const> m_animation_name_source;
-
-    Array<RefPtr<CSSStyleValue const>, number_of_properties> m_property_values;
-    Array<u8, ceil_div(number_of_properties, 8uz)> m_property_important {};
-    Array<u8, ceil_div(number_of_properties, 8uz)> m_property_inherited {};
-
-    HashMap<CSS::PropertyID, NonnullRefPtr<CSSStyleValue const>> m_animated_property_values;
-
     Optional<CSS::Overflow> overflow(CSS::PropertyID) const;
     Vector<CSS::ShadowData> shadow(CSS::PropertyID, Layout::Node const&) const;
 
-    int m_math_depth { InitialValues::math_depth() };
-    mutable RefPtr<Gfx::FontCascadeList> m_font_list;
-
-    Optional<CSSPixels> m_line_height;
+    AK::CopyOnWrite<StyleProperties::Data> m_data;
 };
 
 }